
/* ============================================================
   VARA Expo — hero is 3D, everything below is vector lines.
   Carried-over perf rules (all measured on this project):
     · no background-attachment:fixed
     · no background-clip:text on animated content
     · no animated layout properties (letter-spacing, width, …)
     · no mix-blend-mode on a fixed full-viewport layer
     · text-shadow blur radii stay SMALL. A 46px radius cost 2.3x the
       scroll sweep vs 14px; a 110px radius + backdrop-filter cost 4.4x.
   ============================================================ */

:root{
  /* navy scale. #001D61 is the client's structural ground. */
  --navy-950:#00133F; --navy-900:#001A52; --navy:#001D61; --navy-700:#0A3080;
  --cream:#F3EDDF; --ink:#F6F1E6;
  --coral:#FF8A66; --cyan:#1FE1FE; --violet:#B66CF1; --green:#6BFE96;
  --gut:clamp(20px,4.6vw,72px);
  --maxw:1320px;
}
/* The reset lives in the BASE layer on purpose. Unlayered it beats every
   layer regardless of specificity, so this one-character selector silently
   outranked every .foot/.band/.card rule system/tw.css defines — which is
   what kept the footer credit misaligned here and nowhere else. Deleting it
   outright is not the fix either: preflight's own reset then reaches rules
   this page's inline CSS was relying on it to beat. @layer base puts it back
   in the right tier. */
@layer base{*{box-sizing:border-box;margin:0;padding:0}}
/* #field is a position:fixed canvas covering the viewport behind everything.
   Rubber-band overscroll slides the content up and reveals it bare — a band
   of raw colour field below the footer that reads as unfinished deadspace.
   Two guards: stop the bounce, and paint the root the footer's own ground so
   even a browser that bounces anyway has nothing alien to show. */
html{scroll-behavior:smooth;background:#001D61;overscroll-behavior-y:none}
body{overscroll-behavior-y:none}
body{
  /* Pinned, not inherited. Linking the bundle brought base.css's
     `body{font-size:17.5px}` — the system's body size, which the other
     seventeen pages use — and home had been on the UA default 16px. That one
     inherited property moved 4,279 of 4,329 elements in the geometry
     fingerprint. Adopting the system's type scale on this page is a real
     design decision, not a side effect of a stylesheet link, so the size is
     held here and the question is raised separately. */
  font-size:16px;line-height:normal;
  background:var(--navy-950);color:var(--ink);
  font-family:"General Sans","General Sans fallback",system-ui,-apple-system,sans-serif;
  -webkit-font-smoothing:antialiased;overflow-x:hidden;
}

/* ---------- METRIC-MATCHED FALLBACKS ----------------------------------------
   The page's last layout shift was a font swap. Measured in a 4x trace: at
   1,233ms the hero copy block went 1092x1002 -> 1060x871 and the nav and
   wordmark narrowed 7-14px, all at once, as the real faces replaced the
   system fallback. CLS 0.0041 — every remaining bit of it.

   These three faces are the SAME system fonts the stack already falls back to,
   re-declared with their metrics bent to match the real ones, so the fallback
   occupies the space the webfont will occupy and the swap moves nothing.

   The numbers are measured, not copied from a generator: each real face and
   its fallback were rendered to a canvas at 100px and compared on advance
   width and fontBoundingBox ascent/descent.

     Cabinet Grotesk  vs Arial          3264.90 / 3444.19 -> 94.79%
     General Sans     vs Arial          3293.20 / 3444.19 -> 95.62%
     JetBrains Mono   vs ui-monospace   4259.93 / 3225.24 -> 132.08%

   Cabinet was then tuned by measurement, not left at the computed figure:
   94.79% still rendered the statement 14.5px wide against the real face, so
   it went to 92.10% (and its overrides with it), which lands it at -1.1px.
   The residual on the copy BLOCK is -8px of height and does not come from
   line boxes — line-gap-override at 8%, 12% and 16% all measured identically,
   so it is not worth overfitting to one viewport.

   ascent/descent overrides are expressed against the ADJUSTED em (the real
   metric divided by size-adjust), which is what the spec measures them
   against. If `local()` finds nothing — a Linux or Android device with no
   Arial — the face simply has no source, the family drops out, and the stack
   falls through exactly as it does today. */
@font-face{
  font-family:"Cabinet Grotesk fallback";
  src:local("Arial"),local("Helvetica"),local("Liberation Sans");
  size-adjust:92.10%; ascent-override:94.46%; descent-override:30.40%; line-gap-override:0%;
}
@font-face{
  font-family:"General Sans fallback";
  src:local("Arial"),local("Helvetica"),local("Liberation Sans");
  size-adjust:95.62%; ascent-override:105.63%; descent-override:25.10%; line-gap-override:0%;
}
@font-face{
  font-family:"JetBrains Mono fallback";
  src:local("Menlo"),local("Consolas"),local("DejaVu Sans Mono");
  size-adjust:132.08%; ascent-override:77.23%; descent-override:22.71%; line-gap-override:0%;
}
img{max-width:100%;display:block}
.d{font-family:"Cabinet Grotesk","Cabinet Grotesk fallback","General Sans",sans-serif;font-weight:800;letter-spacing:-.035em;line-height:.93}
.mono{font-family:"JetBrains Mono","JetBrains Mono fallback",ui-monospace,SFMono-Regular,Menlo,monospace}

/* ---------- nav ----------
   Flush at the hero — no background, no blur. The frost only arrives once
   .scrolled is toggled, and that toggle comes from ONE IntersectionObserver
   on a sentinel, never a scroll listener. Re-measured as free on this page. */

/* The bar's entire visual treatment — tint AND blur — lives on one pseudo
   element that is TALLER than the fade, so its hard edge sits where the mask
   has already reached zero and can never be seen.
   Previous attempts failed because something always kept a hard bottom edge:
   first the backdrop-filter on the header box, then (after moving the blur to
   a pseudo) the header's own background gradient, which still stopped dead at
   77px. Measured as the sharpest transition on the page both times. */


/* The hero used to carry a VARA EXPO 2026 title of its own. With the statement
   leading the hero that line has nowhere to sit, so the name moves into the
   nav beside the mark — where a wordmark belongs — instead of being dropped. */



/* 96x92, not square - a fixed 34x34 squashed it 4% (matches system/base.css) */






.pill{
  display:inline-flex;align-items:center;gap:9px;
  padding:11px 21px;border-radius:999px;text-decoration:none;
  font-size:13.5px;font-weight:600;letter-spacing:.01em;
  background:var(--cream);color:#04143C;
  box-shadow:0 0 0 1px rgba(255,255,255,.14);
  transition:transform .35s,box-shadow .35s;
}
.pill:hover{transform:translateY(-1px);box-shadow:0 0 0 1px rgba(255,255,255,.3),0 6px 26px rgba(0,0,0,.35)}
.pill.ghost{background:transparent;color:var(--ink);box-shadow:inset 0 0 0 1.5px rgba(246,241,230,.52)}
.pill.ghost:hover{box-shadow:inset 0 0 0 1.5px var(--cream)}
.dot{width:6px;height:6px;border-radius:50%;background:#FF724F}
a:focus-visible,button:focus-visible{outline:2px solid var(--cream);outline-offset:3px}

/* ---------- hero (3D) ---------- */
#hero{
  position:relative;min-height:100svh;overflow:clip;isolation:isolate;
  /* three rows: title pinned top, lockup centred over the element, statement
     and actions pinned to the base of the viewport */
  /* SPLIT: copy left, element right. This replaces the centred poster stack
     (title pinned top, date centred over the element, statement at the base).
     The inversion is the point of the redesign — the STATEMENT leads and the
     date becomes supporting information, where before the date was the hero
     and the statement sat at the bottom of the viewport. */
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);align-items:center;
  padding:clamp(70px,10vh,110px) var(--gut) clamp(34px,6vh,64px);
  /* DELIBERATE DEVIATION FROM THE HANDOFF README.
     The README specifies renderer.setClearColor(0x37002c) — brand plum.
     The brand ground moved from plum to NAVY (#001D61), so the hero canvas
     is rendered with alpha:true over this navy page instead. Do not revert
     the clear colour to plum; it is not a bug. */
  /* Ends on #001A52 — the exact colour the continuous field starts on — so
     the hero hands off to the rest of the page with no visible seam. It used
     to fall to #00081C (near-black) against a navy field, which drew a hard
     horizontal edge across the boundary. Stay inside the navy family. */
  /* The hero carries the full colour spread — it is composed of all four
     brand hues — and then FADES TO TRANSPARENT at the bottom so the
     continuous field shows through underneath. Two opaque grounds always
     meet in a visible line; one fading into the other does not. */
  background:transparent;   /* the ground lives on ::before so it can be masked */
}
/* The hero ground sits on its own layer so a single mask can fade EVERY
   layer together. Previously the base gradient faded but the four colour
   washes on top of it did not, so they stopped dead at the hero's bottom
   edge and drew the line you could see. One mask, all layers, no seam.
   The hero deliberately carries all four hues — it is the one place the
   whole palette appears at once. */
/* The hero has NO ground of its own. Its ambilight is painted by the shared
   #field canvas via the 'all' weight state, so there is exactly one ground
   for the whole document and nothing to butt against at the hero's edge. */
/* ---- AMBILIGHT -----------------------------------------------------------
   A soft spill of the element's own four hues on the ground behind it, the way
   a backlit screen throws colour onto the wall. It is deliberately UNDER the
   canvas (z-index 0 vs the element's own paint) and deliberately weak: the
   brief is vibrancy, not a wash, and the navy has to stay unmistakably navy.

   Sized and placed to the element's MEASURED frame — centre 72.5% / 53%, about
   52% of the width — not to the section, or the glow sits where the element
   is not. --amb-x/--amb-y are updated with the camera in layout(), so the glow
   follows the element instead of drifting away from it at other widths.

   `plus-lighter` adds light rather than replacing colour, so the navy shows
   through the glow instead of being covered by it — `screen` at this alpha
   flattened the ground to a flat mauve. */
/* Positioned from layout()'s own framing constants, not by eye: the element's
   on-screen centre is 72% / 48% above 900px and 50% / 38% below it — the exact
   values hero-element.js computes — and it is framed at about 52% of the hero
   width. The ambilight is anchored to the same point, because the glow is
   anchored to the element. */
