/* ——————————————————————————————————————————————————————
   Motion · all animations in one place
   Timing is tuned via --d (delay) inline on the element.
—————————————————————————————————————————————————————— */

/* ——— Hero headline: lines rise out of a mask ——— */
.line { display: block; overflow: hidden; padding-bottom: .06em; }
.line > span { display: inline-block; }
.js .line > span {
  transform: translateY(105%);
  animation: rise 1s var(--ease) forwards;
  animation-delay: var(--d, 0s);
}
@keyframes rise { to { transform: translateY(0); } }

/* ——— Hero: rotating word with typewriter (main.js) ——— */
.line-swap { cursor: default; white-space: nowrap; }
.line-swap > span { white-space: nowrap; display: inline-flex; align-items: baseline; }
.caret {
  display: inline-block; width: .055em; height: .78em; margin-left: .07em;
  background: var(--amarillo); border-radius: 1px; vertical-align: -.02em;
  opacity: 0; transition: opacity .25s;
}
.js .line-swap .caret { opacity: 1; animation: blink 1.1s steps(1) infinite; }
.js .line-swap.is-typing .caret { animation: none; opacity: 1; }
@keyframes blink { 50% { opacity: 0; } }

/* ——— Fade up on load ——— */
.js .fade {
  opacity: 0;
  animation: fadeup .9s var(--ease) forwards;
  animation-delay: var(--d, 0s);
}
@keyframes fadeup {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: none; }
}

/* ——— Reveal on scroll (main.js adds .in) ——— */
.js .reveal { opacity: 0; transform: translateY(28px); transition: opacity .9s var(--ease) var(--d, 0s), transform .9s var(--ease) var(--d, 0s); }
.reveal.in { opacity: 1; transform: none; }

/* ——— Status dot ——— */
.pulse::after {
  content: ""; position: absolute; inset: 0; border-radius: 50%;
  background: var(--green);
  animation: ping 1.8s ease-out infinite;
}
@keyframes ping { from { transform: scale(1); opacity: .6; } to { transform: scale(3); opacity: 0; } }

/* ——— Bubble: typing dots, then the message, then a wave ——— */
.typing i { animation: dot 1s ease-in-out infinite; }
.typing i:nth-child(2) { animation-delay: .15s; }
.typing i:nth-child(3) { animation-delay: .3s; }
@keyframes dot { 0%, 100% { opacity: .25; transform: translateY(0); } 50% { opacity: .8; transform: translateY(-3px); } }
/* main.js toggles .is-typing on the bubble for every new line */
.typing { opacity: 0; transition: opacity .2s ease; }
.bubble.is-typing .typing { opacity: 1; }
.bubble-text { display: inline-block; transition: opacity .25s ease; }
.bubble.is-typing .bubble-text { opacity: 0; transition: none; }
.bubble.is-new .bubble-text { animation: msg-in .5s var(--ease) both; }
@keyframes msg-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
html:not(.js) .typing { display: none; }
.bubble.is-new .wave { animation: wave 1.6s ease-in-out .3s 2; }
.bubble:hover .wave { animation: wave 1.6s ease-in-out infinite; }
@keyframes wave { 0%, 60%, 100% { transform: rotate(0); } 10%, 30% { transform: rotate(16deg); } 20%, 40% { transform: rotate(-10deg); } }
.js .bubble-credit { animation: credit-in .6s cubic-bezier(.34, 1.8, .64, 1) 3s both; }
@keyframes credit-in { from { opacity: 0; transform: rotate(4deg) scale(.4); } to { opacity: 1; transform: rotate(4deg) scale(1); } }

