/* ============================================================
   Noé Geste, portfolio shell
   Design system ported from the ABLab site (one committed
   light world). Case studies keep their own embedded systems.
   ============================================================ */

/* ---------- tokens: one committed light world ---------- */
:root{
  --bg:#ffffff; --card:#f5f6fa; --card-2:#ebedf6; --white:#ffffff;
  --ink:#161b3a; --text:#3b4160; --muted:#6f7590; --dim:#9da3b8;
  --indigo:#4b4ed9; --indigo-deep:#3f42c6; --indigo-soft:#eeeefc; --lav:#e9eafc; --lav-2:#c9cbf5; --lav-3:#f5f5fe;
  --line:#e6e8f0; --line-2:#d9dce8;
  --green:#2f8a5b; --green-soft:#e6f4ec; --red:#cc4444; --amber:#c47a06; --purple:#7c3aed; --blue:#2f5fe0;
  --navy:#1f2452;
  --cta:#161b3a; --cta-2:#242c58; --cta-ink:#ffffff; /* deep navy, as the nav logo */
  --display:"Figtree",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
  --body:"Figtree",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
  --num:"DM Sans","Figtree",system-ui,sans-serif;
  --label:"Figtree",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
  --mono:var(--label); /* legacy alias: page-local rules still say --mono */
  --container:1120px; --gutter:28px; --radius:22px;
  --shadow:0 12px 40px rgba(22,27,58,.08),0 2px 6px rgba(22,27,58,.04);
  --ease:cubic-bezier(.2,.7,.2,1);
}
*,*::before,*::after{box-sizing:border-box}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}
html{overflow-x:clip}
body{margin:0;background:var(--bg);color:var(--text);font-family:var(--body);font-size:17px;line-height:1.6;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;overflow-x:clip}
img{max-width:100%;display:block}
a{color:inherit;text-decoration:none}
h1,h2,h3,h4{margin:0;font-family:var(--display);font-weight:400;letter-spacing:-.02em;line-height:1.08;color:var(--ink);text-wrap:balance}
p{margin:0;text-wrap:pretty}
ul{margin:0;padding:0;list-style:none}
button{font:inherit}
:focus-visible{outline:2px solid var(--indigo);outline-offset:3px;border-radius:6px}
.container{max-width:var(--container);margin:0 auto;padding:0 var(--gutter)}
.num{font-family:var(--num);font-variant-numeric:tabular-nums}
.eyebrow{display:inline-block;font-size:12px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--muted)}
.lede{font-size:20px;line-height:1.55;color:var(--muted);max-width:32em;font-weight:400}
.section{padding-top:150px;padding-bottom:150px}
.h2{font-size:clamp(38px,4.6vw,56px)}
.two{display:grid;grid-template-columns:minmax(0,5fr) minmax(0,7fr);gap:72px;align-items:start}
.card{background:var(--card);border-radius:var(--radius)}
.frame{background:var(--white);border:1px solid var(--line);border-radius:14px;overflow:hidden;box-shadow:var(--shadow)}
.frame img{width:100%;height:auto}
.frame video{width:100%;height:auto;display:block}
.cardframe{background:var(--card);border-radius:var(--radius);padding:28px}
.cardframe .frame{box-shadow:0 10px 30px rgba(22,27,58,.08)}
.caption{font-size:14px;color:var(--dim);margin-top:16px;display:flex;justify-content:space-between;gap:16px}
.caption span+span{font-family:var(--label);font-weight:500;font-size:12.5px;letter-spacing:.04em}

/* ---------- buttons ---------- */
/* The button is an object on a canvas. At rest it is quiet type in a hairline
   box. On hover it gets selected the way a frame does in Figma: a 1px accent
   outline, four corner handles, and the name tag underneath. The site already
   carries a Figma cursor, so the pointer and the thing it points at speak the
   same language, in a portfolio about directing design tools.

   No label animation. The motion here is the selection arriving, nothing else. */
.btn{position:relative;display:inline-flex;align-items:center;justify-content:center;gap:7px;
  height:38px;padding:0 14px;border-radius:5px;border:1px solid var(--line-2);
  background:var(--white);color:var(--ink);
  font-weight:500;font-size:14.5px;white-space:nowrap;cursor:pointer;
  transition:border-color .2s var(--ease),box-shadow .2s var(--ease),
             background .2s var(--ease),color .2s var(--ease)}
.btn-primary{background:var(--cta);color:var(--cta-ink);border-color:transparent}

/* the four corner handles come from the button's and the label's own
   pseudo-elements, so a button stays two spans of markup. All four share one
   containing block (.btn is the positioned ancestor), so they line up. */
.btn::before,.btn::after,.btn .lbl::before,.btn .lbl::after{
  content:"";position:absolute;width:7px;height:7px;border-radius:1px;
  background:var(--white);border:1px solid var(--indigo);
  opacity:0;pointer-events:none;transition:opacity .16s var(--ease)}