.hero-still{
  display:none;position:absolute;z-index:0;pointer-events:none;
  /* The art is cropped to its own alpha bounds, so this box IS the mark — no
     invisible padding to reason about. aspect-ratio reserves the height before
     the file arrives, so the still cannot introduce a shift of its own. */
  left:72%;top:48%;width:42%;aspect-ratio:1400/1378;height:auto;
  transform:translate(-50%,-50%);
}
html.no-3d .hero-still{display:block}
html.no-3d #stage{display:none}
/* Narrow: MIRROR `#hero canvas` exactly rather than guess a percentage.
   Below 900px the hero is a two-row grid (`auto auto`) and the canvas is a
   grid ITEM in row 1 at `height:min(42svh,360px)` — that row is what reserves
   space above the copy. An absolutely positioned still is out of flow, so it
   ignored the row entirely and landed on top of the statement whatever
   percentage it was given; 86% and 66% were both wrong for the same reason.
   Taking the same box means the still sits where the element sits, at every
   aspect ratio, with no numbers to re-tune. `contain` keeps the art centred in
   that box without distorting it. */
@media (max-width:900px){
  .hero-still{
    position:relative;inset:auto;grid-row:1;transform:none;
    width:100%;height:min(42svh,360px);aspect-ratio:auto;object-fit:contain;
  }
}

.ambilight{
  position:absolute;z-index:0;pointer-events:none;
  left:var(--amb-x,72.5%);top:var(--amb-y,53%);
  width:var(--amb-w,58%);aspect-ratio:1;transform:translate(-50%,-50%);
  mix-blend-mode:plus-lighter;opacity:.55;
  /* FOUR HUES, NO BLUE BASE. There used to be a fifth, central blue radial
     holding the others together — but adding blue light to an already blue
     ground is the one thing that cannot show, so all it did was raise the
     brightness and make the whole spill read as a blue blob.

     Each stop sits where its stroke actually is in the element: green upper
     left, violet upper right, cyan lower left, coral lower right. The warm
     pair stays a little under the cool pair — coral and violet on blue read as
     pink much faster than green and cyan read as anything, which is what
     turned the copy side mauve before — but not so far under that the glow
     loses its colour, which is what happened when they were halved. */
  /* Re-tuned for the DARK ground. These alphas were set against a much
     brighter blue ramp; on #001D61 with a #0B2A7A bloom the whole layer was
     adding a peak of RGB(7,18,10) — luma 15 against a ground of RGB(11,42,122),
     which is to say nothing at all. Roughly 3x now, which is what it takes for
     additive light to read as colour on a dark ground rather than as a faint
     lift in brightness. */
  background:
    radial-gradient(37% 41% at 41% 27%, rgba(107,254,150,.42), transparent 67%),
    radial-gradient(34% 37% at 69% 23%, rgba(182,108,241,.36), transparent 68%),
    radial-gradient(39% 43% at 32% 69%, rgba(31,225,254,.42), transparent 68%),
    radial-gradient(37% 40% at 73% 71%, rgba(255,138,102,.38), transparent 68%);
  filter:blur(56px) saturate(1.3);
}
@media (prefers-reduced-motion:reduce){ .ambilight{filter:blur(56px) saturate(1.3)} }
@media (max-width:900px){ .ambilight{--amb-x:50%;--amb-y:62%;--amb-w:96%;opacity:.46} }

#hero canvas{
  /* Still full-bleed, NOT confined to the right column. The element is framed
     by shifting the camera's frustum window (see layout() below), which keeps
     the approved pose exactly; sizing the canvas to a column instead would
     shrink the element and re-crop it. Full-bleed also lets it bleed past the
     right edge, which is what the layout wants. */
  position:absolute;inset:0;width:100%;height:100%;display:block;z-index:0;
  background-repeat:no-repeat;
  /* CONTAIN, not 100% 100%. The fallback is a snapshot of the ELEMENT only.
     Stretching it to the hero box distorted it, and because older snapshots
     had a ground baked in it painted a gradient that could never match the
     live field — that edge was the second band on this page. The ground is
     the shared #field now, so this layer must stay transparent. */
  background-size:contain;background-position:center;
}
/* The hero contrast scrim is GONE.
   It was a 4-stop gradient over the hero only. Each stop is a slope change,
   and on a background this soft a slope change reads as a band — ablation
   put it at exactly y=892, and removing it dropped the sharpest transition
   on screen from 24.4 to 15.9. With every overlay off, the shared field
   measures 4.9, i.e. genuinely smooth: every band on this page has been an
   effect layer that does not span the document drawing its own edge.
   Hero contrast is carried by text-shadow on the type instead, which has
   no geometry and therefore no edge. */
/* ---- hero type ---------------------------------------------------------
   Four things only: title, date, place, statement. The descriptor line and
   the countdown moved below the fold — the descriptor duplicated the VARA
   EXPO title, and the countdown was a fifth element competing in the centre.
   Shadows are layered + directional + navy-tinted (see --sh-sm/--sh-lg);
   a centred 0 0 glow reads as a halo, which is what looked amateurish. */
:root{
  --sh-sm:0 1px 1px rgba(0,9,30,.30),0 2px 4px rgba(0,9,30,.22),
          0 4px 10px rgba(0,9,30,.18),0 9px 22px rgba(0,9,30,.13),
          0 18px 44px rgba(0,9,30,.09);
  --sh-lg:0 1px 2px rgba(0,9,30,.26),0 4px 8px rgba(0,9,30,.20),
          0 9px 22px rgba(0,9,30,.16),0 20px 48px rgba(0,9,30,.12),
          0 38px 78px rgba(0,9,30,.08);
}
/* BALANCE. Measured against the reference: its "STAND OUT" spans 34.6% of the
   viewport, mine spanned 26.7% — the copy was ~1.3x too small for the half it
   sits in, which left a dead vertical band between the text and the element
   and made the element look oversized. Everything here is scaled to close
   that, and the block is allowed to run to ~530px so the rules reach across
   the column instead of stopping a third of the way.

   The block is INSET past --gut on wide screens. It first sat hard on the
   gutter, on the argument that every other page starts there — but the hero is
   the one page with nothing above the fold to align to, and on the gutter it
   read as pinned to the edge rather than placed. The inset is deliberately
   small: enough to look chosen, not enough to drift toward the element.
   Stacked layouts centre the block and cancel it. */
.hero-copy{position:relative;z-index:2;grid-column:1;
  display:grid;justify-items:start;text-align:left;
  margin-left:clamp(0px,2.4vw,40px);
  gap:clamp(18px,2.8vh,32px);max-width:min(530px,100%)}

/* The statement is the hero now. Two beats: the setup at one size, the payoff
   bigger and uppercase, on its own line. */
#hero h1{margin:0;color:var(--cream);letter-spacing:-.03em;line-height:1.02;
  display:grid;gap:clamp(2px,.6vh,8px);
  filter:drop-shadow(0 1px 1px rgba(0,9,30,.30)) drop-shadow(0 3px 8px rgba(0,9,30,.22))
         drop-shadow(0 8px 20px rgba(0,9,30,.16)) drop-shadow(0 18px 42px rgba(0,9,30,.10))}
#hero h1 .say{font-size:clamp(30px,5.05vw,72px);font-weight:700;letter-spacing:-.035em}
/* ONE LINE, always. Wrapped onto two it stopped being a payoff and the
   gradient restarted on the second line, so "OUT" came back round to coral
   instead of finishing on cyan. nowrap + a size that fits the column. */
#hero h1 .grad{font-size:clamp(40px,7.1vw,102px);font-weight:800;text-transform:uppercase;
  letter-spacing:-.045em;line-height:.96;white-space:nowrap}

/* gradient on the payoff only — STATIC, because an animated
   background-clip:text fill was the measured scroll stutter */
#hero h1 .grad{
  background-image:linear-gradient(94deg,#FF8A66 0%,#FB85E5 46%,#61FFF1 100%);
  -webkit-background-clip:text;background-clip:text;
  color:transparent;-webkit-text-fill-color:transparent}

/* ---- the supporting block: name, descriptor, date, place ----
   Two hairlines group it into "what this is" and "when and where". They are
   the only rules in the hero; everything else is type. */
.hero-meta{display:grid;justify-items:start;gap:clamp(5px,.8vh,9px);width:100%}
/* ONE rule, and a quieter one. There were two, bracketing the name line; with
   that line gone a second rule had nothing left to separate, and at .34 the
   pair read as structure the block does not need. This is the single break
   between the statement and the facts under it. */
.hero-meta .hrule{width:min(62%,330px);height:1px;border:0;
  background:linear-gradient(90deg,rgba(246,241,230,.20),rgba(246,241,230,.02));
  margin:clamp(4px,.8vh,10px) 0 clamp(8px,1.4vh,16px)}
/* the date needs the air the second rule used to give it */
.hero-meta .hm-when{margin-top:clamp(10px,1.7vh,20px)}
.hero-meta p{margin:0;text-shadow:var(--sh-sm)}
/* The VARA EXPO 2026 line is GONE from the hero. The wordmark sits in the nav
   beside the mark, the <title> carries it, and repeating it under a headline
   this size was the page saying its own name twice. */
.hm-sub{font-size:clamp(10px,1vw,14px);letter-spacing:.17em;text-transform:uppercase;
  color:rgba(246,241,230,.72)}
.hm-when{font-family:"Cabinet Grotesk","Cabinet Grotesk fallback","General Sans",sans-serif;
  font-size:clamp(25px,3.25vw,47px);letter-spacing:-.02em;color:var(--cream);line-height:1.05}
.hm-when b{font-weight:800}
.hm-when span{font-weight:400;color:rgba(246,241,230,.76)}
.hm-where{font-size:clamp(13px,1.5vw,21px);letter-spacing:.12em;text-transform:uppercase;
  color:rgba(246,241,230,.76)}
/* gradient on the last beat only — STATIC, because an animated
   background-clip:text fill was the measured scroll stutter */
#hero h1 .grad{
  background-image:linear-gradient(94deg,#FF8A66 0%,#FB85E5 46%,#61FFF1 100%);
  -webkit-background-clip:text;background-clip:text;
  color:transparent;-webkit-text-fill-color:transparent}