/* ——— Badge + bubble ——— */
.spin  { animation: spin 18s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
.float { animation: float 5s ease-in-out infinite; }
@keyframes float {
  0%, 100% { transform: translateY(0) rotate(-3deg); }
  50%      { transform: translateY(-10px) rotate(2deg); }
}

/* ——— Parallax (main.js writes --mx/--my for mouse, --py for scroll) ——— */
[data-mouse], [data-parallax] {
  transform: translate3d(calc(var(--mx, 0) * 1px), calc((var(--my, 0) + var(--py, 0)) * 1px), 0);
  transition: transform .7s var(--ease);
  will-change: transform;
}
[data-parallax]:not([data-mouse]) { transition: none; }
.about-side[data-parallax] { transition: transform .15s linear; }

/* ——— About: playful entry (main.js splits the title into .w words, adds .in on scroll,
   and counts the fact numbers up) ——— */
.js .about-head.reveal { opacity: 1; transform: none; }           /* block stays, words animate */
.js .about-head:not(.in) .eyebrow { opacity: 0; }
.about-head.in .eyebrow { animation: slide-in .6s var(--ease) both; }
@keyframes slide-in { from { opacity: 0; transform: translateX(-18px); } to { opacity: 1; transform: none; } }

.js .about-head:not(.in) .w { opacity: 0; }
.about-head.in .w { animation: pop .85s cubic-bezier(.34, 1.56, .64, 1) both; animation-delay: calc(var(--i, 0) * 60ms); }
@keyframes pop {
  from { opacity: 0; transform: translateY(.55em) rotate(var(--rot, -4deg)) scale(.6); }
  65%  { opacity: 1; transform: translateY(-.05em) rotate(calc(var(--rot, -4deg) * -0.25)) scale(1.05); }
  to   { opacity: 1; transform: none; }
}

.js .about-grid.reveal { opacity: 1; transform: none; }
.js .about-grid:not(.in)::before { transform: scaleX(0); }
.about-grid.in::before { animation: draw 1.1s var(--ease) both; }
@keyframes draw { from { transform: scaleX(0); } to { transform: scaleX(1); } }

.js .about-grid:not(.in) .about-text { opacity: 0; }
.about-grid.in .about-text { animation: fadeup .9s var(--ease) .25s both; }

/* Facts: each hairline draws from the left, then number + label rise (numbers count up via main.js) */
/* shapes grow in with an elastic overshoot ("scale" composes with the parallax transform) */
.js .about-grid:not(.in) .orb { scale: 0; }
.about-grid.in .orb { animation: orb-in 1.3s cubic-bezier(.34, 1.56, .64, 1) both; }
.about-grid.in .orb-a { animation-delay: .1s; }
.about-grid.in .orb-c { animation-delay: .55s; }
.orb-asterisk { animation: spin 28s linear infinite; transform-origin: 50% 50%; }
@keyframes orb-in { from { scale: 0; } to { scale: 1; } }

/* glass card pops in, rows drop in one by one */
.js .about-grid:not(.in) .about-facts { opacity: 0; }
.about-grid.in .about-facts { animation: frost-in 1s cubic-bezier(.34, 1.56, .64, 1) .3s both; }
@keyframes frost-in { from { opacity: 0; scale: .92; } to { opacity: 1; scale: 1; } }
.js .about-grid:not(.in) .fact { opacity: 0; }
.about-grid.in .fact { animation: drop .9s cubic-bezier(.34, 1.56, .64, 1) both; }
.about-grid.in .fact:nth-child(1) { animation-delay: .55s; }
.about-grid.in .fact:nth-child(2) { animation-delay: .7s; }
.about-grid.in .fact:nth-child(3) { animation-delay: .85s; }
.about-grid.in .fact:nth-child(4) { animation-delay: 1s; }
@keyframes drop { from { opacity: 0; transform: translateY(-28px) rotate(-1.5deg); } to { opacity: 1; transform: none; } }

/* "live" tag: pulsing dot like the hero status */
.live-dot::after {
  content: ""; position: absolute; inset: 0; border-radius: 50%;
  background: var(--green);
  animation: ping 1.8s ease-out infinite;
}

/* the numbers lean in a touch on hover */
.fact-num { display: inline-block; transition: translate .4s cubic-bezier(.34, 1.56, .64, 1); }
.fact:hover .fact-num { translate: 8px 0; }

/* Direction / People / Delivery: pill spins in, title and text rise */
.js .how.reveal { opacity: 1; transform: none; }
.js .how:not(.in) .how-idx, .js .how:not(.in) .how-title, .js .how:not(.in) .how-text { opacity: 0; }
.how.in .how-idx { animation: spin-in .8s cubic-bezier(.34, 1.56, .64, 1) var(--d, 0s) both; }
.how.in .how-title { animation: fadeup .8s var(--ease) calc(var(--d, 0s) + .15s) both; }
.how.in .how-text { animation: fadeup .8s var(--ease) calc(var(--d, 0s) + .28s) both; }
@keyframes spin-in { from { opacity: 0; transform: rotate(-150deg) scale(.3); } to { opacity: 1; transform: none; } }

/* ——— Clients / Work / Contact: same playful language as About ——— */
/* Section titles: words pop in (shared .w keyframes) */
.js .work-head.reveal, .js .contact.reveal, .js .clients.reveal { opacity: 1; transform: none; }
.js .work-head:not(.in) .w { opacity: 0; }
.work-head.in .w { animation: pop .85s cubic-bezier(.34, 1.56, .64, 1) both; animation-delay: calc(var(--i, 0) * 70ms); }
.js .work-head:not(.in) .eyebrow, .js .contact:not(.in) .eyebrow { opacity: 0; }
.work-head.in .eyebrow, .contact.in .eyebrow { animation: slide-in .6s var(--ease) .3s both; }

/* Clients: rows swing in from alternating sides, chips bounce up one by one */
.js .clients:not(.in) .marquee { opacity: 0; }
.clients.in .marquee { animation: swing-l 1.1s cubic-bezier(.34, 1.4, .64, 1) both; }
.clients.in .marquee:nth-child(2) { animation-name: swing-r; animation-delay: .12s; }
.clients.in .marquee:nth-child(3) { animation-delay: .24s; }
@keyframes swing-l { from { opacity: 0; translate: -30vw 0; rotate: -2deg; } to { opacity: 1; translate: 0 0; rotate: 0deg; } }
@keyframes swing-r { from { opacity: 0; translate: 30vw 0; rotate: 2deg; } to { opacity: 1; translate: 0 0; rotate: 0deg; } }
.js .clients:not(.in) .chip { scale: .4; }
.clients.in .chip { animation: chip-in .7s cubic-bezier(.34, 1.8, .64, 1) both; }
.clients.in .track-group .chip:nth-of-type(1) { animation-delay: .35s; }
.clients.in .track-group .chip:nth-of-type(2) { animation-delay: .45s; }
.clients.in .track-group .chip:nth-of-type(3) { animation-delay: .55s; }
.clients.in .track-group .chip:nth-of-type(4) { animation-delay: .65s; }
.clients.in .track-group .chip:nth-child(n+5) { animation-delay: .75s; }
@keyframes chip-in { from { scale: .4; } to { scale: 1; } }

/* Work: each job tips in from below with a small tilt */
.js .job.reveal { opacity: 0; transform: translateY(46px) rotate(var(--tilt, -1.2deg)); transform-origin: left center;
  transition: opacity .7s var(--ease), transform .9s cubic-bezier(.34, 1.56, .64, 1); }
.js .job.reveal.in { opacity: 1; transform: none; }
.jobs::after { transition: transform .25s linear; }   /* smooths the scroll-driven rail fill */
@keyframes flick { from { scale: .3; rotate: -90deg; } to { scale: 1; rotate: 0deg; } }

/* Contact: big "Say hi." rises, mail button stamps down, links and status follow */
.js .contact:not(.in) .say-big, .js .contact:not(.in) .contact-row > *, .js .contact:not(.in) .status-line { opacity: 0; }
.contact.in .say-big { animation: fadeup 1s var(--ease) .1s both; }
.contact.in .copy-wrap { animation: stamp .9s cubic-bezier(.34, 1.56, .64, 1) .45s both; }
@keyframes stamp { from { opacity: 0; scale: 1.25; rotate: -3deg; } 60% { opacity: 1; scale: .98; rotate: 1deg; } to { opacity: 1; scale: 1; rotate: 0deg; } }
.contact.in .contact-links { animation: fadeup .8s var(--ease) .7s both; }
.contact.in .status-line { animation: fadeup .8s var(--ease) .9s both; }



.js .about-grid:not(.in) .deco-sticker { opacity: 0; }
.about-grid.in .deco-sticker { animation: sticker-in .7s cubic-bezier(.34, 1.8, .64, 1) .9s both, sticker-float 5s ease-in-out 1.6s infinite; }
@keyframes sticker-in { from { opacity: 0; transform: translateY(-14px) scale(.6); } to { opacity: 1; transform: none; } }
@keyframes sticker-float { 0%, 100% { transform: none; } 50% { transform: translateY(-5px); } }   /* stays straight */
/* Mouse follow uses `translate` so it stacks on the float `transform` above */
.deco-sticker[data-mouse] { translate: calc(var(--mx, 0) * 1px) calc(var(--my, 0) * 1px); transition: translate .7s var(--ease); }

/* ——— Highlighter on "great products" / "nice people" ——— */
.mark {
  background-size: 100% 38%;
  background-image: linear-gradient(var(--amarillo), var(--amarillo));
  background-repeat: no-repeat;
  background-position: 0 88%;
  transition: background-size 1s .4s var(--ease);
}
.js .reveal:not(.in) .mark { background-size: 0% 38%; }
.reveal.in .mark { background-size: 100% 38%; }
.about-head.in .mark { transition-delay: 1.15s; } /* after the words have popped in */

/* ——— Hero hover: pixelate + freeze ———
   Headline lines pixelate (SVG mosaic), the lead frosts over, the photo swaps to a
   coarse pixel copy under a glass tint. .is-frozen mirrors :hover for touch/testing. */
/* Copy: a pixelated clone sits exactly over the text and is revealed only inside a
   small circle around the cursor (main.js builds the clone and writes --x/--y). */
.fx-wrap { position: relative; }
.fx-clone {
  position: absolute; inset: 0; margin: 0; pointer-events: none; user-select: none;
  background: var(--paper); /* hides the sharp original inside the lens */
  opacity: 0; transition: opacity .2s var(--ease);
  filter: url(#pixelate);
  --r: 72px;
  -webkit-mask-image: radial-gradient(circle var(--r) at var(--x, -200px) var(--y, -200px), #000 70%, transparent 100%);
          mask-image: radial-gradient(circle var(--r) at var(--x, -200px) var(--y, -200px), #000 70%, transparent 100%);
}
.fx-clone.on { opacity: 1; }
.fx-clone.hero-lead { filter: url(#pixelate-sm); --r: 48px; }
.fx-clone, .fx-clone .line > span { animation: none !important; transform: none !important; }
.fx-clone .caret { display: none; }
.blob-pixel, .blob::after { transition: opacity .35s var(--ease); }
.blob:hover .blob-pixel, .blob.is-frozen .blob-pixel { opacity: 1; }
.blob:hover::after, .blob.is-frozen::after { opacity: 1; }
.blob:hover .blob-img { filter: blur(2px); }
.blob-img { transition: filter .35s var(--ease), opacity .5s var(--ease); }
.blob.is-swapping .blob-img { opacity: 0; }

/* ——— Client marquees ——— */
.track { animation: marquee 34s linear infinite; }
.track-reverse { animation-direction: reverse; animation-duration: 40s; }
.track-slow { animation-duration: 46s; }
@keyframes marquee { to { transform: translateX(-50%); } }
.marquee:hover .track { animation-play-state: paused; }
.chip { transition: background .25s, color .25s, transform .25s; }
a.chip:hover { background: var(--antracita); color: var(--paper); transform: translateY(-3px); }

/* ——— Buttons + arrows ——— */
.btn { transition: transform .2s ease, box-shadow .2s ease, background-color .2s ease, color .2s ease; }
.btn:hover { transform: translateY(-2px); box-shadow: 0 6px 0 var(--antracita); }
.arrow { transition: transform .25s; }
a:hover .arrow { transform: translate(3px, -3px); }

/* ——— Work accordion ——— */
.job-toggle { transition: background .25s; }
.job-toggle:hover { background: var(--antracita-2); }
.job-chev { transition: rotate .45s cubic-bezier(.34, 1.56, .64, 1), stroke .25s; }
/* Opening: panel grows, then meta, text and link rise in one after another */
.job-panel { transition: grid-template-rows .6s var(--ease); }
.job-panel-inner { transition: padding .6s var(--ease); }
.job-body > * { opacity: 0; transform: translateY(14px); transition: opacity .25s ease, transform .25s ease; }
.job.is-open .job-body > * { opacity: 1; transform: none;
  transition: opacity .5s ease var(--od, .15s), transform .6s cubic-bezier(.34, 1.56, .64, 1) var(--od, .15s); }
.job.is-open .job-meta { --od: .15s; }
.job.is-open .job-body p { --od: .25s; }
.job.is-open .job-link { --od: .38s; }
.job.is-open .job-toggle::before { animation: node-pop .6s cubic-bezier(.34, 1.8, .64, 1); }
@keyframes node-pop { 40% { scale: 1.9; } }

/* ——— Hero links: glass browser preview that follows the cursor (main.js) ———
   data-preview = live URL in an iframe (only sites that allow framing),
   data-preview-img = screenshot, shown at once and kept for sites that block framing. */
.link-preview {
  --w: 360px;
  position: fixed; left: 0; top: 0; z-index: 30; width: var(--w); padding: 6px;
  pointer-events: none; opacity: 0; scale: .9; transform-origin: 0 0;
  transition: opacity .2s var(--ease), scale .35s cubic-bezier(.34, 1.56, .64, 1);
  border-radius: 18px; background: rgba(244, 241, 234, .84);
  -webkit-backdrop-filter: blur(26px) saturate(1.5); backdrop-filter: blur(26px) saturate(1.5);
  border: 1px solid rgba(255, 255, 255, .75);
  box-shadow: 0 24px 60px rgba(47, 47, 50, .18), inset 0 1px 0 rgba(255, 255, 255, .6);
}
.link-preview.on { opacity: 1; scale: 1; }
.lp-bar { display: flex; align-items: center; gap: 10px; height: 28px; padding: 0 6px 6px; }
.lp-dots { display: flex; gap: 5px; flex: none; }
.lp-dots i { width: 8px; height: 8px; border-radius: 50%; background: rgba(47, 47, 50, .22); }
.lp-url {
  flex: 1; min-width: 0; margin-right: 34px; padding: 3px 10px; border-radius: 999px;
  background: rgba(255, 255, 255, .6); color: var(--darkgrey);
  font-size: 11px; line-height: 1.3; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.lp-view { position: relative; aspect-ratio: 16 / 10; overflow: hidden; border-radius: 12px; background: #fff; }
.lp-view img { width: 100%; height: 100%; object-fit: cover; object-position: top; }
/* iframe renders at desktop size and is scaled into the window (1280 → var(--w) minus padding) */
.lp-view iframe {
  position: absolute; left: 0; top: 0; width: 1280px; height: 800px; border: 0;
  transform: scale(calc((360 - 12) / 1280)); transform-origin: 0 0;
  opacity: 0; transition: opacity .3s ease;
}
.lp-view iframe.loaded { opacity: 1; }
.is-previewing .fx-clone { opacity: 0 !important; }   /* no pixel lens while the preview is open */

/* ——— Respect reduced motion ——— */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
  .js .line > span { transform: none; }
  .js .fade, .js .reveal { opacity: 1; transform: none; }
  .js .reveal:not(.in) .mark { background-size: 100% 38%; }
  [data-mouse], [data-parallax] { transform: none !important; }
  .caret { display: none; }
  .about-head .w, .about-head .eyebrow, .about-text, .about-facts, .about-facts li, .how-idx, .how-title, .how-text { opacity: 1 !important; }
  .fact { opacity: 1 !important; }
  .orb, .about-facts { scale: 1 !important; }
  .typing { display: none !important; }
  .deco-sticker { opacity: 1 !important; }
  .deco-sticker[data-mouse] { translate: none !important; }
  .bubble-text, .bubble-credit { opacity: 1 !important; }
  .marquee, .chip, .say-big, .contact-row > *, .status-line, .w { opacity: 1 !important; scale: 1 !important; translate: none !important; rotate: none !important; }
  .job.reveal { opacity: 1 !important; transform: none !important; }
  .about-grid::before { transform: none !important; }
}
