/* ============================================================
   UNLIMITED GATES — shared stylesheet
   Dark, editorial, gate-motif. Used by every page.
   ============================================================ */

/* ---------- WEBFONT ----------
   Self-hosted, not fetched from fonts.googleapis.com. The Google-hosted
   version cost a blocking stylesheet on one origin plus the woff2 on a
   second (fonts.gstatic.com) before any text could paint — four extra
   round trips, and they got expensive the further a reader sat from the
   nearest Google POP. Same bytes off our own edge now, one hop, and the
   latin file is preloaded from the <head> of every page.

   Variable weight axis 400-700, so the whole family is two files per style
   instead of four. Familjen Grotesk is SIL Open Font License 1.1 —
   assets/fonts/OFL.txt travels with the files, which is what the licence
   asks for. Subsets are latin and latin-ext; drop in vietnamese from
   Google's css2 output if the site ever needs it. */
@font-face{
  font-family:'Familjen Grotesk';
  font-style:normal;
  font-weight:400 700;
  font-display:swap;
  src:url(fonts/familjen-grotesk-latin.woff2) format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face{
  font-family:'Familjen Grotesk';
  font-style:normal;
  font-weight:400 700;
  font-display:swap;
  src:url(fonts/familjen-grotesk-latin-ext.woff2) format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face{
  font-family:'Familjen Grotesk';
  font-style:italic;
  font-weight:400 700;
  font-display:swap;
  src:url(fonts/familjen-grotesk-italic-latin.woff2) format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face{
  font-family:'Familjen Grotesk';
  font-style:italic;
  font-weight:400 700;
  font-display:swap;
  src:url(fonts/familjen-grotesk-italic-latin-ext.woff2) format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}

/* ---------- TOKENS ---------- */
:root{
  /* Monochrome. Black ground, white ink, and a grey ramp in between —
     no accent hue anywhere. Hierarchy is carried by VALUE and weight only,
     which is why the type scale and the hairline opacities do the work a
     brand colour would normally do. */
  --bg:#000000;
  --bg-2:#070707;
  --surface:#0b0b0b;
  --surface-2:#151515;
  --line:rgba(255,255,255,.10);
  --line-2:rgba(255,255,255,.20);
  --line-3:rgba(255,255,255,.34);

  /* Contrast ratios against #000, so the ramp stays legible rather than just
     looking quiet: --txt 18.9:1, --txt-2 7.5:1, --txt-3 6.1:1. --txt-3 carries
     the smallest type on the site (eyebrows, form labels, placeholders) so it
     cannot go below the 4.5:1 body-text floor — #606060 was 3.3:1 and failed. */
  --txt:#f2f2f2;
  --txt-2:#9a9a9a;
  --txt-3:#8a8a8a;

  --accent:#ffffff;
  --accent-soft:rgba(255,255,255,.12);
  --glow:rgba(255,255,255,.32);

  /* One webfont. Body and mono ride the OS stack — faster, and it stops the
     page reading as a font-pairing exercise. */
  --disp:'Familjen Grotesk',system-ui,sans-serif;
  --head:'Familjen Grotesk',system-ui,sans-serif;
  --body:ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
  --mono:ui-monospace,'SF Mono',SFMono-Regular,Menlo,Consolas,'Liberation Mono',monospace;

  --ease:cubic-bezier(.16,1,.3,1);
  --ease-gate:cubic-bezier(.76,0,.24,1);

  --maxw:1320px;
  --gut:clamp(20px,5vw,64px);
}

*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html{
  -webkit-text-size-adjust:100%;scroll-padding-top:80px;
  /* Mobile browsers may use <html>, not <body>, as the root scroller. Keep
     transformed door/marquee tracks from widening that scrollable canvas. */
  max-width:100%;overflow-x:hidden;overflow-x:clip;
}
html:focus-within{scroll-behavior:smooth}

body{
  background:var(--bg);
  color:var(--txt);
  font-family:var(--body);
  font-size:16px;
  line-height:1.6;
  max-width:100%;overflow-x:hidden;overflow-x:clip;
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
}
body.is-locked{overflow:hidden}

img,svg,video{max-width:100%;display:block}
a{color:inherit;text-decoration:none}
button,input,textarea,select{font:inherit;color:inherit}
button{background:none;border:none;cursor:pointer}
::selection{background:var(--accent);color:var(--bg)}

::-webkit-scrollbar{width:9px}
::-webkit-scrollbar-track{background:var(--bg)}
::-webkit-scrollbar-thumb{background:#2a2a2a;border-radius:9px}
::-webkit-scrollbar-thumb:hover{background:#3d3d3d}

:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:2px}

/* Seven focus stops sit between the top of the page and the content — brand,
   four nav links, the CTA, the menu button — on every page. This is the way
   past them. Off-screen rather than display:none, so it stays focusable. */
.skip{
  position:absolute;left:var(--gut);top:0;z-index:100;
  transform:translateY(-140%);
  padding:12px 18px;background:var(--accent);color:var(--bg);
  font-family:var(--mono);font-size:11px;letter-spacing:.16em;text-transform:uppercase;
  transition:transform .25s var(--ease);
}
.skip:focus{transform:translateY(14px)}

/* ---------- LAYOUT ---------- */
.wrap{max-width:var(--maxw);margin-inline:auto;padding-inline:var(--gut)}

/* A grid or flex child's min-width defaults to `auto`, i.e. the width of its
   longest unbreakable run of text. One long URL, email address or compound
   word is then wider than its column, the track grows to fit it, and the whole
   row pushes past the viewport at exactly the widths where the column is
   narrowest. Pinning the floor to 0 and letting long words break is what keeps
   every one of these layouts inside the screen as it narrows. */
.grid > *,.split > *,.foot__top > *,.row > *,.card > *,.member__body > *{min-width:0}
p,li,dd,h1,h2,h3,h4,.lead,.h1,.h2,.h3,.h4,.h-display{overflow-wrap:break-word}
section{position:relative;z-index:2}
.section{padding-block:clamp(80px,12vw,180px)}
.rule{height:1px;background:var(--line);border:0}

/* ---------- TYPE ---------- */
.eyebrow{
  font-family:var(--mono);font-size:11px;letter-spacing:.22em;text-transform:uppercase;
  color:var(--txt-3);display:flex;align-items:center;gap:12px;
}
.eyebrow::before{
  content:'';width:22px;height:2px;background:var(--accent);flex:none;
}
.h-display{
  font-family:var(--disp);font-weight:700;line-height:.9;letter-spacing:-.035em;
  font-size:clamp(48px,10.5vw,150px);
}
.h1{font-family:var(--disp);font-weight:700;line-height:.93;letter-spacing:-.035em;font-size:clamp(40px,7.5vw,104px)}
.h2{font-family:var(--disp);font-weight:600;line-height:1;letter-spacing:-.03em;font-size:clamp(32px,5.2vw,72px)}
.h3{font-family:var(--head);font-weight:500;line-height:1.15;letter-spacing:-.01em;font-size:clamp(20px,2.4vw,30px)}
.h4{font-family:var(--head);font-weight:500;line-height:1.25;font-size:clamp(17px,1.6vw,21px)}
.lead{font-size:clamp(16px,1.5vw,20px);line-height:1.55;color:var(--txt-2);max-width:56ch}
.muted{color:var(--txt-2)}
.dim{color:var(--txt-3)}
.mono{font-family:var(--mono);font-size:12px;letter-spacing:.14em;text-transform:uppercase}
.italic{font-style:italic}
.serif{font-family:var(--disp)}

/* ============================================================
   GATE MARK (logo)
   Single source of truth: assets/img/ug-mark.webp — the gate symbol cropped to
   its ink, white on black, 454 x 512. Derived from UG-icon.png, which is the
   1254px master and stays out of the deployed image; regenerate the webp
   from it if the mark is ever redrawn.
   No blend mode or contrast filter: the file's black IS the page's black,
   so it composites correctly by doing nothing.
   assets/img/favicon.png is the same master cropped square for the browser tab.
   ============================================================ */
.gate-mark{
  display:block;height:26px;aspect-ratio:454/512;flex:none;
  transition:transform .6s var(--ease-gate),opacity .4s var(--ease);
}
.gate-mark img{width:100%;height:100%;display:block}
a:hover > .gate-mark,.gate-hover:hover .gate-mark{transform:scale(1.07)}

/* ---------- NAV ---------- */
/* Three tracks, not space-between. space-between only centres the middle item
   when the two outer ones happen to be the same width — the brand is ~250px
   and the CTA ~150px, so the links were sitting ~43px right of centre. Equal
   1fr outer tracks put the middle track on the viewport centre whatever the
   brand and CTA measure. Columns are assigned explicitly so that hiding
   .nav-links below 820px leaves the CTA in track 3 rather than sliding it
   into the middle. */
.nav{
  position:fixed;top:0;left:0;right:0;z-index:60;
  display:grid;grid-template-columns:1fr auto 1fr;align-items:center;
  gap:24px;padding:18px var(--gut);
  transition:background .4s var(--ease),border-color .4s var(--ease),padding .4s var(--ease);
  border-bottom:1px solid transparent;
}
.nav.is-stuck{
  background:rgba(0,0,0,.78);
  /* .is-stuck is on from 24px of scroll onward, i.e. effectively always, and
     this bar is full width and fixed — so the blur is recomputed every
     composited frame while scrolling. 8px costs roughly half of 14px and is
     indistinguishable behind an 78%-opaque black. */
  backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
  border-bottom-color:var(--line);padding-block:12px;
}
.nav-brand{grid-column:1;justify-self:start;display:flex;align-items:center;gap:18px;color:var(--txt);min-width:0}
.nav-brand .gate-mark{height:34px}
.nav-brand b{
  font-family:var(--head);font-weight:600;font-size:20px;
  letter-spacing:.12em;text-transform:uppercase;white-space:nowrap;
}
.nav-links{grid-column:2;justify-self:center;display:flex;align-items:center;gap:clamp(18px,2.6vw,38px)}
.nav-links a{
  font-family:var(--mono);font-size:11.5px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--txt-2);position:relative;padding-block:6px;transition:color .3s var(--ease);
}
.nav-links a::after{
  content:'';position:absolute;left:0;bottom:0;width:0;height:1px;
  background:var(--accent);transition:width .45s var(--ease);
}
.nav-links a:hover,.nav-links a[aria-current="page"]{color:var(--txt)}
.nav-links a:hover::after,.nav-links a[aria-current="page"]::after{width:100%}
.nav-cta{grid-column:3;justify-self:end;display:flex;align-items:center;gap:14px}
.nav-toggle{display:none;width:40px;height:40px;position:relative;flex:none}
.nav-toggle span{
  position:absolute;left:9px;width:22px;height:1.5px;background:var(--txt);
  transition:transform .45s var(--ease),opacity .3s var(--ease);
}
.nav-toggle span:nth-child(1){top:16px}
.nav-toggle span:nth-child(2){top:23px}
body.menu-open .nav-toggle span:nth-child(1){transform:translateY(3.5px) rotate(45deg)}
body.menu-open .nav-toggle span:nth-child(2){transform:translateY(-3.5px) rotate(-45deg)}

/* mobile drawer */
.drawer{
  position:fixed;inset:0;z-index:55;background:var(--bg);
  display:flex;flex-direction:column;justify-content:center;gap:6px;
  padding:0 var(--gut);
  clip-path:inset(0 0 100% 0);
  transition:clip-path .7s var(--ease-gate),visibility 0s linear .7s;
  pointer-events:none;
  visibility:hidden;                 /* keeps links out of the tab order when closed */
}
body.menu-open .drawer{
  clip-path:inset(0 0 0 0);pointer-events:auto;visibility:visible;
  transition:clip-path .7s var(--ease-gate),visibility 0s;
}
.drawer a{
  font-family:var(--disp);font-weight:600;letter-spacing:-.03em;
  font-size:clamp(34px,9vw,62px);line-height:1.15;
  color:var(--txt-2);transition:color .3s var(--ease),transform .5s var(--ease);
  transform:translateY(20px);opacity:0;
}
body.menu-open .drawer a{transform:none;opacity:1;transition-delay:calc(.12s + var(--i,0) * .06s)}
.drawer a:hover{color:var(--txt)}
.drawer .drawer-foot{margin-top:38px;display:flex;flex-wrap:wrap;gap:20px}
.drawer .drawer-foot a{font-family:var(--mono);font-size:12px;letter-spacing:.16em;text-transform:uppercase}

/* ---------- BUTTONS ---------- */
.btn{
  display:inline-flex;align-items:center;gap:10px;
  font-family:var(--mono);font-size:11.5px;letter-spacing:.17em;text-transform:uppercase;
  padding:14px 24px;border:1px solid var(--line-2);border-radius:999px;
  position:relative;overflow:hidden;color:var(--txt);
  transition:color .45s var(--ease),border-color .45s var(--ease);
  white-space:nowrap;
}
.btn::before{
  content:'';position:absolute;inset:0;background:var(--accent);z-index:-1;
  transform:translateY(101%);transition:transform .55s var(--ease-gate);
}
.btn span,.btn svg{position:relative;z-index:1}
.btn:hover{color:var(--bg);border-color:var(--accent)}
.btn:hover::before{transform:translateY(0)}
.btn--solid{background:var(--accent);color:var(--bg);border-color:var(--accent)}
.btn--solid::before{background:var(--txt)}
.btn--solid:hover{color:var(--bg)}
.btn--sm{padding:10px 18px;font-size:10.5px}
.btn svg{width:13px;height:13px;flex:none}

.link-u{
  position:relative;display:inline-block;
  background-image:linear-gradient(currentColor,currentColor);
  background-size:100% 1px;background-repeat:no-repeat;background-position:0 100%;
  transition:background-size .45s var(--ease);
}
.link-u:hover{background-size:0% 1px;background-position:100% 100%}

/* ============================================================
   REVEALS
   Every hiding rule is scoped to .js (set by an inline script in
   <head>). Without JS nothing is ever hidden — no blank page.
   ============================================================ */
.js [data-reveal]{opacity:0;transform:translateY(26px);transition:opacity .9s var(--ease),transform .9s var(--ease)}
.js [data-reveal].in{opacity:1;transform:none}
.js [data-reveal][data-d="1"]{transition-delay:.08s}
.js [data-reveal][data-d="2"]{transition-delay:.16s}
.js [data-reveal][data-d="3"]{transition-delay:.24s}
.js [data-reveal][data-d="4"]{transition-delay:.32s}
.js [data-reveal][data-d="5"]{transition-delay:.4s}

.clip-up{overflow:hidden;display:block}
.js .clip-up > *{display:block;transform:translateY(105%);transition:transform 1.05s var(--ease)}
.js .clip-up.in > *{transform:none}
.js .clip-up[data-d="1"] > *{transition-delay:.1s}
.js .clip-up[data-d="2"] > *{transition-delay:.2s}
.js .clip-up[data-d="3"] > *{transition-delay:.3s}

/* gate wipe reveal — panels part to expose content */
.gate-reveal{position:relative;overflow:hidden}
.js .gate-reveal::before,.js .gate-reveal::after{
  content:'';position:absolute;top:0;bottom:0;width:50.5%;background:var(--bg);z-index:3;
  transition:transform 1.1s var(--ease-gate);
}
.js .gate-reveal::before{left:0}
.js .gate-reveal::after{right:0}
.js .gate-reveal.in::before{transform:translateX(-101%)}
.js .gate-reveal.in::after{transform:translateX(101%)}

/* ---------- WORD / LINE STAGGER (data-split) ---------- */
/* padding + negative margin so descenders (g, y, p) aren't clipped
   by the overflow:hidden box */
.split-w{
  display:inline-block;overflow:hidden;vertical-align:top;
  padding-bottom:.16em;margin-bottom:-.16em;
}
.split-w > i{display:inline-block;font-style:inherit;transform:translateY(110%);transition:transform .95s var(--ease)}
/* NOTE: this MUST NOT be called .split — that name is already the two-column
   grid layout class, and headings were inheriting display:grid from it. */
.is-split.in .split-w > i{transform:none}

/* ============================================================
   INTRO GATE
   Full-screen logo that opens like a pair of doors as you scroll,
   revealing the hero behind it.

   Everything that hides content is scoped to .js — without
   JavaScript the doors are removed and the hero just shows.
   ============================================================ */
.intro{position:relative;z-index:3}
.js .intro{height:230vh}                 /* scroll distance for the opening */

.intro__stick{
  position:relative;min-height:100svh;
  display:flex;align-items:center;
  padding-block:clamp(120px,16vh,180px) clamp(60px,8vh,90px);
}
/* NOTE: deliberately no overflow:hidden here — it would clip the hero text on
   short screens. The doors slide past the viewport edges instead, and the
   global body{overflow-x:hidden} stops any sideways scrollbar. */
.js .intro__stick{position:sticky;top:0;height:100svh;padding-block:0}

/* the hero content, sitting behind the doors */
.intro__content{position:relative;z-index:1;width:100%}
.js .intro__content{opacity:0;will-change:opacity,transform}

/* keep the hero inside one screen so nothing spills past the pinned frame */
.intro__content .h-display{font-size:clamp(36px,8vw,132px)}
@media (max-height:760px){
  .intro__content .h-display{font-size:clamp(30px,5.6vw,68px)}
  .intro__content .lead{font-size:15px}
  .intro__content .hero__meta{margin-top:24px;gap:14px 26px}
}

/* the two doors — opaque panels, each carrying half the mark */
/* EXACTLY 50% each. Any overlap would shift each door's copy of the logo
   off the true screen centre and the two halves would not line up.
   A sub-pixel gap between them is invisible: both doors and the page
   behind them are the same pure black. */
.intro__door{
  position:absolute;top:0;bottom:0;width:50%;
  background:var(--bg);overflow:hidden;z-index:5;
  will-change:transform;
}
.intro__door--l{left:0}
.intro__door--r{right:0}
html:not(.js) .intro__door{display:none}

/* Mark + wordmark live in ONE block per door, centred on that door's inner
   edge (the screen centre). Each door's overflow clips away the half it
   shouldn't show. Keeping them in a single block means they can never
   drift apart from each other or between the two halves. */
.intro__brand{
  position:absolute;top:50%;
  display:flex;flex-direction:column;align-items:center;
  gap:clamp(18px,3.6vmin,46px);
  width:max-content;
  will-change:opacity;   /* JS fades this out as the doors part */
}
.intro__door--l .intro__brand{right:0;transform:translate(50%,-50%)}
.intro__door--r .intro__brand{left:0;transform:translate(-50%,-50%)}

/* The intro lockup is the vector mark plus the wordmark set as real text.
   It used to be a full-page JPEG of the two together, which needed a
   contrast crush and a pair of mask gradients to stop its near-black
   background reading as a rectangle. Vector + text needs none of that,
   stays crisp at any size and costs ~400 bytes instead of 77 KB. */
.intro__logo{width:clamp(96px,22vmin,224px);aspect-ratio:454/512}
.intro__logo img{width:100%;height:100%;display:block}
.intro__word{
  font-family:var(--disp);font-weight:500;
  font-size:clamp(19px,4.6vmin,52px);
  letter-spacing:.18em;text-transform:uppercase;
  color:var(--txt);white-space:nowrap;
  /* the wordmark sits optically centred once the trailing letter-space goes */
  margin-right:-.18em;
}

/* light spilling through the opening */
.intro__beam{
  position:absolute;top:0;bottom:0;left:50%;width:2px;z-index:6;
  transform:translateX(-50%);opacity:0;pointer-events:none;
  background:linear-gradient(180deg,transparent,var(--accent) 45%,transparent);
}
html:not(.js) .intro__beam{display:none}

.intro__cue{
  position:absolute;left:50%;bottom:26px;transform:translateX(-50%);z-index:7;
  display:flex;flex-direction:column;align-items:center;gap:10px;
  font-family:var(--mono);font-size:10.5px;letter-spacing:.22em;
  text-transform:uppercase;color:var(--txt-3);white-space:nowrap;
}
.intro__cue i{
  display:block;width:1px;height:34px;
  background:linear-gradient(var(--line-3),transparent);
  animation:cue 2.2s var(--ease) infinite;
}
html:not(.js) .intro__cue{display:none}

/* ============================================================
   SPLITTING GATE (reusable, smaller version)
   ============================================================ */
.gatesplit{
  position:relative;
  width:clamp(160px,27vw,320px);
  aspect-ratio:1/1;
  margin-bottom:clamp(26px,4vw,44px);
  color:var(--txt);
}
.gatesplit__half{position:absolute;inset:0;overflow:hidden;will-change:transform,opacity}
.gatesplit__half--l{clip-path:inset(0 50% 0 0)}
.gatesplit__half--r{clip-path:inset(0 0 0 50%)}
.gatesplit__half svg{width:100%;height:100%;display:block}
/* the two halves are already the two gate leaves — no hover drift here */
.gatesplit .gate-mark .leaf{transition:none}

/* light spilling through the opening */
.gatesplit__beam{
  position:absolute;top:6%;bottom:6%;left:50%;width:1px;
  transform:translateX(-50%) scaleY(0);transform-origin:50% 50%;
  background:linear-gradient(180deg,transparent,rgba(255,255,255,.85),transparent);
  opacity:0;pointer-events:none;
}
html:not(.js) .gatesplit__beam{display:none}

/* ---------- SCROLL PROGRESS ---------- */
.progress{
  position:fixed;top:0;left:0;right:0;height:2px;z-index:80;
  background:var(--accent);transform:scaleX(0);transform-origin:0 50%;
  will-change:transform;
}

/* ---------- PINNED WORD-SCRUB MANIFESTO ---------- */
.scrub{position:relative}
.scrub__stick{
  position:sticky;top:0;min-height:100svh;
  display:flex;align-items:center;
}
/* The paragraph sits to the right of a hairline that fills as you read.
   --scrub-p is written by site.js every frame (0 → 1 across the pin). */
.scrub__body{position:relative;padding-left:clamp(22px,3.4vw,54px)}
.scrub__rule{
  position:absolute;left:0;top:.3em;bottom:.25em;width:1px;
  background:var(--line);overflow:hidden;
}
.scrub__rule i{
  display:block;width:100%;height:100%;background:var(--txt);
  transform:scaleY(var(--scrub-p,0));transform-origin:50% 0;
}
html:not(.js) .scrub__rule{display:none}

.scrub__text{
  font-family:var(--disp);font-weight:500;
  font-size:clamp(26px,4.6vw,62px);line-height:1.18;letter-spacing:-.025em;
  max-width:20ch;
}
/* JS wraps each word in a span; without JS the text just reads normally */
.scrub__text span{color:rgba(255,255,255,.13);transition:color .3s linear}
.scrub__text span.lit{color:var(--txt)}
.scrub__text em{font-style:normal}

/* Words that were inside <em>: an underline is drawn under each one as it
   lights, so the two key phrases build themselves up as you scroll past. */
.scrub__text span.key{
  background-image:linear-gradient(var(--txt),var(--txt));
  background-repeat:no-repeat;
  background-position:0 100%;
  background-size:0% 1px;
  padding-bottom:.1em;
  transition:color .3s linear,background-size .45s var(--ease);
}
.scrub__text span.key.lit{background-size:100% 1px}
.scrub__i{
  position:absolute;top:50%;right:0;transform:translateY(-50%);
  font-family:var(--mono);font-size:11px;letter-spacing:.2em;color:var(--txt-3);
  writing-mode:vertical-rl;
}

/* ---------- PARALLAX / SCALE MEDIA ---------- */
/* No will-change here on purpose. These carry a translate3d from the scroll
   engine, which already gets them their own layer while they move; a standing
   will-change instead pins a layer for every parallax element for the life of
   the page, which is where the memory goes on integrated GPUs. */
[data-parallax]{transform:translateZ(0)}
.js [data-scale]{transform:scale(1.18);transition:transform 1.4s var(--ease)}
.js [data-scale].in{transform:scale(1)}
.zoomer{overflow:hidden}

/* ============================================================
   ACCORDION  (How we work)
   <details>/<summary> underneath, so keyboard, screen readers and
   no-JS all work without help. Everything here is presentation.
   ============================================================ */
.acc{border-top:1px solid var(--line)}
.acc__item{border-bottom:1px solid var(--line)}

.acc__head{
  display:flex;align-items:center;justify-content:space-between;gap:24px;
  padding-block:clamp(18px,2.4vw,26px);
  cursor:pointer;list-style:none;
  transition:padding-left .5s var(--ease);
}
/* kill the native disclosure triangle in every engine */
.acc__head::-webkit-details-marker{display:none}
.acc__head::marker{content:''}
.acc__item:hover .acc__head{padding-left:10px}
/* the whole row is the control, so the ring belongs on the row */
.acc__head:focus-visible{outline:2px solid var(--accent);outline-offset:4px;border-radius:2px}

.acc__t{
  font-family:var(--disp);font-weight:600;letter-spacing:-.025em;
  font-size:clamp(19px,2.2vw,27px);line-height:1.15;
  color:var(--txt-2);transition:color .4s var(--ease);
}
.acc__item[open] .acc__t,.acc__item:hover .acc__t{color:var(--txt)}

/* + turning into − */
.acc__mark{
  position:relative;width:15px;height:15px;flex:none;
  color:var(--txt-3);transition:color .4s var(--ease),transform .5s var(--ease-gate);
}
.acc__mark::before,.acc__mark::after{
  content:'';position:absolute;left:50%;top:50%;
  width:15px;height:1.5px;background:currentColor;
  transform:translate(-50%,-50%);
}
.acc__mark::after{transform:translate(-50%,-50%) rotate(90deg);transition:opacity .35s var(--ease)}
.acc__item[open] .acc__mark{color:var(--txt);transform:rotate(180deg)}
.acc__item[open] .acc__mark::after{opacity:0}   /* + becomes − */

/* An ANIMATION, not a transition. A closed <details> doesn't render its
   content at all, so on open there is no previous state to transition from
   and a transition would simply not run. A keyframe animation fires the
   moment the element first renders, which is exactly what's needed. */
.acc__panel{overflow:hidden}
.acc__item[open] .acc__panel{animation:accIn .5s var(--ease) both}
@keyframes accIn{
  from{opacity:0;transform:translateY(-8px)}
  to{opacity:1;transform:none}
}
.acc__body p{max-width:52ch}

.acc__list{
  list-style:none;display:grid;gap:9px;
  margin-top:16px;padding-bottom:clamp(20px,2.4vw,28px);
}
.acc__list li{
  position:relative;padding-left:20px;
  font-size:14.5px;line-height:1.5;color:var(--txt-2);
}
.acc__list li::before{
  content:'';position:absolute;left:0;top:.66em;
  width:9px;height:1px;background:var(--line-3);
}

/* ---------- PAGE TRANSITION ---------- */
.page-gate{
  position:fixed;inset:0;z-index:200;pointer-events:none;display:flex;
}
.page-gate i{
  flex:1;background:var(--bg);
  transform:translateY(-101%);          /* parked off-screen above */
  transition:transform .7s var(--ease-gate);
  animation:gateOpen .9s var(--ease-gate) both;  /* on load: slide away downward */
}
.page-gate i:nth-child(2){animation-delay:.06s;transition-delay:.06s}
.page-gate i:nth-child(3){animation-delay:.12s;transition-delay:.12s}
.page-gate i:nth-child(4){animation-delay:.18s;transition-delay:.18s}
@keyframes gateOpen{from{transform:translateY(0)}to{transform:translateY(101%)}}

/* JS clears the animation before adding .closing, so the panels
   fall back to -101% (still off-screen) and then slide down to 0. */
.page-gate.closing{pointer-events:auto}
.page-gate.closing i{transform:translateY(0)}

/* used by the members run */
@keyframes scrollX{to{transform:translateX(-50%)}}

/* ---------- CARDS / GRIDS ---------- */
.grid{display:grid;gap:clamp(16px,2vw,26px)}
.grid-2{grid-template-columns:repeat(2,1fr)}
.grid-3{grid-template-columns:repeat(3,1fr)}
.grid-4{grid-template-columns:repeat(4,1fr)}
.split{display:grid;grid-template-columns:minmax(0,.9fr) minmax(0,1.1fr);gap:clamp(28px,6vw,90px);align-items:start}

.card{
  position:relative;background:var(--surface);border:1px solid var(--line);
  border-radius:3px;padding:clamp(24px,3vw,40px);
  overflow:hidden;transition:border-color .5s var(--ease),background .5s var(--ease),transform .6s var(--ease);
  height:100%;display:flex;flex-direction:column;
}
.card::after{
  content:'';position:absolute;left:0;right:0;top:0;height:1px;
  background:linear-gradient(90deg,transparent,var(--line-3),transparent);
  transform:scaleX(0);transition:transform .7s var(--ease);
}
.card:hover{border-color:var(--line-2);background:var(--surface-2);transform:translateY(-4px)}
.card:hover::after{transform:scaleX(1)}
.card__icon{
  display:block;line-height:0;margin-bottom:16px;
  color:var(--txt-3);transition:color .5s var(--ease),transform .5s var(--ease);
}
.card__icon svg{width:28px;height:28px;display:block;stroke:currentColor;fill:none;
  stroke-width:1.35;stroke-linecap:round;stroke-linejoin:round}
.card:hover .card__icon{color:var(--accent);transform:translateY(-2px)}
.card h3{margin-bottom:12px}
.card p{color:var(--txt-2);font-size:15px}
.card ul{list-style:none;margin-top:18px;display:flex;flex-direction:column;gap:9px}
.card ul li{
  font-size:14px;color:var(--txt-2);padding-left:18px;position:relative;line-height:1.45;
}
.card ul li::before{
  content:'';position:absolute;left:0;top:.62em;width:7px;height:1px;background:var(--accent);
}

/* ============================================================
   WORK GALLERY  (work.html — site.js §9)

   Panels are positioned at the gallery origin. The icon row stays in
   layout for return measurements. Both views share a stable stage height;
   all text contributes to the reserved height. No panel scrolls internally.
   ============================================================ */
.pstage{display:grid;position:relative;isolation:isolate;overflow-anchor:none;min-height:clamp(560px,65svh,680px);--project-glow:192 210 255}
.picons,.ppanel{grid-area:1 / 1;min-width:0}

.picons{
  display:flex;flex-wrap:wrap;gap:clamp(20px,3vw,40px);
  opacity:1;transition:opacity .35s var(--ease),filter .45s var(--ease);
  align-items:flex-start;
}
.picons.is-fading{opacity:0;filter:blur(6px);pointer-events:none}
.picon__btn.is-selected .picon__frame{visibility:hidden}
.picon__btn{display:flex;flex-direction:column;align-items:center;gap:12px;width:clamp(120px,16vw,180px)}
.picon__frame{
  position:relative;width:100%;aspect-ratio:1/1;overflow:hidden;border-radius:30%;
  border:1px solid var(--line);background:var(--surface-2);
  transition:border-color .35s var(--ease),transform .35s var(--ease);
}
.picon__frame img{width:100%;height:100%;object-fit:cover}
.picon__frame--empty{display:grid;place-items:center}
.picon__frame--empty img{width:38%;height:auto;object-fit:contain;opacity:.16}
.picon__label{
  font-family:var(--mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--txt-3);text-align:center;transition:color .3s var(--ease);
}
.picon__btn:hover .picon__frame,
.picon__btn:focus-visible .picon__frame{border-color:var(--line-2);transform:translateY(-3px)}
.picon__btn:hover .picon__label,
.picon__btn:focus-visible .picon__label{color:var(--txt)}

.ppanel{position:relative;min-width:0;visibility:hidden;pointer-events:none}
.ppanel.is-visible{visibility:visible}
#ppanel-hiwar{--project-glow:80 139 255}
#ppanel-falafilji{--project-glow:235 191 104}
.ppanel__inner{
  position:relative;isolation:isolate;overflow:hidden;
  display:block;
  height:100%;padding:88px 0 0;border:1px solid var(--line-2);
  border-radius:18px;background:linear-gradient(135deg,var(--surface-2),var(--bg-2) 65%);
  opacity:0;filter:blur(5px);pointer-events:none;
  transition:opacity .42s var(--ease),filter .5s var(--ease);
}
.ppanel__inner::before{
  content:'';position:absolute;inset:0;z-index:-1;pointer-events:none;
  background:radial-gradient(ellipse 65% 360px at 50% 0%,rgb(var(--project-glow) / .24),transparent 75%);
  opacity:0;transition:opacity .9s var(--ease);
}
.ppanel.is-open .ppanel__inner::before{opacity:1}
.ppanel.is-open .ppanel__inner{opacity:1;transform:none;filter:blur(0);pointer-events:auto}
.ppanel__inner:focus{outline:none}
.ppanel__body{
  position:relative;z-index:1;width:58%;min-width:0;
  padding:0 clamp(22px,4vw,52px) clamp(22px,4vw,52px);
}
.ppanel__body:focus{outline:none}
.ppanel__meta{display:flex;align-items:center;gap:14px;margin-bottom:20px}
.ppanel__n{font-family:var(--mono);font-size:11px;letter-spacing:.18em;color:var(--txt-3)}
/* Provenance. Every panel says who delivered it and in what capacity, so a
   university project cannot be read as client work. */
.ppanel__tag{
  font-family:var(--mono);font-size:11px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--txt-3);border:1px solid var(--line);border-radius:2px;padding:5px 10px;
}
.ppanel__t{
  font-family:var(--disp);font-weight:600;line-height:1.04;letter-spacing:-.03em;
  font-size:clamp(27px,3.6vw,44px);margin-bottom:18px;max-width:20ch;
}
.ppanel__d{color:var(--txt-2);font-size:clamp(15px,1.25vw,16.5px);max-width:60ch}
.ppanel__stack{
  font-family:var(--mono);font-size:11.5px;letter-spacing:.06em;
  color:var(--txt-3);margin-top:20px;line-height:1.7;
}
.ppanel__by{
  font-family:var(--mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--txt-3);margin-top:12px;
}
.ppanel .msplit__go{margin-top:26px}

.ppanel__gallery{
  position:absolute;inset:0;z-index:-2;overflow:clip;pointer-events:none;
}
.ppanel__gallery::after{
  content:'';position:absolute;inset:0;
  background:linear-gradient(90deg,var(--bg-2) 4%,rgb(7 7 7 / .96) 30%,rgb(7 7 7 / .65) 54%,rgb(7 7 7 / .12) 100%);
}
.pgal__slot{
  position:absolute;inset:0;display:grid;place-items:center;
  background:radial-gradient(ellipse at 80% 45%,rgb(var(--project-glow) / .16),transparent 75%);
}
.pgal__slot img,.pgal__slot video{width:100%;height:100%;object-fit:cover;display:block}
.pgal__slot span{
  position:absolute;right:8%;top:50%;max-width:30%;text-align:center;
  font-family:var(--mono);font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--txt-3);
}
@media(max-width:760px){
  .ppanel__body{width:100%}
  .ppanel__gallery::after{background:rgb(7 7 7 / .86)}
  .pgal__slot span{display:none}
}

/* The shared badge is moved into the stage on initialization. */
.picon-badge{
  position:absolute;top:-36px;left:calc(50% - 36px);z-index:3;
  width:72px;height:72px;border-radius:30%;
  /* Matches .picon__frame's resting border/background exactly — the badge
     starts each FLIP transformed to overlay a row icon precisely, so it
     needs to already look like one, not just be the same shape. */
  border:1px solid var(--line);background:var(--surface-2);
  display:none;transform-origin:0 0;
  transition:box-shadow .8s var(--ease),border-color .8s var(--ease);
}
.picon-badge.is-visible{display:block}
.picon-badge.is-docked{
  border-color:rgb(var(--project-glow) / .65);
  box-shadow:0 0 24px rgb(var(--project-glow) / .45),0 14px 70px 16px rgb(var(--project-glow) / .25);
}
.picon-badge__img{width:100%;height:100%;object-fit:cover;display:block;border-radius:inherit}
.picon-badge--empty .picon-badge__img{width:38%;height:auto;margin:auto;opacity:.16}
.picon-badge__hint{
  position:absolute;left:50%;top:calc(100% + 12px);transform:translateX(-50%);white-space:nowrap;
  padding:7px 10px;border:1px solid var(--line-2);border-radius:6px;
  background:var(--surface);color:var(--txt);font-family:var(--mono);font-size:10px;
  letter-spacing:.08em;opacity:0;transition:opacity .25s var(--ease);
}
.picon-badge.is-docked .picon-badge__hint{opacity:1}
.picon-badge::after{
  content:'↩';position:absolute;inset:0;display:grid;place-items:center;
  border-radius:inherit;background:var(--bg);color:var(--txt);font-size:28px;
  opacity:0;transition:opacity .25s var(--ease);
}
.picon-badge.is-docked:hover::after,.picon-badge.is-docked:focus-visible::after{opacity:.85}
@media(max-width:600px){
  .picons{display:grid;grid-template-columns:repeat(2,minmax(0,1fr))}
  .picon__btn{width:100%}
  .picon-badge{width:64px;height:64px;top:-32px;left:calc(50% - 32px)}
}

/* numbered list rows */
.rows{border-top:1px solid var(--line)}
/* Numbers belong on .rows--seq only, where the order carries meaning (the
   four project stages). A list of six parallel services is not a sequence,
   so those rows drop the column entirely rather than fake one. */
.rows:not(.rows--seq) .row{grid-template-columns:minmax(0,1fr) auto}
.row{
  display:grid;grid-template-columns:64px minmax(0,1fr) auto;
  gap:clamp(14px,3vw,40px);align-items:baseline;
  padding-block:clamp(22px,3vw,36px);
  border-bottom:1px solid var(--line);
  transition:padding-left .5s var(--ease),border-color .5s var(--ease);
}
.row:hover{padding-left:12px;border-bottom-color:var(--line-2)}
.row__n{font-family:var(--mono);font-size:11px;letter-spacing:.18em;color:var(--txt-3)}
.row:hover .row__n{color:var(--accent)}
.row__t{font-family:var(--disp);font-weight:600;letter-spacing:-.03em;font-size:clamp(24px,3.4vw,44px);line-height:1.08}
.row__d{color:var(--txt-2);font-size:14.5px;max-width:44ch}
.row__go{
  font-family:var(--mono);font-size:11px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--txt-3);display:inline-flex;align-items:center;gap:8px;white-space:nowrap;
  transition:color .4s var(--ease),gap .4s var(--ease);
}
.row:hover .row__go{color:var(--txt);gap:14px}