@supports not ((-webkit-background-clip:text) or (background-clip:text)){
  #hero h1 .grad{color:#FF8A66;-webkit-text-fill-color:#FF8A66;background-image:none}
}

/* ---- the band under the hero ---- */
/* The countdown was a right-hand item in a space-between row, so it read as a
   detail parked beside the descriptor rather than as the thing the page is
   counting to. It is now a centred column: copy on top, the clock in its own
   panel below it.

   The panel is the page's existing .glass component, not a new surface —
   a second raised treatment invented here is exactly the drift that
   DESIGN-SYSTEM.md §0.5 exists to stop. .glass carries a negative left margin
   because it was built to bleed off the hero's copy column, so that is zeroed
   and the box is sized to its contents instead. */
#openin{position:relative;z-index:1;padding:clamp(28px,5vh,56px) var(--gut);
  display:flex;flex-direction:column;align-items:center;text-align:center;
  gap:clamp(20px,3.2vh,36px)}
#openin .who{font-size:clamp(14px,1.25vw,17.5px);color:rgba(246,241,230,.88);
  max-width:62ch;margin:0}
/* width:fit-content made this a box floating in a wide band — the panel read
   as a chip rather than as the section. It is now a proper band at --maxw, so
   it occupies the row it was given. */
#openin .cdwrap{margin-left:0;width:min(100%,var(--maxw));
  padding:clamp(26px,3.4vw,44px) clamp(24px,3.4vw,52px)}
/* the clock needs saying what it is; "48" alone is not a sentence */
#openin .cdlbl{margin:0 0 clamp(14px,2vh,22px);font-size:var(--t-label);
  letter-spacing:var(--ls-label);text-transform:uppercase;font-weight:600;
  color:var(--ink-56)}

/* THE DIGITS CARRY THIS, NOT THE PANEL.
   At clamp(28px,4.6vw,62px) the figures were sized for a countdown tucked
   beside a paragraph, and dropping that unchanged into a highlighted band is
   what still read as weak — a frame around quiet type reads quieter, not
   louder. They now run at --t-display-lg, the stat-figure step, which is the
   scale this page already uses when a number IS the content. */
#openin .cd{gap:clamp(12px,2.2vw,34px)}
#openin .cd .n{font-size:var(--t-display-lg);line-height:.9;letter-spacing:-.04em}
#openin .cd .k{font-size:clamp(9.5px,.85vw,12px);letter-spacing:.2em;
  color:var(--ink-84);margin-top:clamp(10px,1.4vh,14px)}
/* the rules run the full height of the figures now rather than stopping short,
   so the row reads as one instrument instead of four stacked pairs */
#openin .cd .sep{margin-bottom:0;align-self:stretch;
  background:linear-gradient(180deg,transparent,var(--ink-rim) 22%,var(--ink-rim) 78%,transparent)}
@media (max-width:640px){
  #openin .cd .n{font-size:clamp(34px,13vw,56px)}
  #openin .cdwrap{padding:clamp(20px,5vw,30px) clamp(14px,4vw,24px)}
}
.cta{display:flex;gap:12px;flex-wrap:wrap;justify-content:center;margin-top:4px}

/* ---------- countdown ----------
   JetBrains Mono 700/800, tabular-nums: fixed digit advance, so a ticking
   second never reflows. Hairline rules instead of colons. No shadow, no
   filter. Writes only on change; the interval stops on visibilitychange. */
/* `contain:paint` is not decoration. The countdown writes new digits once a
   second INSIDE .glass.cdwrap, which carries
   backdrop-filter:blur(22px) saturate(1.75) brightness(0.52). A text change
   inside a backdrop-filtered element makes Chrome re-read its backdrop, and
   the backdrop here is the fixed full-viewport #field — so every tick
   invalidated the WHOLE DOCUMENT. Measured in a 31s idle trace: two Paint
   events every second, each with a clip of 3420x9962, i.e. the entire page,
   for as long as the tab is open. Paint containment cuts the invalidation off
   at this box, which holds four two-digit figures and three separators and
   overflows nothing. */
.cd{display:flex;justify-content:center;align-items:stretch;gap:clamp(7px,1.3vw,22px);margin-top:4px;contain:paint}
.cd .u{text-align:center}
.cd .n{font-family:"JetBrains Mono","JetBrains Mono fallback",ui-monospace,SFMono-Regular,Menlo,monospace;
  font-weight:800;font-variant-numeric:tabular-nums;
  font-size:clamp(28px,4.6vw,62px);line-height:.94;letter-spacing:-.03em;color:var(--cream)}
.cd .k{font-family:"JetBrains Mono","JetBrains Mono fallback",ui-monospace,SFMono-Regular,Menlo,monospace;
  font-weight:700;font-size:clamp(8.5px,.78vw,11px);letter-spacing:.17em;
  text-transform:uppercase;color:rgba(246,241,230,.94);margin-top:9px}
.cd .sep{width:1px;flex:0 0 1px;background:rgba(246,241,230,.32);align-self:stretch;margin-bottom:24px}

/* ---------- THE COLOUR FIELD ----------
   ONE fixed, full-viewport canvas for the WHOLE document. Not one wash per
   section: per-section backgrounds are exactly what produced the hard edges
   at section boundaries the client rejected. There is a single field; the
   zones only say where each hue PEAKS, never when it switches.

   Painted into a canvas, so no animated custom property is ever written to
   :root — writing one there invalidates style for the entire document every
   frame. No mix-blend-mode on this layer (measured cost), no filter, no
   background-attachment:fixed. */
#field{
  position:fixed;inset:0;z-index:0;display:block;width:100%;height:100%;
  pointer-events:none;
}
/* everything real sits above the field in the root stacking context */
main,footer{position:relative;z-index:1}

/* ---------- zones: lines only, no WebGL below the hero ---------- */
section.zone,#scale{
  position:relative;isolation:isolate;   /* no overflow:hidden — it chopped every line at the section seam */
  padding:clamp(96px,14vh,170px) var(--gut);
  background:transparent;          /* the field shows through — no per-zone wash */
}
section.zone{min-height:100svh;display:grid;align-content:center}
/* THE HERO SEAM — root cause, found by ablation.
   This scrim was rgba(0,10,32,.40) with inset:0 on every .zone and #scale.
   Its own comment claimed it "adds no boundary of its own" because it is
   identical in every section — true BETWEEN zones, false at the hero, which
   was never in the selector list. So the field rendered at full brightness
   across the hero and 40% darker from the first zone down, and that step WAS
   the line. Measured: hiding main entirely left the field smooth (row delta
   1.0); with main visible the same rows differed by ~40.
   The contrast floor it protected is now held by text-shadow on .body and
   .specs li, which costs nothing and has no edges. */
/* (scrim deliberately removed — do not reinstate without also covering #hero) */
/* ONE line layer for the whole document, not one per section. Per-section
   hosts with overflow:hidden cut every stroke at the section boundary and
   produced a hard horizontal seam across the page. Lines now live in
   document space and cross section edges uninterrupted. */
/* z-index 0, NOT -1. A negative z-index child paints behind its parent's
   background, and body has an opaque one — every line was rendering under
   the page. At 0 and placed directly after #field in DOM order it sits
   above the field and below the sections. */
/* The ribbons run past their section on purpose and the vertical bleed is the
   whole effect — but sideways bleed past the viewport gives the landing page a
   horizontal scrollbar (2331px of content in a 1440px window, measured; it is
   on the live site too, and predates the hero redesign). `overflow-x:clip`
   removes the overflow from the scroll region without making this a scroll
   container, and `overflow-clip-margin` lets the ribbons keep bleeding
   vertically. NOT fixable with overflow-x on html or body — both were tried
   and both broke vertical scrolling (see system/base.css). */
/* NOTHING in the line layer clips horizontally. A ribbon that stops at a
   container edge reads as a cut, not a bleed — the whole effect is that the
   strokes run off the page background, past every gutter and section seam.
   The one horizontal guard is body{overflow-x:hidden} at the top of this
   file; mixing overflow-x:clip with overflow-y:visible also silently drops
   overflow-clip-margin (computed 0px, measured), so those container clips
   were cutting at the box edge exactly, which is what they looked like.
   Test vertical scroll at 370px after touching this. */
#lineLayer{position:absolute;left:0;top:0;width:100%;z-index:0;pointer-events:none}
.lines{position:absolute;left:0;width:100%;pointer-events:none;overflow:visible}
/* will-change lists BOTH properties the rAF loop writes. Promoting for
   transform alone left the per-frame opacity write repainting the layer.
   The opacity TRANSITION is gone on purpose: paint() writes opacity every
   frame, so a 500ms transition was being restarted sixty times a second and
   never completing — the browser interpolating a value that had already
   changed again. Together with the SVG mask that used to wrap these ribbons,
   that was the stutter. The fade it provided is already in the easing. */
.lines svg{position:absolute;overflow:visible;will-change:transform,opacity}
.lines path{fill:none;stroke-linecap:round;stroke-linejoin:round}

/* ---------- COPY SHIELD ----------
   The field alone stays under the contrast ceiling, but a 30–40px ribbon
   passing straight behind a line of copy does not — measured at 1.5:1 in the
   education and market zones before this existed. ONE wide, very soft haze
   per copy block. It is IDENTICAL in every section and every zone, so it adds
   no boundary of its own; it is a local floor, not a wash. The fade is
   deliberately enormous (its stops span hundreds of px) so it reads as haze
   rather than as a card — a tight version of this measured fine but looked
   like a panel nobody asked for. Plain gradient: no filter, no blend mode,
   no backdrop-filter (already measured on this page at 4.4x the scroll
   sweep). */
.sinner{position:relative;z-index:0;max-width:var(--maxw);margin:0 auto}
/* NO panel behind the copy — not a dark radial, not a glass sheet.
   Both were the same mistake: a full-width layer wrapping the entire
   section. The glass version also caused the hero seam — its
   brightness(.62) started at the section's top edge and multiplied
   everything behind it, drawing a hard horizontal line exactly at the
   boundary (measured: every colour channel dropped ~37-45 at that row).
   h2 is 30-64px and clears AA on its own. Only the small copy needs help,
   and it gets it from a tight text-shadow rather than a panel. */
