/* ============================================================
   Anuj Patel — portfolio v4 · dark cinematic
   System: Raycast/Cursor register. True-black base, one glowing
   accent (#8744F2, sampled from the hero film's core), layered macOS-style shadows, Inter + Geist Mono.
   ============================================================ */
:root{
  --bg:#000;--bg-2:#08080a;--surf:#0e0e11;--surf-2:#141417;--line:rgba(234,231,224,.09);--line-2:rgba(234,231,224,.16);
  --fg:#eae7e0;--fg-2:#a9a49a;--fg-3:#7f7a71;
  --acc:#8744F2;--acc-2:#C693F9;--acc-glow:rgba(135,68,242,.35);--ok:#3fd68c;--warn:#ff6b6b;
  --sans:'Inter',-apple-system,system-ui,sans-serif;--mono:'Geist Mono',ui-monospace,monospace;
  --display:'Instrument Serif','Iowan Old Style',Georgia,serif;
  --ease:cubic-bezier(.22,1,.36,1);--wrap:1200px;--pad:28px;
  /* one type scale: 12 · 14 · 16 · 20 · 26 · 46 · hero. Nothing on the page is set below 12px. */
  --t-1:12px;--t-2:14px;--t-3:16px;--t-4:20px;--t-5:clamp(22px,1.8vw,26px);--t-6:clamp(32px,3.4vw,46px);
  --sec-y:clamp(72px,7.2vw,104px);--head-gap:clamp(34px,3.4vw,48px);
  --sh-1:0 1px 0 rgba(255,255,255,.04) inset,0 0 0 1px rgba(255,255,255,.06);
  --sh-2:0 1px 0 rgba(255,255,255,.05) inset,0 0 0 1px rgba(255,255,255,.08),0 24px 60px -24px rgba(0,0,0,.8);
  --sh-3:0 1px 0 rgba(255,255,255,.06) inset,0 0 0 1px rgba(255,255,255,.1),0 40px 90px -30px rgba(0,0,0,.9),0 0 80px -20px var(--acc-glow);
}
*{margin:0;padding:0;box-sizing:border-box}
html{-webkit-font-smoothing:antialiased}
/* Lenis owns the scroll when motion is allowed; native smooth scrolling would
   fight it. Without Lenis (reduced motion, or the script failing) anchors just
   jump, which is the correct behaviour in both cases. */