/* ---------- FORM ---------- */
.form{display:grid;gap:20px}
.field{display:grid;gap:9px}
.field label{font-family:var(--mono);font-size:10.5px;letter-spacing:.18em;text-transform:uppercase;color:var(--txt-3)}
.field input,.field textarea,.field select{
  background:var(--surface);border:1px solid var(--line);border-radius:2px;
  padding:14px 16px;font-size:15px;color:var(--txt);width:100%;
  transition:border-color .4s var(--ease),background .4s var(--ease);
  font-family:var(--body);
}
.select-wrap{position:relative}
.select-wrap.is-enhanced>select{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;
}
.select-trigger{
  display:flex;align-items:center;justify-content:space-between;gap:16px;
  width:100%;min-height:51px;padding:14px 16px;
  color:var(--txt-3);background:var(--surface);border:1px solid var(--line);border-radius:2px;
  font-family:var(--body);font-size:15px;text-align:left;
  transition:border-color .3s var(--ease),background .3s var(--ease),color .3s var(--ease);
}
.select-trigger:hover{color:var(--txt);border-color:var(--line-2)}
.select-trigger:focus-visible{outline:none;border-color:var(--accent);background:var(--surface-2)}
.select-trigger.has-value{color:var(--txt)}
.select-trigger svg{
  flex:0 0 auto;width:16px;height:16px;color:var(--txt-3);
  transition:color .3s var(--ease),transform .3s var(--ease);
}
.select-wrap.is-open .select-trigger{color:var(--txt);border-color:var(--accent);background:var(--surface-2)}
.select-wrap.is-open .select-trigger svg{color:var(--accent);transform:rotate(180deg)}
.select-menu{
  position:absolute;z-index:30;left:0;right:0;top:calc(100% + 7px);
  max-height:252px;overflow-y:auto;padding:6px;
  background:var(--surface-2);border:1px solid var(--line-2);border-radius:2px;
  box-shadow:0 18px 40px rgba(0,0,0,.48);
  opacity:0;visibility:hidden;pointer-events:none;transform:translateY(-6px);
  transition:opacity .2s var(--ease),transform .2s var(--ease),visibility .2s;
  scrollbar-width:thin;scrollbar-color:var(--line-3) var(--surface-2);
}
.select-wrap.is-open .select-menu{
  opacity:1;visibility:visible;pointer-events:auto;transform:translateY(0);
}
.select-option{
  display:grid;grid-template-columns:30px minmax(0,1fr) 18px;align-items:center;
  width:100%;min-height:42px;padding:9px 10px;color:var(--txt-2);text-align:left;
  border:1px solid transparent;border-radius:1px;
  transition:color .2s var(--ease),background .2s var(--ease),border-color .2s var(--ease);
}
.select-option__n{font-family:var(--mono);font-size:9px;letter-spacing:.12em;color:var(--txt-3)}
.select-option__mark{width:5px;height:5px;background:currentColor;opacity:0;justify-self:end}
.select-option:hover,.select-option.is-active{color:var(--txt);background:rgba(255,255,255,.055)}
.select-option[aria-selected="true"]{color:var(--txt);border-color:var(--line-2)}
.select-option[aria-selected="true"] .select-option__mark{opacity:1}
.select-option:first-child .select-option__n{color:transparent}
@media (prefers-reduced-motion:reduce){
  .select-menu,.select-trigger,.select-trigger svg,.select-option{transition:none}
}
.field textarea{min-height:150px;resize:vertical}
.field input:focus,.field textarea:focus,.field select:focus{
  outline:none;border-color:var(--accent);background:var(--surface-2);
}
.field input::placeholder,.field textarea::placeholder{color:var(--txt-3)}
.form-note{font-size:13px;color:var(--txt-3)}
/* Driven by aria-invalid, which site.js sets on a failed submit and clears as
   soon as the field is edited. Not `:invalid` — that matches from first paint,
   so every required field would be marked before the visitor typed anything,
   and it carries no meaning for a screen reader. */