.body,.specs li{text-shadow:none}
/* Liquid-glass panels on the description blocks. Scoped to the block, never
   the section — a section-wide panel is what drew the hero seam earlier, and
   a block that cannot span a boundary cannot show an edge. Contrast measured
   on the true backdrop (text hidden): worst pane 4.84:1, floor 4.5. */
.glass{
  position:relative;isolation:isolate;
  border-radius:26px;
  padding:clamp(20px,2vw,30px) clamp(22px,2.2vw,34px);
  margin-left:clamp(-22px,-2.2vw,-34px);
  /* LIQUID GLASS.
     A flat blur reads as frosted, not liquid. What makes a lens read as a
     lens is that the EDGE behaves differently from the middle: it bends and
     concentrates what is behind it, picks up a bright specular rim, and
     separates colour very slightly. So this is built as three layers —
       body : heavy blur, the "thick" centre of the pane
       rim  : a ring with far LESS blur but much more saturation and
              brightness, which is what selling the lensing depends on
       sheen: a directional specular highlight across the top-left
     Centre stays near brightness 1 so the artwork behind survives. */
  /* brightness pulled well under 1 so a bright blob behind the panel cannot
   blow the contrast floor — measured 2.16:1 against cream where the cyan
   passed through at brightness 1.04. Saturation stays HIGH so the colour
   still reads as colour rather than grey, and the rim + sheen below are what
   keep it looking like glass rather than a dark box (the earlier dark
   version had neither). */
  -webkit-backdrop-filter:blur(22px) saturate(1.75) brightness(0.52);
  backdrop-filter:blur(22px) saturate(1.75) brightness(0.52);
  background:linear-gradient(158deg,rgba(255,255,255,.075),rgba(255,255,255,.015) 46%,rgba(6,18,50,.30));
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.34),
    inset 0 0 0 1px rgba(255,255,255,.10),
    inset 0 -14px 30px -22px rgba(255,255,255,.22),
    0 26px 60px -34px rgba(0,0,0,.66);
}
/* the lens rim: barely blurred, strongly saturated, so the edge magnifies and
   brightens what passes behind it instead of matching the centre */
.glass::before{
  /* The lens rim, FEATHERED. It was a hard ring (mask-composite:exclude with
     a 14px pad), so the rim's blur(3px) met the body's blur(22px) at a sharp
     line and that step drew a second rectangle inside the panel.
     Now the mask is the union of an x and a y edge-gradient, each fading to
     transparent ~22% in, so the lensing falls off smoothly and there is no
     inner boundary anywhere. */
  content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;z-index:-1;
  -webkit-backdrop-filter:blur(4px) saturate(2.0) brightness(0.98);
  backdrop-filter:blur(4px) saturate(2.0) brightness(0.98);
  -webkit-mask-image:
    linear-gradient(to right,  #000 0%, transparent 22%, transparent 78%, #000 100%),
    linear-gradient(to bottom, #000 0%, transparent 22%, transparent 78%, #000 100%);
  mask-image:
    linear-gradient(to right,  #000 0%, transparent 22%, transparent 78%, #000 100%),
    linear-gradient(to bottom, #000 0%, transparent 22%, transparent 78%, #000 100%);
  -webkit-mask-composite:source-over;
  mask-composite:add;
}
/* directional specular sweep — the giveaway that the surface is wet/glassy */
.glass::after{
  content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  /* Pulled back hard. At .20/.16 the sheen itself was the brightest thing
     inside the panel and was the limiting factor on text contrast — it
     measured brighter than the backdrop it was sitting over. A specular
     highlight only has to be visible at the very top edge, not across the
     copy. */
  background:
    linear-gradient(152deg,rgba(255,255,255,.10) 0%,rgba(255,255,255,.02) 14%,transparent 30%),
    radial-gradient(90% 40% at 4% -14%, rgba(255,255,255,.10), transparent 62%);
}
.inner{position:relative;z-index:1;max-width:var(--maxw);margin:0 auto;width:100%;
  display:grid;grid-template-columns:minmax(0,.92fr) minmax(0,1.08fr);
  gap:clamp(30px,5vw,88px);align-items:start}
.zone.flip .col-a{order:2}.zone.flip .col-b{order:1}
/* Accent-carrying chrome. The BACKGROUND of .ztag and the RULE colours are
   written per-frame from the same damped OKLab value that drives the field,
   so nothing in the page snaps while the field glides. Written as inline
   styles on these few elements — never as a custom property on :root, which
   would invalidate style for the whole document every frame. */
.num{font-size:11px;letter-spacing:.2em;text-transform:uppercase;font-weight:700;
  color:rgba(246,241,230,.86)}
.ztag{display:inline-flex;align-items:center;margin-top:16px;padding:8px 16px;border-radius:999px;
  font-size:11px;font-weight:700;letter-spacing:.13em;text-transform:uppercase;
  color:#06122C;background:var(--accent)}
.rule{display:block;height:1px;border:0;margin-top:20px;width:clamp(56px,9vw,120px);
  background:rgba(246,241,230,.5)}
h2{font-family:"Cabinet Grotesk","Cabinet Grotesk fallback","General Sans",sans-serif;font-weight:800;
  letter-spacing:-.035em;line-height:.95;
  font-size:clamp(30px,4.4vw,64px);max-width:14ch;margin-top:18px;
  text-shadow:0 0 14px rgba(0,8,28,.5)}
.body{font-size:clamp(15px,1.25vw,18.5px);line-height:1.58;max-width:52ch;
  color:rgba(246,241,230,.94)}
.specs{list-style:none;margin-top:26px;border-top:1px solid rgba(246,241,230,.24)}
.specs li{display:flex;gap:16px;padding:14px 0;border-bottom:1px solid rgba(246,241,230,.15);
  font-size:15px;line-height:1.45;color:rgba(246,241,230,.94)}
.specs b{font-family:"Cabinet Grotesk",sans-serif;font-weight:700;font-size:12px;
  opacity:.78;min-width:22px;flex:0 0 22px}

/* ---------- scale ---------- */
#scale{text-align:center}
#scale h2{margin:14px auto 0;max-width:18ch;text-align:center}
.figs{max-width:var(--maxw);margin:clamp(44px,7vh,86px) auto 0;
  display:grid;grid-template-columns:repeat(4,1fr);gap:clamp(18px,3vw,48px)}
.fig .n{font-family:"Cabinet Grotesk",sans-serif;font-weight:800;letter-spacing:-.04em;
  font-size:clamp(34px,5.6vw,82px);line-height:1;color:var(--cream);
  text-shadow:0 0 14px rgba(0,8,28,.5)}
.fig .l{margin-top:10px;font-size:12px;letter-spacing:.18em;text-transform:uppercase;
  font-weight:600;color:rgba(246,241,230,.9)}


/* The shared footer component, copied from system/base.css because home loads
   no stylesheet. This is a duplicate and duplicates drift — the real fix is
   home linking system/tw.css like the other sixteen pages. Flagged, not done:
   that is a change to how this page is built, not to how it looks. */
/* ---- Footer — the "Sign-off" ------------------------------------------
   Replaces the cream lightbox that used to sit on home and the plain column
   footer on the subpages. Those were two different footers for one site; this
   is one component, emitted for every page by tools/normalise-shell.py.

   The page now ends on the event's own wordmark rather than on a credit box,
   and the organiser and partner credits shrink to what they are: credits. */
/* the organiser column — the ministry is the one display-set thing up here */
/* partner credit — an inline pill, not a panel. It is a credit. */
/* The wordmark is an SVG, not styled text. A clamp()ed font-size can only
   approximate the column width, so at some viewport it either clips or leaves
   a gap — it did both. textLength with lengthAdjust="spacingAndGlyphs" fits
   the glyphs to the viewBox and the viewBox scales to the column, so it can
   never crop at any width. */
/* capped at --maxw like .cols and .base: without it the wordmark spans the
   full padding box and sits wider than the columns it signs off */
/* Build credit. The only OUTBOUND links in the shell, so they are underlined
   rather than left to colour alone — every other footer link is internal and
   reads as navigation by position. */
@media (max-width:820px){ .foot .cols{grid-template-columns:1fr 1fr} }
@media (max-width:560px){ .foot .cols{grid-template-columns:1fr} }

/* ---- Sponsors: "The Scale Bento" --------------------------------------
   Replaces the four-column card grid. Banded density, but each cell's width
   is set by the length of the name it holds: --w becomes the flex-basis and
   flex-grow divides the slack on whatever row it landed on. Long names get
   wide cells, short names narrow ones, and no row can end short — a fixed
   column count stranded four empty cells at the end of the sponsor band.

   Lines are Tier 1.5 shaded ribbons (system/tube.js), placed in the gutters
   rather than across the names, drawn from the real model strokes. See the
   module at the foot of this file. */
/* The heading needs real space above the bento. This was 0 — the only
   section on the page where it was (zones 16, scale 40, news 31) — and at
   line-height .93 the display face's descenders overflow their own box, so
   zero read as the card touching the word. --gap-lg puts it in the same
   range as the other two heading-to-block gaps. */
#sponsors-home .sb{position:relative;isolation:isolate;margin-top:var(--gap-lg)}
/* The clip lives on the full-bleed SECTION, never on .sb. .sb is inset by the
   gutter, so clipping there cut every ribbon dead on the card edge — the
   straight vertical seam down the bento. The section spans the page, so the
   strokes now bleed across the gutters and the page background and are cut
   only where the viewport ends. No overflow-clip-margin anywhere: it widens
   the clip region on EVERY axis, so the vertical allowance was also letting
   844px (100vh) of horizontal overflow straight back into the scroll region
   — measured. overflow-y:visible gives the vertical bleed instead, for free
   and without limit. */

#sponsors-home .ribbons{position:absolute;inset:0;z-index:0;pointer-events:none;overflow:visible}
#sponsors-home .ribbons svg{position:absolute;overflow:visible}
#sponsors-home .sb > *:not(.ribbons){position:relative;z-index:1}

/* The band is rounded; the cells inside it are not. Rounding each CELL would
   put gaps between them and turn this back into the row of cards it replaced,
   so the band is the panel and the cells keep their shared hairline edges,
   clipped to its silhouette. `clip` not `hidden` — hidden makes a scroll
   container. */
/* .srow, not .band: .band is the system's full-width page-section component
   (99px/66px padding). These are rows INSIDE the bento, and borrowing the name
   meant every one of them silently inherited section padding — masked until now
   only by an unlayered * reset on this page. */
#sponsors-home .srow{display:flex;flex-wrap:wrap;gap:1px;background:var(--ink-hairline);
  border:1px solid var(--ink-hairline);margin-bottom:clamp(18px,2.4vh,30px);
  /* --r-card, not --r-panel or --r-section. DESIGN-SYSTEM.md §3 assigns those
     by role: --r-panel (26px) is for GLASS PANELS, --r-section (28-54px) for
     LARGE MEDIA BLOCKS. A sponsor band is neither — it is a card holding
     cells, the same thing .ncard is, and .ncard uses --r-card. The lead band
     at --r-section was a 54px slab one section away from 18px news cards,
     which is the drift the tokens exist to prevent. */
  border-radius:var(--r-card);overflow:clip}
#sponsors-home .c{position:relative;min-width:0;flex:1 1 calc(var(--w,25) * 1%);
  padding:clamp(16px,1.9vw,26px);
  display:flex;flex-direction:column;justify-content:flex-end;gap:clamp(5px,.7vh,9px);
  /* translucent, so the ribbons read THROUGH the grid rather than being
     buried behind an opaque lid */
  background:rgba(0,19,63,.72);backdrop-filter:blur(2px);transition:background .28s}
#sponsors-home .c:hover{background:rgba(0,26,82,.82)}
/* the role is a label ON the name, so the pair sits together at the cell's
   baseline; space-between turned the spare min-height into a hole between them */
#sponsors-home .c .role{font-size:9.5px;letter-spacing:var(--ls-label);
  text-transform:uppercase;color:var(--ink-56);font-weight:600}
#sponsors-home .c .co{font-family:var(--f-display);font-weight:700;
  letter-spacing:-.028em;line-height:1.06;color:var(--cream)}
/* zone accent: a short length of ribbon with the logo's own radius and cap.
   An accent is a zone identity — never a fill, a link colour or a state. */
#sponsors-home .c[data-z]::before{content:"";position:absolute;height:3px;
  border-radius:var(--r-pill);width:clamp(28px,3vw,52px);
  left:clamp(16px,1.9vw,26px);top:clamp(14px,1.6vw,20px);opacity:.9}