html.lenis,html.lenis body{height:auto}
.lenis.lenis-smooth{scroll-behavior:auto!important}
.lenis.lenis-smooth [data-lenis-prevent]{overscroll-behavior:contain}
.lenis.lenis-stopped{overflow:hidden}
body{background:var(--bg);color:var(--fg);font-family:var(--sans);font-size:16px;line-height:1.55;letter-spacing:.005em;overflow-x:hidden;font-feature-settings:"calt","kern","liga","ss03"}
::selection{background:var(--acc);color:#fff}
a{color:inherit;text-decoration:none}img{display:block;max-width:100%;height:auto}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
kbd{font-family:var(--mono);font-size:var(--t-1);padding:2px 5px;border-radius:4px;margin-left:7px;background:linear-gradient(#1a1b1f,#0d0d0f);box-shadow:0 0 0 1px rgba(255,255,255,.12),0 1px 0 rgba(255,255,255,.06) inset;color:var(--fg-2)}
:focus-visible{outline:2px solid var(--acc);outline-offset:3px;border-radius:6px}
.wrap{width:100%;max-width:var(--wrap);margin:0 auto;padding:0 var(--pad)}
.skip{position:fixed;top:-100px;left:16px;z-index:10001;background:var(--fg);color:var(--bg);padding:10px 14px;border-radius:8px;font-size:var(--t-2)}.skip:focus{top:16px}
.prog{position:fixed;top:0;left:0;height:2px;background:var(--acc);z-index:9999;width:0;box-shadow:0 0 12px var(--acc)}
.glow{position:fixed;top:-30vh;left:50%;width:120vw;height:80vh;z-index:0;pointer-events:none;
  --gx:0px;--gy:0px;transform:translate3d(calc(-50% + var(--gx)),var(--gy),0);will-change:transform;
  background:radial-gradient(50% 60% at 50% 30%,rgba(135,68,242,.22),transparent 70%);opacity:0;animation:glowIn 1.6s var(--ease) .2s forwards}
@keyframes glowIn{to{opacity:1}}
main,.nav,.footer{position:relative;z-index:1}

/* type */
/* section label: a mono index, then the label in plain Inter, then the serif statement.
   Mono is reserved for the index (and counters); the number is the only thing that
   carries the accent — one functional colour. */
.sec-n{font-family:var(--mono);font-size:var(--t-1);letter-spacing:.06em;color:var(--acc-2);margin-right:12px}
.tag{font-size:var(--t-2);font-weight:500;color:var(--fg-3)}
.sec-h .sec-n,.sec-h .tag,.about .sec-n,.about .tag{display:inline}
.sec-h h2,.about h2{margin-top:16px}
h2{font-family:var(--display);font-size:var(--t-6);font-weight:400;letter-spacing:-.018em;line-height:1.05;max-width:20ch;color:var(--fg)}
h3{font-size:var(--t-4);font-weight:600;letter-spacing:-.02em;line-height:1.2}
.h1{font-family:var(--display);font-size:clamp(44px,5.1vw,72px);font-weight:400;letter-spacing:-.02em;line-height:1;max-width:16ch;
  background:linear-gradient(180deg,#fffdf8 0%,#cbc4b6 100%);-webkit-background-clip:text;background-clip:text;color:transparent}
.h1 em{font-style:italic;background:linear-gradient(90deg,var(--acc-2),#E6D2FF);-webkit-background-clip:text;background-clip:text;color:transparent}
.line{display:block;overflow:hidden;padding-bottom:.08em;margin-bottom:-.08em}
.line span{display:block;transform:translateY(105%);transition:transform .9s var(--ease)}
.loaded .line span{transform:none}.loaded .line:nth-child(2) span{transition-delay:.06s}.loaded .line:nth-child(3) span{transition-delay:.12s}

/* buttons */
.btn{display:inline-flex;align-items:center;gap:9px;font-size:var(--t-2);font-weight:500;padding:12px 18px;border-radius:10px;color:var(--fg);
  background:var(--surf-2);box-shadow:var(--sh-1);transition:transform .18s var(--ease),box-shadow .18s,background .18s}
.btn:hover{background:#1c1e24;transform:translateY(-1px);box-shadow:var(--sh-1),0 10px 24px -14px rgba(0,0,0,.8)}.btn:active{transform:translateY(0)}
.btn.primary{background:var(--acc);color:#fff;box-shadow:0 1px 0 rgba(255,255,255,.2) inset,0 0 0 1px rgba(135,68,242,.6),0 12px 30px -12px var(--acc-glow)}
.btn.primary:hover{background:#6472f0;box-shadow:0 1px 0 rgba(255,255,255,.25) inset,0 0 0 1px rgba(198,147,249,.7),0 16px 36px -12px var(--acc-glow)}
.btn.sm{padding:9px 13px;font-size:var(--t-2);border-radius:8px}.btn.lg{padding:16px 24px;font-size:var(--t-3);border-radius:12px}
.pill-btn{display:inline-flex;align-items:center;gap:8px;font-size:var(--t-2);font-weight:500;padding:8px 13px;border-radius:100px;background:var(--surf);box-shadow:var(--sh-1);transition:background .2s}
.pill-btn:hover{background:var(--surf-2)}.pill-btn.primary{background:var(--fg);color:var(--bg);box-shadow:none}.pill-btn.primary:hover{background:#fff}

/* nav */
.nav{position:fixed;top:14px;left:50%;transform:translateX(-50%);z-index:900;width:min(calc(100% - 32px),980px);height:56px;padding:0 10px 0 12px;display:flex;align-items:center;justify-content:space-between;
  background:rgba(12,13,16,.72);backdrop-filter:blur(18px) saturate(1.4);-webkit-backdrop-filter:blur(18px) saturate(1.4);border-radius:100px;box-shadow:0 1px 0 rgba(255,255,255,.04) inset,0 24px 60px -24px rgba(0,0,0,.8);
  transition:height .34s var(--ease),width .34s var(--ease),top .34s var(--ease),background .34s}
/* the cards' rim: lit at the top-left and bottom-right ends only, fading out between */
.nav::before{content:"";position:absolute;inset:0;border-radius:inherit;padding:1px;pointer-events:none;
  /* "to bottom right", not 135deg: on a bar this wide a fixed angle lights both ends top and bottom;
     the corner keyword runs the dark middle through the other two corners */
  background:linear-gradient(to bottom right,rgba(255,255,255,.6) 0%,rgba(255,255,255,.16) 22%,rgba(255,255,255,0) 40%,
    rgba(255,255,255,0) 60%,rgba(255,255,255,.16) 78%,rgba(255,255,255,.6) 100%);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;
  mask:linear-gradient(#000 0 0) content-box exclude,linear-gradient(#000 0 0)}
.scrolled .nav{height:48px;top:10px;width:min(calc(100% - 32px),880px);background:rgba(12,13,16,.88)}
/* the AP monogram from the Framer site, cut out of its black tile so it sits straight on the glass */
.logo{display:grid;place-items:center;width:40px;height:40px;border-radius:12px;transition:background-color .2s var(--ease)}
.logo:hover{background:rgba(255,255,255,.06)}
.logo .mark{display:block;width:30px;height:30px}
.links{display:flex;gap:4px}.links a{font-size:var(--t-2);font-weight:500;color:var(--fg-2);padding:8px 12px;border-radius:100px;transition:color .2s,background .2s}.links a:hover{color:var(--fg);background:rgba(255,255,255,.06)}
.nav-r{display:flex;gap:8px;align-items:center}
/* Let's talk: frosted glass like the footer's email pill, a shade lighter than the nav it
   sits in so it still reads as the one thing to press, with the same corner-lit rim */
.nav .pill-btn.primary{position:relative;color:#fff;background:rgba(255,255,255,.1);
  -webkit-backdrop-filter:blur(14px) saturate(1.4);backdrop-filter:blur(14px) saturate(1.4);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 8px 20px -10px rgba(0,0,0,.7);transition:background .25s var(--ease)}
.nav .pill-btn.primary::before{content:"";position:absolute;inset:0;border-radius:inherit;padding:1px;pointer-events:none;
  background:linear-gradient(to bottom right,rgba(255,255,255,.65) 0%,rgba(255,255,255,.18) 30%,rgba(255,255,255,0) 50%,
    rgba(255,255,255,.18) 70%,rgba(255,255,255,.65) 100%);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;
  mask:linear-gradient(#000 0 0) content-box exclude,linear-gradient(#000 0 0)}
.nav .pill-btn.primary:hover{background:rgba(255,255,255,.18)}

/* ============================================================
   hero — the meeting point
   A scroll-scrubbed film: a machine hand and a human hand reach for one
   point, connect, and settle into a single line. The copy is centred at
   the top; the film rises into the frame beneath it as the copy steps back,
   and owns the moment of contact.
   Phase numbers are written onto .hero by main.js:
     --u subtext in   --c copy exit   --v film rise
     --k1..k3 labels in   --kx labels out   --w Selected work rising over the film
   ------------------------------------------------------------
   TWEAK HERE ------------------------------------------------- */
.hero{
  --hero-story:320vh;   /* scroll length: headline, subtext, film, then the
                           last 100vh where Selected work rises over it    */
  --video-dim:.78;      /* film opacity at rest — high enough that the part
                           under the copy actually reads as overlapping     */
  --film-rest-y:37%;    /* how far below centre the film waits at rest.
                           Always full-width, so only the bottom edge of the
                           screen ever crops it — never an inner rectangle  */
  --scrim:.78;          /* shade pooled behind the copy, for legibility       */
  /* ---------------------------------------------------------- */
  --u:0;--c:0;--v:0;--k1:0;--k2:0;--k3:0;--kx:0;--w:0;
  position:relative}

.hero-stage{position:relative;min-height:100svh;overflow:hidden;display:flex;flex-direction:column;
  justify-content:flex-start;align-items:center;padding:clamp(112px,15vh,150px) 0 0}

/* ---- the film ---- */
.hv-media{position:absolute;inset:0;z-index:0;pointer-events:none;background:#000}
.hv-video,.hv-still,.hv-poster{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}
.hv-video,.hv-poster{opacity:calc(var(--video-dim) + var(--v) * (1 - var(--video-dim)))}
/* two-step film (desktop): the sharp poster covers the soft preview while the film
   is at rest; the sharp film waits invisible on top until it's fully downloaded */
.hv-lo,.hv-poster{display:none}
@media(min-width:821px){.hv-lo,.hv-poster{display:block}}
.hv-poster{transition:opacity .25s linear}
.hero.film-moving .hv-poster,.hero.film-hd .hv-poster{opacity:0}
.hv-hd{transition:opacity .5s var(--ease)}
@media(min-width:821px){.hero:not(.film-hd) .hv-hd{opacity:0}}
.hv-still{display:none}
.hv-marks{display:none}
/* A soft pool of shade behind the copy, not a band across the screen, so the
   image stays visible around and under the text while the words stay legible.
   Fixed to the stage: it must not move with the film. */
.hv-scrim{position:absolute;inset:0;z-index:0;pointer-events:none;
  background:radial-gradient(ellipse 46% 42% at 50% 34%,rgba(0,0,0,var(--scrim)) 0%,rgba(0,0,0,.55) 48%,transparent 78%);
  opacity:calc(1 - var(--v))}

/* ---- the copy: centred, at the top ---- */
.hv-copy{position:relative;z-index:1;display:grid;gap:clamp(16px,2vw,24px);justify-items:center;text-align:center}
.hv-sub{display:grid;gap:inherit;justify-items:center}
/* who, before what: shown at rest with the headline, and leaves with it */
.hv-eyebrow{font-size:var(--t-2);letter-spacing:.01em;
  color:var(--fg-3);margin-bottom:-8px;
  opacity:0;transform:translateY(8px);transition:opacity .8s var(--ease) .05s,transform .8s var(--ease) .05s}
.hv-eyebrow span{color:var(--fg);font-weight:500}
.loaded .hv-eyebrow{opacity:1;transform:none}
.hero .h1{max-width:none;background:none;font-size:clamp(38px,5.2vw,78px)}
/* the gradient lives on the spans, offset per line so it still reads as one wash;
   --lines on the h1 sizes it to however many lines the headline has */
.hero .h1 .line span{background:linear-gradient(180deg,#fffdf8 0%,#cbc4b6 100%);
  background-size:100% calc(var(--lines,3) * 1em);background-position:0 calc(var(--li,0) * -1em);
  -webkit-background-clip:text;background-clip:text;color:transparent}

.hero-sub{max-width:48ch;color:var(--fg-2);font-size:clamp(16px,1.25vw,18px);line-height:1.55;
  opacity:0;transform:translateY(10px);transition:opacity .8s var(--ease) .35s,transform .8s var(--ease) .35s}
.loaded .hero-sub{opacity:1;transform:none}
/* The bottom 30% of the copy sits over the image, and the core's brightest glow
   dropped contrast behind these letters to ~3:1. A tight halo darkens only what
   is directly behind the glyphs, so the overlap stays visible around them. */
.hero-sub{text-shadow:0 0 14px rgba(0,0,0,.9),0 0 4px rgba(0,0,0,.85),0 1px 1px rgba(0,0,0,.6)}

/* the same pill as the scroll cue it replaces: frosted, thin rim, a small label,
   and an arrow that keeps drifting down toward the work */
.h-cta{display:inline-flex;align-items:center;gap:10px;width:max-content;position:relative;
  padding:10px 18px;border-radius:999px;border:1px solid rgba(234,231,224,.22);
  background:rgba(14,14,17,.55);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);
  box-shadow:0 10px 30px -12px rgba(0,0,0,.8);
  font-size:var(--t-2);font-weight:500;letter-spacing:.01em;color:var(--fg-2);
  opacity:0;transform:translateY(10px);
  transition:opacity .8s var(--ease) .5s,transform .8s var(--ease) .5s,border-color .25s var(--ease),background-color .25s var(--ease),color .25s var(--ease)}
.loaded .h-cta{opacity:1;transform:none}
.h-cta .arw{display:inline-block;font-size:1.1em;letter-spacing:0;line-height:1;color:var(--acc-2);
  animation:h-cta-drift 1.9s var(--ease) infinite}
@keyframes h-cta-drift{0%,100%{transform:translateY(-1px)}50%{transform:translateY(3px)}}
.h-cta:hover,.h-cta:focus-visible{border-color:rgba(198,147,249,.6);background:rgba(135,68,242,.14);color:var(--fg)}
.h-cta:focus-visible{outline:2px solid var(--acc-2);outline-offset:4px}
@media (prefers-reduced-motion:reduce){.h-cta .arw{animation:none}}

/* ---- scroll cue: a pill button — mouse outline with one purple bead travelling
   down, and a label. Only where the hero is scrubbed (desktop, motion allowed);
   elsewhere the subtext is already on screen and there is nothing to discover. */
.hv-cue{display:none}
.hv-cue-mouse{position:relative;display:block;flex:none;width:14px;height:22px;border-radius:999px;
  border:1.5px solid rgba(234,231,224,.7);transition:border-color .25s var(--ease)}
.hv-cue-mouse i{position:absolute;left:50%;top:4px;width:3px;height:3px;margin-left:-1.5px;border-radius:50%;
  background:var(--acc-2);box-shadow:0 0 6px var(--acc),0 0 2px var(--acc-2);
  animation:hv-bead 1.9s var(--ease) infinite}
@keyframes hv-cue-in{from{opacity:0}}
@keyframes hv-bead{
  0%{transform:translateY(0);opacity:0}
  18%{opacity:1}
  70%{transform:translateY(var(--bead-run,8px));opacity:0}
  100%{transform:translateY(var(--bead-run,8px));opacity:0}}
.hv-cue-txt{font-size:var(--t-2);font-weight:500;letter-spacing:.01em;
  color:var(--fg-2);transition:color .25s var(--ease)}
.hv-cue:hover,.hv-cue:focus-visible{border-color:rgba(198,147,249,.6);background:rgba(135,68,242,.14)}
.hv-cue:hover .hv-cue-mouse,.hv-cue:focus-visible .hv-cue-mouse{border-color:var(--acc-2)}
.hv-cue:hover .hv-cue-txt,.hv-cue:focus-visible .hv-cue-txt{color:var(--fg)}
.hv-cue:focus-visible{outline:2px solid var(--acc-2);outline-offset:4px}


/* ===== >=821px · pinned and scrubbed ==================================== */
@media(min-width:821px){
  /* The film fills the stage with object-fit:cover, so its rendered scale is
     whichever of width or height is larger: max(100vw, 177.78vh) for a 16:9
     frame. --img is one hundredth of that. Every hero size below is a multiple
     of it, so text grows exactly as the image grows and the ratio between them
     is the same on a 13" laptop and a 32" monitor. Coefficients are calibrated
     to reproduce 1440x900 pixel-for-pixel, where --img = 16px. */
  .hero{height:var(--hero-story);--img:max(1vw,1.7778svh)}
  .hero-stage{padding-top:max(112px,calc(var(--img) * 8.4375))}
  .hv-copy{gap:calc(var(--img) * 1.5)}
  .hv-eyebrow{font-size:max(var(--t-2),calc(var(--img) * .875));margin-bottom:calc(var(--img) * -.5)}
  .hero .h1{font-size:min(calc(var(--img) * 4.68),13vw,220px)}   /* 13vw: never wider than a tall, narrow window */
  .hero-sub{font-size:calc(var(--img) * 1.1)}
  .h-cta{font-size:max(var(--t-2),calc(var(--img) * .875));gap:max(10px,calc(var(--img) * .75));
    padding:max(9px,calc(var(--img) * .6)) max(18px,calc(var(--img) * 1.25))}
  .hero-stage{position:sticky;top:0;height:100svh}
  .hv-copy{opacity:calc(1 - var(--c));transform:translate3d(0,calc(var(--c) * -28px),0)}
  /* The headline has the screen to itself at rest; the first scroll brings the
     subtext and CTA up beneath it, landing on the image. Scroll drives the group,
     so the children's own load-in is switched off here. */
  .hero .hero-sub{opacity:1;transform:none;transition:none}
  .hero .h-cta{opacity:1;transform:none;transition:border-color .25s var(--ease),background-color .25s var(--ease),color .25s var(--ease)}
  .hv-sub{opacity:var(--u);transform:translate3d(0,calc((1 - var(--u)) * 18px),0)}
  /* a keyboard user can tab to the CTA before scrolling — show it when they do */
  .hv-sub:focus-within{opacity:1;transform:none}
  /* the cue shares the subtext's row, so it is centred just under the headline
     and hands the space over as the subtext rises into it */
  .hv-eyebrow{grid-row:1}.hero .h1{grid-row:2}
  .hv-sub,.hv-cue{grid-row:3;grid-column:1}
  .hv-cue{display:inline-flex;align-items:center;align-self:start;justify-self:center;
    gap:max(10px,calc(var(--img) * .75));margin-top:calc(var(--img) * 1);
    padding:max(9px,calc(var(--img) * .6)) max(18px,calc(var(--img) * 1.25));
    border-radius:999px;border:1px solid rgba(234,231,224,.22);
    background:rgba(14,14,17,.55);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);
    box-shadow:0 10px 30px -12px rgba(0,0,0,.8);color:inherit;cursor:pointer;
    transition:border-color .25s var(--ease),background-color .25s var(--ease);
    opacity:clamp(0,calc(1 - var(--u) * 3),1);
    transform:translate3d(0,calc(var(--u) * 14px),0)}
  .hv-cue-mouse{width:max(14px,calc(var(--img) * .9));height:max(22px,calc(var(--img) * 1.4));
    --bead-run:max(8px,calc(var(--img) * .55))}
  .hv-cue-mouse i{top:max(4px,calc(var(--img) * .25))}
  .hv-cue-txt{font-size:max(var(--t-2),calc(var(--img) * .875))}
  /* arrives just after the headline has settled */
  .hv-cue{animation:hv-cue-in .9s var(--ease) 1s backwards}
  /* once the subtext is on its way the cue is gone: not clickable, not tabbable,
     and its loop stops running */
  .hero.cue-off .hv-cue{visibility:hidden;pointer-events:none}
  .hero.cue-off .hv-cue-mouse i{animation:none}
  /* ---- labels on the film ----
     Sized like the covered video (a 16:9 box at least as big as the stage, centred),
     so a position given as a fraction of the frame lands on the same subject whether
     the screen is wide or nearly square. */
  .hv-marks{display:block;position:absolute;left:50%;top:50%;width:max(100%,177.78svh);
    aspect-ratio:16/9;transform:translate(-50%,-50%);pointer-events:none}
  /* the section-heading pattern used down the page — a mono index over a serif word —
     so the labels read as part of the same editorial system, not as tooltips */
  .hv-mark{position:absolute;left:var(--x);top:var(--y);display:flex;align-items:center;
    gap:calc(var(--img) * .45);white-space:nowrap;text-align:center;
    text-shadow:0 0 16px rgba(0,0,0,.95),0 0 4px rgba(0,0,0,.9);
    opacity:calc(var(--k) * (1 - var(--kx)))}
  .hv-mark i{font-family:var(--mono);font-style:normal;font-size:max(var(--t-1),calc(var(--img) * .7));
    letter-spacing:.1em;color:var(--acc-2)}
  .hv-mark b{font-family:var(--display);font-weight:400;font-size:calc(var(--img) * 1.9);
    line-height:1;letter-spacing:-.015em;color:var(--fg)}
  .hv-mark::after{content:"";width:1px;height:calc(var(--img) * 2.2);flex:none}
  /* sits above its subject: label on top, leader running down to it */
  .hv-mark{flex-direction:column}
  .mk-up{transform:translate(-50%,calc(-100% + (1 - var(--k)) * 10px))}
  .mk-up::after{background:linear-gradient(var(--fg-2),transparent)}
  /* sits below its subject: leader running up to it, label underneath */
  /* leader moved above with order, so the index still sits over the word */
  .mk-down{transform:translate(-50%,calc((1 - var(--k)) * -10px))}
  .mk-down::after{order:-1}
  .mk-down::after{background:linear-gradient(transparent,var(--fg-2))}

  /* ---- the handoff: Selected work rises over the film ----
     Work is pulled up by exactly one screen, so its top edge enters from the
     bottom while the film is still pinned, and reaches the top of the screen at
     the same scroll position the pin releases — the two sections overlap, then
     carry on together with no jump. Work has no background of its own, so the
     settled film stays visible around its heading and between the cards, dimming
     as it is covered. Stacked above the hero by source order and z-index. */
  #work{margin-top:-100svh;z-index:2;
    /* The dissolve. Fully transparent at Work's top edge, so there is no line where
       the section begins, then eased to solid black over half a screen — enough that
       the heading sits over a softly dimmed film and the cards sit on black. Stepped
       along a smooth curve rather than two stops, which would band. In svh so the
       fade covers the same share of the screen on a laptop and a tall monitor. */
    background:linear-gradient(to bottom,
      rgba(0,0,0,0)   0,
      rgba(0,0,0,.05) 6svh,
      rgba(0,0,0,.16) 12svh,
      rgba(0,0,0,.33) 19svh,
      rgba(0,0,0,.55) 27svh,
      rgba(0,0,0,.76) 35svh,
      rgba(0,0,0,.91) 43svh,
      #000            52svh)}
  /* Work's glow was brightest right at its top edge (radial at 50% 0%) — invisible
     on black, but a hard horizontal line once it sits over the film. Fade it in. */
  #work::before{-webkit-mask-image:linear-gradient(to bottom,transparent 0,#000 55%);
                mask-image:linear-gradient(to bottom,transparent 0,#000 55%)}
  /* while it rises through the dissolve the heading passes over the bright halftone
     of the hands; the small mono label all but vanished there. A tight halo keeps it
     readable without re-darkening the film around it. */
  #work .sec-h .sec-n,#work .sec-h .tag,#work .sec-h h2{
    text-shadow:0 0 14px rgba(0,0,0,.95),0 0 4px rgba(0,0,0,.9)}

  /* full-width throughout; it only rises, so nothing reads as a cutout */
  .hv-media{opacity:calc(1 - var(--w) * .6);will-change:transform;
    transform:translate3d(0,calc(var(--film-rest-y) * (1 - var(--v))),0)}
}

/* ===== <=820px · a film that plays once, not a scrub ==================== */
@media(max-width:820px){
  .hero-stage{min-height:auto;overflow:visible;padding:108px 0 0}
  .hv-copy{order:0;padding:0 var(--pad)}
  .hv-media{position:relative;inset:auto;order:1;width:100%;aspect-ratio:16/9;margin-top:clamp(30px,8vw,44px)}
  .hv-video{opacity:1}
  .hv-scrim{display:none}
  .hero .h1{font-size:clamp(34px,11.5vw,60px)}   /* sized for the two-line headline */
}

/* ===== reduced motion · the connected frame, held still ================= */
@media(prefers-reduced-motion:reduce){
  .hero{height:auto}
  .hero-stage{position:relative}
  .hv-video,.hv-poster{display:none}
  .hv-still{display:block}
  .hv-copy,.hv-sub,.hv-media,.hv-eyebrow{opacity:1;transform:none}
  .hv-cue{display:none}
  #work{margin-top:0}
  .hv-scrim{opacity:1}
}


/* sections */
.sec{position:relative;isolation:isolate}
.sec::before{content:"";position:absolute;z-index:-1;top:-40px;left:50%;width:min(1100px,92vw);height:420px;
  transform:translateX(-50%);pointer-events:none;opacity:0;
  background:radial-gradient(52% 58% at 50% 0%,var(--bloom,rgba(135,68,242,.16)),transparent 72%);
  transition:opacity .9s var(--ease)}
.sec.lit::before{opacity:1}

/* section heads: the label fades in, the statement rises line by line like the hero
   headline. main.js sets html.rv and the classes; nothing is hidden without them. */
.rv .rv-h :is(.sec-n,.tag){opacity:0;transition:opacity .7s var(--ease)}
.rv .rv-h h2{opacity:0}
.rv .rv-h.in :is(.sec-n,.tag),.rv .rv-h.in h2{opacity:1}
.rv-ln{display:block;overflow:hidden;white-space:nowrap;padding-bottom:.16em;margin-bottom:-.16em}
.rv-ln > span{display:block;transform:translateY(110%);transition:transform .8s var(--ease) .08s}
.rv-ln:nth-child(2) > span{transition-delay:.15s}.rv-ln:nth-child(3) > span{transition-delay:.22s}
.rv-h.go .rv-ln > span{transform:none}
@media print{.rv .rv-h :is(.sec-n,.tag,h2){opacity:1}}
#work{--bloom:rgba(135,68,242,.17)}
#lab{--bloom:rgba(63,214,140,.10)}
#skills{--bloom:rgba(135,68,242,.11)}
#praise{--bloom:rgba(198,147,249,.12)}
@media (prefers-reduced-motion:reduce){.sec::before{transition:none}}

.sec{padding:var(--sec-y) 0}
.sec-h{margin-bottom:var(--head-gap)}

/* work grid */
/* the thread — one long curve drawn down the page as you read. It is laid over
   the page but masked out wherever it crosses a card or a block of text, so it
   reads as passing underneath them. */
/* one screen tall and fixed: the page-tall version made Safari repaint ~7,000px of svg
   every frame the line grew (1–2fps while scrolling); main.js slides .thread-cam instead */
.thread{position:fixed;left:0;top:0;width:100%;height:100vh;overflow:hidden;z-index:5;pointer-events:none}
.thread path{fill:none;stroke-linecap:round;stroke-linejoin:round}
.thread-route{stroke:rgba(234,231,224,.06);stroke-width:1;stroke-dasharray:2 8}
.thread-ink{stroke:url(#threadInk);stroke-width:1.6;opacity:.9}
.thread-glow{stroke:#8744F2;stroke-width:6;opacity:.35}
.thread-head{fill:#fff;filter:drop-shadow(0 0 4px #C693F9) drop-shadow(0 0 10px #8744F2);opacity:0;transition:opacity .4s var(--ease)}
.thread.drawing .thread-head{opacity:1}
.thread-sparks circle{fill:#E6D2FF;opacity:0}
@media (prefers-reduced-motion:reduce){.thread-sparks,.thread-head{display:none}}

/* work — outcome-led cards in one sideways row.
   On wide screens the row stays inside the content column like every other
   section, and fades at whichever edge has more cards behind it. On phones it
   runs edge to edge, so the next card peeks in. */
.rail-head{display:flex;align-items:flex-end;justify-content:space-between;gap:24px;margin-bottom:var(--head-gap)}
.rail-head .sec-h{margin-bottom:0}
.rail{--fl:0px;--fr:0px;
  display:flex;gap:22px;overflow-x:auto;overscroll-behavior-x:contain;
  scroll-snap-type:x mandatory;padding:8px 0 34px;scrollbar-width:none;
  -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 var(--fl),#000 calc(100% - var(--fr)),transparent 100%);
  mask-image:linear-gradient(90deg,transparent 0,#000 var(--fl),#000 calc(100% - var(--fr)),transparent 100%)}
.rail[data-more-l]{--fl:72px}
.rail[data-more-r]{--fr:72px}
.rail::-webkit-scrollbar{display:none}
.rail > .card{flex:0 0 clamp(300px,32vw,440px);scroll-snap-align:start}
.rail-nav{display:flex;gap:6px;flex:none}
.rail-nav[hidden]{display:none}
.rail-btn{display:grid;place-items:center;width:58px;height:48px;border:0;border-radius:14px;background:none;
  color:var(--fg);cursor:pointer;transition:color .25s var(--ease),opacity .25s var(--ease),transform .25s var(--ease)}
.rail-btn svg{width:40px;height:20px;fill:none;stroke:currentColor;stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round}
.rail-btn:hover:not(:disabled){color:var(--acc-2)}
.rail-btn[data-dir="-1"]:hover:not(:disabled){transform:translateX(-3px)}
.rail-btn[data-dir="1"]:hover:not(:disabled){transform:translateX(3px)}
.rail-btn:disabled{opacity:.25;cursor:default}
/* where you are in the row: one quiet dot per resting place, the current one a short pill */
.rail-dots{display:flex;justify-content:center;gap:4px;margin-top:-14px}
.rail-dots[hidden]{display:none}
.rail-dot{display:grid;place-items:center;width:22px;height:22px;padding:0;border:0;background:none;cursor:pointer}
.rail-dot::before{content:"";display:block;width:6px;height:6px;border-radius:6px;background:rgba(234,231,224,.2);
  transition:width .35s var(--ease),background-color .35s var(--ease)}
.rail-dot:hover::before{background:rgba(234,231,224,.45)}
.rail-dot[aria-current="true"]{width:34px}
.rail-dot[aria-current="true"]::before{width:22px;background:rgba(234,231,224,.75)}
.rail-dot:focus-visible{outline:2px solid var(--acc-2);outline-offset:1px;border-radius:8px}
@media(max-width:820px){
  /* --edge (screen edge → content edge, px) is measured in main.js: 100vw would count
     the scrollbar, and a % inside scroll-padding resolves against the row */
  .rail{scroll-padding-inline:var(--edge,0px);margin-inline:calc(var(--edge,0px) * -1);
    padding-inline:var(--edge,0px);-webkit-mask-image:none;mask-image:none}
  .rail > .card{flex-basis:84vw}
  .rail-nav{display:none}
}
/* the same glass as the testimonial cards: a dark pane that thins toward the bottom,
   frosted, with a rim lit only at the top-left and bottom-right corners */
.card{position:relative;top:0;display:flex;flex-direction:column;border-radius:28px;overflow:hidden;
  background:linear-gradient(180deg,rgba(9,9,11,.94) 0%,rgba(9,9,11,.86) 55%,rgba(9,9,11,.7) 100%);
  -webkit-backdrop-filter:blur(18px) saturate(1.15);backdrop-filter:blur(18px) saturate(1.15);
  box-shadow:0 30px 80px -30px rgba(0,0,0,.9);
  /* lifted with top, not transform: a transformed card is drawn on its own layer and
     its text goes soft whenever the grid puts it on a fractional pixel */
  transition:top .5s var(--ease),box-shadow .5s var(--ease)}
.card::before{content:"";position:absolute;inset:0;z-index:2;border-radius:inherit;padding:1.5px;pointer-events:none;
  background:linear-gradient(135deg,rgba(255,255,255,.62) 0%,rgba(255,255,255,.16) 22%,rgba(255,255,255,0) 40%,
    rgba(255,255,255,0) 60%,rgba(255,255,255,.16) 78%,rgba(255,255,255,.62) 100%);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;
  mask:linear-gradient(#000 0 0) content-box exclude,linear-gradient(#000 0 0);
  opacity:.6;transition:opacity .5s var(--ease)}
/* the testimonial beam, as a pool of accent light rising from the card's bottom edge on hover */
.card::after{content:"";position:absolute;left:0;right:0;bottom:0;height:60%;z-index:-1;pointer-events:none;
  background:radial-gradient(55% 80% at 50% 100%,rgba(135,68,242,.34),rgba(135,68,242,.1) 45%,transparent 75%);
  opacity:0;transition:opacity .6s var(--ease)}
.card{isolation:isolate}
.card:hover,.card:focus-visible{top:-4px;box-shadow:0 30px 80px -30px rgba(0,0,0,.9),0 0 60px -20px rgba(135,68,242,.35)}
/* the global focus ring squares corners to 6px; keep the card's own radius */
.card:focus-visible{border-radius:28px;outline-offset:4px}
.card:hover::before,.card:focus-visible::before{opacity:1}
.card:hover::after,.card:focus-visible::after{opacity:1}
/* the device renders are cut out on transparency, so the stage behind them is ours:
   near-black, lit from below in the accent like the testimonial beam */
.cover{aspect-ratio:16/10;overflow:hidden;flex:none;
  background:radial-gradient(62% 46% at 50% 104%,rgba(135,68,242,.42),rgba(135,68,242,.12) 55%,transparent 80%),
             radial-gradient(55% 55% at 50% 45%,rgba(198,147,249,.07),transparent 70%),#0b0b0e}
.cover img{filter:drop-shadow(0 26px 28px rgba(0,0,0,.6))}
.cover img{width:100%;height:100%;object-fit:cover;transition:transform .9s var(--ease)}
.card:hover .cover img{transform:scale(1.035)}
.card-b{padding:20px 22px 18px;display:flex;flex-direction:column;gap:10px;flex:1}
/* the product's name, then where it was built — plain Inter, no ticker-tape caps */
.card-b .eyebrow{display:flex;align-items:center;gap:7px;font-size:var(--t-2);color:var(--fg-3);margin:0}
.card-b .eyebrow b{font-weight:500;color:var(--fg-2)}
.card-b .eyebrow i{font-style:normal;opacity:.5}
.card h3{font-size:var(--t-4);font-weight:600;letter-spacing:-.022em;line-height:1.25;color:var(--fg);max-width:26ch}
.card p{font-size:var(--t-2);line-height:1.55;color:var(--fg-2);max-width:46ch}
.card p b{color:var(--fg);font-weight:500}
/* outcomes: three mini cards, the number in the display serif */
.outc{list-style:none;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px;margin:4px 0 4px}
.outc li{display:flex;flex-direction:column;gap:3px;min-width:0;padding:10px 11px 11px;border-radius:12px;
  background:rgba(255,255,255,.03);box-shadow:inset 0 0 0 1px var(--line)}
.outc b{font-family:var(--display);font-weight:400;font-size:clamp(20px,1.6vw,24px);line-height:1;letter-spacing:-.01em;
  color:var(--fg);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.outc span{font-size:var(--t-1);line-height:1.3;color:var(--fg-3)}
.proof{list-style:none;display:flex;flex-wrap:wrap;gap:5px;margin:2px 0 4px}
.proof li{font-size:var(--t-1);color:var(--fg-3);background:var(--bg-2);box-shadow:var(--sh-1);
  border-radius:100px;padding:5px 10px;font-variant-numeric:tabular-nums}
.proof b{color:var(--fg);font-weight:650;margin-right:3px}
.meta{display:flex;gap:14px;flex-wrap:wrap;align-items:center;margin-top:auto;padding-top:12px;border-top:1px solid var(--line)}
.arrow{margin-left:auto;color:var(--fg-3);transition:transform .4s var(--ease),color .3s}
.card:hover .arrow{transform:translate(3px,-3px);color:var(--acc-2)}
.meta span{font-size:var(--t-2);color:var(--fg-3)}
/* the page accent, like every other link-like label; green is kept only for "available" dots */
.meta .live{color:var(--acc-2)}

/* lab — two open-source tools, in the same glass cards as Selected work.
   The title link stretches over the whole card; the secondary link sits above it. */
.lab{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:22px}
.lab-proj h3 a{color:inherit}
.lab-proj h3 a::after{content:"";position:absolute;inset:0;z-index:1;border-radius:inherit}
.lab-proj h3 a:focus-visible{outline:none}
.lab-proj:focus-within{top:-4px;box-shadow:0 30px 80px -30px rgba(0,0,0,.9),0 0 60px -20px rgba(135,68,242,.35)}
.lab-proj:focus-within::before,.lab-proj:focus-within::after{opacity:1}
.lab-proj:has(.lab-main:focus-visible){outline:2px solid var(--acc);outline-offset:4px}
.meta a.lab-link{position:relative;z-index:3;font-size:var(--t-2);color:var(--acc-2);transition:color .2s var(--ease)}
.meta a.lab-link:hover{color:var(--fg)}

/* process stepper (compact, tabbed) */
.steps{border-radius:16px;background:var(--surf);box-shadow:var(--sh-2);overflow:hidden}
.steps-nav{display:grid;grid-template-columns:repeat(5,1fr);border-bottom:1px solid var(--line)}
.steps-nav button{display:flex;align-items:center;gap:10px;padding:16px 18px;font-size:13.5px;font-weight:500;color:var(--fg-3);border-right:1px solid var(--line);transition:color .2s,background .2s;position:relative}
.steps-nav button:last-child{border-right:0}.steps-nav button b{font-family:var(--mono);font-weight:500;font-size:11px;letter-spacing:.06em}
.steps-nav button:hover{color:var(--fg);background:rgba(255,255,255,.03)}
.steps-nav button[aria-selected="true"]{color:var(--fg);background:var(--surf-2)}
.steps-nav button[aria-selected="true"]::after{content:'';position:absolute;left:0;right:0;bottom:-1px;height:2px;background:var(--acc)}
.steps-nav button.rej b{color:var(--warn)}.steps-nav button.rej[aria-selected="true"]::after{background:var(--warn)}
.steps-body{position:relative;min-height:380px}
.step{display:none;grid-template-columns:1.2fr 1fr;gap:0}
.step.on{display:grid;animation:stepIn .45s var(--ease)}
@keyframes stepIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.step-art{padding:34px 36px;border-right:1px solid var(--line);font-size:17px;line-height:1.6;color:var(--fg-2);display:flex;flex-direction:column;justify-content:center;gap:10px;min-height:380px}
.step-art.doc,.step-art.term{display:block;padding-top:60px}.step-art.doc .doc-h,.step-art.term .doc-h{display:block;margin-bottom:14px}.step-art b{display:inline}
.step-art b{color:var(--fg);font-weight:600}
.step-art.img{padding:0;position:relative;overflow:hidden}.step-art.img img{width:100%;height:100%;object-fit:cover;object-position:left top}
.doc-h,.badge{font-family:var(--mono);font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--fg-3)}
.badge{position:absolute;left:16px;bottom:16px;background:var(--bg);color:var(--fg);padding:7px 11px;border-radius:100px;box-shadow:var(--sh-1)}
.term{font-family:var(--mono);font-size:13.5px;line-height:1.7}.pr{color:var(--acc-2);margin-right:8px}
.draft{display:inline-block;margin-top:14px;font-family:var(--mono);font-size:10.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--warn);padding:5px 9px;border-radius:6px;box-shadow:0 0 0 1px rgba(255,107,107,.5)}
.diff{font-family:var(--mono);font-size:13.5px;line-height:1.75}.diff>div{margin:6px 0}.del{color:var(--warn);text-decoration:line-through}.add{color:var(--ok)}.ctx{color:var(--fg-3);font-style:italic;margin:12px 0!important}
.step-c{padding:34px 36px;display:flex;flex-direction:column;justify-content:center;gap:12px}
.step-c h3{font-size:24px}.step-c p{color:var(--fg-2);font-size:15px;line-height:1.6}

/* about */
.about{display:grid;grid-template-columns:minmax(260px,380px) minmax(0,1fr);gap:clamp(40px,6vw,88px);align-items:center}
.about-t{display:grid;gap:28px}
.about-t h2{max-width:22ch}

/* profile card — the photo carries its own light: a blurred copy of it glows
   behind the card, and the controls sit on it as frosted glass */
.pcard{position:relative;margin:0;width:100%;max-width:380px;justify-self:center}
/* a pre-blurred copy (anuj-glow.webp, 6KB): a live blur(64px) on the full photo cost a
   ~200ms frame the first time it scrolled into view. The asset carries its own 128px of
   spill on every side, so the box is that much larger than the card. */
.pcard-glow{position:absolute;left:calc(-4% - 128px);top:calc(2% - 128px);width:calc(108% + 256px);height:calc(100% + 256px);
  object-fit:fill;opacity:.55;pointer-events:none}
.pcard-in{position:relative;aspect-ratio:5/7;border-radius:32px;overflow:hidden;isolation:isolate;
  box-shadow:0 40px 90px -40px rgba(0,0,0,.95)}
/* the testimonial cards' rim: lit at the top-left and bottom-right corners only,
   fading to nothing along the other two; it brightens when the card is hovered */
.pcard-rim{position:absolute;inset:0;z-index:2;border-radius:inherit;padding:1.5px;pointer-events:none;
  background:linear-gradient(135deg,rgba(255,255,255,.62) 0%,rgba(255,255,255,.16) 22%,rgba(255,255,255,0) 40%,
    rgba(255,255,255,0) 60%,rgba(255,255,255,.16) 78%,rgba(255,255,255,.62) 100%);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;
  mask:linear-gradient(#000 0 0) content-box exclude,linear-gradient(#000 0 0);
  opacity:.75;transition:opacity .5s var(--ease)}
.pcard:hover .pcard-rim{opacity:1}
.pcard-img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:-2}
/* shade under the controls at top and bottom, with a wash of the accent low down */
.pcard-in::before{content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;
  background:linear-gradient(180deg,rgba(6,8,30,.38) 0%,rgba(6,8,30,0) 22%,rgba(6,8,30,0) 58%,rgba(40,16,90,.38) 82%,rgba(8,6,24,.72) 100%)}
.pcard-who,.pcard-btn{background:rgba(255,255,255,.2);-webkit-backdrop-filter:blur(14px) saturate(1.3);backdrop-filter:blur(14px) saturate(1.3);
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.18),0 8px 24px -10px rgba(0,0,0,.45)}
.pcard-who{position:absolute;left:14px;top:14px;display:flex;align-items:center;gap:10px;padding:6px 18px 6px 6px;border-radius:999px;margin:0}
.pcard-who img{width:40px;height:40px;border-radius:50%;object-fit:cover;box-shadow:0 0 0 2px rgba(255,255,255,.35)}
.pcard-who b{display:block;font-size:var(--t-2);font-weight:600;letter-spacing:-.01em;line-height:1.2;color:#fff}
.pcard-who span span{display:block;font-size:var(--t-1);line-height:1.3;color:rgba(255,255,255,.78)}
.pcard-btn{display:grid;place-items:center;width:48px;height:48px;border-radius:50%;color:#fff;
  transition:background-color .25s var(--ease),transform .25s var(--ease)}
.pcard-btn svg{width:21px;height:21px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.pcard-btn svg.fill{fill:currentColor;stroke:none}
a.pcard-btn:hover{background:rgba(255,255,255,.34)}
a.pcard-btn:focus-visible{outline:2px solid #fff;outline-offset:3px;border-radius:50%}
.pcard-open{position:absolute;right:14px;top:14px;width:44px;height:44px}
.pcard-open i{width:10px;height:10px;border-radius:50%;background:var(--ok);box-shadow:0 0 0 4px rgba(63,214,140,.25),0 0 12px var(--ok)}
.pcard-acts{position:absolute;left:14px;bottom:14px;display:flex;gap:10px}
.pcard-end{position:absolute;right:14px;bottom:14px}

/* hover: the card tilts toward the pointer, a soft glare follows it across the glass,
   the photo eases in and the glow behind swells. Tilt is set by main.js; without it
   (touch, no JS) the zoom, glare and glow still answer the hover. */
.pcard{perspective:1100px}
.pcard-in{transform:none;transition:transform .7s var(--ease),box-shadow .6s var(--ease)}
.pcard.is-tilt .pcard-in{transform:rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg)) scale(1.015);transition:transform .15s ease-out,box-shadow .6s var(--ease)}
.pcard:hover .pcard-in{box-shadow:0 50px 100px -40px rgba(0,0,0,.95),0 0 70px -20px rgba(135,68,242,.45)}
.pcard-img{transition:transform .9s var(--ease)}
.pcard:hover .pcard-img{transform:scale(1.05)}
.pcard-glow{transition:opacity .6s var(--ease)}
.pcard:hover .pcard-glow{opacity:.8}
.pcard-in::after{content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;opacity:0;
  background:radial-gradient(circle at var(--gx,50%) var(--gy,25%),rgba(255,255,255,.16),rgba(255,255,255,0) 45%);
  transition:opacity .5s var(--ease)}
.pcard:hover .pcard-in::after{opacity:1}
/* the controls step up one after another */
.pcard-who,.pcard-open,.pcard-acts .pcard-btn,.pcard-end{transition:transform .5s var(--ease),background-color .25s var(--ease)}
.pcard:hover .pcard-who,.pcard:hover .pcard-open{transform:translateY(2px)}
.pcard:hover .pcard-acts .pcard-btn,.pcard:hover .pcard-end{transform:translateY(-3px)}
.pcard:hover .pcard-acts .pcard-btn:nth-child(2){transition-delay:.05s}
.pcard:hover .pcard-end{transition-delay:.1s}
.pcard:hover a.pcard-btn:hover{transform:translateY(-5px);transition-delay:0s}
@media (prefers-reduced-motion:reduce){
  .pcard-in,.pcard.is-tilt .pcard-in,.pcard:hover .pcard-img,.pcard:hover .pcard-who,.pcard:hover .pcard-open,
  .pcard:hover .pcard-acts .pcard-btn,.pcard:hover .pcard-end{transform:none;transition:none}
}
.about-c p{color:var(--fg-2);line-height:1.65;font-size:var(--t-3);max-width:52ch;margin-bottom:16px}
.about-c p b{color:var(--fg);font-weight:500}
/* the closing line, set apart: the display serif, with an accent rule */
.about-q{margin:26px 0 8px;padding-left:18px;border-left:2px solid var(--acc-2);max-width:40ch;
  font-family:var(--display);font-style:italic;font-size:var(--t-5);line-height:1.3;color:var(--fg)}
.about-cv{margin-top:16px;gap:10px}
.about-cv svg{flex:none;transition:transform .18s var(--ease)}
.about-cv:hover svg{transform:translateY(2px)}

/* skills — seven of the page's glass cards, four to a row, the forward-looking one
   closing the grid wide. Index at the top, the discipline at the bottom, nothing
   else; the same corner-lit rim and hover light as the work cards. */
.skills{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px}
.sk{position:relative;isolation:isolate;overflow:hidden;display:flex;flex-direction:column;justify-content:space-between;gap:36px;
  min-height:150px;padding:20px 22px 22px;border-radius:22px;
  background:linear-gradient(180deg,rgba(14,14,17,.9) 0%,rgba(9,9,11,.82) 100%);
  box-shadow:0 30px 70px -40px rgba(0,0,0,.9);transition:box-shadow .5s var(--ease)}
.sk::before{content:"";position:absolute;inset:0;z-index:1;border-radius:inherit;padding:1.5px;pointer-events:none;
  background:linear-gradient(to bottom right,rgba(255,255,255,.5) 0%,rgba(255,255,255,.12) 22%,rgba(255,255,255,0) 40%,
    rgba(255,255,255,0) 60%,rgba(255,255,255,.12) 78%,rgba(255,255,255,.5) 100%);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;
  mask:linear-gradient(#000 0 0) content-box exclude,linear-gradient(#000 0 0);
  opacity:.55;transition:opacity .5s var(--ease)}
/* light rising from the bottom edge — always on for the AI card, on hover for the rest */
.sk::after{content:"";position:absolute;left:0;right:0;bottom:0;height:75%;z-index:-1;pointer-events:none;
  background:radial-gradient(60% 90% at 50% 100%,rgba(135,68,242,.26),rgba(135,68,242,.07) 50%,transparent 78%);
  opacity:0;transition:opacity .6s var(--ease)}
.sk:hover{box-shadow:0 30px 70px -40px rgba(0,0,0,.9),0 0 60px -24px rgba(135,68,242,.35)}
.sk:hover::before{opacity:1}
.sk:hover::after{opacity:1}
.sk:nth-child(7){grid-column:span 2}
.sk:nth-child(7)::after{opacity:.75}
.sk:nth-child(7):hover::after{opacity:1}
.sk-n{font-family:var(--mono);font-size:var(--t-1);letter-spacing:.06em;color:var(--fg-3)}
.sk:nth-child(7) .sk-n{color:var(--acc-2)}
.sk h3{font-family:var(--display);font-weight:400;font-size:var(--t-5);line-height:1.1;letter-spacing:-.01em;color:var(--fg)}

/* testimonials — a lit carousel
   One quote at a time stands in a beam of accent light rising from a bright
   slit below it; the neighbours sit either side, smaller and dimmed. Every
   card shares one grid cell, so the stage is as tall as the longest quote and
   position is nothing but a transform. main.js writes --o, each card's offset
   from the active one. Without JS it is a plain horizontal scroller. */
.pr{--card-w:clamp(300px,28vw,420px);--step:clamp(270px,31vw,460px);--side:.72;
  position:relative;overflow-x:clip;padding:0 var(--pad)}
/* a custom property takes round() unchecked, so an older browser would lose the width
   entirely rather than fall back — gate it */
@supports (width:round(1px,2px)){@media(min-width:821px){.pr{--card-w:round(clamp(300px,28vw,420px),2px)}}}
.pr-stage{position:relative;max-width:1440px;margin:0 auto;padding:clamp(20px,3vw,40px) 0 clamp(120px,12vw,170px)}
.pr-slides{display:flex;gap:18px;overflow-x:auto;scroll-snap-type:x mandatory;padding-bottom:6px}

.pr-card{position:relative;flex:0 0 var(--card-w);scroll-snap-align:center;margin:0;
  display:flex;flex-direction:column;padding:clamp(28px,2.6vw,38px);border-radius:28px;
  /* dark at the top where the words are, thinning toward the bottom so the beam shows through */
  background:linear-gradient(180deg,rgba(9,9,11,.94) 0%,rgba(9,9,11,.82) 55%,rgba(9,9,11,.5) 100%);
  box-shadow:none}
/* the rim catches light at two opposite corners only — top-left and bottom-right —
   and fades to nothing along the other two, like the edge of a glass pane.
   A gradient ring cut out of a pseudo-element, since border-image ignores radius. */
.pr-card::before{content:"";position:absolute;inset:0;border-radius:inherit;padding:1.5px;pointer-events:none;
  background:linear-gradient(135deg,rgba(255,255,255,.62) 0%,rgba(255,255,255,.16) 22%,rgba(255,255,255,0) 40%,
    rgba(255,255,255,0) 60%,rgba(255,255,255,.16) 78%,rgba(255,255,255,.62) 100%);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;
  mask:linear-gradient(#000 0 0) content-box exclude,linear-gradient(#000 0 0);
  opacity:.45;transition:opacity .6s var(--ease)}
.pr.is-ready .pr-card[data-pos="active"]::before{opacity:1}
.pr-mark{font-family:var(--display);font-size:64px;line-height:.6;height:28px;color:var(--acc-2)}
.pr-card blockquote{margin-top:12px;font-family:var(--sans);font-size:var(--t-3);
  line-height:1.62;letter-spacing:-.006em;color:var(--fg)}
.pr-who{margin-top:auto;padding-top:30px;display:flex;align-items:center;justify-content:flex-start;gap:12px}
.pr-who b{display:block;font-size:var(--t-2);font-weight:600;letter-spacing:-.01em;color:var(--fg);line-height:1.25}
.pr-who > span > span{display:block;margin-top:2px;font-size:var(--t-1);color:var(--fg-3)}
/* the "button" of the reference: a solid pill, bright only on the lit card */
/* their photo, from the Framer site — a thin light rim, brighter on the lit card */
.pr-av{flex:none;display:block;width:44px;height:44px;border-radius:50%;object-fit:cover;
  box-shadow:0 0 0 1.5px rgba(234,231,224,.18);transition:box-shadow .5s var(--ease)}

.pr-arw{display:none}
.pr-beam{display:none}
.pr-count{display:none}

/* ---- JS on: the stage ---- */
.pr.is-ready .pr-slides{display:grid;overflow:visible;padding:0;justify-items:center;position:relative;z-index:1;
  width:round(down,100%,2px)}   /* even row + even card = the centred card starts on a whole pixel */
.pr.is-ready .pr-card{grid-area:1/1;width:var(--card-w);
  transform:translate3d(calc(var(--o,0) * var(--step)),0,0) scale(var(--side));
  opacity:0;pointer-events:none;
  transition:transform .75s var(--ease),opacity .6s var(--ease),box-shadow .6s var(--ease),background .6s var(--ease)}
.pr.is-ready .pr-card[data-pos="side"]{opacity:.38;pointer-events:auto;cursor:pointer;z-index:1}
.pr.is-ready .pr-card[data-pos="side"]:hover{opacity:.55}
/* transform:none at rest, not an identity matrix: a card left on its own GPU layer at a
   half-pixel offset (it is centred in an odd-width row) rasterises its text soft.
   Frosted like glass, so the beam behind diffuses into an even glow instead of
   showing the cone's edges through the card. */
.pr.is-ready .pr-card[data-pos="active"]{opacity:1;z-index:2;transform:none;
  -webkit-backdrop-filter:blur(18px) saturate(1.15);backdrop-filter:blur(18px) saturate(1.15);
  background:linear-gradient(180deg,rgba(9,9,11,.9) 0%,rgba(9,9,11,.66) 45%,rgba(20,8,40,.12) 100%);
  box-shadow:0 30px 80px -30px rgba(0,0,0,.9),0 0 60px -20px rgba(135,68,242,.35)}
.pr.is-ready .pr-card[data-pos="active"] .pr-av{box-shadow:0 0 0 1.5px rgba(234,231,224,.55),0 0 18px -4px rgba(198,147,249,.55)}
/* the role sits in the brightest part of the beam; one step lighter keeps it above 4.5:1 */
.pr.is-ready .pr-card[data-pos="active"] .pr-who > span > span{color:var(--fg-2)}

/* the light: a hot slit below the card, a cone rising from it, a pool where it lands */
.pr.is-ready .pr-beam{display:block;position:absolute;left:50%;bottom:clamp(70px,7vw,96px);z-index:0;
  width:calc(var(--card-w) * 1.08);height:calc(100% - clamp(70px,7vw,96px));transform:translateX(-50%);pointer-events:none}
/* blurred as a group, so the cone's clipped edges soften instead of staying sharp */
/* pre-rendered (testimonials-cone.webp): the same clipped gradient blurred by 22px, with its
   spill included — a live blur here cost a long frame the first time the carousel appeared */
.pr-cone{position:absolute;top:calc(18% - 66px);left:-66px;right:-66px;bottom:-66px;
  background:url(assets/testimonials-cone.webp) center/100% 100% no-repeat}
.pr-beam::after{content:"";position:absolute;left:50%;bottom:0;width:40%;height:3px;transform:translateX(-50%);border-radius:3px;
  background:linear-gradient(90deg,rgba(198,147,249,0),#EBDDFF 30%,#fff 50%,#EBDDFF 70%,rgba(198,147,249,0));
  box-shadow:0 0 18px 4px rgba(198,147,249,.75),0 0 60px 14px rgba(135,68,242,.55)}

.pr.is-ready .pr-arw{display:grid;place-items:center;position:absolute;top:calc(50% - clamp(50px,6vw,85px));z-index:3;
  width:64px;height:64px;margin-top:-32px;border:0;border-radius:50%;background:none;color:var(--fg);cursor:pointer;
  transition:color .25s var(--ease),transform .25s var(--ease)}
.pr-arw svg{width:48px;height:24px;fill:none;stroke:currentColor;stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round}
.pr-prev{left:0}.pr-next{right:0}
.pr-arw:hover,.pr-arw:focus-visible{color:var(--acc-2)}
.pr-prev:hover{transform:translateX(-4px)}.pr-next:hover{transform:translateX(4px)}
.pr-arw:focus-visible{outline:2px solid var(--acc-2);outline-offset:2px}

.pr.is-ready .pr-count{display:flex;align-items:center;justify-content:center;gap:12px;position:relative;z-index:1;
  margin-top:calc(clamp(-60px,-5vw,-44px));font-family:var(--mono);font-size:var(--t-1);letter-spacing:.1em;color:var(--fg-3)}
.pr-auto{position:relative;display:grid;place-items:center;width:30px;height:30px;padding:0;border:0;border-radius:50%;
  background:none;color:var(--fg-2);cursor:pointer;transition:color .2s var(--ease)}
.pr-auto:hover{color:var(--fg)}
.pr-auto:focus-visible{outline:2px solid var(--acc-2);outline-offset:2px}
.pr-ring{position:absolute;inset:1px;width:28px;height:28px;transform:rotate(-90deg)}
.pr-ring circle{fill:none;stroke:var(--line-2);stroke-width:1.5}
/* 2πr for r=12 */
.pr-ring .pr-ring-fill{stroke:var(--acc-2);stroke-linecap:round;stroke-dasharray:75.4;stroke-dashoffset:75.4}
.pr-auto.run .pr-ring-fill{animation:pr-fill var(--auto,3s) linear forwards}
.pr-auto.hold .pr-ring-fill{animation-play-state:paused}
@keyframes pr-fill{to{stroke-dashoffset:0}}
.pr-ic{width:11px;height:11px;fill:none;stroke:currentColor;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
.pr-ic-play{fill:currentColor;stroke-width:1}
.pr-ic-play,.pr-auto[aria-pressed="true"] .pr-ic-pause{display:none}
.pr-auto[aria-pressed="true"] .pr-ic-play{display:block}
.pr-count b{font-weight:500;color:var(--fg)}
.pr.is-ready .pr-stage{touch-action:pan-y}

/* too narrow for arrows beside the cards: they move down and flank the counter */
@media(max-width:1100px){
  .pr-stage{padding-bottom:120px}
  .pr.is-ready .pr-beam{bottom:66px;height:calc(100% - 66px)}
  .pr.is-ready .pr-arw{top:auto;bottom:-8px;margin-top:0;width:56px;height:56px}
  .pr-prev{left:calc(50% - 124px)}.pr-next{right:calc(50% - 124px)}
  .pr-arw svg{width:36px;height:18px}
  .pr.is-ready .pr-count{margin-top:-34px}
}
@media(max-width:820px){
  .pr{--card-w:min(84vw,420px);--step:calc(var(--card-w) + 14px);--side:.9}
}
@media (prefers-reduced-motion:reduce){
  .pr.is-ready .pr-card,.pr-av,.pr-arw{transition:none}
}

/* two across: the opener stays wide, the other six pair up */
@media(max-width:1000px){.skills{grid-template-columns:repeat(3,minmax(0,1fr))}}
@media(max-width:820px){.skills{grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.sk{gap:26px;min-height:124px}}

/* footer */
.footer{padding:var(--sec-y) 0 30px;overflow:hidden;background:var(--bg)}
.footer > .wrap{position:relative;z-index:1}
/* the only light in the footer: a hairline across the top, brightest in the middle and
   gone at the ends, with the faintest wash of accent falling from it. The same idea
   as the slit of light under the testimonial cards, turned into the page's last line. */
/* the lily: rooted at the footer's bottom edge, its head in the open space between the
   heading and the email pill. Stem and leaves fade into the dark before the link
   columns, so it never sits behind text at strength. It sways, barely. */
.f-flower{position:absolute;left:50%;top:clamp(36px,5vw,72px);bottom:0;z-index:0;pointer-events:none;
  /* the bloom (46% across the image) centred in the open space between the
     heading's text and the email pill, which sits 30px right of centre at every width */
  transform:translateX(calc(-46% + 30px));
  -webkit-mask-image:linear-gradient(to bottom,#000 0,#000 40%,rgba(0,0,0,.2) 60%,rgba(0,0,0,.12) 80%,transparent 97%);
          mask-image:linear-gradient(to bottom,#000 0,#000 40%,rgba(0,0,0,.2) 60%,rgba(0,0,0,.12) 80%,transparent 97%)}
/* a little of its own colour on the black around the bloom */
.f-flower::before{content:"";position:absolute;left:-20%;right:-20%;top:-4%;height:48%;z-index:-1;
  background:radial-gradient(ellipse 50% 50% at 52% 45%,rgba(135,68,242,.09),rgba(64,180,255,.03) 55%,transparent 75%)}
/* dim until the thread reaches it, then it wakes */
.f-flower{opacity:.26;transition:opacity 1.6s var(--ease)}
.footer.bloom .f-flower{opacity:.6}
.f-flower img{display:block;height:100%;width:auto;max-width:none}

/* ---- the lily's motion ----------------------------------------------------------
   wake   : it waits a touch small and low, and rises to full size as the thread arrives
   sway   : tiny, from the stem's base, so the stamen stays on the end of the line
   shine  : a band of light crosses the glass every few seconds, masked to the flower
   pollen : once awake, specks lift off the stamens and fade
   burst  : one ring of light from the stamen tip at the moment the line touches it   */
.f-wake{height:100%;transform:scale(.965);transform-origin:56% 100%;transition:transform 1.8s var(--ease)}
.footer.bloom .f-wake{transform:none}
.f-sway{position:relative;height:100%;transform-origin:56% 100%;animation:f-sway 11s ease-in-out infinite alternate}
@keyframes f-sway{from{transform:rotate(-.4deg)}to{transform:rotate(.4deg)}}

.f-shine{position:absolute;inset:0;pointer-events:none;mix-blend-mode:screen;
  -webkit-mask:var(--flower) center/100% 100% no-repeat;mask:var(--flower) center/100% 100% no-repeat;
  background:linear-gradient(105deg,rgba(255,255,255,0) 40%,rgba(230,210,255,.55) 50%,rgba(255,255,255,0) 60%) no-repeat;
  background-size:260% 100%;background-position:140% 0;animation:f-shine 8s cubic-bezier(.45,0,.2,1) infinite}
@keyframes f-shine{0%{background-position:140% 0}38%,100%{background-position:-40% 0}}

/* the stamens sit in the top-right of the bloom: roughly 52–72% across, 2–14% down */
.f-pollen{position:absolute;left:52%;top:2%;width:20%;height:12%;pointer-events:none;opacity:0;transition:opacity 1.2s var(--ease) .6s}
.footer.bloom .f-pollen{opacity:1}
.f-pollen i{position:absolute;left:var(--x);top:var(--y);width:3px;height:3px;border-radius:50%;
  background:#ffe2b8;box-shadow:0 0 6px 1px rgba(255,190,240,.8);opacity:0;
  animation:f-pollen var(--t) ease-out var(--d) infinite}
.f-pollen i:nth-child(1){--x:62%;--y:6%;--t:6s;--d:0s;--dx:-10px}
.f-pollen i:nth-child(2){--x:88%;--y:28%;--t:7.5s;--d:1.2s;--dx:14px}
.f-pollen i:nth-child(3){--x:40%;--y:40%;--t:6.8s;--d:2.6s;--dx:-18px}
.f-pollen i:nth-child(4){--x:70%;--y:52%;--t:8.2s;--d:3.4s;--dx:8px}
.f-pollen i:nth-child(5){--x:22%;--y:70%;--t:7s;--d:4.6s;--dx:-6px}
.f-pollen i:nth-child(6){--x:96%;--y:62%;--t:6.4s;--d:5.3s;--dx:20px}
.f-pollen i:nth-child(7){--x:52%;--y:18%;--t:9s;--d:6.1s;--dx:-14px}
@keyframes f-pollen{
  0%{opacity:0;transform:translate(0,0) scale(.6)}
  15%{opacity:1}
  100%{opacity:0;transform:translate(var(--dx),-90px) scale(1.1)}}

/* sits outside the wake/sway wrappers so it lands exactly where the thread ends */
.f-burst{position:absolute;left:66.1%;top:3.72%;width:14px;height:14px;margin:-7px 0 0 -7px;border-radius:50%;
  pointer-events:none;opacity:0;box-shadow:0 0 0 1.5px rgba(230,210,255,.9),0 0 18px 4px rgba(198,147,249,.6)}
.footer.bloom .f-burst{animation:f-burst 1.6s cubic-bezier(.2,.7,.2,1) 1}
@keyframes f-burst{0%{opacity:1;transform:scale(.4)}100%{opacity:0;transform:scale(9)}}
@media(max-width:860px){
  .f-flower{left:auto;right:-14%;top:24px;bottom:auto;height:440px;transform:none;opacity:.1}
  .footer.bloom .f-flower{opacity:.26}
}
@media (prefers-reduced-motion:reduce){.f-sway,.f-shine,.f-pollen i,.footer.bloom .f-burst{animation:none}.f-wake{transform:none;transition:none}.f-pollen{display:none}}
.f-horizon{position:absolute;left:0;right:0;top:0;height:220px;pointer-events:none;z-index:0;
  background:radial-gradient(ellipse 42% 100% at 50% 0%,rgba(135,68,242,.10),rgba(135,68,242,0) 70%)}
.f-horizon::before{content:"";position:absolute;left:0;right:0;top:0;height:1px;
  background:linear-gradient(90deg,rgba(198,147,249,0) 8%,rgba(198,147,249,.55) 50%,rgba(198,147,249,0) 92%)}

.f-cta{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:48px;align-items:start;
  padding-bottom:clamp(44px,5vw,64px)}
.f-cta h2{margin-top:2px}
.f-cta > div:first-child > p{color:var(--fg-2);margin-top:16px;font-size:var(--t-3);line-height:1.6;max-width:44ch}
.f-avail{display:inline-flex!important;align-items:center;gap:9px;margin-top:18px!important;
  font-size:var(--t-2)!important;color:var(--fg-2)!important}
.f-avail .dot{width:7px;height:7px;border-radius:50%;background:var(--ok);flex:none;
  box-shadow:0 0 0 0 rgba(63,214,140,.5);animation:fpulse 2.4s infinite}
@keyframes fpulse{70%{box-shadow:0 0 0 8px rgba(63,214,140,0)}100%{box-shadow:0 0 0 0 rgba(63,214,140,0)}}

.f-act{display:flex;flex-direction:column;align-items:flex-end;gap:12px}
/* frosted glass like the profile card's pills, with the nav's rim: lit at the
   top-left and bottom-right ends only */
.f-mail{position:relative;display:inline-flex;align-items:center;gap:14px;padding:16px 24px;border-radius:999px;
  background:rgba(255,255,255,.07);-webkit-backdrop-filter:blur(18px) saturate(1.4);backdrop-filter:blur(18px) saturate(1.4);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.08),0 18px 40px -18px rgba(0,0,0,.8);
  transition:background-color .25s var(--ease),transform .3s var(--ease)}
.f-mail::before{content:"";position:absolute;inset:0;border-radius:inherit;padding:1px;pointer-events:none;
  background:linear-gradient(to bottom right,rgba(255,255,255,.6) 0%,rgba(255,255,255,.16) 22%,rgba(255,255,255,0) 40%,
    rgba(255,255,255,0) 60%,rgba(255,255,255,.16) 78%,rgba(255,255,255,.6) 100%);
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;
  mask:linear-gradient(#000 0 0) content-box exclude,linear-gradient(#000 0 0)}
.f-mail:hover{background:rgba(255,255,255,.12);transform:translateY(-2px)}
.f-mail:focus-visible{outline:2px solid var(--acc-2);outline-offset:4px;border-radius:999px}
.f-mail .v{font-size:var(--t-3);font-weight:600;letter-spacing:-.015em;color:#fff}
.f-mail svg{color:rgba(255,255,255,.6);transition:transform .35s var(--ease),color .3s}
.f-mail:hover svg{transform:translate(3px,-3px);color:var(--acc-2)}
.f-time{font-family:var(--mono);font-size:var(--t-1);letter-spacing:.04em;color:var(--fg-3)}

.f-cols{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:32px;
  padding:clamp(30px,3.4vw,42px) 0;border-top:1px solid var(--line)}
.f-col{display:flex;flex-direction:column;gap:9px;align-items:flex-start}
.f-h{font-size:var(--t-1);font-weight:500;color:var(--fg-3);margin-bottom:3px}
.f-col a{font-size:var(--t-2);color:var(--fg-2);transition:color .2s}
.f-col a:hover{color:var(--fg)}

/* ---- Pixel, the cleanup robot ----------------------------------------------------
   An original character in the spirit of a certain lonely trash-compacting robot.
   It lives on the footer's ground line, compacts design junk into cubes and stops to
   look after the lily. main.js drives it; positions are fractions of the row's width. */
.bot-row{position:relative;height:118px;margin-top:6px}
.bot-stage{position:absolute;inset:0}
.junk,.cube{position:absolute;bottom:0;left:0;transform:translateX(var(--x,0px));transition:opacity .35s var(--ease),transform .45s var(--ease)}
.junk{display:inline-flex;align-items:center;gap:5px;white-space:nowrap;margin-bottom:2px}
.junk.taken{opacity:0;transform:translateX(var(--to,0px)) translateY(-10px) scale(.2)}
.j-lorem{font:italic 11px var(--mono);color:var(--fg-3);padding:3px 7px;border:1px dashed var(--line-2);border-radius:5px}
.j-comic{font:700 20px/1 "Comic Sans MS","Comic Neue","Chalkboard SE",cursive;color:#ffb27a;transform:translateX(var(--x,0px)) rotate(-8deg)}
.j-file{font:10.5px var(--mono);color:var(--fg-2);padding:4px 8px 4px 6px;border-radius:6px;background:rgba(255,255,255,.05);box-shadow:inset 0 0 0 1px var(--line-2)}
.j-file i{width:9px;height:11px;border-radius:2px;background:linear-gradient(135deg,#a259ff,#f24e1e 50%,#1abcfe)}
.j-nudge{width:22px;height:18px}
.j-nudge i{position:absolute;inset:0;border:1.5px solid var(--fg-3);border-radius:3px;transform:translate(3px,-3px)}
.j-nudge em{position:absolute;left:100%;top:-14px;margin-left:4px;font:italic 9.5px var(--mono);color:#ff8a80;font-style:normal}
.cube{width:14px;height:14px;border-radius:3.5px;margin-bottom:0;
  background:linear-gradient(150deg,#3d3d49 0%,#1f1f26 55%,#141418 100%);
  box-shadow:inset 1px 1px 0 rgba(255,255,255,.28),inset -1px -1px 0 rgba(255,255,255,.06),inset 0 -3px 5px rgba(135,68,242,.22),0 3px 6px -2px rgba(0,0,0,.7)}
.cube::after{content:"";position:absolute;left:3px;right:3px;top:4px;height:1px;background:rgba(255,255,255,.14);box-shadow:0 3px 0 rgba(255,255,255,.07),0 6px 0 rgba(198,147,249,.18)}
.cube.new{animation:bot-cube .5s cubic-bezier(.3,1.6,.5,1)}
@keyframes bot-cube{from{transform:translateX(var(--x)) translateY(-26px) scale(.4);opacity:0}}

.bot{position:absolute;left:0;bottom:0;width:62px;height:70px;padding:0;border:0;background:none;cursor:pointer;
  transform:translateX(var(--x,-60px)) scaleX(var(--face,1));transform-origin:31px 100%;-webkit-tap-highlight-color:transparent}
.bot:focus-visible{outline:2px solid var(--acc-2);outline-offset:4px;border-radius:12px}
.bot-art{display:block;width:62px;height:70px;overflow:visible}
/* the art carries its own gradients; CSS adds light and life */
.bot-shadow{fill:#000;opacity:.55}
.bot-eyes ellipse{transform-box:fill-box;transform-origin:center;fill:#E9D8FF;filter:drop-shadow(0 0 2.5px #A86BFF) drop-shadow(0 0 .8px #fff);transition:transform .35s var(--ease),fill .25s var(--ease)}
.bot-eyes{transform-box:fill-box;transform-origin:center;animation:bot-blink 4.2s infinite}
@keyframes bot-blink{0%,93%,100%{transform:scaleY(1)}96%{transform:scaleY(.1)}}
.bot-ant-tip{filter:drop-shadow(0 0 3px #8744F2)}
.bot-ant-halo{fill:#8744F2;opacity:.3;transform-box:fill-box;transform-origin:center;animation:bot-halo 2.6s ease-in-out infinite}
@keyframes bot-halo{0%,100%{transform:scale(.8);opacity:.35}50%{transform:scale(1.5);opacity:0}}
.bot-led{fill:#6fcb9a;filter:drop-shadow(0 0 1.5px #6fcb9a)}
.bot-led-a{fill:#C693F9;filter:drop-shadow(0 0 1.5px #8744F2);animation:bot-led 1.6s steps(2) infinite}
@keyframes bot-led{50%{opacity:.35}}
.bot-arm{transform-box:fill-box;transition:transform .3s var(--ease)}
.bot-arm-l{transform-origin:100% 0}.bot-arm-r{transform-origin:0 0}
.bot-wheel{transform-box:fill-box;transform-origin:center;transform:rotate(var(--spin,0deg))}
.bot:hover .bot-eyes ellipse{fill:#fff}
.bot.happy .bot-eyes ellipse{transform:scaleY(.55) translateY(-1px)}
.bot-head{transform-box:fill-box;transform-origin:50% 100%;transition:transform .45s var(--ease)}

.bot.moving .bot-art{animation:bot-bob .32s ease-in-out infinite alternate}
@keyframes bot-bob{to{transform:translateY(-1.5px)}}
.bot.scoop .bot-arm-l{transform:rotate(-40deg)}.bot.scoop .bot-arm-r{transform:rotate(40deg)}
.bot.compact .bot-art{animation:bot-squash .6s ease-in-out}
@keyframes bot-squash{0%,100%{transform:none}25%{transform:scale(1.08,.86) translateY(3px)}55%{transform:scale(.94,1.06)}75%{transform:scale(1.04,.95)}}
.bot.look .bot-head{transform:rotate(-12deg)}
.bot.look .bot-eyes ellipse{transform:translateY(-2.5px)}
.bot.hug .bot-arm-l{transform:rotate(-70deg)}.bot.hug .bot-arm-r{transform:rotate(70deg)}
.bot.happy .bot-art{animation:bot-hop .5s ease-in-out 2}
@keyframes bot-hop{50%{transform:translateY(-6px)}}

/* speech: a small glass pill above its head; the art is mirrored when it faces left, the text never is */
.bot-say{position:absolute;left:50%;bottom:calc(100% + 8px);transform:translateX(-50%) scaleX(var(--face,1)) translateY(4px);
  white-space:nowrap;padding:6px 11px;border-radius:999px;font:500 12px var(--sans);color:var(--fg);
  background:rgba(20,20,26,.9);box-shadow:inset 0 0 0 1px var(--line-2),0 10px 24px -10px rgba(0,0,0,.8);
  opacity:0;pointer-events:none;transition:opacity .25s var(--ease),transform .25s var(--ease)}
.bot-say.on{opacity:1;transform:translateX(-50%) scaleX(var(--face,1)) translateY(0)}
.bot-say.heart{font-size:16px;padding:4px 10px;color:#f7a8d8}

/* ---- Pixel as a companion: rides the bottom-left corner while you read ----
   Section junk only shows when the companion is on (desktop, motion allowed, not
   dismissed); otherwise the footer plays the whole scene on its own. */
.sjunk{position:absolute;bottom:clamp(18px,2.6vw,34px);left:max(var(--pad),calc((100% - var(--wrap)) / 2 + var(--pad)));
  z-index:2;display:none;align-items:center;gap:5px;white-space:nowrap;pointer-events:none}
.pixel-on .sjunk{display:inline-flex}
.sjunk-fly{position:fixed;z-index:850;margin:0;pointer-events:none;display:inline-flex;align-items:center;gap:5px;white-space:nowrap}
.pixel{position:fixed;right:max(12px,calc((100vw - var(--wrap)) / 2 - 50px));bottom:18px;z-index:800;
  transform:translateY(150%);opacity:0;transition:transform .7s var(--ease),opacity .4s var(--ease)}
.pixel.on{transform:none;opacity:1}
/* on the right it faces into the page: the robot is mirrored, its words are not; the cubes it tows end up on its left */
.pixel .bot-c{position:relative;display:block;transform:scaleX(-1);width:52px;height:59px}
.pixel .bot-art{width:52px;height:59px}
.pixel .bot-say{left:0;right:auto;transform:scaleX(-1) translateY(4px)}   /* left:0 inside the mirrored robot = its right edge on screen, so the pill grows into the page */
.pixel .bot-say.on{transform:scaleX(-1)}
/* the cubes it has made so far, towed alongside */
.bot-cargo{position:absolute;left:calc(100% + 3px);bottom:3px;display:flex;flex-direction:column-reverse;gap:2px;pointer-events:none}
.bot-cargo i{display:block;width:10px;height:10px;border-radius:2.5px;background:linear-gradient(150deg,#3d3d49 0%,#1f1f26 55%,#141418 100%);
  box-shadow:inset 1px 1px 0 rgba(255,255,255,.28),inset 0 -2px 4px rgba(135,68,242,.25),0 2px 4px -1px rgba(0,0,0,.7);animation:bot-pop .45s cubic-bezier(.3,1.6,.5,1)}
@keyframes bot-pop{from{transform:translateY(-14px) scale(.4);opacity:0}}
.bot:not(.bot-c) .bot-cargo{transform:scaleX(var(--face,1))}
.pixel-x{position:absolute;top:-8px;left:-10px;width:20px;height:20px;padding:0;border:0;border-radius:50%;
  background:rgba(20,20,26,.95);color:var(--fg-2);font:500 13px/20px var(--sans);text-align:center;cursor:pointer;
  box-shadow:inset 0 0 0 1px var(--line-2);opacity:0;transition:opacity .2s var(--ease),color .2s var(--ease)}
.pixel:hover .pixel-x,.pixel:focus-within .pixel-x{opacity:1}
.pixel-x:hover{color:var(--fg)}
@media(max-width:820px){.pixel,.sjunk{display:none!important}}

@media (prefers-reduced-motion:reduce){
  .bot-eyes,.bot-ant-halo,.bot-led-a,.bot.moving .bot-art,.bot.compact .bot-art,.bot.happy .bot-art,.cube.new,.bot-cargo i{animation:none}
}

.f-b{display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap;padding-top:22px;
  border-top:1px solid var(--line);font-size:var(--t-1);color:var(--fg-3)}

@media(max-width:860px){
  .f-cta{grid-template-columns:1fr;gap:28px}
  .f-act{align-items:flex-start}
  .f-cols{grid-template-columns:1fr 1fr;gap:26px}
}
@media(max-width:560px){.hero-sub{font-size:var(--t-3)}}
@media(max-width:520px){.f-cols{grid-template-columns:1fr}.f-mail .v{font-size:var(--t-2)}}

/* responsive */
@media(max-width:820px){.lab{grid-template-columns:1fr}}
@media(max-width:820px){
  :root{--pad:18px}.links{display:none}.nav{top:10px;height:52px}
  :root{--sec-y:clamp(56px,11vw,76px)}
  .h1{font-size:clamp(44px,9vw,60px)}
  .steps-nav{grid-template-columns:repeat(5,minmax(0,1fr))}.steps-nav button{padding:12px 8px;font-size:12px;flex-direction:column;gap:4px;align-items:flex-start}
  .step.on{grid-template-columns:1fr}.step-art{border-right:0;border-bottom:1px solid var(--line);min-height:0;padding:24px}.step-art.img{min-height:220px}.step-c{padding:24px}
  .about{grid-template-columns:1fr;gap:40px}.pcard{max-width:340px}.f-in{grid-template-columns:1fr}.f-r{align-items:flex-start}
}
@media(prefers-reduced-motion:reduce){
  *,*::before,*::after{animation:none!important;transition-duration:.01ms!important}
  .line span{transform:none}.hero-sub,.h-cta{opacity:1;transform:none}.glow{opacity:1}
}

/* ===== cursor ==========================================================
   A precise dot that sits exactly on the pointer, and a glass ring that
   follows with a soft lag. The ring grows over anything clickable and turns
   into a label over work cards, lab cards, the carousel and Pixel. Fine
   pointers only; main.js adds .has-cursor, so without it nothing changes. */
.has-cursor,.has-cursor *{cursor:none!important}
.cur-dot,.cur-ring,.cur-spark{position:fixed;left:0;top:0;pointer-events:none;z-index:10000}
.cur-dot{width:6px;height:6px;margin:-3px 0 0 -3px;border-radius:50%;background:#E6D2FF;
  box-shadow:0 0 8px 1px rgba(168,107,255,.9);transition:opacity .2s var(--ease),transform .2s var(--ease)}
.cur-ring{display:grid;place-items:center;width:34px;height:34px;margin:-17px 0 0 -17px;border-radius:999px;
  border:1px solid rgba(234,231,224,.35);background:rgba(135,68,242,0);
  transition:width .35s var(--ease),height .35s var(--ease),margin .35s var(--ease),background-color .3s var(--ease),
    border-color .3s var(--ease),opacity .25s var(--ease),backdrop-filter .3s var(--ease)}
.cur-ring span{font:500 12px/1 var(--sans);letter-spacing:.01em;color:#fff;white-space:nowrap;opacity:0;transform:scale(.8);
  transition:opacity .2s var(--ease),transform .25s var(--ease)}
/* over something clickable */
.cur-hover .cur-ring{width:52px;height:52px;margin:-26px 0 0 -26px;background:rgba(135,68,242,.14);border-color:rgba(198,147,249,.6)}
.cur-hover .cur-dot{opacity:0;transform:scale(.4)}
/* with a label: a glass pill */
.cur-label .cur-ring{width:var(--cw,88px);height:40px;margin:-20px 0 0 calc(var(--cw,88px) / -2);
  background:rgba(135,68,242,.72);border-color:rgba(230,210,255,.55);
  -webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);box-shadow:0 10px 30px -10px rgba(135,68,242,.8)}
.cur-label .cur-ring span{opacity:1;transform:none}
.cur-label .cur-dot{opacity:0}
/* pressing */
.cur-down .cur-ring{transform:scale(.82)}
.cur-ring{transform:scale(1)}
/* leaving the window, or over a video/iframe */
.cur-gone .cur-dot,.cur-gone .cur-ring{opacity:0}
.cur-spark{width:4px;height:4px;margin:-2px 0 0 -2px;border-radius:50%;background:#C693F9;box-shadow:0 0 6px #8744F2}


/* ===== Safari ==========================================================
   Safari re-renders these every frame while the page scrolls, and scrubs the hero
   film underneath some of them, so the same effects that are free in Chrome cost it
   whole frames. html.is-safari (set in <head>) swaps in look-alikes:
   - frosted glass → the same tint, a little more opaque, no backdrop blur
   - the work row's edge fade → no mask (it scrolls over plain black anyway)
   - the flowing line's blur filter → a soft wide stroke
   - the lily's moving shine → off; the lily itself is unchanged */
.is-safari :is(.card,.pr.is-ready .pr-card[data-pos="active"],.hv-cue,.h-cta,.f-mail,.nav .pill-btn.primary,.pcard-who,.pcard-btn){
  -webkit-backdrop-filter:none!important;backdrop-filter:none!important}
.is-safari :is(.hv-cue,.h-cta){background:rgba(14,14,17,.82)}
.is-safari :is(.pcard-who,.pcard-btn){background:rgba(40,36,60,.62)}
.is-safari .rail{-webkit-mask-image:none;mask-image:none}
.is-safari .f-shine{display:none}
.is-safari .thread-glow{filter:none;stroke-width:4;opacity:.16}
.is-safari .thread-head{filter:none;box-shadow:none}

/* native scrolling in Safari (no Lenis): in-page links still glide */
@media (prefers-reduced-motion:no-preference){html.is-safari{scroll-behavior:smooth}}

/* Safari, measured: the corner-lit rims (a masked gradient on every card) and the robot's
   glow filters each cost it frames while scrolling. Plain look-alikes instead. */
.is-safari :is(.card,.sk,.pr-card,.lab-proj)::before{-webkit-mask:none;mask:none;padding:0;background:none;
  box-shadow:inset 1px 1px 0 rgba(255,255,255,.22),inset -1px -1px 0 rgba(255,255,255,.08)}
.is-safari :is(.bot-eyes ellipse,.bot-ant-tip,.bot-led,.bot-led-a){filter:none}
.is-safari .bot-ant-halo{animation:none;opacity:.25}

/* Safari: a drop-shadow filter on each large cover render is re-filtered while the page
   scrolls past; the stage's own light already grounds the devices */
.is-safari .cover img{filter:none}

/* Safari: with no mask, the line would draw over text; instead it runs behind the page,
   so words and cards paint over it and it shows faintly through the glass. Two solid
   black grounds would hide it there, so in Safari they end where nothing needs covering:
   Selected work only has to black out the pinned film for its first screen, and the
   footer sits on the page's own black. */
.is-safari .thread{z-index:0}
.is-safari .footer{background:transparent}
@media(min-width:821px){
  .is-safari #work{background:linear-gradient(to bottom,
      rgba(0,0,0,0) 0, rgba(0,0,0,.05) 6svh, rgba(0,0,0,.16) 12svh, rgba(0,0,0,.33) 19svh,
      rgba(0,0,0,.55) 27svh, rgba(0,0,0,.76) 35svh, rgba(0,0,0,.91) 43svh, #000 52svh,
      #000 100svh, rgba(0,0,0,0) 100svh)}
}