/* Specific enough to outrank `.field input:focus`, so the mark survives the
   focus that reportValidity() puts on the first bad field. */
.form .field [aria-invalid="true"]{border-color:var(--txt);background:rgba(255,255,255,.06)}
/* Empty until site.js writes to it, so it takes no space and announces nothing
   on load. aria-live is on the element in the HTML. */
.form-status:empty{display:none}
.form-status{
  font-size:14px;line-height:1.5;color:var(--txt);
  border-left:2px solid var(--accent);padding:10px 0 10px 14px;
}
/* No-JS path only. With JS the fetch handler writes to .form-status and the
   URL never gets a fragment; without it the browser posts natively and the
   relay 303s back to #sent-ok / #sent-err, which :target then reveals. */
.form-fallback{display:none}
.form-fallback:target{display:block}

/* ---------- HERO ---------- */
.hero{
  min-height:100svh;display:flex;flex-direction:column;justify-content:center;
  padding-block:clamp(120px,16vh,180px) clamp(60px,8vh,90px);
  position:relative;z-index:2;
}
.hero__mark{width:clamp(72px,11vw,132px);color:var(--txt);margin-bottom:clamp(28px,4vw,46px)}
.hero__meta{
  display:flex;flex-wrap:wrap;gap:18px 34px;align-items:center;
  margin-top:clamp(34px,5vw,54px);
}
.scroll-cue{
  position:absolute;left:var(--gut);bottom:26px;
  font-family:var(--mono);font-size:10.5px;letter-spacing:.2em;text-transform:uppercase;
  color:var(--txt-3);display:flex;align-items:center;gap:10px;
}
.scroll-cue i{display:block;width:1px;height:34px;background:linear-gradient(var(--line-3),transparent);animation:cue 2.2s var(--ease) infinite}
@keyframes cue{0%{transform:scaleY(.3);transform-origin:top}50%{transform:scaleY(1);transform-origin:top}51%{transform-origin:bottom}100%{transform:scaleY(.3);transform-origin:bottom}}