#sponsors-home .c[data-z="1"]::before{background:var(--coral)}
#sponsors-home .c[data-z="2"]::before{background:var(--green)}
#sponsors-home .c[data-z="3"]::before{background:var(--cyan)}
#sponsors-home .c[data-z="4"]::before{background:var(--violet)}
/* main sponsor at display-sm/700, not display-md/800: at 800 it matched the
   section's own h2 and a sponsor credit outranked the page title */
#sponsors-home .b1 .c{flex:1 1 100%;padding:clamp(28px,3.4vw,52px);
  min-height:clamp(150px,19vh,220px);gap:clamp(10px,1.4vh,16px);
  background:linear-gradient(180deg,rgba(0,26,82,.58),rgba(0,19,63,.78))}
#sponsors-home .b1 .c::before{content:"";position:absolute;inset:0 0 auto;height:3px;
  width:auto;border-radius:0;opacity:1;
  background:linear-gradient(90deg,var(--coral),var(--green),var(--cyan),var(--violet))}
#sponsors-home .b1 .c .co{font-size:var(--t-display-sm);font-weight:700;
  letter-spacing:var(--ls-display-tight)}
#sponsors-home .b1 .c .role{color:var(--coral-tag);font-size:11px}
#sponsors-home .b1 .c p{margin:0;max-width:56ch;font-size:var(--t-body-lg);
  line-height:var(--lh-body);color:var(--ink-84)}
#sponsors-home .b2 .c{min-height:clamp(104px,12vh,138px)}
#sponsors-home .b2 .c .co{font-size:clamp(16px,1.55vw,22px)}
#sponsors-home .b3 .c{min-height:clamp(74px,9vh,98px)}
#sponsors-home .b3 .c .co{font-family:var(--f-body);font-weight:500;
  font-size:clamp(13px,1.05vw,15px);letter-spacing:0;line-height:1.3;color:var(--ink-90)}
#sponsors-home .ndiv{display:flex;align-items:center;gap:clamp(18px,2vw,28px);
  margin:clamp(30px,4vh,48px) 0 clamp(14px,2vh,20px)}
#sponsors-home .ndiv .n{font-family:var(--f-mono);font-weight:700;
  font-size:var(--t-label);letter-spacing:var(--ls-label);color:var(--violet);flex:none}
#sponsors-home .ndiv .t{font-family:var(--f-display);font-weight:800;
  letter-spacing:var(--ls-display-tight);font-size:var(--t-display-sm);
  color:var(--cream);flex:none}
#sponsors-home .ndiv::after{content:"";flex:1;height:2px;border-radius:var(--r-pill);
  background:linear-gradient(90deg,var(--violet),transparent);opacity:.5}
/* below 900 the weights stop earning their keep: a 14%-weighted cell is
   unreadable on a phone */
@media (max-width:900px){ #sponsors-home .b2 .c,#sponsors-home .b3 .c{flex:1 1 calc((100% - 1px) / 2)} }
@media (max-width:520px){
  #sponsors-home .b2 .c{flex:1 1 100%}
  #sponsors-home .b3 .c{flex:1 1 calc((100% - 1px) / 2)}
}
/* The hidden state comes from .armed, which only JS adds and only once it
   knows the observer exists. opacity:0 in CSS alone made the whole sponsor
   list invisible whenever the observer never fired. */
@media (prefers-reduced-motion:no-preference){
  #sponsors-home .sb.armed .c{opacity:0;transform:translateY(14px);
    transition:opacity .55s cubic-bezier(.215,.61,.355,1),
               transform .55s cubic-bezier(.215,.61,.355,1),background .28s}
  #sponsors-home .sb.armed .c.in{opacity:1;transform:none}
}

/* ---------- footer ----------
   GONE. Home carried its own cream ".lightbox" footer while the other
   fifteen pages used ".foot" from system/base.css — two footers for one
   site. The Sign-off component now serves every page and is emitted by
   tools/normalise-shell.py, so there is nothing to style here. */

/* ---------- responsive ---------- */
/* Below 900px the split collapses: copy on top, element under it. 900 is the
   same threshold `layout()` uses for `wide`, so the CSS layout and the camera
   framing always change together — if these two ever disagree the element is
   framed for a column that isn't there. */
@media (max-width:900px){
  /* STACKED, NOT LAYERED. The canvas is position:absolute inset:0 on wide,
     which is right there. On narrow that same rule laid the element straight
     over the copy — headline, date and both buttons sat on the strokes.
     Row 1 is the element, row 2 the copy. layout() sizes from the canvas box,
     so the framing follows without the module being told. */
  #hero{grid-template-columns:1fr;grid-template-rows:auto auto;align-items:start;justify-items:center;gap:clamp(4px,0.8vh,12px);
    padding-top:clamp(96px,16vh,150px)}
  /* 52svh/430px made the hero 906px tall in an 844px viewport: the copy ran to
     856px and BOTH CTAs were cut off the bottom of the screen — on the 3D path
     as well as the still, since they share this row. The hero is
     min-height:100svh, so the one thing it must do is fit in one. 42svh/360px
     is the largest row that keeps the statement, the date, the place and both
     buttons inside the first screen at 370x844. */
  #hero canvas{position:relative;inset:auto;grid-row:1;width:100%;height:min(42svh,360px)}
  .hero-copy{grid-row:2}
  .hero-copy{grid-column:1;justify-items:center;text-align:center;
    max-width:min(30ch,100%);margin:0 auto}
  .hero-meta{justify-items:center}
  .hero-meta .hrule{background:linear-gradient(90deg,rgba(246,241,230,.02),
    rgba(246,241,230,.20),rgba(246,241,230,.02));width:min(200px,64%)}
  .cta{justify-content:center}
}
@media (max-width:1000px){
  /* nav.links{display:none} was here. It is the nav component's rule and now
     lives in system/base.css — held here it was UNLAYERED, so it beat
     `header.navopen nav.links{display:flex}` from the bundle whatever the
     specificity, and the sheet opened empty: the trigger flipped to a close
     X, the header took .navopen, but the links stayed display:none. */
  .inner{grid-template-columns:1fr}
  .zone.flip .col-a{order:1}.zone.flip .col-b{order:2}
  section.zone{min-height:auto;padding:clamp(80px,11vh,120px) var(--gut)}
}
@media (max-width:640px){
  .figs{grid-template-columns:1fr 1fr;gap:26px 16px}
  #hero h1 .say{font-size:clamp(23px,6.6vw,34px)}
  #hero h1 .grad{font-size:clamp(34px,11.6vw,58px)}
  .eyebrow hr{display:none}
  .eyebrow{gap:0;font-size:10px;letter-spacing:.16em}
  /* The bar's own small-screen refinement moved to system/base.css with the
     rest of the nav — it is the component's, not this page's. Held here it
     made the same nav 72px on home and 80px everywhere else. */
  .pill{padding:10px 16px;font-size:12.5px}
  .cd{gap:6px}
  .cd .n{font-size:clamp(25px,8vw,36px)}
  .cd .k{font-size:8.5px;letter-spacing:.1em;margin-top:7px}
  .cd .sep{margin-bottom:20px}
  /* mobile raster budget: phone GPUs are the one case this machine cannot
     measure, so shadows drop further and the nav blur radius halves. */
  /* NOT #hero h1. Its payoff line is background-clip:text with
     -webkit-text-fill-color:transparent, and a text-shadow under transparent
     glyphs shows THROUGH them — a centred zero-offset blur paints a dark halo
     inside each letter, which reads as an inner glow rather than a gradient
     fill. The h1 already has layered drop-shadow() filters, which apply to the
     rendered result and composite correctly with a clipped background. */
  h2,.fig .n{text-shadow:0 0 10px rgba(0,8,28,.5)}
  header.scrolled::before{-webkit-backdrop-filter:blur(14px) saturate(1.4);backdrop-filter:blur(14px) saturate(1.4)}
}

/* ---------- hero intro: CSS-owned ----------
   Never gsap.set(opacity:0) on already-painted content. Everything is visible
   by default; these keyframes animate opacity + transform only (compositor,
   CLS 0) and start the moment the markup exists. Type paints first; the
   element (≈1.2s of mesh build, ≈320k triangles) arrives after. */