.btn::before{left:-4px;top:-4px}
.btn::after{right:-4px;top:-4px}
.btn .lbl::before{left:-4px;bottom:-4px}
.btn .lbl::after{right:-4px;bottom:-4px}

/* the name tag: carries the destination, the way Figma names the selected
   frame. Real information, not decoration. */
.btn .tagx{position:absolute;left:-1px;top:calc(100% + 7px);white-space:nowrap;
  padding:2.5px 6px;border-radius:3px;background:var(--indigo);color:#fff;
  font-size:10.5px;font-weight:500;letter-spacing:.03em;pointer-events:none;
  opacity:0;transform:translateY(-4px);
  transition:opacity .2s var(--ease),transform .28s cubic-bezier(.34,1.3,.64,1)}
/* only where it is a genuine affordance: a link that really opens a new tab */
.btn .ext{flex:none;font-size:12px;line-height:1;opacity:.5;
  transition:opacity .2s var(--ease),transform .3s cubic-bezier(.34,1.3,.64,1)}

@media (hover:hover){
  .btn:hover{border-color:var(--indigo);box-shadow:0 0 0 1px var(--indigo)}
  .btn:hover::before,.btn:hover::after,
  .btn:hover .lbl::before,.btn:hover .lbl::after{opacity:1}
  .btn:hover .tagx{opacity:1;transform:none}
  .btn:hover .ext{opacity:.9;transform:translate(1px,-1px)}
}
/* keyboard gets the same selection, on top of the global focus ring */
.btn:focus-visible{border-color:var(--indigo)}
.btn:focus-visible::before,.btn:focus-visible::after,
.btn:focus-visible .lbl::before,.btn:focus-visible .lbl::after{opacity:1}
.btn:focus-visible .tagx{opacity:1;transform:none}

.btn-sm{height:32px;padding:0 11px;font-size:13.5px}
/* 38px is under the 44px touch minimum, and a coarse pointer never gets the
   hover state to confirm the hit either */
@media (pointer:coarse){.btn{height:44px}.btn-sm{height:40px}}
@media (prefers-reduced-motion:reduce){.btn,.btn .tagx,.btn .ext,
  .btn::before,.btn::after,.btn .lbl::before,.btn .lbl::after{transition:none}}