/* page header (inner pages) */
.phead{padding-block:clamp(130px,18vh,200px) clamp(40px,6vw,70px)}
.phead .h1{margin-top:20px}
.phead .lead{margin-top:26px}

/* ---------- CTA BAND ---------- */
.cta{
  position:relative;z-index:2;text-align:center;
  padding-block:clamp(90px,14vw,180px);
  border-top:1px solid var(--line);
}
.cta .h1{margin-bottom:34px}
.cta__row{display:flex;flex-wrap:wrap;gap:14px;justify-content:center}

/* ---------- FOOTER ---------- */
.foot{position:relative;z-index:2;border-top:1px solid var(--line);padding-block:clamp(48px,6vw,72px) 34px}
.foot__top{display:grid;grid-template-columns:1.4fr 1fr 1fr;gap:clamp(28px,5vw,60px)}
.foot__brand{display:flex;align-items:center;gap:12px;margin-bottom:18px}
.foot__brand .gate-mark{height:22px}
.foot__brand b{font-family:var(--head);font-weight:600;font-size:13px;letter-spacing:.19em;text-transform:uppercase}
/* h3, not h5: these follow the page's h2s, and jumping two levels breaks the
   heading outline screen readers navigate by. The size is the class's job. */
.foot h3{font-family:var(--mono);font-size:10.5px;letter-spacing:.2em;text-transform:uppercase;color:var(--txt-3);margin-bottom:18px;font-weight:400}
.foot ul{list-style:none;display:grid;gap:11px}
.foot ul a{color:var(--txt-2);font-size:14.5px;transition:color .3s var(--ease)}
.foot ul a:hover{color:var(--txt)}
.foot__bot{
  margin-top:clamp(40px,6vw,70px);padding-top:24px;border-top:1px solid var(--line);
  display:flex;flex-wrap:wrap;gap:14px 26px;justify-content:space-between;align-items:center;
  font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--txt-3);
}
/* The bar is space-between with the copyright as its only other child, so this
   takes the right-hand end without any layout of its own. min-height rather
   than padding: the row is centre-aligned, so it reaches a 44px touch target
   by growing the bar instead of pushing the type off its baseline. */