@keyframes rise{from{opacity:0;transform:translateY(26px)}to{opacity:1;transform:none}}
/* ONE HERO GESTURE, THEN A SHOCKWAVE — not a five-step ladder. The old
   sequence ticked title .04 / date .14 / place .24 / statement .34 / CTAs .44:
   five evenly-spaced beats, which is exactly the one-by-one cadence that reads
   as lifeless. Now the statement leads in two overlapping beats and everything
   supporting it arrives near-unison off the back of it. */
#hero h1 .say,#hero h1 .grad,#hero .hero-meta>*,#hero .cta{
  animation:rise 1.1s cubic-bezier(.215,.61,.355,1) both}
#hero h1 .say{animation-delay:.04s}
#hero h1 .grad{animation-delay:.13s}          /* overlaps the setup, never after it */
/* the shockwave: a 60ms spread across six elements, near-unison by design */
#hero .hero-meta>*{animation-delay:.30s}
#hero .hero-meta .hm-when{animation-delay:.34s}
#hero .hero-meta .hm-where{animation-delay:.36s}
#hero .cta{animation-delay:.36s}

@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  #hero h1 .say,#hero h1 .grad,#hero .hero-meta>*,#hero .cta{animation:none!important}
  .lines svg{transition:none!important}
  *{scroll-behavior:auto!important}
}

/* ---- News tiles + Sponsors (home) --------------------------------------
   Written in this page's own vocabulary (.sinner/.num/.d/.pill and its inline
   tokens), because the landing page predates system/base.css and does not load
   it. When home is migrated onto the system these become .ncard and the
   sponsor wall from the Sponsors template — until then they must not import
   classes that do not exist here. */
/* row gap is deliberately larger than the column gap: when the grid drops to
   one or two columns the tiles stacked almost touching. */
#news-home .nrow{display:grid;grid-template-columns:repeat(3,1fr);
  gap:clamp(34px,4.2vw,56px) clamp(18px,2.2vw,30px);
  margin-top:clamp(26px,3.4vh,44px)}
#news-home .ntile{display:grid;gap:14px;text-decoration:none;color:inherit;
  transition:transform .32s cubic-bezier(.215,.61,.355,1)}
#news-home .ntile:hover{transform:translateY(-3px)}
#news-home .nmedia{aspect-ratio:16/10;border-radius:18px;position:relative;overflow:hidden;
  background:linear-gradient(150deg,color-mix(in oklab,var(--accent) 24%,#0A3080),#001D61 58%,#00133F);
  box-shadow:inset 0 0 0 1px rgba(246,241,230,.14)}
#news-home .nmedia svg{position:absolute;inset:0;width:100%;height:100%;opacity:.9}
#news-home .ndate{font-family:"JetBrains Mono","JetBrains Mono fallback",ui-monospace,monospace;font-variant-numeric:tabular-nums;
  font-size:12px;color:rgba(246,241,230,.56)}
#news-home .ntile h3{font-family:"Cabinet Grotesk",sans-serif;font-weight:800;letter-spacing:-.035em;
  font-size:clamp(17px,1.5vw,21px);line-height:1.16;margin:0;color:var(--cream)}
#news-home .ntile p{margin:0;font-size:14px;line-height:1.5;color:rgba(246,241,230,.84)}
#news-home .more{font-size:13px;font-weight:600;color:rgba(246,241,230,.9)}
#news-home .ntile:hover .more{color:var(--cream)}
@media (max-width:900px){ #news-home .nrow{grid-template-columns:1fr} }


/* ---- Zones as a stepped tab panel (home) -------------------------------
   Was four full-height sections you scrolled through in sequence. As tabs the
   whole floor plan is comparable in one screen, the page loses three screens
   of near-identical scrolling, and the zone you are reading drives the section
   accent — so the colour still changes, it just changes on demand instead of
   by scroll position.
   Written in this page's own vocabulary; it does not load system/base.css. */
#zones .zlede{max-width:46ch;margin:16px 0 0;color:rgba(246,241,230,.84);
  font-size:clamp(15px,1.3vw,18px);line-height:1.55}
#zones .zwrap{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(0,.85fr);
  gap:clamp(26px,3.4vw,58px);align-items:start;margin-top:clamp(30px,4.2vh,56px)}

/* [hidden] is only `display:none` from the UA stylesheet, so ANY author
   `display` declaration beats it — `.zpanel{display:grid}` made all four
   panels render at once no matter what the JS set. The guard has to be
   explicit and it has to win. */
#zones .zpanel[hidden]{display:none!important}
#zones .zpanel{display:grid;gap:16px}
#zones .zpanel h3{font-size:clamp(24px,3vw,44px);line-height:1.02;margin:0;color:var(--cream)}

/* the rail: one line per zone, the active one opens its description */
#zones .zrail{display:grid;gap:0;align-content:start}
#zones .zstep{appearance:none;background:none;border:0;cursor:pointer;text-align:left;
  display:grid;gap:6px;padding:clamp(16px,1.9vh,24px) 0 clamp(16px,1.9vh,24px) clamp(16px,1.6vw,24px);
  border-left:2px solid rgba(246,241,230,.14);
  transition:border-color .32s cubic-bezier(.215,.61,.355,1),opacity .32s cubic-bezier(.215,.61,.355,1)}
#zones .zstep:hover{border-left-color:rgba(246,241,230,.4)}
#zones .zstep:focus-visible{outline:2px solid var(--cream);outline-offset:3px}
/* The row label IS the chip — the zone's colour as a filled pill, the same
   language as .ztag, so a collapsed row reads as its zone at a glance. This is
   what "add the colour to the collapsed state, maybe a chip" asked for; a 9px
   dot was my wrong first reading of it. align-self:start so the pill hugs its
   text instead of stretching the grid column. */
#zones .zstep .sn{font-family:"JetBrains Mono","JetBrains Mono fallback",ui-monospace,monospace;font-size:10.5px;
  letter-spacing:.17em;color:rgba(246,241,230,.56)}
#zones .zstep .st{font-family:"Cabinet Grotesk",sans-serif;font-weight:800;letter-spacing:-.03em;
  font-size:clamp(17px,1.6vw,23px);line-height:1.12;color:rgba(246,241,230,.72);
  transition:color .32s cubic-bezier(.215,.61,.355,1)}
#zones .zstep .sd{display:none;font-size:14px;line-height:1.55;color:rgba(246,241,230,.84);
  max-width:40ch}
/* only the selected step is expanded and carries the accent — one marker, not two */
#zones .zstep[aria-selected="true"]{border-left-color:var(--accent)}
#zones .zstep[aria-selected="true"] .sn{color:var(--accent)}
#zones .zstep[aria-selected="true"] .st{color:var(--cream)}
#zones .zstep[aria-selected="true"] .sd{display:block}

@media (max-width:900px){
  #zones .zwrap{grid-template-columns:1fr}
  /* rail first on mobile: pick a zone, then read it */
  #zones .zpanels{order:2}
  #zones .zrail{order:1}
}

/* ---- data-zone -> --accent -------------------------------------------
   This page defines the four hues but never mapped them to --accent, so every
   var(--accent) in it resolved to nothing. tokens.css does this for the rest of
   the site; home does not load it, so it needs its own copy.
   The values match DESIGN-SYSTEM.md 1: Career coral, Education green,
   Skills cyan, Market violet. */
[data-zone="coral"] { --accent:var(--coral)  }
[data-zone="green"] { --accent:var(--green)  }
[data-zone="cyan"]  { --accent:var(--cyan)   }
[data-zone="violet"]{ --accent:var(--violet) }
[data-zone="navy"]  { --accent:var(--cream)  }

/* ---- fixes from review -------------------------------------------------- */

/* 1. The frost was abrupt because header.scrolled::before CREATED the layer —
      there was nothing to transition from. The layer now always exists and
      only its opacity moves. */


/* 4. SPACING: section.zone and #scale carried the section rhythm; the news and
      sponsors sections were added later and inherited none of it,
so they sat
      flush against their neighbours. */
/* #sponsors-home belongs here too. This rule read
   `#news-home,#sponsors-home` and was narrowed to news alone during the
   sponsors rebuild — one of the four narrowings its handover flagged as worth
   checking. It is the rule that carries the page gutter, so sponsors lost it
   and ran edge to edge: measured 0px side padding against 20px on every other
   section at 370, with the rounded band's corners cut off at the viewport. */
#news-home,#sponsors-home{
  position:relative;isolation:isolate;
  padding:clamp(96px,14vh,170px) var(--gut);
}

/* 6. A CALM GROUND for the content-heavy sections — a SOLID ground behind a
      SOLID line, not a fade. The first version masked the scrim to nothing at
      both edges, which turned a section boundary into a 400px blur: it read as
      the page being out of focus rather than as a deliberate change. A crisp
      rule states the transition and the ground behind it is opaque, so the
      colour field stops where the content starts. */
.calm{background:var(--navy-950);border-top:1px solid rgba(246,241,230,.16)}

/* 2. The 2025 numbers read as another text block. Now they sit on their own
      panel, divided, with the figures doing the work. */
#scale .figs{position:relative;margin-top:clamp(32px,4.4vh,60px);
  border-radius:26px;padding:clamp(26px,3.2vw,46px) clamp(22px,2.6vw,38px);
  background:rgba(10,30,80,.28);
  -webkit-backdrop-filter:blur(22px) saturate(1.35) brightness(1.02);
  backdrop-filter:blur(22px) saturate(1.35) brightness(1.02);
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.14),var(--sh-lg)}
#scale .fig{position:relative;padding-left:clamp(16px,2vw,30px)}
#scale .fig:first-child{padding-left:0}
#scale .fig + .fig::before{content:"";position:absolute;left:0;top:.1em;bottom:.6em;
  width:1px;background:rgba(246,241,230,.16)}
@media (max-width:640px){
  #scale .fig{padding-left:0}
  #scale .fig + .fig::before{display:none}
}

/* 3. News tiles on glass. */
#news-home .nmedia{
  background:rgba(10,30,80,.3);
  -webkit-backdrop-filter:blur(22px) saturate(1.35) brightness(1.02);
  backdrop-filter:blur(22px) saturate(1.35) brightness(1.02);
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.14),var(--sh-sm)}
#news-home .ntile{padding:14px;border-radius:22px;
  transition:transform .32s cubic-bezier(.215,.61,.355,1),background .32s cubic-bezier(.215,.61,.355,1)}