/* ---------- brand (shared with the viewer drawer) ---------- */
.brand{display:flex;align-items:center;gap:10px;font-family:var(--display);font-weight:600;font-size:20px;letter-spacing:-.02em;color:var(--ink)}
.brand-mark{width:32px;height:32px;border-radius:9px;background:var(--indigo);display:grid;place-items:center;color:#fff;font-size:13px;font-weight:700;letter-spacing:0}
.brand-mark.brand-smile{background:transparent;border-radius:0;color:var(--ink)}
.brand-smile svg{display:block;width:100%;height:100%}

/* ---------- hero ---------- */
.hero{padding-top:clamp(92px,calc(32.2vh - 121px),150px);padding-bottom:0}
/* home hero: natural height, with clear air above and below */
.hero-vh{padding-top:150px;padding-bottom:48px}
/* anchor landings (same-page glides and cross-page hashes alike) sit
   the section comfortably below the floating capsule, never under it */
section[id]{scroll-margin-top:108px}
.hero h1{font-size:clamp(40px,5.6vw,64px);max-width:17ch;font-weight:400;letter-spacing:-.025em}
.hero h1 .soft{font-style:normal;color:var(--dim)}
/* headline two-tone (Figma-style differentiation):
   .hsoft mutes the scaffolding words so the claim stays ink */
.hsoft{color:var(--dim)}
.hero .lede{font-size:clamp(17px,1.5vw,20px);margin-top:26px;max-width:36em}
/* chip eyebrow with a live dot */
.hero-chip{display:inline-flex;align-items:center;gap:9px;padding:8px 15px;border:1px solid var(--line);border-radius:999px;background:var(--white);font-size:13.5px;font-weight:500;color:var(--text);box-shadow:0 1px 3px rgba(22,27,58,.05)}
.hdot{flex:none;width:7px;height:7px;border-radius:50%;background:var(--green);position:relative}
.hdot::after{content:"";position:absolute;inset:-3px;border-radius:50%;border:1px solid var(--green);animation:hping 2.6s ease-out infinite}
@keyframes hping{0%{transform:scale(.55);opacity:.6}70%,100%{transform:scale(1.35);opacity:0}}
/* a small circular smiley riding the end of the headline */
.h-mini{display:inline-grid;place-items:center;width:.78em;height:.78em;margin-left:.22em;vertical-align:-.08em;color:var(--ink)}
.h-mini svg{width:100%;height:100%}
/* the smiley is a toy: pick it up and drop it anywhere on the page. It moves by
   transform rather than by leaving the flow, so the headline never reflows and
   nothing below it shifts while you drag. */
.h-mini{cursor:grab;touch-action:none;position:relative;will-change:transform}
.h-mini.dragging{cursor:grabbing}
.h-mini.moved{z-index:60}
/* the heading reveal leaves `transition: transform .5s ... .6s` on the smiley,
   so a drag lagged 600ms behind the pointer before easing into place. Dragging
   has to track instantly; only the trip home is animated. !important because
   the reveal rule is `.js .sw .h-mini`, which outranks a two-class selector. */
.h-mini.dragging,.h-mini.moved{transition:none!important}
.h-mini.homing{transition:transform .5s cubic-bezier(.34,1.3,.64,1)!important}
.h-mini::after{content:attr(data-tip);position:absolute;left:50%;bottom:calc(100% + 9px);
  transform:translateX(-50%) translateY(-3px);white-space:nowrap;
  padding:3px 7px;border-radius:4px;background:var(--cta);color:var(--cta-ink);
  font-family:var(--body);font-size:11px;font-weight:500;letter-spacing:.02em;line-height:1.4;
  opacity:0;pointer-events:none;
  transition:opacity .18s var(--ease),transform .26s cubic-bezier(.34,1.3,.64,1)}
@media (hover:hover){
  .h-mini:hover::after{opacity:1;transform:translateX(-50%) translateY(0)}
}
.h-mini.dragging::after{opacity:0}
@media (prefers-reduced-motion:reduce){.h-mini.homing{transition:none}.h-mini::after{transition:none}}
/* verifiable facts as a compact chip row */
.hero-facts{display:flex;flex-wrap:wrap;gap:8px;margin-top:28px}
.hero-facts span{display:inline-flex;align-items:center;gap:7px;font-size:13px;padding:7px 13px;border-radius:999px;border:1px solid var(--line);background:var(--white);color:var(--muted);white-space:nowrap}
.hero-facts b{color:var(--ink);font-weight:600}
@media (prefers-reduced-motion:reduce){.hdot::after{animation:none;opacity:0}}
.hero-cta{display:flex;flex-wrap:wrap;gap:14px;margin-top:22px;align-items:center}
.hero-cta+.hero-facts{margin-top:40px}
.hero-meta{margin-top:22px;font-size:15px;color:var(--dim);display:flex;flex-wrap:wrap;gap:8px 14px;align-items:center}
.hero-meta .sep{width:3px;height:3px;border-radius:50%;background:var(--line-2)}
.hero-shot{margin-top:clamp(40px,calc(26.7vh - 136px),88px)}

/* ---------- tools belt: automatic logo marquee under the hero ----------
   Fills the air between the hero and the case studies with the software
   the practice actually runs on. Marks are uniform 24x24 icon paths, so
   every logo carries the same optical weight; the track is the item set
   twice over and slides exactly -50%, which loops seamlessly. */
.tools{padding:8px 0}
.tools-sprite{position:absolute;width:0;height:0;overflow:hidden}
.tools-label{text-align:center;font-family:var(--label);font-weight:600;font-size:11.5px;letter-spacing:.16em;text-transform:uppercase;color:var(--dim)}
.tools-belt{margin-top:26px;overflow:hidden;
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 12%,#000 88%,transparent);
          mask-image:linear-gradient(90deg,transparent,#000 12%,#000 88%,transparent)}
.tools-track{display:flex;width:max-content;animation:toolsroll 34s linear infinite}
.tools-belt:hover .tools-track{animation-play-state:paused}
.tools-set{display:flex;align-items:center;gap:64px;padding-right:64px}
.tool{display:inline-flex;align-items:center;gap:12px;color:var(--dim);transition:color .35s var(--ease)}
.tool-mark{flex:none;width:24px;height:24px;display:block;fill:currentColor}
.tool-name{font-size:15px;font-weight:500;letter-spacing:-.01em;white-space:nowrap}
@media (hover:hover){.tool:hover{color:var(--brand,var(--ink))}}
@keyframes toolsroll{from{transform:translate3d(0,0,0)}to{transform:translate3d(-20%,0,0)}}
@media (max-width:860px){
  .tools{padding:0}
  .tools-set{gap:44px;padding-right:44px}
  .tool-name{font-size:14px}
}
@media (prefers-reduced-motion:reduce){
  .tools-track{animation:none;width:100%;justify-content:center;flex-wrap:wrap}
  .tools-set:not(:first-child){display:none}
  .tools-set{flex-wrap:wrap;justify-content:center;gap:28px 44px;padding-right:0}
  .tools-belt{-webkit-mask-image:none;mask-image:none}
}

/* ---------- centered case-page hero ---------- */
.hero-c{text-align:center}
.hero-c h1{margin-left:auto;margin-right:auto}
.hero-c .lede{margin-left:auto;margin-right:auto}
.hero-c .chips{justify-content:center}
.hero-c .facts,.hero-c .caption{text-align:left}

/* ---------- chips ---------- */
.chips{display:flex;gap:6px;flex-wrap:wrap}
.chips span{font-size:13px;padding:6px 12px;border-radius:999px;border:1px solid var(--line);background:var(--white);color:var(--muted)}
.chips b{color:var(--ink);font-weight:600}

/* ---------- stats ---------- */
.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:24px;margin-top:24px}
.stats.three{grid-template-columns:repeat(3,1fr)}
.stat{background:var(--card);border-radius:18px;padding:28px 28px 26px}
.stat .v{font-family:var(--num);font-weight:500;font-size:38px;letter-spacing:-.03em;line-height:1;color:var(--ink);font-variant-numeric:tabular-nums}
.stat .l{margin-top:10px;font-size:15px;color:var(--muted);line-height:1.45}

/* ---------- case cards (work index) ---------- */
.case{--cpad:40px;position:relative;overflow:hidden;display:block;background:var(--card);
  border-radius:var(--radius);padding:40px var(--cpad) 32px;
  transition:transform .55s cubic-bezier(.34,1.3,.64,1)}
/* the shot runs edge to edge instead of floating in an even margin: the card
   reads as a composed layout rather than a padded content box */
.case .frame{margin-left:calc(var(--cpad) * -1);margin-right:calc(var(--cpad) * -1);border-radius:0}

/* hover signal: a small springy tilt, like a print being picked up
   (.js .case.rv repeated so it outranks the reveal rule's transform:none) */
@media (hover:hover){
  .case:hover,.js .case.rv:hover{transform:translateY(-5px)}
}
.case+.case{margin-top:28px}
/* viewport deck (desktop): each case fits the screen, 10px under the
   capsule and 10px above the viewport bottom; the media flexes to
   absorb the remaining height and each wheel gesture steps one case
   (driven by the smooth-scroll module in site.js) */
@media (min-width:861px){
  /* the card tracks the viewport but stops at 880px: past that the frame
     outgrows what the media actually wants at this width and object-fit
     starts eating the screenshot (a 27" screen was showing ~60% of it) */
  .js .vdeck .case{height:min(calc(100vh - 86px),880px);display:flex;flex-direction:column;padding:34px var(--cpad) 28px}
  .js .vdeck .case+.case{margin-top:86px} /* at rest the previous card sits 10px past the viewport top */
  .js .vdeck .case h3{margin-top:0;font-size:clamp(24px,2.6vw,34px)}
  .js .vdeck .case .desc{margin-top:0;font-size:16px}
  .js .vdeck .case .chips{margin-top:14px}
  .js .vdeck .case .frame{flex:1;min-height:0;margin-top:28px}
  .js .vdeck .case .chead{margin-top:22px}
  /* width first: media always spans the full frame width; the frame
     clips whatever height doesn't fit */
  .js .vdeck .case .frame img,.js .vdeck .case .frame video{width:100%;height:auto;min-height:100%;object-fit:cover;object-position:top}
  .js .vdeck .case .c-foot{margin-top:20px}
}

.case .kick{font-family:var(--label);font-weight:500;font-size:12.5px;letter-spacing:.04em;color:var(--dim)}
/* card header: number chip, project name, status tag */
.c-top{display:flex;align-items:center;gap:14px}
.c-num{display:grid;place-items:center;min-width:36px;height:26px;padding:0 9px;border-radius:999px;background:var(--ink);color:var(--bg);font-family:var(--num);font-weight:600;font-size:12.5px;letter-spacing:.02em;transition:transform .3s var(--ease),background .3s var(--ease)}
/* the color signal: the number pill takes the brand indigo on hover */
.case:hover .c-num{transform:translateX(4px);background:var(--indigo)}
.c-proj{font-family:var(--label);font-weight:600;font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
.c-tag{margin-left:auto;font-family:var(--label);font-weight:500;font-size:12.5px;color:var(--dim)}
/* card footer: chips and the CTA share one line */
.c-foot{display:flex;align-items:center;justify-content:space-between;gap:18px;margin-top:18px}
.c-foot .chips{margin:0}
.c-foot .open{margin:0;white-space:nowrap}
/* The card head was a stack: headline, then description, both hugging the
   left edge with 10-12px between them, while the whole right half of the card
   sat empty above the image. Two columns fill that void, give each block its
   own measure, and hand the reclaimed height back to the shot. */
.chead{margin-top:18px}
@media (min-width:900px){
  .chead{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:0 56px;align-items:start}
}
.case h3{margin-top:0;font-size:clamp(28px,3.2vw,40px);max-width:22ch;transition:color .2s}
.case h3 .hsoft{transition:color .35s var(--ease)}
.case:hover h3 .hsoft{color:var(--muted)}
.case .desc{margin-top:14px;color:var(--muted);font-size:17px;line-height:1.6;max-width:56ch}
@media (min-width:900px){.case .desc{margin-top:0;max-width:none;padding-top:.35em}}
.case .chips{margin-top:20px}
.case .frame{margin-top:28px}
.case .caption{margin-top:16px}
.case .open{display:inline-flex;align-items:center;gap:8px;margin-top:6px;font-weight:500;color:var(--ink)}
/* CTA arrow (shared): on hover the arrow exits right and an indigo
   twin re-enters from the left, one clean swap */
.arr{position:relative;display:inline-block;width:1.15em;height:1.2em;overflow:hidden;margin-left:2px}
.arr i{position:absolute;inset:0;display:grid;place-items:center;font-style:normal;transition:transform .38s cubic-bezier(.22,1,.36,1)}
.arr i+i{transform:translateX(-140%);color:var(--indigo)}
.case:hover .arr i:first-child,.site:hover .arr i:first-child{transform:translateX(140%)}
.case:hover .arr i+i,.site:hover .arr i+i{transform:translateX(0)}

/* ---------- feature-ish trio (souveraineté) ---------- */
.grid3{display:grid;grid-template-columns:repeat(3,1fr);gap:24px;margin-top:80px}
.feat{background:var(--card);border-radius:var(--radius);padding:32px 30px 34px;display:flex;flex-direction:column}
.feat h3{font-size:24px;line-height:1.15}
.feat p{margin-top:12px;color:var(--muted);font-size:16px;line-height:1.55}

/* ---------- site index (shipped coach sites, live previews) ---------- */
.sitegrid{display:grid;grid-template-columns:repeat(2,1fr);gap:24px;margin-top:64px}
.site{display:block;background:var(--card);border-radius:var(--radius);padding:22px 22px 20px}
.site .kick{font-size:12px;font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:var(--dim)}
.site h3{margin-top:10px;font-size:22px;transition:color .2s}
.site .thumb{position:relative;display:block;margin-top:16px;aspect-ratio:16/10;border:1px solid var(--line);border-radius:14px;overflow:hidden;background:var(--white)}
.site .thumb img{width:100%;height:100%;object-fit:cover;object-position:top}
.site .caption{margin-top:14px}
.site .caption span+span{white-space:nowrap;flex:none;font-weight:500;color:var(--ink);font-family:var(--body);font-size:14px;transition:color .2s}
/* an odd card out: the last self-initiated build takes the full row so the
   grid never ends on an orphan, and its shot gets a wider crop to suit */
.site-wide{grid-column:1/-1}
.site-wide .thumb{aspect-ratio:21/9}
@media (max-width:640px){.sitegrid{grid-template-columns:1fr}}
/* micro-interactions: on hover the card skews slightly and every
   element nudges with its own beat; the arrow swaps through indigo.
   (Hover transitions live behind [data-stagger].done, set by site.js
   after the entrance choreography, so the reveal stagger keeps its
   delays and the hover stays instant.) */
/* outside the hover gate so the press release below springs back on touch
   as well: .done only lands after the entrance stagger has played */
.js [data-stagger].done>.site{transition:transform .5s cubic-bezier(.34,1.3,.64,1);transition-delay:0s}
@media (hover:hover){
  /* repeated at stagger specificity so the reveal's transform:none
     doesn't suppress the hover (the round-66 trap, again) */
  .site:hover,.js [data-stagger].in>.site:hover{transform:translateY(-4px) skewY(-.5deg)}
  .site .kick,.site h3,.site .caption{transition:transform .45s cubic-bezier(.34,1.3,.64,1)}
  .site .thumb{transition:transform .45s cubic-bezier(.34,1.3,.64,1)}
  .site:hover .kick{transform:translateX(4px)}
  .site:hover h3{transform:translateX(7px);transition-delay:.03s}
  .site:hover .thumb{transform:translateY(-2px);transition-delay:.05s}
  .site:hover .caption{transform:translateX(4px);transition-delay:.08s}
}
@media (prefers-reduced-motion:reduce){.site:hover,.site:hover .kick,.site:hover h3,.site:hover .thumb,.site:hover .caption{transform:none}}

/* ---------- archive ---------- */
/* three teasers since Galleri Anti moved out (it is case 04 on this page) */
.arch{display:grid;grid-template-columns:repeat(3,1fr);gap:24px;margin-top:64px}
.arch figure{margin:0}
.arch .frame img{aspect-ratio:4/5;object-fit:cover}
.arch figcaption{font-size:14px;color:var(--dim);margin-top:12px;display:flex;justify-content:space-between;gap:10px}
.arch figcaption span+span{font-family:var(--label);font-weight:500;font-size:11.5px;letter-spacing:.04em;text-align:right}
/* series carousel: hover or click flips through the pieces inside the frame */
.arch figure[data-car]{cursor:pointer}
.arch figure[data-car] .frame{position:relative}
.arch figure[data-car] .frame img.layer{position:absolute;inset:0;height:100%;opacity:0;transition:opacity .45s var(--ease)}
.arch figure[data-car] .frame img.layer.show{opacity:1}
.arch figure[data-car]:focus-visible{outline:2px solid var(--indigo);outline-offset:3px;border-radius:8px}
@media (prefers-reduced-motion:reduce){.arch figure[data-car] .frame img.layer{transition:none}}

/* ---------- rows (capability list) ---------- */
/* ---------- about: pixel portrait + practice ---------- */
.about-grid{display:grid;grid-template-columns:minmax(260px,340px) minmax(0,1fr);gap:56px;align-items:start}
.about-grid>.about-wide{margin-top:26px}
.about-face{margin:0;background:var(--card);border-radius:var(--radius);padding:16px 16px 14px}
.about-face canvas{display:block;width:100%;height:auto;border-radius:14px;background:var(--white)}
.about-face figcaption{display:flex;justify-content:space-between;gap:12px;margin-top:12px;font-family:var(--label);font-weight:500;font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:var(--dim)}
.about-rows{grid-template-columns:1fr 1fr;margin-top:30px}
/* the bio paragraphs under the lede */
.bio{margin-top:16px;color:var(--muted);font-size:16px;line-height:1.62;max-width:44em}
/* about: the four short blocks that document the practice, in a full-width
   band under the portrait row so the portrait column stops trailing white */
.about-wide{grid-column:1/-1;margin-top:8px}
.about-blocks{display:grid;grid-template-columns:repeat(auto-fit,minmax(228px,1fr));gap:30px 40px}
.ab h3{font-family:var(--label);font-size:11.5px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--indigo);line-height:1.4}
.ab p{margin-top:10px;color:var(--muted);font-size:15.5px;line-height:1.6}
.about-wide .about-rows{margin-top:44px;grid-template-columns:repeat(auto-fit,minmax(210px,1fr))}
@media (max-width:860px){.about-blocks{grid-template-columns:1fr;gap:22px}.about-wide{margin-top:0}}
html:not(.js) .about-face{display:none}
@media (max-width:860px){
  .about-grid{grid-template-columns:1fr;gap:36px}
  .about-face{max-width:340px}
  .about-rows{grid-template-columns:1fr}
}

.rows{display:grid;gap:8px}
.row{display:flex;align-items:center;gap:10px;background:var(--white);border-radius:12px;padding:11px 13px;font-size:14px;color:var(--ink);box-shadow:0 1px 2px rgba(22,27,58,.05)}
.row .ic{flex:none;width:22px;text-align:center;font-family:var(--num);font-weight:500;font-size:12px;color:var(--dim)}
.row .lb{flex:1;min-width:0;line-height:1.35}

/* ---------- pull quote ---------- */
.quote{background:var(--card);border-radius:var(--radius);padding:40px 44px;margin-top:80px}
.quote p{font-size:clamp(22px,2.6vw,30px);line-height:1.3;color:var(--ink);max-width:26em;letter-spacing:-.015em}

/* ---------- cta card ---------- */
.cta-card{background:var(--card);border-radius:var(--radius);padding:72px 56px;display:grid;grid-template-columns:minmax(0,7fr) minmax(0,5fr);gap:48px;align-items:center}
.cta-card .h2{font-size:clamp(32px,3.8vw,46px)}
.cta-card .meta{margin-top:18px;color:var(--muted);font-size:16px}
.cta-actions{display:flex;flex-wrap:wrap;gap:14px;justify-content:flex-end}

/* ---------- next case ---------- */
.next{display:block;background:var(--card);border-radius:var(--radius);padding:44px 44px 46px;transition:transform .25s var(--ease)}
.next:hover{transform:translateY(-3px)}
.next .eyebrow{color:var(--dim)}
.next h3{margin-top:14px;font-size:clamp(26px,3vw,38px);max-width:24ch;transition:color .2s}
.next .arrow{display:inline-block;margin-left:10px;transition:transform .25s var(--ease)}
.next:hover .arrow{transform:translate(4px,-4px)}
.next-grid{display:grid;grid-template-columns:minmax(0,7fr) minmax(0,5fr);gap:44px;align-items:center}
.next .next-shot{display:block;border-radius:14px;overflow:hidden;border:1px solid var(--line);box-shadow:var(--shadow);background:var(--white)}
.next .next-shot img{width:100%;height:auto;display:block;transition:transform .7s var(--ease)}
.next:hover .next-shot img{transform:scale(1.035)}
@media (max-width:860px){.next-grid{grid-template-columns:1fr;gap:28px}}

/* ---------- footer ---------- */
footer{border-top:1px solid var(--line);padding:44px 0 52px;color:var(--dim);font-size:15px}
.foot{display:flex;flex-wrap:wrap;gap:20px 32px;align-items:center;justify-content:space-between}
.foot-links{display:flex;flex-wrap:wrap;gap:8px 24px}
.foot-links a:hover{color:var(--ink)}
.foot-bottom{margin-top:28px;display:flex;flex-wrap:wrap;gap:8px 24px;justify-content:space-between}

/* ---------- motion layer ---------- */
.js .rv-blur{opacity:0;transform:translateY(16px);filter:blur(10px);transition:opacity .85s var(--ease),transform .85s var(--ease),filter .85s var(--ease)}
.js .rv-blur.in{opacity:1;transform:none;filter:none}

/* masked heading reveal (an original take on zajno.com's split text):
   the heading itself stays put; site.js wraps each word in a clipped
   .w_ window and the inner .w rises out of it with a small stagger.
   The window carries padding + equal negative margins, so glyph
   overhang never clips while the layout width stays untouched. */
.js .rv-blur.sw{opacity:1;transform:none;filter:none;transition:none}
.sw .w_{display:inline-block;overflow:hidden;vertical-align:bottom;padding:.12em .08em .18em;margin:-.12em -.08em -.18em}
.js .sw .w{display:inline-block;transform:translateY(145%);transition:transform .85s cubic-bezier(.19,1,.22,1);transition-delay:calc(var(--wi,0)*45ms);will-change:transform}
.js .sw.in .w{transform:none}
.js .sw .h-mini{opacity:0;transform:translateY(12px) scale(.7);transition:opacity .5s var(--ease) .6s,transform .5s var(--ease) .6s}
.js .sw.in .h-mini{opacity:1;transform:none}

/* staggered children, site.js writes --i per child */
.js [data-stagger]>*{opacity:0;transform:translateY(14px);transition:opacity .6s var(--ease),transform .6s var(--ease);transition-delay:calc(var(--i,0)*70ms)}
.js [data-stagger].in>*{opacity:1;transform:none}

/* case cards: choreographed entrance, container stays put (the card
   keeps its transform transition so the hover tilt still animates) */
.js .case.rv{opacity:1;transform:none;transition:transform .55s cubic-bezier(.34,1.3,.64,1)}
.js .case.rv .c-top,
.js .case.rv h3,
.js .case.rv .desc,
.js .case.rv .c-foot{opacity:0;transform:translateY(12px);transition:opacity .6s var(--ease),transform .6s var(--ease)}
.js .case.rv .frame{clip-path:inset(0 0 100% 0);transition:clip-path 1s var(--ease) .3s}
.js .case.rv .frame img{transform:scale(1.08);transition:transform 1.3s var(--ease) .3s}
.js .case.rv.in .c-top{opacity:1;transform:none}
.js .case.rv.in h3{opacity:1;transform:none;transition-delay:.08s}
.js .case.rv.in .desc{opacity:1;transform:none;transition-delay:.16s}
.js .case.rv.in .frame{clip-path:inset(0 0 0 0)}
.js .case.rv.in .frame img{transform:scale(1)}
.js .case.rv.in .c-foot{opacity:1;transform:none;transition-delay:.7s}
.case .frame img{transition:transform .8s var(--ease)}
.js .case.rv.in:hover .frame img,.js .case.rv.in:hover .frame video{transform:scale(1.045)}

/* ---------- press: the card sinks under the pointer, springs back on release
   (.press is set by site.js on pointerdown so touch behaves like mouse; a
   plain :active never shows on most phones). The bigger object moves less.
   Repeated at reveal specificity, after the hover rules, so neither the
   reveal's transform:none nor the hover lift can swallow it. ---------- */
.case,.site{-webkit-tap-highlight-color:transparent}
.case.press,.js .case.rv.press,.js .case.rv:hover.press{transform:translateY(-1px) scale(.985);transition:transform .16s cubic-bezier(.2,.7,.2,1)}
.site.press,.js [data-stagger].in>.site.press,.js [data-stagger].in>.site:hover.press{transform:translateY(-1px) scale(.975);transition:transform .16s cubic-bezier(.2,.7,.2,1);transition-delay:0s}
@media (prefers-reduced-motion:reduce){.case.press,.site.press{transform:none;transition:none}}
/* the press burst, built by site.js at the press point: a ripple ring and a
   few thrown shapes. Sits above the page and below the capsule (z 80). */
.pop{position:fixed;left:0;top:0;width:0;height:0;z-index:75;pointer-events:none}
.pop svg,.pop i{position:absolute;left:0;top:0;display:block;will-change:transform,opacity}
.pop svg{fill:currentColor;overflow:visible;filter:drop-shadow(0 1px 2px rgba(22,27,58,.18))}
/* a hairline of page colour behind every shape so it reads on a busy shot */
.pop svg *{paint-order:stroke;stroke:var(--bg);stroke-width:2.2px;stroke-linejoin:round}
.pop svg [stroke="currentColor"]{stroke:currentColor;paint-order:normal;filter:drop-shadow(0 0 1.5px var(--bg))}
.pop i{width:64px;height:64px;border:2px solid var(--indigo);border-radius:50%;opacity:0;box-shadow:0 0 0 1.5px var(--bg),inset 0 0 0 1.5px var(--bg)}

/* ---------- preloader: greeting words + curved-sheet exit
   (an original take on Skiper UI's words preloader, skiper8) ---------- */
html.preload{overflow:hidden}
.pre{position:fixed;inset:0;z-index:9998;will-change:transform}
.pre.out{transition:transform .75s cubic-bezier(.76,0,.24,1);transform:translateY(-130vh)}
.no-pre .pre{display:none}
.pre-sheet{position:absolute;inset:0;background:var(--bg);display:flex;align-items:center;justify-content:center}
.pre-word{font-family:var(--display);font-weight:400;font-size:clamp(40px,6vw,72px);letter-spacing:-.02em;color:var(--muted)}
/* the curved trailing edge hangs below the sheet, off-screen until the exit */
.pre-curve{position:absolute;top:100%;left:0;width:100%;height:30vh;display:block;filter:drop-shadow(0 26px 44px rgba(22,27,58,.14))}
.pre-curve path{fill:var(--bg)}

/* ---------- hero carousel (an original take on motion.dev's
   carousel-item-offset: the active piece sits upright, the queue
   trails off on a diagonal, tilting and fading with distance) ---------- */
.hero-grid{display:grid;grid-template-columns:minmax(0,7fr) minmax(0,5fr);gap:56px;align-items:center}
.portrait-wrap{justify-self:end;width:min(430px,100%)}
.hcar{position:relative;width:100%;aspect-ratio:1/1;outline-offset:6px}
.hcar .hc-it{position:absolute;left:50%;top:50%;display:block;border-radius:14px;overflow:hidden;background:var(--white);border:1px solid var(--line);box-shadow:0 18px 50px rgba(22,27,58,.18);transition:transform .68s cubic-bezier(.22,1,.36,1),opacity .5s ease;will-change:transform,opacity;cursor:pointer}
.hcar .hc-it.wide{width:96%}
.hcar .hc-it.tall{width:58%}
.hcar .hc-it video,.hcar .hc-it img{display:block;width:100%;height:auto}
@media (max-width:1000px){.hero-grid{grid-template-columns:1fr;gap:44px}.portrait-wrap{justify-self:start;width:min(340px,86%)}}

/* ---------- reveal ---------- */
.js .rv{opacity:0;transform:translateY(12px);transition:opacity .7s var(--ease),transform .7s var(--ease)}
.js .rv.in{opacity:1;transform:none}
@media (prefers-reduced-motion:reduce){.js .rv,.js .rv-blur{opacity:1;transform:none;filter:none;transition:none}.btn:hover{transform:none}.case:hover{transform:none}html{scroll-behavior:auto}}

/* ---------- responsive ---------- */
@media (max-width:1000px){
  .grid3{grid-template-columns:1fr 1fr}
  .two{grid-template-columns:1fr;gap:40px}
  .stats{grid-template-columns:1fr 1fr}
  .stats.three{grid-template-columns:1fr 1fr}
  .arch{grid-template-columns:1fr 1fr}
  .cta-card{grid-template-columns:1fr}
  .cta-actions{justify-content:flex-start}
}
@media (max-width:860px){
  .cnav{top:10px}
  .cnav-tabs{display:none}
  .section{padding-top:104px;padding-bottom:104px}
  .hero{padding-top:118px}
  .case{--cpad:28px;padding:28px var(--cpad) 26px}
}
@media (max-width:640px){
  .grid3,.stats,.stats.three,.arch{grid-template-columns:1fr}
  .caption span+span{display:none}
  .cardframe{padding:14px}
  .cta-card{padding:44px 32px}
  .quote{padding:32px 28px}
  /* the card footer stops fighting for one line: chips take the full
     width, the CTA sits under them instead of in a squeezed column */
  .c-foot{flex-direction:column;align-items:flex-start;gap:14px}
  .c-foot .chips{width:100%}
  /* case credits collapse to a single readable column (the .facts
     pages stop at two, .meta pages already do this) */
  .facts,.meta{grid-template-columns:1fr}
}

/* ---------- dark world: the same tokens, restated ----------
   The theme is an explicit choice on <html data-theme>, set before first
   paint by a small head script, so there is no flash and a chosen theme
   always beats the OS preference. ---------- */
:root[data-theme="dark"]{
  --bg:#0e1016; --card:#161923; --card-2:#1d212c; --white:#191d27;
  --ink:#eceffa; --text:#c0c7da; --muted:#929ab3; --dim:#6c7590;
  --indigo:#7d80f2; --indigo-deep:#9093f5; --indigo-soft:#1c1f3a;
  --lav:#212545; --lav-2:#4b4f8f; --lav-3:#191c2c;
  --line:#262a36; --line-2:#343a4a;
  --green:#4ade80; --green-soft:#102b1c; --red:#f4756e; --amber:#f0b429;
  --purple:#a78bfa; --blue:#7295ff; --navy:#d3d8ea;
  --shadow:0 14px 44px rgba(0,0,0,.5),0 2px 8px rgba(0,0,0,.36);
  --cta:#e4e8f7; --cta-2:#ffffff; --cta-ink:#0e1016;
  color-scheme:dark;
}
/* surfaces that were literal white rather than a token */
:root[data-theme="dark"] .brand-mark{color:#0e1016}
:root[data-theme="dark"] .frame,
:root[data-theme="dark"] .about-face canvas{background:var(--card-2)}
:root[data-theme="dark"] .row{box-shadow:0 1px 2px rgba(0,0,0,.4)}
:root[data-theme="dark"] .hero-chip{box-shadow:0 1px 3px rgba(0,0,0,.4)}
:root[data-theme="dark"] .tool{color:var(--dim)}