.foot__social{display:inline-flex;align-items:center;gap:9px;min-height:44px;color:var(--txt-3);transition:color .3s var(--ease)}
.foot__social:hover{color:var(--txt)}
.foot__social svg{width:15px;height:15px;flex:none}

/* ============================================================
   IMAGERY
   All photos are desaturated + darkened so they sit inside the
   black palette instead of fighting the type.
   ============================================================ */
.fig{position:relative;overflow:hidden;background:var(--surface-2)}
.fig img{
  width:100%;height:100%;object-fit:cover;display:block;
  filter:grayscale(1) contrast(1.08) brightness(.72);
  transition:filter .8s var(--ease),transform 1.2s var(--ease);
}
.fig::after{
  content:'';position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(180deg,rgba(0,0,0,.15),rgba(0,0,0,.55));
}
.fig--tall{aspect-ratio:3/4}
.fig--wide{aspect-ratio:16/9}
.fig--sq{aspect-ratio:1/1}
a:hover .fig img,.tile:hover .fig img,.fig-hover:hover .fig img{
  filter:grayscale(.45) contrast(1.05) brightness(.9);
  transform:scale(1.05);
}

/* parallax inside a fixed-height frame: image is oversized and drifts */
.fig--px{overflow:hidden}
.fig--px img{
  height:128%;position:absolute;top:-14%;left:0;will-change:transform;
  /* no transform transition — JS writes it every frame */
  transition:filter .8s var(--ease);
}
a:hover .fig--px img,.fig-hover:hover .fig--px img{transform:none}