#news-home .ntile:hover{background:rgba(255,255,255,.04)}
/* 5. SPONSORS,
rebuilt. Was a flat wrapped list of names with the role set in
      accent underneath — no hierarchy,
nothing to scan,
and the roles read as
      errors. Now: the main sponsor as a feature,
partners as a dense card grid
      where the ROLE leads (that is the useful information),
and the remaining
      sponsors as a quiet name grid. */
/* The tag sat ABOVE the copy on its own line,
stacked{display:flex;align-items:center;flex-wrap:wrap;
  gap:clamp(10px,1.1vw,16px)}

/* partners: a real grid{display:grid;grid-template-columns:repeat(4,1fr);
  gap:clamp(12px,1.4vw,20px);list-style:none;margin:0;padding:0}

/* the rest: a quiet name grid{display:grid;grid-template-columns:repeat(4,1fr);
  gap:10px clamp(16px,2vw,30px);list-style:none;margin:0;padding:0}

@media (max-width:1000px){

}
@media (max-width:640px){

}

/* Same trap, everywhere else: never let a display rule outrank [hidden]. */
[hidden]{display:none!important}

/* ---- the inner pages' line elements on home ---------------------------
   .lineset is the subpage graphic layer (system/graphics.css + system/lines.js).
   Home carries its own CSS, so the few rules it needs are copied here rather
   than pulling in the whole stylesheet. NOTE: no overflow clipping and no
   contain — the ribbons are sized past their section on purpose; clipping them
   chops a stroke at the seam. The vertical mask fades each set out inside its
   own section so a ribbon never crosses into the next one's text. */
.lineset{position:absolute;inset:0;z-index:0;pointer-events:none;
  -webkit-mask-image:linear-gradient(180deg,transparent 0,#000 22%,#000 78%,transparent 100%);
  mask-image:linear-gradient(180deg,transparent 0,#000 22%,#000 78%,transparent 100%);
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-size:100% 100%;mask-size:100% 100%}
.lineset svg{position:absolute;overflow:visible;will-change:transform}
.lineset path{fill:none;stroke-linecap:round;stroke-linejoin:round}
/* [data-lines] is the system's, in system/graphics.css. Not redefined here. */
[data-lines] > *:not(.lineset){position:relative;z-index:1}
@media (prefers-reduced-motion:reduce){ .lineset path{stroke-dashoffset:0!important} }

/* ---- zone panel + numbers, second review ------------------------------- */

/* 1. The zone tag stretched the full width of the panel because .zpanel is a
      grid and the tag had no width of its own — a pill should hug its text. */
#zones .zpanel .ztag{display:inline-flex;width:auto;justify-self:start;align-self:start}

/* 2. THE WHOLE PANEL on glass, not just the body copy. The tag and heading sat
      on the raw field while the paragraph sat on glass, so one panel had two
      grounds. */
#zones .zpanel{
  padding:clamp(24px,2.6vw,40px);border-radius:26px;
  background:rgba(10,30,80,.3);
  -webkit-backdrop-filter:blur(22px) saturate(1.35) brightness(1.02);
  backdrop-filter:blur(22px) saturate(1.35) brightness(1.02);
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.14),var(--sh-lg)}
/* the inner glass is now redundant — one ground per panel */
#zones .zpanel .glass{background:none;backdrop-filter:none;-webkit-backdrop-filter:none;
  box-shadow:none;margin:0;padding:0;border-radius:0}
#zones .zpanel .glass::before,#zones .zpanel .glass::after{display:none}

/* 3. VISUALS: the zone's COLOURED ICON, in the same accent-filled well the
      subpages use (.icowell.solid). This was briefly a large ribbon
      illustration behind the panel; the decision on record is the icon only,
      so the illustration is gone and the icon leads the panel beside its tag. */
#zones .zpanel{position:relative}
#zones .zhead{display:flex;align-items:center;gap:clamp(12px,1.3vw,18px);
  margin-bottom:clamp(14px,1.6vh,22px)}
#zones .zhead .ztag{margin-top:0;align-self:center;justify-self:auto}
.icowell{display:grid;place-items:center;width:52px;height:52px;flex:none;
  border-radius:20px;color:var(--accent);
  background:color-mix(in oklab, var(--accent) 14%, transparent);
  box-shadow:inset 0 0 0 1px color-mix(in oklab, var(--accent) 26%, transparent)}
.icowell.solid{background:var(--accent);color:var(--tag-ink,#06122C);
  box-shadow:0 1px 2px rgba(0,9,30,.26),0 6px 16px rgba(0,9,30,.18)}
.icowell.lg{width:60px;height:60px;border-radius:22px}
.ico{width:var(--ico,20px);height:var(--ico,20px);flex:none;display:inline-block;
  vertical-align:-.18em;color:inherit}
.ico.lg{--ico:28px}

/* 4. LEGIBILITY: the rail sits straight on the colour field, which swings from
      green to violet behind it. Type that light on a mid-tone needs a shadow
      to hold its edge — the field cannot be darkened without losing the point
      of it. */
#zones .zstep .st,#zones .zstep .sd,#zones .zstep .sn{
  text-shadow:0 1px 2px rgba(0,9,30,.5),0 2px 10px rgba(0,9,30,.35)}
#zones .sinner > .num,#zones .sinner > h2,#zones .zlede{
  text-shadow:0 1px 2px rgba(0,9,30,.45),0 3px 14px rgba(0,9,30,.3)}

/* 5. THE NUMBERS, celebrated. The flat glass panel with a heavy drop shadow
      read as a grey box with a bruise under it. Now a gradient stroke around
      the panel (padding-box/border-box double background — a real gradient
      border, not an approximation), a lighter shadow, and each figure in its
      own zone hue so the four numbers are four things, not one block. */
#scale .figs{position:relative;border:0;
  background:rgba(10,30,80,.3);
  box-shadow:0 1px 2px rgba(0,9,30,.22),0 10px 30px rgba(0,9,30,.16)}
/* A gradient STROKE, nothing else. The padding-box/border-box double-background
   trick failed here because the padding-box layer is translucent glass, so the
   border-box gradient showed through the whole panel and filled it. A masked
   ring paints only the 2px edge and leaves the glass interior untouched. */
#scale .figs::after{
  content:"";position:absolute;inset:0;border-radius:inherit;padding:2px;
  pointer-events:none;
  background:linear-gradient(104deg,var(--coral),var(--violet) 34%,var(--cyan) 68%,var(--green));
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;mask-composite:exclude}
#scale .fig:nth-child(1) .n{color:var(--coral)}
#scale .fig:nth-child(2) .n{color:var(--green)}
#scale .fig:nth-child(3) .n{color:var(--cyan)}
#scale .fig:nth-child(4) .n{color:var(--violet)}
#scale .fig .n{text-shadow:0 1px 1px rgba(0,9,30,.3),0 4px 16px rgba(0,9,30,.22)}
#scale .fig .l{color:rgba(246,241,230,.9)}

/* ---- edge-only ribbons ---------------------------------------------------
   The ribbons crossed the sponsor grid and made 16 company names hard to read.
   Masking them to the OUTER EDGES keeps the gesture — it still enters and
   leaves the frame — without any ink over the content column. Percentages, not
   --maxw: the gutter is only 60px each side at 1440 and nothing at all below
   1280, so an absolute measure has nowhere to put them.
   Two masks composited: outer-edges horizontally, faded top and bottom
   vertically so a ribbon still never reaches the next section's text. */