/* ============================================================
   MEMBERS
   ============================================================ */
/* Same treatment as the two split panels: photo ungraded, nothing cropped,
   and on hover it grows up out of the card as a 3D layer.
   overflow-x:clip / overflow-y:visible again — clipped sideways so a card
   never spills into its neighbour, open at the top so it can lift. */
.member{
  position:relative;display:block;border:1px solid var(--line);border-radius:4px;
  overflow-x:clip;overflow-y:visible;
  background:var(--surface);z-index:1;
  transition:border-color .5s var(--ease),transform .6s var(--ease),background .5s var(--ease);
}
.member:hover,
.member:focus-within{
  border-color:var(--line-2);transform:translateY(-6px);background:var(--surface-2);
  z-index:20;
}
.member .fig{aspect-ratio:4/5;background:transparent}
/* kill the generic dark overlay that sits on top of .fig images */
.member .fig::after{display:none}
.member .fig img{
  /* `cover` while these are landscape placeholder images — `contain` would
     letterbox them badly. When you swap in real cut-out portraits like the
     two above, add the class `member--cutout` to that card (below) so the
     whole figure shows uncropped. */
  object-fit:cover;
  object-position:center top;
  filter:none;                /* own colours, no grayscale or darkening */
  transform-origin:50% 100%;  /* grows upward, out of the card */
  transform:scale(1);
  transition:transform 1.2s var(--ease);
}
/* for cards holding a transparent cut-out portrait */
.member--cutout .fig{background:transparent}
.member--cutout .fig img{object-fit:contain;object-position:center bottom}
/* focus-within, not focus-visible: the card is an <article> now and never takes
   focus itself — the links inside it do. */
.member:hover .fig img,
.member:focus-within .fig img{
  transform:scale(1.14);
  filter:none;
}
.member__body{padding:clamp(20px,2.4vw,30px)}
.member__name{font-family:var(--disp);font-weight:600;letter-spacing:-.03em;font-size:clamp(22px,2.6vw,34px);line-height:1.1}
.member__role{
  font-family:var(--mono);font-size:10.5px;letter-spacing:.17em;text-transform:uppercase;
  color:var(--txt-3);margin-top:9px;
}
.member__bio{color:var(--txt-2);font-size:14.5px;margin-top:16px}
/* Two destinations per card — the portfolio and a CV file — so the card can no
   longer be one big <a>: a second link nested inside the first is invalid, and
   browsers unnest it rather than honour it. Instead the card is an <article>,
   "View portfolio" carries a ::after stretched over the whole card, and the CV
   link is layered above that. Clicking anywhere still opens the portfolio. */
.member__links{display:flex;flex-wrap:wrap;align-items:center;gap:18px;margin-top:20px}
.member__links .msplit__go{margin-top:0}
.member__links .msplit__go::after{content:"";position:absolute;inset:0;z-index:1}
.member__cv{
  position:relative;z-index:2;             /* above the stretched link */
  display:inline-flex;align-items:center;gap:7px;
  min-height:44px;                         /* touch target; the row centres it */
  font-family:var(--mono);font-size:10.5px;letter-spacing:.17em;text-transform:uppercase;
  color:var(--txt-3);transition:color .3s var(--ease);
}
.member__cv:hover{color:var(--txt)}
.member__cv svg{width:13px;height:13px;flex:none}
/* the "view portfolio" affordance reused inside a card */
.member .msplit__go{font-size:10.5px}
.member:hover .msplit__go{gap:18px;border-bottom-color:var(--txt)}
.member__skills{display:flex;flex-wrap:wrap;gap:7px;margin-top:18px}
.member__go{
  position:absolute;top:16px;right:16px;width:40px;height:40px;border-radius:50%;
  border:1px solid var(--line-2);background:rgba(0,0,0,.5);backdrop-filter:blur(6px);
  display:grid;place-items:center;z-index:4;
  transform:scale(.7);opacity:0;transition:transform .5s var(--ease),opacity .4s var(--ease);
}
.member:hover .member__go{transform:none;opacity:1}
.member__go svg{width:15px;height:15px}

/* ============================================================
   MEMBER SPLIT
   Two portraits side by side, each one a link to that person's
   portfolio. The whole panel is the button.
   ============================================================ */
/* z-index 3 puts this whole block above .marquee (z-index 2), so a photo
   growing past its panel passes in FRONT of the bar rather than behind it. */
/* The panels now live in a draggable run (.mrun, further down); the old
   two-column grid wrapper is gone, but every .msplit__* rule below still
   dresses the panels themselves. */
/* Clip sideways, but NOT upward.
   `overflow: clip` is the one value that can differ per axis — `hidden` would
   force the other axis to auto and kill the upward growth. So the photo can
   rise out of the top and cover the bar, while never spilling into the panel
   beside it. */
.msplit__item{
  position:relative;display:flex;align-items:flex-end;
  min-height:100svh;
  overflow-x:clip;
  overflow-y:visible;
  border-right:1px solid var(--line);
  color:var(--txt);
  z-index:1;
}
.msplit__item:last-child{border-right:0}
/* the hovered panel jumps above its neighbour AND above the marquee */
.msplit__item:hover,
.msplit__item:focus-visible{z-index:20}

.msplit__item img{
  position:absolute;inset:0;width:100%;height:100%;
  /* `contain`, not `cover` — the whole photo is always visible, nothing is
     cropped off. The panel is wider than these 2:3 portraits, so there's
     margin at the sides; the files are transparent cut-outs, so that margin
     is simply the page showing through. */
  object-fit:contain;
  object-position:center;
  /* NO filter — the photos show in their own tones, ungraded and undarkened */
  /* grows from the bottom edge, so the extra height goes upward over the bar */
  transform-origin:50% 100%;
  transform:scale(1);
  transition:transform 1.4s var(--ease);
  will-change:transform;
  /* no top fade — the photo should COVER the bar, not blend into it */
}
.msplit__item:hover img,
.msplit__item:focus-visible img{
  transform:scale(1.12);
}

/* Very light scrim at the very bottom only. The photos are cut-outs on a
   black page and both subjects wear black, so the copy is already legible —
   this is just insurance, not a darkening layer. */
.msplit__item::after{
  content:'';position:absolute;inset:0;z-index:1;pointer-events:none;
  background:linear-gradient(180deg,
    transparent 0%,transparent 68%,
    rgba(0,0,0,.28) 86%,rgba(0,0,0,.72) 100%);
}

/* hovering one panel eases the others back — the live version of this is
   scoped to the run's track, down in the .msplit-run block */

/* Same trick on the small grid card: this portrait has a studio backdrop
   instead of an alpha channel, so `screen` drops the black away. */
.member--blend .fig img{
  mix-blend-mode:screen;
  filter:grayscale(1) contrast(1.12);
  object-position:center 18%;
}

/* ---------- MEMBERS AS A RUN ----------
   Moved by transform, not native overflow: overflow-x:auto would force
   overflow-y to auto and clip the photos where they rise over the bar. That
   only matters where there is a hover to clip — on touch the strip becomes a
   real scroller instead, further down. */
/* above the marquee (z-index:2), same as the old .msplit wrapper — this is what
   lets a hovered photo paint over the bar instead of under it */
.mrun{position:relative;z-index:3;border-block:1px solid var(--line)}
.mrun__view{
  /* `clip` is the only overflow value that can differ per axis: hold the track
     in sideways, leave the top open so a hovered photo can rise over the bar */
  overflow-x:clip;
  overflow-y:visible;
  touch-action:pan-y;            /* vertical scrolling still belongs to the page */
}
/* The keyframe here is the no-JS path only: one element, content repeated
   inside it, shifting -50% so the second copy lands where the first started.
   With JS, site.js §9 sets `animation:none` and drives the same motion from a
   variable instead — a keyframe and a drag cannot both own `transform`, and the
   strip has to be draggable. */
.mrun__track{
  display:flex;width:max-content;
  animation:scrollX 60s linear infinite;
  will-change:transform;
}
/* Nothing stops the run any more. Hover-pause read as a fault — the strip
   halted the instant a reader moved across to look at a face — and the
   pause/play button was removed after it.

   That leaves no on-page mechanism, which WCAG 2.2.2 asks for on anything
   that auto-moves past five seconds. The reduced-motion block at the foot of
   this file still parks the strip completely and hands the axis to a native
   scroller, so the preference is honoured; readers who have not set it have
   the drag and nothing else. Deliberate, and the place to undo it. */

/* ---- the strip is a handle ----
   The panels are anchors wrapping portraits, so a press-and-push would start a
   native image drag or select the name underneath instead of moving the strip.
   site.js §9 also preventDefaults `dragstart`, for Firefox, which has no
   -webkit-user-drag. */
.mrun__view{cursor:grab}
.mrun__view.is-dragging{cursor:grabbing}
.mrun__view.is-dragging .mrun__track{user-select:none}
.mrun__view img{-webkit-user-drag:none;user-select:none}
/* vw, not %: once the track is max-content, a percentage width would resolve
   against the track itself rather than the viewport */
.mrun__track > .msplit__item{flex:none;--pw:50vw;width:var(--pw)}
@media(max-width:900px){.mrun__track > .msplit__item{--pw:82vw}}
.mrun .msplit__item:last-child{border-right:0}
.mrun__track:hover .msplit__item{opacity:.7;transition:opacity .7s var(--ease)}
.mrun__track:hover .msplit__item:hover{opacity:1}

/* no JS: fall back to a normal swipe strip. overflow-y goes to auto here, so
   the photos stay inside the panel — an acceptable trade for it working at all */
html:not(.js) .mrun__view{overflow-x:auto;overflow-y:hidden}

/* ---- touch: a swipe strip, not a marquee ----
   The transform loop exists so a hovered photo can rise over the bar without
   overflow-x:auto forcing overflow-y to auto and clipping it. There is no hover
   on a phone, so that trade buys nothing there — and handing the axis back to
   the platform gives momentum, snapping, and a position the reader controls
   instead of one that only moves on its own. WCAG 2.2.2 stops applying with it:
   nothing auto-animates, so there is nothing to pause. site.js §9 skips its
   panel duplication under the same query — the copies exist to hide the loop's
   seam, and in a manual scroller they are just unreachable panels to swipe past. */
@media (hover:none) and (pointer:coarse){
  .mrun__view{
    overflow-x:auto;overflow-y:hidden;
    touch-action:pan-x pan-y;          /* the page keeps the vertical axis */
    scroll-snap-type:x mandatory;
    -webkit-overflow-scrolling:touch;
    scrollbar-width:none;
  }
  .mrun__view::-webkit-scrollbar{display:none}
  .mrun__track{animation:none;transform:none}
  .mrun__track > .msplit__item{scroll-snap-align:center}
}

/* This portrait is a studio shot with a dark backdrop, not a transparent
   cut-out like the others. `screen` drops everything at black away, so it sits
   on the page the same way the cut-outs do. */
.msplit__item--blend img{
  mix-blend-mode:screen;
  filter:grayscale(1) contrast(1.12);
  object-fit:cover;
  object-position:center 22%;
}

.msplit__body{position:relative;z-index:2;width:100%;padding:clamp(26px,3.6vw,56px)}
.msplit__no{
  font-family:var(--mono);font-size:10.5px;letter-spacing:.22em;
  text-transform:uppercase;color:var(--txt-3);display:block;margin-bottom:14px;
}
.msplit__name{
  font-family:var(--disp);font-weight:700;
  font-size:clamp(28px,3.7vw,60px);line-height:1;letter-spacing:-.035em;
}
.msplit__role{
  font-family:var(--mono);font-size:10.5px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--txt-2);margin-top:14px;
}
/* the "button" affordance — slides out on hover */
.msplit__go{
  display:inline-flex;align-items:center;gap:10px;margin-top:24px;
  font-family:var(--mono);font-size:11px;letter-spacing:.17em;text-transform:uppercase;
  padding-bottom:6px;border-bottom:1px solid var(--line-2);
  transition:gap .45s var(--ease),border-color .45s var(--ease);
}
.msplit__item:hover .msplit__go,
.msplit__item:focus-visible .msplit__go{gap:18px;border-bottom-color:var(--accent);color:var(--accent)}
.msplit__go svg{width:13px;height:13px;flex:none}

@media (max-width:820px){
  /* the run still scrolls sideways on phones, just with narrower panels */
  .mrun__track > .msplit__item{--pw:clamp(280px,86vw,520px)}
  /* no hover on touch — never leave a panel dimmed */
  .mrun__track:hover .msplit__item{opacity:1}
}

/* ---------- PORTRAIT ----------
   `min-height:100svh` on a panel is only sane while the panel is also about
   half the viewport wide: on desktop that lands near the 2:3 of the cut-outs,
   so a contained portrait fills the slot. Stand the device up and the same
   panel becomes roughly 0.4 wide-to-tall, which no portrait can fill — the
   three cut-outs shrank to 44–59% of the panel height and floated in the
   middle of it with dead black above and below, while Aman's 1:1 studio shot
   was `cover` and lost about a third of its width off each side.

   So in portrait the panel stops taking its height from the viewport and
   takes it from the photo instead: 1.5x its own width (the 2:3 of the tallest
   cut-out) plus a foot of clear space for the name. Every portrait then spans
   the full panel width and stands on the same floor. --pw carries the panel
   width so the height can be derived from it; max-height keeps a short screen
   from being overrun. */
@media (orientation:portrait){
  .msplit__item{
    --foot:clamp(96px,15svh,150px);
    min-height:0;
    height:calc(var(--pw,86vw) * 1.5 + var(--foot));
    max-height:100svh;
  }
  /* the photo gets the panel minus that foot, and sits on its floor rather
     than being centred in the whole panel */
  .msplit__item img{
    /* anchored off the bottom with an explicit height, not top+bottom insets:
       `height:auto` on a replaced element resolves to the intrinsic height and
       the bottom inset is then dropped as over-constrained, which pinned every
       photo back to the top of the panel */
    inset:auto 0 var(--foot) 0;
    height:calc(100% - var(--foot));
    object-position:center bottom;
  }
  /* `contain`, not `cover`: a square studio shot in a 2:3 slot loses a third
     of its width to the crop, and `screen` already drops the backdrop away,
     so there is nothing for `cover` to buy here */
  .msplit__item--blend img{object-fit:contain;object-position:center bottom}
}

/* Cards, same cause. The figure is 4/5 (0.8) but the four portraits are
   2:3 (0.667), 1187x1325 (0.896) and 1:1 — so at one and two columns Qais and
   Abdallah filled the height with margins at their sides, Ahmad spanned the
   full width instead and left a band of empty card above him, and Aman was
   cropped 40px off each edge. A 1/1 figure is wider than all four sources, so
   every one of them becomes height-limited: same photo height, same top, same
   floor, nothing cropped, and the card is shorter than it was. */
@media (orientation:portrait) and (max-width:1024px){
  .member .fig{aspect-ratio:1/1}
  .member--blend .fig img{object-fit:contain;object-position:center}
}

/* ---------- PORTRAIT, ON TOUCH ----------
   The block above overlaps the copy onto the photo and reserves --foot for it.
   That foot is a guess, and on a phone the guess is short: measured at 390x844
   the copy needs ~218px (26px padding twice, the eyebrow, two lines of name at
   28px, a role that wraps to two lines, and the "View portfolio" rule) while
   15svh gives 128px. The copy overruns the floor by ~90px and lands on the
   subject's own body — light type on the light part of a cut-out.

   Rather than grow the foot to another guess, remove the overlap. The photo is
   only absolute so it can scale past the panel on hover; there is no hover on a
   phone, so it can sit in normal flow above the copy and nothing is on top of
   the name to hide it. Gated on `hover:none`, not on orientation alone — a
   portrait desktop monitor matches orientation:portrait, still has a pointer,
   and keeps the overlapping layout and its hover-grow.

   Panels are flex items of .mrun__track, which stretches them all to the
   tallest, and each panel bottom-aligns its own contents — so the names still
   line up across the strip even though the portraits differ in height. */