[data-lines-edge] .lineset{
  -webkit-mask-image:
    linear-gradient(90deg,#000 0,#000 10%,transparent 21%,transparent 79%,#000 90%,#000 100%),
    linear-gradient(180deg,transparent 0,#000 18%,#000 82%,transparent 100%);
  mask-image:
    linear-gradient(90deg,#000 0,#000 10%,transparent 21%,transparent 79%,#000 90%,#000 100%),
    linear-gradient(180deg,transparent 0,#000 18%,#000 82%,transparent 100%);
  -webkit-mask-composite:source-in; mask-composite:intersect;
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
  -webkit-mask-size:100% 100%; mask-size:100% 100%;
}

/* The zone tag was a fixed coral, so Market, Education and Career all showed
   the same pill while the field behind them changed with scroll — which read
   as the colour being tied to scroll position. It takes the zone's accent. */
.ztag{background:var(--accent);color:var(--tag-ink,#06122C)}

/* =====================================================================
   ZONES — the mobile stepped accordion
   =====================================================================
   At <=900px the two-column tab layout stacked into two DETACHED blocks:
   a rail of four steps, then — up to 239px further down the page, past the
   sticky header — a separate glass card holding the answer. Tapping a step
   put the reply somewhere you could not see, and the collapsed rows each
   showed `.sd`, a sentence cut mid-clause.

   The fix is presentational, not structural: `.zrail` and `.zpanels` go
   `display:contents` so their children become items of ONE `.zwrap` grid,
   and explicit grid-rows interleave them — step 1, panel 1, step 2, panel 2
   ... Each panel therefore renders inside its own step's row. No DOM moves,
   so the desktop two-column tablist is untouched.

   Semantics follow the presentation: see the zone script, which swaps the
   tablist roles for accordion roles at this same 900px breakpoint.

   Accent discipline: the four hues stay ZONE IDENTITIES — here a vertical
   rule down the open row and the index key. No fill, no link, no state. */

/* #zones carries no inline gutter of its own. Every other section matches
   `section.zone,#scale`, `#openin`, `#news-home,#sponsors-home` or `footer`;
   #zones was added later, has no class="zone", and matches none of them.
   Above 1320px `.sinner`'s max-width:var(--maxw) centres the content and
   hides the omission; at 1320 and below there is no margin left and the
   whole section ran hard into both screen edges. 1320 is exactly where
   --maxw stops leaving a margin, so wider screens are provably unaffected.
   (This restores the rule the earlier mobile pass documented; that pass's
   edits are not present in this working tree.) */
@media (max-width:1320px){
  #zones{padding-inline:var(--gut)}
}

/* The chevron is an affordance for the mobile accordion ONLY. Above 900px the
   rail is a tab strip and the marker is the accent rule, so it is not rendered
   at all — .zstep is a grid and a fourth child would add a row. */
#zones .zstep .zchev{display:none}

@media (max-width:900px){
  #zones{
    /* the page's own local vocabulary — same hairline `.specs li` uses and
       the same 2px the desktop rail rule already carries */
    --zrule:rgba(246,241,230,.15);
    --zmark:2px;
    --zrow-pad:clamp(14px,1.8vh,20px);
    /* ONE indent value, used as the collapsed row's indent AND as the open
       card's padding — so the title does not shift sideways when it opens */
    --zrow-ind:clamp(17px,4.6vw,23px);
    /* the open row's glass: the panel's own ground, unchanged */
    --zglass:rgba(10,30,80,.3);
    /* THE RAIL. One continuous neutral line down the whole list; the open
       row's segment of it is the zone accent. Neutral, not per-zone: four
       dim hues would be four more things, and the point of the rail is that
       it is ONE line with one lit segment. */
    --ztrack:rgba(246,241,230,.3);
  }

  /* ONE list. The two source containers stop being boxes. */
  #zones .zwrap{
    display:grid;grid-template-columns:100%;gap:0;
    margin-top:clamp(24px,3.4vh,40px);
    border-bottom:1px solid var(--zrule)}
  /* the panel half of the band is square on the bottom, so the closing
     hairline of the list is the only rule under an open last row */
  #zones .zrail,#zones .zpanels{display:contents}

  /* interleave — every panel sits in the row under its own step */
  #zones .zstep:nth-of-type(1){grid-row:1} #zones .zpanel:nth-of-type(1){grid-row:2}
  #zones .zstep:nth-of-type(2){grid-row:3} #zones .zpanel:nth-of-type(2){grid-row:4}
  #zones .zstep:nth-of-type(3){grid-row:5} #zones .zpanel:nth-of-type(3){grid-row:6}
  #zones .zstep:nth-of-type(4){grid-row:7} #zones .zpanel:nth-of-type(4){grid-row:8}

  /* the collapsed row: index key + headline, full width, nothing else.
     Two columns now — the copy, and the chevron parked on the right. */
  #zones .zstep{
    width:100%;gap:4px clamp(12px,3vw,18px);
    grid-template-columns:minmax(0,1fr) auto;
    align-items:center;
    padding:var(--zrow-pad) var(--zrow-ind) var(--zrow-pad) var(--zrow-ind);
    border-left:var(--zmark) solid var(--ztrack);
    border-top:1px solid var(--zrule);
    transition:border-color .32s cubic-bezier(.215,.61,.355,1),
               background-color .18s linear}
  #zones .zstep:nth-of-type(1){border-top:0}
  /* THE CHIP IS MOBILE-ONLY. The row label becomes a filled pill in its zone's
     colour, which is what makes a collapsed row scannable on a phone. Desktop
     is a two-column tablist where the label is a quiet mono key over the panel
     it selects — a row of full-width colour bars there is noise, and it was
     never asked for. justify-self:start so the pill hugs its text instead of
     stretching the grid column. */
  #zones .zstep .sn{grid-column:1;grid-row:1;justify-self:start;
    display:inline-flex;align-items:center;width:auto;
    background:var(--accent);color:var(--tag-ink);
    padding:5px 12px;border-radius:var(--r-pill);
    /* No shadow on the chip. The one on .st/.sd holds light type against the
       colour field, which cannot be darkened without losing the point of it —
       but the chip brings its own opaque ground and dark ink, so the same
       shadow has nothing to do except read as a drop shadow under the text. */
    text-shadow:none}
  #zones .zstep .st{grid-column:1;grid-row:2}

  /* A zone-hue dot on every index key was tried here and removed: with a
     chevron, a hairline and the open card's accent edge already in play it
     read as one more thing rather than as identity. The hue now appears in
     exactly one place — the open card's left edge — which is what makes it
     mean something.

     AFFORDANCE 1 — the chevron. This is the part that says "control", and it
     is deliberately NEUTRAL: the hues stay zone identities, so the thing that
     means "tap me" must not be one of them. */
  #zones .zstep .zchev{
    display:block;--ico:20px;grid-column:2;grid-row:1 / span 2;
    color:rgba(246,241,230,.74);
    transition:transform .32s cubic-bezier(.215,.61,.355,1),color .32s cubic-bezier(.215,.61,.355,1)}

  /* AFFORDANCE 2 — a press state, so a tap answers immediately. Cream, not
     accent: this is a state, and states are never a zone hue. */
  #zones .zstep:active{background:rgba(246,241,230,.07)}
  #zones .zstep:hover{border-left-color:var(--ztrack)}

  /* LEGIBILITY. These rows sit straight on a colour field that swings from
     green through cyan to violet; at .72/.56 the collapsed headline and key
     were losing their edge against the bright passages. Raised here only —
     the desktop rail sits over a much narrower range of the field. */
  #zones .zstep .st{color:rgba(246,241,230,.9)}
  #zones .zstep .sn{color:var(--tag-ink,#06122C)}
  /* a truncated teaser is worse than no teaser — the open row says it in full */
  #zones .zstep .sd{display:none!important}

  /* THE OPEN ROW IS ONE GLASS BAND.
     Square, flush, full-bleed to the row — NOT a floating card. A rounded,
     inset card would break the rail into segments and put a second frame
     around content the rail already brackets; this pattern is a stepped
     list with one segment lit, so the open row is a band of glass in the
     list, not an object on top of it.

     Step and panel are two grid items, so the band is two halves sharing
     one ground and one blur. Nothing is drawn across the join and there is
     no ring: the band is defined by its ground and by the accent rail
     beside it, which is enough and is one thing instead of three.
     [aria-selected] is the pre-script fallback; the script swaps it for
     [aria-expanded] as soon as it runs. */
  #zones .zstep[aria-expanded="true"],
  #zones .zstep[aria-selected="true"]{
    border-left-color:var(--accent);
    padding-bottom:clamp(10px,1.3vh,15px);
    background:var(--zglass);
    -webkit-backdrop-filter:blur(22px) saturate(1.35) brightness(1.02);
    backdrop-filter:blur(22px) saturate(1.35) brightness(1.02)}
  #zones .zstep[aria-expanded="true"] .sn,
  #zones .zstep[aria-selected="true"] .sn{color:var(--tag-ink,#06122C)}
  #zones .zstep[aria-expanded="true"] .st,
  #zones .zstep[aria-selected="true"] .st{color:var(--cream)}
  #zones .zstep[aria-expanded="true"] .zchev,
  #zones .zstep[aria-selected="true"] .zchev{
    transform:rotate(180deg);color:var(--cream)}

  /* the panel is the BOTTOM HALF of that same card — same ground, same blur,
     ring on its outer edges only, radius on its bottom corners only. Its own
     .zhead and h3 stay dropped: the step above is the card's title. */
  #zones .zpanel{
    gap:0;margin:0;border-radius:0;box-shadow:none;
    border-left:var(--zmark) solid var(--accent);
    padding:0 var(--zrow-ind) clamp(20px,2.6vh,28px) var(--zrow-ind);
    background:var(--zglass);
    -webkit-backdrop-filter:blur(22px) saturate(1.35) brightness(1.02);
    backdrop-filter:blur(22px) saturate(1.35) brightness(1.02)}
  #zones .zpanel .zhead,#zones .zpanel h3{display:none}

  /* no nested card. .glass is a plain block inside the one glass surface. */
  #zones .zpanel .glass{
    margin:0;padding:0;border-radius:0;background:none;box-shadow:none;
    -webkit-backdrop-filter:none;backdrop-filter:none}
  #zones .zpanel .body{font-size:15px;line-height:1.6;color:rgba(246,241,230,.92)}
  #zones .zpanel .specs{margin-top:18px}
  /* the card's own bottom padding closes the list */
  #zones .zpanel .specs li:last-child{border-bottom:0;padding-bottom:0}
}

/* =====================================================================
   MOBILE SITE NAV — the missing hamburger
   =====================================================================
   `@media (max-width:1000px){ nav.links{display:none} }` hid the whole
   eight-item site nav below 1000px and put nothing in its place, so on a
   phone or a tablet the header offered exactly two destinations: the brand
   and Register. About, Exhibitors, Schedule, Gallery, Interview Lounge,
   News, Venue and FAQ had no route from the bar at all.

   The sheet is the SAME <nav class="links"> element, restyled — not a second
   copy of the links. So there is still one Primary nav landmark in the
   document, the link list cannot drift out of sync with itself, and when the
   sheet is closed the nav is display:none and simply absent from the
   accessibility tree (no aria-hidden bookkeeping to get wrong).

   The trigger is inert above 1000px — `display:none` outside the query — so
   the desktop header flex row is untouched. */



@media (max-width:1000px){



  /* The bars are DRAWN, not an icon. `system/icons.js` is a curated 70-icon
     subset and it has no `menu` glyph — the <i data-lucide="menu"> rendered
     as an empty 24x24 box, which is why the trigger looked blank while the
     `x` beside it worked. The nearest thing in the bundle is `align-left`,
     a text-align glyph with uneven bars, and adding `Menu` would mean
     editing system/icons.js. Three CSS bars cost nothing, need no bundle,
     and give a real morph into the X instead of swapping two glyphs. */





  /* open: the middle bar goes, the outer two cross */



  /* the hamburger's reduced-motion rule is system/base.css's, like the rest
     of the nav — this was the last nav declaration still living on the page */

  /* THE SHEET — full screen, bottom-anchored, one indexed line per
     destination. Not a dropdown: at this size the nav IS the screen, so the
     list gets real typographic weight instead of being a small menu hanging
     off the bar.

     The 01-08 index keys are CSS counters, not markup — so they cannot fall
     out of step with the list, and adding or removing a link renumbers it
     for free. They use the page's mono-meta register, the same one `.sn`
     and `.hm-sub` use. */

  /* the bar's own controls stay above the sheet and keep working */

  /* one Register, never two: the bar's pill stands down while the sheet is up */


  /* pushes the list to the bottom but still scrolls if the viewport is short,
     which `justify-content:flex-end` would not */






  /* the page you are on reads brighter — NOT a zone hue. The four accents are
     zone identities; a nav state is not one of them. */



  /* the foot: the one action, then the standing detail */



}