@media (orientation:portrait) and (hover:none){
  .msplit__item{
    flex-direction:column;
    justify-content:flex-end;
    align-items:stretch;
    min-height:0;
    height:auto;
  }
  .msplit__item img{
    position:static;
    width:100%;
    /* auto, not a derived height: each portrait keeps its own ratio and stands
       on the copy, so none of them is letterboxed inside a 2:3 slot */
    height:auto;
    max-height:calc(100svh - 280px);   /* keeps the copy on screen */
    object-fit:contain;
    object-position:center bottom;
    /* the layer promotion exists for the hover scale, which cannot fire here.
       It also puts the photo on a compositing layer that WebKit will paint over
       the copy despite the copy's higher z-index. */
    will-change:auto;
  }
  .msplit__item--blend img{object-fit:contain;object-position:center bottom}
  /* the scrim was insurance for copy sitting on the photo; with no overlap left
     it only darkens the subject's feet */
  .msplit__item::after{display:none}
}

/* ============================================================
   PRELOADER
   Markup is inline in index.html so it paints on first frame.
   enhance.js drives the counter and removes it. There is also an
   inline fallback at the bottom of index.html that strips it if
   the script never runs — it must never be able to trap the page.
   ============================================================ */
.preloader{
  position:fixed;inset:0;z-index:300;background:var(--bg);
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:26px;
  transition:opacity .7s var(--ease),visibility 0s linear .7s;
}
.preloader.is-done{opacity:0;visibility:hidden;pointer-events:none}
.preloader__num{
  font-family:var(--disp);font-weight:700;letter-spacing:-.04em;
  font-size:clamp(56px,13vw,150px);
  line-height:.9;color:var(--txt);font-variant-numeric:tabular-nums;
}
.preloader__bar{
  width:min(240px,46vw);height:1px;background:var(--line-2);overflow:hidden;
}
.preloader__bar i{
  display:block;width:100%;height:100%;background:var(--accent);
  transform:scaleX(0);transform-origin:0 50%;
}
.preloader__label{
  font-family:var(--mono);font-size:10.5px;letter-spacing:.24em;
  text-transform:uppercase;color:var(--txt-3);
}

/* ============================================================
   FLOATING ELEMENTS
   data-float="0.6" on any element — enhance.js drifts it and
   leans it toward the cursor. Purely decorative.
   ============================================================ */
[data-float]{
  position:absolute;pointer-events:none;z-index:0;
  will-change:transform;
}
/* Inline SVG line-art rather than photos: genuinely transparent, nothing to
   load, and it scales cleanly. No rectangular edges against the black. */
.floaty{
  width:clamp(70px,11vw,170px);aspect-ratio:1/1;
  color:var(--txt);opacity:.2;
}
.floaty svg{width:100%;height:100%;display:block;overflow:visible}
.floaty img{width:100%;height:100%;object-fit:contain;display:block}

@media (max-width:760px){
  .floaty{width:clamp(56px,17vw,110px);opacity:.14}
}

/* ---------- RESPONSIVE ---------- */
@media (max-width:1024px){
  .grid-4{grid-template-columns:repeat(2,1fr)}
  .grid-3{grid-template-columns:repeat(2,1fr)}
  .split{grid-template-columns:1fr;gap:34px}
  .foot__top{grid-template-columns:1fr 1fr}
  .foot__brand-col{grid-column:1/-1}
}
@media (max-width:820px){
  .nav-links{display:none}
  .nav-cta .btn{display:none}
  .nav-toggle{display:block;width:44px;height:44px}
  .row{grid-template-columns:1fr;gap:10px}
  .row__go{margin-top:6px}
}
@media (max-width:620px){
  :root{--gut:clamp(18px,5.5vw,24px)}
  html{scroll-padding-top:68px}
  body{font-size:16px;line-height:1.65}
  .section{padding-block:clamp(64px,20vw,96px)}
  .nav{
    padding-top:max(12px,env(safe-area-inset-top));
    padding-right:max(var(--gut),env(safe-area-inset-right));
    padding-bottom:12px;
    padding-left:max(var(--gut),env(safe-area-inset-left));
    gap:12px;
  }
  .nav.is-stuck{padding-top:max(10px,env(safe-area-inset-top));padding-bottom:10px}
  .nav-brand{gap:12px}
  .nav-brand .gate-mark{height:26px}
  .nav-brand b{font-size:14px;letter-spacing:.08em}
  .drawer{
    padding-top:max(76px,calc(env(safe-area-inset-top) + 64px));
    padding-right:max(var(--gut),env(safe-area-inset-right));
    padding-bottom:max(28px,env(safe-area-inset-bottom));
    padding-left:max(var(--gut),env(safe-area-inset-left));
  }
  .drawer a{min-height:44px;display:flex;align-items:center}
  .h-display{font-size:clamp(42px,14vw,68px);line-height:.94}
  .h1{font-size:clamp(38px,12vw,58px);line-height:.98}
  .h2{font-size:clamp(32px,10vw,46px);line-height:1.04}
  .lead{font-size:16px;line-height:1.6}
  .eyebrow{font-size:10px;letter-spacing:.18em}
  .grid-2,.grid-3,.grid-4{grid-template-columns:1fr}
  .foot__top{grid-template-columns:1fr}
  .foot__bot{align-items:flex-start;flex-direction:column}
  .btn{min-height:44px;padding:12px 18px}
  .row{padding-block:24px;gap:14px}
  .row__t{font-size:clamp(25px,8vw,34px);line-height:1.08;overflow-wrap:anywhere}
  .row__d{font-size:15px;line-height:1.6}
  .row__go{min-height:44px;margin-top:0;justify-self:start}

  /* The desktop word-by-word pin becomes a normal reading section on phones.
     Long scroll choreography makes this paragraph feel fragmented on a small
     viewport, so mobile gets the full thought at once. */
  .scrub{height:auto!important}
  .scrub__stick{
    position:relative;top:auto;min-height:0;
    padding-block:clamp(76px,22vw,112px);
    display:block;
  }
  .scrub__body{padding-left:18px;padding-right:4px}
  .scrub__rule{top:.2em;bottom:.2em}
  .scrub__rule i{transform:scaleY(1)}
  .scrub__text{
    max-width:none;font-size:clamp(27px,8.2vw,38px);
    line-height:1.2;letter-spacing:-.02em;
    text-wrap:pretty;
  }
  .scrub__text span{color:var(--txt)}
  .scrub__text span.key{background-size:100% 1px}
  .scrub__i,.scrub .floaty{display:none}

  .cta{padding-bottom:max(72px,env(safe-area-inset-bottom))}
}

/* ============================================================
   PAGE TRANSITION + ROW HOVER
   These were three GSAP tweens. GSAP is ~70 KB over the wire and runs its
   own ticker; all three are a transform and an opacity between two fixed
   states, which is what a CSS transition already is. The reduced-motion
   block below flattens every duration, so none of this needs guarding there.

   .pt-enter / .pt-out are put on by enhance.js rather than sitting in the
   markup: if that script never runs, <main> is simply visible, instead of
   stuck at opacity 0 behind a fade that has nothing left to trigger it.
   ============================================================ */
main{will-change:auto}
main.pt-enter{opacity:0;transform:translate3d(0,22px,0)}
main.pt-in{transition:opacity .9s var(--ease),transform .9s var(--ease)}
main.pt-out{
  opacity:0;transform:translate3d(0,-26px,0);
  transition:opacity .5s cubic-bezier(.55,.09,.68,.53),transform .5s cubic-bezier(.55,.09,.68,.53);
}

/* Hover nudge on the big row links. Hover-capable pointers only — on touch,
   :hover sticks after a tap and the title would stay shoved sideways. */
@media (hover:hover) and (pointer:fine){
  .row__t{transition:transform .55s var(--ease)}
  .row:hover .row__t{transform:translate3d(14px,0,0)}
}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important}
  main.pt-enter,main.pt-out{opacity:1;transform:none}
  [data-reveal]{opacity:1;transform:none}
  .clip-up > *{transform:none}
  .gate-reveal::before,.gate-reveal::after{display:none}
  .page-gate{display:none}
  /* members run — stopped, and scrollable by hand instead */
  .mrun__track{animation:none!important;transform:none!important}
  .mrun__view{overflow-x:auto}
  /* unpin the scroll-driven section */
  .scrub__stick{position:static;min-height:0;height:auto;overflow:visible}
  .scrub__text span{color:var(--txt)!important}
  /* the scroll engine is off, so nothing writes --scrub-p — show the
     rule filled and the underlines drawn rather than empty */
  .scrub__rule i{transform:none}
  .scrub__text span.key{background-size:100% 1px}
  .scrub__i{display:none}
  .split-w > i{transform:none!important}
  .js [data-scale]{transform:none!important}
  [data-parallax]{transform:none!important}
  .stack > .stack__card{position:static}
  .acc__item[open] .acc__panel{animation:none}
  .progress{display:none}
  .gatesplit__half{transform:none!important}
  .gatesplit__beam{display:none}
  .preloader{display:none!important}
  [data-float]{transform:none!important}
  /* no door animation — show the hero straight away */
  .js .intro{height:auto}
  .js .intro__stick{position:relative;height:auto;min-height:100svh;padding-block:clamp(120px,16vh,180px) clamp(60px,8vh,90px)}
  .js .intro__content{opacity:1!important;transform:none!important}
  .intro__door,.intro__beam,.intro__cue{display:none!important}
  .intro__brand{opacity:1!important}
}
