
/* POPPINS, SELF-HOSTED — the page's last third-party request, now gone.
   Everything else on this page is already inlined, so a <link> to
   fonts.googleapis.com was the one thing that made it not self-contained: it
   renders in a fallback font on any network that blocks Google, it costs two DNS
   lookups and two round trips on the critical path, and it is a third-party
   connection somebody has to justify in a privacy review before handover.

   These are the SAME latin subsets Google serves (from @fontsource/poppins),
   31KB raw for all four weights — about 42KB of base64, which is 7% of a page
   that is already 620KB. Poppins is SIL OFL 1.1, which permits this;
   POPPINS-OFL-LICENSE.txt travels with the source kit.

   font-display:swap is kept from the Google URL's &display=swap. The bytes are
   in the document, so there is nothing to wait for and no flash of invisible
   text either way — but if the @font-face is ever moved back out to a file, the
   descriptor is already correct.

   unicode-range is deliberately NOT declared. Google splits its CSS into subsets
   and uses the range to pick one; with a single face per weight and no siblings
   to choose between, a range only adds a way to get it wrong. Three glyphs in the
   features list — U+2194, U+25AD, U+25C9 — are outside this subset and fall
   through to system-ui, which is exactly what they did before. */
@font-face{font-family:Poppins;font-style:normal;font-weight:400;
  font-display:swap;src:url(/assets/7d93459d86585bfcdbb7.woff2) format('woff2')}
@font-face{font-family:Poppins;font-style:normal;font-weight:500;
  font-display:swap;src:url(/assets/cd36de204aca2d5fa263.woff2) format('woff2')}
@font-face{font-family:Poppins;font-style:normal;font-weight:600;
  font-display:swap;src:url(/assets/f4e80d9dfd374d02989b.woff2) format('woff2')}
@font-face{font-family:Poppins;font-style:normal;font-weight:700;
  font-display:swap;src:url(/assets/9338e65fc077355c7a87.woff2) format('woff2')}
/* ===========================================================================
   BeHive landing page — ARCHITECTURE PROTOTYPE v4
   Rough fidelity on purpose. This exists to judge FLOW, not finish.

   WHAT CHANGED FROM v2, AND WHY. v2 had the right arc and the wrong motif. It
   put the comb in a 112px column at a fixed x (64%), which produced three
   defects visible in the first screenshot pass:

     · the column ran straight THROUGH the four-up copy on the light zone —
       hexagons sitting behind body text, unreadable
     · it clipped to hard vertical edges on both sides, so it read as a stripe
       pasted on rather than as part of the surface
     · content sat left of it, so the right third of every dark screen was
       simply empty

   All three come from the same mistake: treating the motif as an OBJECT placed
   on the page. In v3 it is the GROUND.

   1 · THE FIELD. A full-bleed hexagonal lattice behind everything, at 7-9%
       contrast. It never clips, because it has no edges. It never leaves dead
       space, because it is everywhere. It is a texture, not a graphic.

   2 · THE ROUTE. One lit path travels the whole page inside that lattice, and
       its x position is a function of page depth — declared once, in ROUTE
       below, as a set of keyframes. Continuity is not a coincidence: the same
       function generates the path across all four zones AND the three seams
       between them, so it is a single unbroken object from the hero to the
       close, where it arrives at the mark.

       The route is chosen against where the content ISN'T. It hugs the right
       through the dark opening (content is left), crosses at the first seam,
       runs the left margin through the light chamber (content is full width),
       sweeps right for the card, and converges to centre for the close. That is
       what "guide attention instead of distracting" means here: the light is
       always in the room the copy leaves empty.

   3 · OCCLUSION. Where the route does pass behind text, the lit cells and the
       connecting line drop to a tenth of their brightness — measured from the
       real layout, not guessed. So the motif can be bold in open space without
       ever competing with a word.

   4 · GEOMETRY IS STATIC, LIGHT MOVES. Nothing is scroll-driven. No pinning,
       no hijack, no snap. The travelling pulse is a CSS animation with a
       per-cell delay along the route, so the light descends. Under reduced
       motion it stops and simply stays lit.

   JS OFF: the four-zone arc, the three seams, all copy, all CTAs and the whole
   layout are CSS. Only the lattice texture is missing. Nothing is gated.

   Placeholders are marked PH. Real assets drop into those slots unchanged.
   =========================================================================== */

:root{
  /* the arc — one ramp, sampled at eight depths */
  --d1:#0B2430;  --d2:#081E29;  --d3:#061520;
  --d4:#F4F8F5;  --d5:#FFFFFF;
  --d6:#F7EEDC;  --d6b:#F0E1C4;   /* the retired warm pivot — see below */
  --d7:#0A2030;  --d8:#061520;

  /* BAND COLOURS, from the client's "Section Band Colors" sheet. This replaced
     the four-zone scheme, where each zone was ONE continuous surface with no
     band edges inside it. The sheet asks for a colour per SECTION instead, so
     the light run is now four flat bands rather than one gradient, and the card
     band moves from warm cream to dark — which retires the warm zone entirely
     and takes the page from three tonal transitions to five.
     The hero is deliberately NOT changed; its suggestion was declined. */
  --b-what:#F6FBF9;              /* 02 how it works        */
  --b-bercor:#FFFFFF;            /* 03 built on trust      */
  --b-inside:#F2F8F6;            /* 04 you stay in control */
  --b-why:#FFFFFF;               /* 05 made for people     */
  --b-card1:#072C33;             /* 06 bercor pay, top     */
  --b-card2:#0B3940;             /* 06 bercor pay, bottom  */
  --b-faq:#FFFFFF;               /* 07 good to know        */

  --green:#0C8F5B; --green-ui:#0B8656; --green-ink:#076B44;
  --mint:#BFE5D3; --teal:#4AD3AC; --deep:#024427;
  --honey:#E8A317; --navy:#0A202B;
  --ink:#0A202B; --ink-60:rgba(10,32,43,.72); --ink-40:rgba(10,32,43,.44);
  --lit:rgba(255,255,255,.88);
  /* Secondary text on dark. This was white at 40%, which measures 3.66:1 on
     --d1 and 3.77:1 on --d3 -- below AA for body copy on the plain background,
     before any WebGL was involved. 62% clears it with margin on every dark
     surface in the arc while still reading as clearly secondary to --lit. */
  --lit-40:rgba(255,255,255,.62);

  --maxw:75rem; --gut:clamp(1.25rem,5vw,2.5rem);
}
*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{margin:0;font-family:Poppins,system-ui,sans-serif;background:var(--d1);
  color:var(--lit);-webkit-font-smoothing:antialiased;overflow-x:hidden}
.wrap{max-width:var(--maxw);margin:0 auto;padding-inline:var(--gut);
  position:relative;z-index:2}

/* ---------------------------------------------------------------------------
   TYPE — six steps, and nothing off-scale. The old page had 20+ sizes between
   12 and 18.5px and NOTHING between 19 and 42. Feature is the level it lacked.
   --------------------------------------------------------------------------- */
.t-display{font-size:clamp(2.1rem,5.4vw,3.9rem);line-height:1.04;
  letter-spacing:-.03em;font-weight:600;margin:0}
.t-section{font-size:clamp(1.72rem,3.5vw,2.65rem);line-height:1.12;
  letter-spacing:-.022em;font-weight:600;margin:0}
.t-feature{font-size:clamp(1.35rem,2.2vw,1.9rem);line-height:1.2;
  letter-spacing:-.015em;font-weight:600;margin:0}
.t-lede{font-size:clamp(1.02rem,1.55vw,1.2rem);line-height:1.6;margin:0;max-width:56ch}
.t-body{font-size:.95rem;line-height:1.62;margin:0;max-width:38ch}
.t-micro{font-size:.8rem;line-height:1.55;margin:0;letter-spacing:.01em}
.eyebrow{font-size:.8rem;font-weight:600;letter-spacing:.17em;text-transform:uppercase;
  margin:0 0 18px}
.hl{color:var(--teal)}
.lit .t-lede,.lit .t-body{color:var(--lit-40)}
.dim{color:var(--ink-60)}

/* ---------------------------------------------------------------------------
   DEPTH — three padding tiers, so space carries information. v2's error here
   was the opposite of the old page's: the old page used one clamp everywhere
   (8,000px of uniform rhythm), v2 left 400px voids under short blocks.
   --------------------------------------------------------------------------- */
.depth{position:relative;padding-block:clamp(64px,7.5vw,112px)}
.depth.tight{padding-block:clamp(44px,5vw,72px)}      /* evidence */
.depth.open{padding-block:clamp(84px,10vw,150px)}     /* arrival, close */

/* The zones. The hero and the close are still ONE continuous surface each; the
   light run is now four flat bands from the colour sheet, and the pivot is a
   two-stop dark gradient rather than warm cream. */
.zone{position:relative;isolation:isolate}
.zone-descent{background:linear-gradient(180deg,var(--d1) 0%,var(--d2) 44%,var(--d3) 100%)}
.zone-chamber{background:var(--b-bercor);color:var(--ink)}
.zone-pivot{background:linear-gradient(180deg,var(--b-card1) 0%,var(--b-card2) 100%);
  color:#fff}
.zone-return{background:linear-gradient(180deg,var(--d7) 0%,var(--d8) 100%)}

/* PER-SECTION BANDS, drawn on a ::before at z-index -1 rather than as a
   background on the section itself. The comb field is a child of the ZONE at
   z-index 0, so a plain background on the section would paint over it and the
   lattice would vanish from the light run. A negative-z pseudo-element sits
   below the field and above the zone's own background, which is exactly the
   layer a band colour wants. The zone's isolation:isolate keeps it contained.
   Only the two bands that differ from the zone base are declared. */
#what::before,#inside::before{content:"";position:absolute;inset:0;z-index:-1;
  background:var(--band)}

/* ─────────────────── THE MESH · the network lattice on white ────────────────
   A second, quieter hexagon layer for the flat white bands. Those bands went
   flat when the colour sheet replaced the chamber gradient — measured, the whole
   light run carried 53 comb cells across 2,900px and they faded out inside the
   first 520px, so below the first band there was nothing at all.

   FLAT-TOP, and that is the one deliberate difference from the comb field.
   Every other hexagon on this page was measured at vertex angle 30 (mod 60) —
   pointy-top: the comb field's 231 cells, the four Why marks, the two card base
   cells. The round-trip diagram's 267 polygons are at 0, flat-top. So the mesh
   borrows the diagram's orientation rather than inventing a third angle, and
   since the diagram sits in the band immediately above `#bercor`, the two read
   as related. A small arbitrary rotation (12 was tried) reads as a misalignment
   next to strong horizontals, not as a decision.

   Drawn as a native flat-top lattice (step 1.5R x sqrt(3)R, odd columns offset
   half a step) rather than by rotating the pointy-top one 30 degrees. Same
   result, and it avoids generating a padded over-area to cover the corners.

   z-index 0: above the band colour on ::before at -1, below .wrap at 2. */
.mesh{position:absolute;inset:0;z-index:0;pointer-events:none;overflow:hidden}
.mesh svg{position:absolute;inset:0;width:100%;height:100%;display:block}
.mx{fill:none;stroke:var(--green);stroke-linejoin:round;
  stroke-opacity:var(--a,.06);stroke-width:var(--w,2px)}
/* ── THE MESH PULSES · a front that travels down and leaves nothing ─────────
   The brief: hexagons light up, the lit band moves top to bottom, they fade out
   behind it. A blinking path that does not stay lit.

   WHY THIS REPLACED A DIP. The previous version dimmed each cell 35% below its
   own resting value on a 22s left-to-right wave, and it was reported invisible
   twice. It was. Measured on the real page: a full cycle moved a maximum of TEN
   RGB LEVELS, across about a hundred pixels out of a million. The animation was
   correct and the substrate could not carry it — a 1-2px stroke at 3-11% opacity
   in green on near-white has nowhere to go. Raising the base 3.4x (in
   buildMesh) is what made an animation possible at all; this keyframe is the
   second half of that change, not a tweak to the first.

   THE FRONT IS 10% OF THE CYCLE. Most of the lattice sits at rest at any moment
   and a narrow band is lit — that is what the eye catches as movement. A wide
   soft wave averages out into "general shimmer", which is what the old one was.
   With a 6s cycle over the full band height, the lit band is about a tenth of
   the height and crosses in six seconds.

   OPACITY AND WIDTH TOGETHER, per the mesh's own rule. --a and --w are the
   cell's resting values, set per element by the builder; the keyframes scale
   both. Animating one alone gives a wash or a wireframe rather than a pulse.

   THE PEAK IS A NEW STATE AND IT IS VERIFIED, NOT ASSUMED. The old keyframe
   could never brighten past --a, so the frame contrast_v4 already passed was by
   definition the worst case. That guarantee is gone the moment cells light UP —
   so contrast_v4 now pins every mx animation to this peak frame and measures
   there. Do not raise these multipliers without re-running it. */
@keyframes mxPulse{
  0%      {stroke-opacity:calc(var(--a,.06) * .55); stroke-width:var(--w,2px)}
  1.5%    {stroke-opacity:calc(var(--a,.06) * .55); stroke-width:var(--w,2px)}
  5%      {stroke-opacity:calc(var(--a,.06) * 1.55);
           stroke-width:calc(var(--w,2px) * 1.45)}
  11%     {stroke-opacity:calc(var(--a,.06) * .55); stroke-width:var(--w,2px)}
  100%    {stroke-opacity:calc(var(--a,.06) * .55); stroke-width:var(--w,2px)}
}
/* THE FALL IS LONGER THAN THE RISE — 420ms up, 720ms down. A symmetric blink is
   what reads as flicker; a light that arrives quickly and decays slowly reads as
   something passing through. Same total lit time either way, and it is most of
   what took the "distracting" edge off at the same brightness. */
[data-mesh] > .mesh .mx{
  animation:mxPulse 12s linear infinite;
  animation-delay:var(--md,0ms)}
/* Paused when the band is off-screen. A 6s period costs exactly what a 60s one
   costs — animations tick every frame whatever their duration, so only the
   element count matters, and neither stroke-opacity nor stroke-width is
   compositor-accelerated.
   POLARITY: running is the DEFAULT and script only ever adds the pause, so a
   page with no JS keeps the motion and JS only removes work nobody is watching.
   The inverse — animating only once a class is added — would leave the mesh dead
   for every no-JS visitor. */
[data-mesh] > .mesh.mx-idle .mx{animation-play-state:paused}
@media (prefers-reduced-motion:reduce){
  [data-mesh] > .mesh .mx{animation:none}
}
/* contrast_v4 adds this to freeze every cell at the brightest frame the pulse
   ever reaches, so AA is measured against the actual worst case rather than
   against whichever five moments the sampler happened to catch. */
html.mx-peak [data-mesh] > .mesh .mx{
  animation:none;
  stroke-opacity:calc(var(--a,.06) * 1.55);
  stroke-width:calc(var(--w,2px) * 1.45)}
#what{--band:var(--b-what)}
#inside{--band:var(--b-inside)}

/* THE THREE TRANSITIONS. A 220px blend, not an edge — this is what makes the
   page read as one surface rather than as stacked panels. The route runs
   through these too, which is the point: the seam is where it crosses. */
.seam{height:220px;position:relative;margin-block:-1px;isolation:isolate}
/* The hero-to-light junction gets a longer ramp and tighter flats either side.
   Net vertical distance goes DOWN while the colour change gets more room —
   the complaint was both "too much space" and "too abrupt", and those only
   look contradictory if you assume the gap has to be flat. */
.seam-1{height:200px}
.zone-descent .depth.open{padding-bottom:clamp(6px,1vw,16px)}
#what{padding-top:clamp(10px,1.4vw,22px)}
/* A two-stop ramp from near-black to near-white travels in a straight line
   through desaturated mid-grey — which is why it read as a stark wipe rather
   than a transition. These stops route the same journey through the brand
   green, and cluster more of them in the middle where the eye notices the
   change, so the ramp reads as light arriving rather than as a fade.

   Stops are front-loaded so the light tone has fully arrived by ~78% of the
   seam. A ramp that fills the entire height is what made a 300px seam feel
   like dead space: the eye reads the transition as finished long before the
   geometry agrees. */
.seam-1{background:linear-gradient(180deg,
  var(--d3) 0%, #08222c 12%, #0d3339 26%, #1a4a44 38%,
  #2f6459 48%, #58897a 58%, #8db0a1 67%, #bcd4c7 74%,
  #e2ece5 78%, var(--b-what) 86%, var(--b-what) 100%)}
/* SEAM 2 AND 3 NOW CROSS TONE, so they cannot stay two-stop ramps. They used to
   join white to cream and cream back to white — neighbouring tints, where a
   straight interpolation is invisible. The card band is dark now, so these are
   the same journey seam-1 makes, and they inherit its finding: a two-stop ramp
   between a near-white and a near-black travels in a straight line through
   desaturated mid-grey and reads as a stark wipe, not a transition. Both are
   routed through the brand green instead, with the stops clustered where the
   eye notices the change, and the destination fully arrived by ~80% so the
   remaining height reads as the next band rather than as dead space.

   ROUTED THROUGH TEAL, NOT THROUGH THE BRAND GREEN. Seam 1 goes through green
   because its destination IS green-tinted white, so green is a waypoint between
   the two ends. Here the destination is #072C33, a deep teal, and green sits
   outside both ends — the first cut of these ramps put a hue in the middle that
   neither neighbour has, and both seams read as a green stripe rather than as
   the dark arriving. The waypoints now stay in the card band's own family. */
.seam-2{background:linear-gradient(180deg,
  var(--b-why) 0%, #eef5f5 10%, #d3e5e6 22%, #a9c9cc 33%,
  #7ba9ae 43%, #52868d 53%, #33686f 63%, #1d5058 72%,
  #0e3b43 80%, var(--b-card1) 90%, var(--b-card1) 100%)}
.seam-3{background:linear-gradient(180deg,
  var(--b-card2) 0%, #124349 12%, #1c555c 24%, #2f6d73 36%,
  #528c90 46%, #7baaad 56%, #a6c8ca 66%, #cfe3e4 74%,
  #edf5f5 81%, var(--b-faq) 90%, var(--b-faq) 100%)}

/* ---------------------------------------------------------------------------
   THE FIELD + THE ROUTE. Full bleed, behind everything, never interactive.
   --------------------------------------------------------------------------- */
.field{position:absolute;inset:0;z-index:0;pointer-events:none;overflow:hidden}
.field svg{position:absolute;inset:0;width:100%;height:100%;display:block}

/* THE FIELD IS NOT WALLPAPER.

   One tiled pattern at a single opacity across 7,400px reads as exactly that:
   a repeat. Two things fix it at once, which is why it is worth doing rather
   than just dialling the opacity down. The base tile drops to a whisper, and a
   sparse scatter of individually drawn cells sits on top at real strength —
   some cells simply more present than others, the way a honeycomb photographed
   at depth would be.

   And because the scatter is placed by the SAME occlusion test the route uses,
   the distinct cells never land on copy. So the field is busiest exactly where
   there is nothing to read, and quietest under the words. One move, both jobs. */
/* NOT EVERY CELL. This is the difference between the hero's comb and a
   wallpaper, and it is not opacity — it is COVERAGE. The hero lights a subset
   of the lattice and leaves the rest empty, so the eye reads scattered
   hexagons. Draw every cell of a complete lattice and it reads as a repeating
   tile at any opacity you like, which is exactly what it looked like.

   So the tiled background is gone. Cells are drawn individually, at about 45%
   of positions, in three stroke weights, and the layer is softened by half a
   pixel so the edges are not wiry.

   fill:none is load-bearing: an SVG <polygon> defaults to BLACK fill, so the
   moment these stopped inheriting the old rule they rendered as solid black
   hexagons across the page. */
/* NAMESPACED. These were called .cell, which the diagram's hub cluster has
   used since it was ported — so `.cell{fill:none}` quietly stripped the fill
   off all seven hub hexagons and they could never light. Two features, one
   class name, and the older one lost. */
.cf{fill:none}
.cf-a{stroke-width:1.2} .cf-b{stroke-width:2} .cf-c{stroke-width:3}
.dark .cf{stroke:#8FE8C6} .light .cf{stroke:#0C8F5B} .warm .cf{stroke:#A8760B}
.field svg{filter:blur(.45px)}



/* ---------------------------------------------------------------------------
   STATION — every depth attaches to the page the same way, so there is ONE
   layout idea rather than a per-section recipe. Where a depth has a hero-scale
   visual it becomes two columns; where it has a grid it goes full measure.
   --------------------------------------------------------------------------- */
.station{max-width:52rem}
/* #what ONLY, and it is about two axes disagreeing rather than about alignment.
   Measured at 1547: the wrap is 1200px centred, the diagram below spans it
   (214..1334 — 214 clear left, 213 right), but .station is 52rem = 832px LEFT
   aligned inside it, so the copy sat 40px from the left edge and 328px from the
   right. Two stacked elements on different centres is what reads as "heavier on
   the left" even though nothing is actually misaligned.

   Centring the BLOCK, not the text. 52rem stays — that is the reading measure,
   about 70 characters — and the text stays ragged-right. Only the block moves
   onto the diagram's axis.

   #why, #faq and #inside have the same bare .station in a .wrap and the same
   left bias. Deliberately NOT changed here: a left-aligned accordion is the
   convention and Malcom flagged this band alone. */
#what .station{margin-inline:auto}
.station .eyebrow{color:var(--teal)}
.zone-chamber .station .eyebrow{color:var(--green-ink)}
.zone-chamber .hl{color:var(--green-ink)}
.split{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(0,.85fr);
  gap:clamp(28px,5vw,72px);align-items:center}
/* The trustline is a direct child of .split now, not of .station, so mobile
   gets the order it needs from source order alone: copy, visual, trustline.
   The two-column placement below is DESKTOP ONLY — applied at every width it
   clamped the visual into a 142px sliver and threw it above the store badges,
   because at one column `grid-column:2` has nowhere to go. */
@media (min-width:1001px){
  .depth.open .split{grid-template-rows:auto auto}
  .depth.open .station{grid-column:1;grid-row:1}
  .depth.open .visual{grid-column:2;grid-row:1 / span 2;align-self:center}
  .depth.open .trustline{grid-column:1;grid-row:2}
}
.split .station{max-width:none}

/* ---------------------------------------------------------------------------
   components — rough
   --------------------------------------------------------------------------- */
.ph{border-radius:14px;display:grid;place-items:center;text-align:center;
  font-size:.72rem;letter-spacing:.1em;text-transform:uppercase;font-weight:700;
  border:1px dashed currentColor;opacity:.55;padding:14px;line-height:1.5}
.ph.hexph{border:none;background:rgba(255,255,255,.045)}
.zone-chamber .ph.hexph{background:rgba(10,32,43,.05)}
.lit .ph{color:var(--lit-40)} .zone-chamber .ph{color:var(--ink-40)}

.badges{display:flex;gap:12px;margin-top:28px;flex-wrap:wrap;max-width:27rem}
.badge{display:inline-flex;align-items:center;gap:10px;background:#000;color:#fff;
  border:1px solid rgba(255,255,255,.18);border-radius:12px;padding:10px 18px;
  text-decoration:none;flex:0 0 auto}
/* The glyph must never let the badge stretch: a full-width store badge is a
   standing brand rule on this project. */
.badge svg{width:22px;height:22px;flex:none}
.badge small{display:block;font-size:.6rem;opacity:.7}
.badge strong{display:block;font-size:.95rem;margin-top:-2px}
.trustline{list-style:none;display:flex;flex-wrap:wrap;gap:10px 24px;margin:26px 0 0;
  padding:20px 0 0;border-top:1px solid rgba(255,255,255,.13)}
.trustline li{display:flex;align-items:center;gap:9px;font-size:.82rem;
  color:var(--lit-40);line-height:1.4}
.trustline svg{width:17px;height:17px;flex:none;color:var(--teal)}
/* 1.2rem/700 is deliberate, not cosmetic. Honey on the teal-lit backgrounds
   measures 4.1-4.4:1 — under AA for body text and awkward to fix by nudging the
   colour, because the honey IS the brand accent. At 19.2px bold it is "large
   text" under WCAG, where the threshold is 3.0, and it clears with room. It
   also suits a call-out better than 16px did. */
.rally{margin:16px 0 0;font-weight:700;color:var(--honey);display:flex;
  width:fit-content;align-items:center;gap:12px;font-size:1.2rem}
.rally::before,.rally::after{content:"";height:1px;width:26px;background:rgba(232,163,23,.5)}
.btn{display:inline-block;background:var(--green-ui);color:#fff;text-decoration:none;
  font-weight:600;font-size:.95rem;padding:14px 26px;border-radius:999px;margin-top:26px}

.grid-3{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  gap:clamp(22px,3vw,42px);margin-top:clamp(30px,3.5vw,46px)}
.grid-4{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
  gap:clamp(16px,2vw,26px);margin-top:clamp(30px,3.5vw,46px)}
.item .t-feature{margin-bottom:9px}
.tile{width:46px;height:51px;margin-bottom:16px;display:grid;place-items:center;
  clip-path:polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%);
  font-size:.9rem;font-weight:700}
.lit .tile{background:rgba(74,211,172,.16);color:var(--teal)}
.zone-chamber .tile{background:rgba(12,143,91,.10);color:var(--green-ink)}

.phone{aspect-ratio:9/19.5;border-radius:18px;padding:5px;
  background:linear-gradient(165deg,#274a58,#062434 55%,#03151f)}

/* The warp reveal (gl.js, moment 4). Everything here is opt-out: the canvas is
   never drawn without JS + WebGL, and .warp-hide is only ever added by script,
   so with either missing the four mockups are simply present and flat — which
   is the shippable state. */
#insideRail{position:relative}
/* width/height explicit, NOT `inset:-8% -4%`: a <canvas> is a REPLACED element,
   so with height:auto and both top and bottom set it keeps its intrinsic 150px
   and ignores `bottom`. The reveal then renders into a 150px sliver and looks
   like a shader bug. Larger than the rail because the warped sheet leaves its
   resting box on every axis, and a canvas cut to the rail clips the overshoot
   into a straight line along the edge. */
#warpGl{position:absolute;left:-4%;top:-8%;width:108%;height:118%;
  pointer-events:none;z-index:3}
/* No transition, deliberately. A fade would (a) dim all four phones at once
   the moment the reveal arms, which reads as a flicker, and (b) do nothing
   useful at the handoff: at t=1 the mesh is exactly flat and exactly on the
   resting box, so swapping to the real element is pixel-identical. */
.phone.warp-hide{opacity:0}
@media (prefers-reduced-motion:reduce){
  #warpGl{display:none}
  .phone.warp-hide{opacity:1}
}
.phone>div,.phone>img{width:100%;height:100%;border-radius:14px;display:block}
.phone>img{object-fit:cover}
.phone>div{background:#FAFCFB;display:grid;place-items:center;color:#A9B4B8;
  font-size:.62rem;letter-spacing:.08em;text-transform:uppercase;font-weight:700;
  text-align:center;padding:8px}

.q{max-width:44rem;margin:0}
.q .mk{display:block;font-size:3.4rem;line-height:.7;color:var(--honey);font-weight:700;
  margin-bottom:10px}
.q p{margin:0;font-size:clamp(1.35rem,2.2vw,1.9rem);line-height:1.34;font-weight:500;
  color:#fff;max-width:30ch;letter-spacing:-.015em}
.q footer{display:flex;align-items:center;gap:16px;margin-top:26px;padding-top:20px;
  border-top:1px solid rgba(255,255,255,.16);max-width:28rem}
.attrib{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap}
.attrib::before{content:"";width:22px;height:1px;background:var(--teal);
  align-self:center;flex:none}
.attrib strong{color:#fff;font-size:1rem;font-weight:600}
.attrib>span{font-size:.8rem;color:var(--lit-40)}
.q .portrait{width:54px;height:60px;flex:none;object-fit:cover;
  clip-path:polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%)}
.slot{display:inline-block;border:1px dashed rgba(232,163,23,.5);border-radius:9px;
  padding:4px 10px;font-size:.66rem;letter-spacing:.09em;text-transform:uppercase;
  color:var(--honey);font-weight:700;margin-bottom:14px}

/* THE FAQ. Six rows used to be plain <div>s carrying a "+" that opened
   nothing — no answers in the file, no handler, and not focusable. The answers
   themselves were never missing: they are the approved Q&A from the signed-off
   page, ported verbatim.

   <details>/<summary>, not JS. It opens, it is keyboard operable, it announces
   as a disclosure to a screen reader, and — the reason it is not a choice here —
   IT WORKS WITH JAVASCRIPT OFF. The previous page learned that the hard way: its
   volatility sentence lived inside a collapsed panel and was unreachable without
   JS, which is why that sentence was moved to the footer disclaimer. Nothing on
   this page may be gated on script, least of all an answer about risk.

   The borders moved from .faq-row to .faq-item because the row is now the
   <summary> — first child of its own <details> — so :last-child no longer picks
   out the bottom row of the list. Everything else about the row is unchanged,
   including the comb field's OCCLUDE selector, which still matches. */
.faq-item{border-top:1px solid rgba(255,255,255,.14)}
.faq-item:last-child{border-bottom:1px solid rgba(255,255,255,.14)}
.faq-row{padding:20px 0;display:flex;cursor:pointer;
  justify-content:space-between;gap:20px;align-items:center;
  list-style:none}                 /* Firefox's marker */
.faq-row::-webkit-details-marker{display:none}
.faq-row span{font-size:1.05rem;font-weight:500;color:#fff}
.faq-row i{font-style:normal;color:var(--teal);font-size:1.2rem;
  flex:0 0 auto;transition:transform .18s ease}
/* + becomes x. One glyph, no second character to keep in sync. */
.faq-item[open] .faq-row i{transform:rotate(45deg)}
.faq-row:focus-visible{outline:2px solid var(--teal);outline-offset:3px}
.faq-a{padding:0 0 24px;max-width:64ch}
.faq-a p{margin:0;color:var(--lit-40);font-size:.95rem;line-height:1.7}
.faq-a p+p{margin-top:12px}
/* on light */
/* The card band went dark, so its secondary copy has to come with it. .dim is
   ink at 72% — 1.3:1 on #072C33. The band carries .lit, which handles the lede
   and body, but the micro disclaimer under the button is .t-micro.dim and was
   not covered by it. */
.zone-pivot .dim{color:var(--lit-40)}
.zone-answers{background:var(--b-faq);color:var(--ink)}
.zone-answers .station .eyebrow,.zone-answers .hl{color:var(--green-ink)}
.zone-answers .faq-item{border-top-color:rgba(10,32,43,.16)}
.zone-answers .faq-item:last-child{border-bottom-color:rgba(10,32,43,.16)}
.zone-answers .faq-row span{color:var(--ink)}
.zone-answers .faq-row i{color:var(--green-ink)}
.zone-answers .faq-row:focus-visible{outline-color:var(--green-ink)}
.zone-answers .faq-a p{color:var(--ink-60)}
/* 150px, down from the 220px every other seam gets. Measured, the run from the
   last FAQ row to the BeHive mark at the top of the close was 368px of nothing:
   220 of seam plus 150 of the close's own top padding. The seam still has room to
   carry the tonal ramp — it is the only seam whose job is purely tonal, since the
   comb field on either side now carries the texture across on its own. */
.seam-4{height:150px}
.seam-4{background:linear-gradient(180deg,var(--b-faq) 0%,#9fb3ae 34%,#3f5f5c 62%,#123240 84%,var(--d7) 100%)}

/* The close is the one .depth.open that does not need 150px at the top: the seam
   above it is already 150px of empty tonal ramp, so the two stacked read as one
   long pause before the ask. 96/112 instead of 150/150 takes ~90px out of the
   approach and ~40px off the tail without crowding anything — the rally line
   still clears the footer rule by more than its own height. */
#join{padding-block:clamp(56px,6.2vw,96px) clamp(64px,7vw,112px)}
.close-mark{width:auto;height:96px;display:block;margin:0 auto 22px}
.close-mid{text-align:center}
.close-mid .badges{justify-content:center;margin-inline:auto}
/* width:fit-content took the rally out of the full-width flow, so text-align
   on the parent no longer centres it. */
.close-mid .rally{margin-inline:auto}
.close-mid .grid-3{text-align:left;max-width:62rem;margin-inline:auto}

.marker{position:fixed;left:10px;bottom:10px;z-index:99;background:#11161A;
  color:#7FE0B4;font-size:.66rem;padding:7px 11px;border-radius:8px;font-weight:700;
  letter-spacing:.08em}

/* ---------------------------------------------------------------------------
   HEADER. One dark translucent bar for the whole page rather than a light one.
   A sticky header has to survive four tonal zones here — dark, light, warm,
   dark — and the previous page's light header would have been a hard white
   band sitting on top of the arc. Dark-on-blur reads as a floating bar over
   all four, and needs no scroll listener to swap itself.
   --------------------------------------------------------------------------- */

/* THE QUOTE ON LIGHT. It was built against a dark ground — white text, white
   hairlines, a honey slot chip. On the light chamber every one of those
   inverts. */
.zone-chamber .q p{color:var(--ink)}
.zone-chamber .q footer{border-top-color:rgba(10,32,43,.16)}
.zone-chamber .attrib strong{color:var(--ink)}
.zone-chamber .attrib>span{color:var(--ink-60)}
.zone-chamber .q .mk{color:#B8800A}
.zone-chamber .slot{border-color:rgba(184,128,10,.55);color:#8A5E06}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
.sr-only:focus{position:fixed;top:10px;left:10px;width:auto;height:auto;
  clip:auto;margin:0;padding:10px 16px;background:var(--green-ui);color:#fff;
  border-radius:8px;z-index:200}
.site-header{position:sticky;top:0;z-index:50;
  background:rgba(6,21,32,.78);backdrop-filter:blur(14px);
  -webkit-backdrop-filter:blur(14px);
  border-bottom:1px solid rgba(255,255,255,.09)}
.header-in{max-width:var(--maxw);margin:0 auto;padding:10px var(--gut);
  display:flex;align-items:center;gap:clamp(12px,2vw,28px)}
.logo-link{display:flex;align-items:center;flex:none}
.logo{height:34px;width:auto;display:block}
.nav{display:flex;gap:clamp(14px,1.8vw,26px);margin-inline:auto}
.nav a{color:rgba(255,255,255,.82);text-decoration:none;font-size:.9rem;
  font-weight:500;padding:6px 2px;border-bottom:2px solid transparent}
.nav a:hover,.nav a:focus-visible{color:#fff;border-bottom-color:var(--teal)}
.header-actions{display:flex;align-items:center;gap:10px;flex:none}
.btn-sm{margin-top:0;padding:9px 18px;font-size:.86rem}
.navdrop,.qrdrop{position:relative}
.navdrop{display:none;margin-inline:auto 0}
.navdrop summary,.qrdrop summary{list-style:none;cursor:pointer;
  display:inline-flex;align-items:center;gap:8px;font-size:.86rem;font-weight:500;
  color:rgba(255,255,255,.86);border:1px solid rgba(255,255,255,.20);
  border-radius:999px;padding:8px 14px}
.navdrop summary::-webkit-details-marker,
.qrdrop summary::-webkit-details-marker{display:none}
.navdrop svg,.qrdrop svg{width:17px;height:17px}
.navdrop ul,.qrdrop .pop{position:absolute;right:0;top:calc(100% + 10px);
  background:#08202c;border:1px solid rgba(255,255,255,.14);border-radius:14px;
  box-shadow:0 22px 48px rgba(0,0,0,.5);padding:10px;z-index:60}
.navdrop ul{list-style:none;margin:0;min-width:12.5rem}
.navdrop ul a{display:block;padding:9px 12px;border-radius:9px;
  color:rgba(255,255,255,.88);text-decoration:none;font-size:.92rem}
.navdrop ul a:hover,.navdrop ul a:focus-visible{background:rgba(74,211,172,.14);color:#fff}
.qrdrop:not([open]) .pop{display:none}
.qrdrop .pop{width:15rem;text-align:center}
.qrdrop .qr-img{background:#fff;border-radius:10px;padding:10px;margin-bottom:9px}
.qrdrop .qr-img svg{width:100%;height:auto;display:block}
.qrdrop .pop p{margin:0;font-size:.76rem;line-height:1.5;color:var(--lit-40)}
/* the sticky header must not cover an anchored heading */
[id]{scroll-margin-top:76px}
@media (max-width:1080px){
  .nav{display:none}
  .navdrop{display:block}
}
@media (max-width:560px){
  .qrdrop{display:none}   /* a QR to scan with the phone you are holding */
  .logo{height:29px}
}
@media (max-width:400px){
  /* At 320 the row was logo + "Sections" + Download = 339px against a 320
     viewport, which does not scroll horizontally -- it makes the LAYOUT
     viewport 339 wide, so the whole page silently renders 19px too wide and
     every measurement after it is off. The label goes, the button tightens. */
  .navdrop summary span{display:none}
  .navdrop summary{padding:8px 11px}
  .btn-sm{padding:9px 14px;font-size:.82rem}
  .header-in{gap:10px}
}

/* ---------------------------------------------------------------------------
   THE ROUND TRIP — ported from the previous page, recoloured for the dark arc,
   and re-extracted WITH its media queries intact. Geometry, copy and the four
   stages are unchanged; only colour moved. Desktop draws a hexagon (the cell
   itself); the phone draws a vertical run with a return rail. With JS off the
   groove, the entry stub and the four labelled cards still render — only the
   honeycomb texture and the travelling cells are missing.
   --------------------------------------------------------------------------- */
.circuit{position:relative;margin-top:clamp(26px,3vw,44px);--cyc:15s}
/* MERGED INTO THE BAND — no surface of its own. This carried a radial gradient
   (#FFFFFF -> #E4F3EC -> #D8EDE3), a 1px inset border and a 44px drop shadow, so
   at 1120x560 it was a raised card covering 40.3% of the band's area and 77.8%
   of its width. Against the colour sheet's flat #F6FBF9 that put a second ground
   inside a band the architecture defines as one continuous surface, and reduced
   the specified band colour to a margin around a card of a different colour.

   Three things follow, and the first was the opposite of what I expected:

     * CONTRAST IMPROVES. The panel's ground was DARKER than the band, so every
       element gains separation on #F6FBF9 — the groove 1.11 -> 1.22:1, the trail
       cells 1.50 -> 1.64:1, the turn markers 2.30 -> 2.52:1.
     * GROUPING SURVIVES, which was the real risk and the reason this was mocked
       before it was built. The loop is a closed circuit: the groove and the
       trail make a continuous ring and the four cards sit on it, so the parts
       still read as one mechanism without a box around them. The groove reads
       stronger on the lighter ground, which helps.
     * THE TURN MARKERS BECOME OUTLINES. Their #FFFFFF fill no longer registers
       against a near-white band; their 2.4px green stroke is what carries them
       now. Legible, but it is a change in how they read, not just in tone.

   The geometry is untouched: same 1120x560 viewBox, same aspect-ratio box, so
   the hub's delays — solved from the loop's own geometry — are unaffected.
   overflow:hidden stays. Nothing currently reaches the edge, but it is the only
   thing stopping a future bleed from escaping a box that no longer looks like
   one. */
.cboard{position:relative;aspect-ratio:1120/560;overflow:hidden}
.cboard .flow{position:absolute;inset:0;width:100%;height:100%;display:block}
.cboard .tall{display:none}
.cboard .grid-h{stroke:rgba(12,143,91,.13);stroke-width:1;fill:none}
.cboard .groove{fill:none;stroke:#D2E9DD;stroke-width:22;stroke-linejoin:round;stroke-linecap:round}
.cboard .via{fill:#FFFFFF;stroke:#5FAE93;stroke-width:2.4}
.cboard .cell{stroke:#9CD1B8;stroke-width:2}
.cboard .entry{fill:none;stroke:#B8800A;stroke-width:3;stroke-linecap:round}
.cboard .entry-cap{fill:#B8800A}
.cboard /* A darker mustard than the brand honey. #E8A317 on this near-white board is
   about 2.3:1 — fine for the rally on navy, nowhere near AA for 19px text
   here. This still reads as mustard and clears the bar. */
.entry-t{fill:#8A5E06;opacity:1;font:600 19px Poppins,sans-serif;letter-spacing:.14em}
.cboard .tcell{fill:#BFE5D3;stroke:#9CD1B8;stroke-width:1.4}
.cboard /* Honey, not green. A dark-green head on pale-green track cells was a change
   of shade; honey is a change of HUE, so the movement reads at a glance
   instead of needing to be looked for. It is also the colour the hub now
   lights in, which is what makes the two read as one mechanism. */
.tlit{fill:#E8A317;opacity:0;animation:cellrun var(--cyc) linear infinite}
.cboard .nlit{animation:beat var(--cyc) ease-in-out infinite}
.cboard .core-cell{animation:corebeat 5.5s ease-in-out infinite}
.cboard .via-a{animation:vialit var(--cyc) ease-in-out infinite;animation-delay:-1.6s}
.cboard .via-b{animation:vialit var(--cyc) ease-in-out infinite;animation-delay:5.9s}
.circuit .pin{position:absolute;width:20.7%;z-index:2;text-align:left; transform:translate(-50%,-50%);display:flex;align-items:center;gap:11px; background:linear-gradient(158deg,rgba(10,32,43,.28),rgba(255,255,255,0) 52%),var(--card); border:1px solid rgba(10,32,43,.12);border-top-color:rgba(255,255,255,.45); border-radius:16px;padding:11px 14px 12px; box-shadow:0 1px 2px rgba(10,32,43,.06), 0 12px 24px -12px rgba(10,32,43,.22); animation:card var(--cyc) ease-in-out infinite}
.circuit .pin::before{content:"";position:absolute;inset:-1px;border-radius:16px;
  pointer-events:none;border:2px solid #E8A317;opacity:0;
  box-shadow:0 0 0 4px rgba(232,163,23,.16), 0 10px 26px -8px rgba(184,128,10,.5);
  animation:ring var(--cyc) ease-in-out infinite}
.circuit .ptile{width:36px;height:40px;flex:none;display:grid;place-items:center; clip-path:polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%); background:rgba(255,255,255,.62);color:var(--nc); animation:tile var(--cyc) ease-in-out infinite}
.circuit .ptile svg{width:19px;height:19px}
.circuit .ptxt{display:block;min-width:0}
.circuit .pin h3{margin:0;font-size:.855rem;font-weight:600;color:var(--ink)}
.circuit .pin p{margin:3px 0 0;font-size:.775rem;line-height:1.42;color:rgba(10,32,43,.75)}
/* Four steps, four colours. It was two — green for cards 1 and 4, teal for 2
   and 3 — which is why the diagram looked flat: the eye saw a pair of pairs
   rather than a journey. Now the colour IS the story. Currency is green, it
   converts to teal, it is spent in honey (the same warm the card band uses),
   and it returns to a paler green: back where it started, not identical to it. */
.circuit .pin0{--nc:#076B44;--card:#8ECAA8;--soft:rgba(12,143,91,.26)}
.circuit .pin1{--nc:#08655F;--card:#93D3CB;--soft:rgba(8,101,95,.26)}
.circuit .pin2{--nc:#8A5E06;--card:#F2DDA8;--soft:rgba(184,128,10,.30)}
.circuit .pin3{--nc:#0A7C50;--card:#BCE0CB;--soft:rgba(10,124,80,.26)}
.circuit .pin0{left:23.93%;top:20%}
.circuit .pin1{left:76.07%;top:20%}
.circuit .pin2{left:76.07%;top:80%}
.circuit .pin3{left:23.93%;top:80%}
.circuit .chub{position:absolute;z-index:2;text-align:center;pointer-events:none; left:50%;top:71.4%;transform:translate(-50%,-50%);width:16rem}
/* The hub label lands in the middle of the travelling cells, where the local
   background is whatever the animation is doing that second. A scrim makes the
   contrast floor deterministic instead of leaving it to chance. */
.zone-descent .chub{padding:10px 18px;border-radius:14px;
  background:radial-gradient(62% 74% at 50% 50%,rgba(6,21,32,.90),rgba(6,21,32,0))}
.zone-descent .chub span{color:rgba(255,255,255,.80)}
.circuit .chub strong{display:block;font-size:.95rem;font-weight:600;color:var(--ink)}
.circuit .chub span{display:block;margin-top:2px;font-size:.775rem;color:var(--ink-60)}
.circuit .m-hub{display:none}
.circuit .pin0,.circuit .pin0::before,.circuit .pin0 .ptile,
.circuit .n1{animation-delay:-7.5s}
.circuit .pin1,.circuit .pin1::before,.circuit .pin1 .ptile,
.circuit .n2{animation-delay:-3.2s}
.circuit .pin2,.circuit .pin2::before,.circuit .pin2 .ptile,
.circuit .n3{animation-delay:0s}
.circuit .pin3,.circuit .pin3::before,.circuit .pin3 .ptile,
.circuit .n4{animation-delay:4.3s}
/* The two extra hub cells, solved from the track rather than tuned by eye.
   A cell at fraction f of the lap lights at t = f x CYC, and `beat` peaks at
   50% of its own cycle, so a peak at t needs a delay of (t - CYC/2). The
   groove's six vertices sit at f = 0, .287, .393, .5, .787, .893, i.e.
   t = 0, 4.3, 5.9, 7.5, 11.8, 13.4 — and the two straight runs are the
   midpoints between them. */
.circuit .n5{animation-delay:-5.35s}   /* the top run     t=2.15 */
.circuit .n6{animation-delay:2.15s}    /* the bottom run  t=9.65 */
@media (prefers-reduced-motion:reduce){
.cboard .tlit,.cboard .core-cell,.cboard .nlit,.cboard .via-a,.cboard .via-b,
  .circuit .pin,.circuit .pin::before,.circuit .ptile{animation:none}
.cboard .tlit{opacity:.9}
}
@media (max-width:900px){
.cboard .wide{display:none}
.cboard .tall{display:block}
.cboard{aspect-ratio:640/880}
.circuit .pin{width:66%;padding:8px 12px 9px}
.circuit .pin h3{font-size:.83rem}
.circuit .pin p{font-size:.75rem;line-height:1.36;margin-top:2px}
.circuit .pin0{left:50%;top:17.34%}
.circuit .pin1{left:50%;top:35.52%}
.circuit .pin2{left:50%;top:53.71%}
.circuit .pin3{left:50%;top:71.90%}
.circuit .chub{left:58%;top:87.08%;width:auto;text-align:left}
.circuit .chub strong{font-size:.84rem}
.circuit .chub span{font-size:.72rem}
.circuit .pin0,.circuit .pin0::before,.circuit .pin0 .ptile,
  .circuit .n1{animation-delay:-6.02s}
.circuit .pin1,.circuit .pin1::before,.circuit .pin1 .ptile,
  .circuit .n2{animation-delay:-3.92s}
.circuit .pin2,.circuit .pin2::before,.circuit .pin2 .ptile,
  .circuit .n3{animation-delay:-1.82s}
.circuit .pin3,.circuit .pin3::before,.circuit .pin3 .ptile,
  .circuit .n4{animation-delay:0.28s}
  /* the phone's two extra cells: the bottom turn, then the climb back */
  .circuit .n6{animation-delay:3.6s}
  .circuit .n7{animation-delay:5.25s}
}
/* A longer, brighter tail: the head was visible for 11% of the lap and gone.
   18% gives the eye something to follow rather than a flicker. */
@keyframes cellrun{0%{opacity:1} 6%{opacity:.95} 18%{opacity:0} 100%{opacity:0}}
/* From .12-.9 to 0-1: unlit is now genuinely off, so a cell lighting is an
   event rather than a brightening. */
@keyframes beat{0%,40%{opacity:0} 50%{opacity:1} 62%,100%{opacity:0}}
@keyframes corebeat{0%,100%{opacity:.78} 50%{opacity:1}}
@keyframes vialit{0%,42%{stroke:#3B9385} 50%{stroke:#8CF3C4} 58%,100%{stroke:#3B9385}}
/* off, then fully on. At .18 resting the ring was always faintly there, so
   the moment it peaked read as a slight brightening instead of an arrival. */
@keyframes ring{0%,38%{opacity:0} 50%{opacity:1} 64%,100%{opacity:0}}
@keyframes card{
  0%,36%  {box-shadow:0 1px 2px rgba(10,32,43,.06), 0 12px 24px -12px rgba(10,32,43,.22)}
  50%     {box-shadow:0 1px 2px rgba(10,32,43,.06), 0 14px 26px -12px rgba(10,32,43,.26),
           0 0 0 4px var(--soft)}
  66%,100%{box-shadow:0 1px 2px rgba(10,32,43,.06), 0 12px 24px -12px rgba(10,32,43,.22)}
}
@keyframes tile{0%,36%{transform:none} 50%{transform:scale(1.18)} 66%,100%{transform:none}}


/* ---------------------------------------------------------------------------
   THE THREE WEBGL MOMENTS.

   Polarity matters here: the STATIC layer is the default and the canvas is the
   enhancement. Every stage renders complete with no canvas at all, and JS adds
   .gl-on only once a context has actually compiled and drawn. So "WebGL is
   unavailable" is not an error path — it is the base state.

   Each canvas is gated by an IntersectionObserver and shares one RAF loop, so
   an offscreen moment costs nothing.
   --------------------------------------------------------------------------- */
.stage{position:relative;isolation:isolate}
.stage canvas{position:absolute;inset:0;width:100%;height:100%;display:block;
  opacity:0;transition:opacity .6s ease}
.stage.gl-on canvas{opacity:1}

/* moment 1 · THE HERO FIELD — swarm resolving to crystal.

   Full-bleed over the hero rather than boxed into the right column: the
   ambient comb belongs to the whole section, and only the SWARM is kept off
   the copy (by v_side in the point shader — left of the copy column on wide,
   below the copy entirely on narrow).

   .mark is both the fallback and the ruler. The shader measures its rect and
   places the stage there, so the live field and the static cluster occupy the
   same space at every width without a second set of hand-tuned numbers. */
/* The canvas ends at the section boundary, so its comb and its particles used
   to stop dead on a hard horizontal line right where the tonal ramp begins —
   the one place the eye is already looking. Dissolving the bottom of the
   canvas hands the texture over to the SVG comb in the seam instead of
   cutting between them. */
.hero-gl{position:absolute;inset:0;width:100%;height:100%;display:block;
  z-index:0;pointer-events:none;opacity:0;transition:opacity 1.1s ease;
  -webkit-mask-image:linear-gradient(180deg,#000 72%,transparent 99%);
  mask-image:linear-gradient(180deg,#000 72%,transparent 99%)}
.hero-gl.on{opacity:1}
.mark-mask{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}
.mark{position:relative;display:block;width:100%;max-width:30rem;
  margin-inline:auto;aspect-ratio:560/638;transition:opacity .9s ease}
.mark svg{width:100%;height:100%;display:block}
/* Once the live field is up the static cluster goes entirely. At 20% it was
   still a dull grey mass sitting behind the swarm during the drift phase —
   and it is redundant, because the crystal IS that form, rendered live. It
   keeps its box in the layout: that box is what the shader measures to place
   the stage. */
body.gl .mark{opacity:0}
/* ═══ THE CARD'S ARRIVAL ═════════════════════════════════════════════════════
   A one-shot entrance, NOT the moment that was removed. That one warped the card
   permanently, at rest, following the pointer — a card that was always bent.
   This one is warped only while arriving and lands perfectly flat, then hands
   off to the real <img>, so the resting state is the flat render it is now.

   The drop-shadow is a CSS filter on the CANVAS, which follows the rendered
   alpha — so the shadow bends with the card in flight and needs no shader work.
   That trick was in the removed moment and is the one part worth keeping. */
#card{position:relative}
/* Deliberately LARGER than the band. The band is 599px tall and the card takes
   281 of it, so an origin far enough out to make the growth readable does not
   fit inside — the first frames were being clipped by the section edge. The
   extra 76px top and bottom only ever overlaps the warm seams, which are
   gradients, and the layer is pointer-events:none.
   width/height are explicit because a <canvas> is a REPLACED element: negative
   inset with height:auto leaves it at its intrinsic 150px and ignores `bottom`. */
/* Vertical overhang only. `left:-2%;width:104%` grew scrollWidth at every
   width — 6px at 320 up to 51px at 2560 — because the section does not clip it.
   That is the overflow trap this project has paid for repeatedly, and the
   horizontal room was not needed: the origin is clamped inside the canvas in
   script instead. The vertical overhang costs nothing, since above and below are
   the warm seams and those are gradients. */
/* z-index 3, ABOVE .wrap's 2, and that is load-bearing now. The base below is
   inside .wrap; at an equal z-index the later element in the DOM wins, so the
   base would have painted over the arriving card and the flight would have
   happened behind its own plate. The canvas is transparent everywhere it does
   not draw, is pointer-events:none, and goes display:none the moment the run
   ends, so sitting above the copy's layer costs nothing — and the card is
   never drawn over the copy: its box is beside the text on desktop and below
   it on mobile, with the origin below-right of the box in both cases. */
/* z-index 3 puts the flight ABOVE .wrap (2), so the card draws over the board
   rather than behind it — the board is at -3 inside .stage-card, which is a
   different stacking context entirely, so this is the only thing keeping the
   flight in front. The shadow stack matches the resting card's exactly: at the
   landing frame the canvas hides and the img appears, and any difference between
   the two filters would show as a one-frame flicker on the shadow. */
#cardGl{position:absolute;left:0;top:-76px;width:100%;
  height:calc(100% + 152px);pointer-events:none;z-index:3}
/* The shadow is applied ONLY while the card is in flight — see gl.js. This
   canvas also draws the 9,150-point assembly, and a CSS filter applies to the
   whole element, so leaving the stack on meant the particle field cast its own
   34px shadow across the board. */
#cardGl.flying{filter:drop-shadow(0 0 1px rgba(220,245,238,.55))
         drop-shadow(0 18px 34px rgba(0,14,16,.62))
         drop-shadow(0 2px 6px rgba(0,14,16,.55))}
.stage-card img{transition:none}
.stage-card.fly-hide img{opacity:0}
@media (prefers-reduced-motion:reduce){
  #cardGl{display:none}
  .stage-card.fly-hide img{opacity:1}
}

/* The card. It used to be a WebGL moment — a live specular on a quad that
   rotated from the pointer and carried a slight cylinder — with this static
   render sitting underneath as the base state. The cylinder is what made a flat
   photographed card read as bent plastic, so the canvas is gone and the render
   it was covering is now simply the card. The `.gl-on img{opacity:0}` and the
   canvas drop-shadow that went with it are gone too; leaving them would be dead
   rules waiting to confuse whoever reads this next. */
.stage-card{aspect-ratio:800/505;max-width:32rem}
.stage-card img{position:absolute;inset:0;width:100%;height:100%;
  object-fit:contain;
  filter:drop-shadow(0 0 1px rgba(190,230,220,.40))
         drop-shadow(0 26px 40px rgba(0,0,0,.55))}

/* ────────────────── THE BOARD · the BeHive mark, built from cells ──────────
   The resting state of the card band's moment, and — being plain SVG — also its
   fallback with JS off, with WebGL refused and under reduced motion. The
   animated assembly is a second pass on top of this layer; this is what most
   viewers actually see, so it is built and judged first.

   THE CONSTRUCTION, which took four wrong turns to arrive at. Four rings of
   FLAT-TOP cells on the axial lattice form a super-shape that is itself a
   hexagon — POINTY-TOP, which is the mark's own orientation: the page's hexmark
   polygon is 28 wide by 32 tall, vertices top and bottom, vertical sides. So the
   cell orientation and the silhouette orientation are not in conflict; one
   produces the other for free, at every ring count:

       rings 2 -> 19 cells,  8.00r x  8.66r
       rings 3 -> 37 cells, 11.00r x 12.12r
       rings 4 -> 61 cells, 14.00r x 15.59r      <- this

   Everything before this used an ellipse, a density falloff, or the card's own
   rectangle as the boundary, which is why nothing resolved into a hexagon: it
   never was one. A ring around the card is not a hexagon of hexagons, however
   the cells are drawn.

   THE BOUNDARY IS COMPLETE, not ragged. The reference frames drop the odd corner
   cell, but they have no card across the middle — their whole silhouette is
   visible, so they can afford it. Here the card hides the centre and the outer
   ring is the ONLY thing carrying the hexagon, so every cell missing from it is
   a piece of shape the eye has to infer twice.

   CELL ANATOMY, measured off the reference (lattice vectors (0,84) and
   (72,+-41.5) give flat-top R=48.5; edge directions 0/60/120 confirm it):
   a chamfer ring #167E6B..#08645C lit from the upper left, an inner face inset
   28% of R running #A8F1D3 at its centre to #55BD96 at its edge, and ONE small
   near-white specular point #F2FDF9. Uniform: the same cell at every site, no
   jitter, no rotation, no per-cell opacity. Every earlier attempt added
   variation and every one of them read as noise — the variation WAS the mess.

   ONE symbol and 61 <use>. The alternative was a pre-rendered webp like the card
   asset, which would gloss better but could not scale, could not be recoloured,
   and would cost ~60KB of base64. This is 2.4KB of markup that is sharp at any
   density.

   THE GLOW is two drop-shadows on the svg, not an SVG filter over 61 shapes.
   drop-shadow blurs the union alpha, so the bloom takes the hexagon's own
   silhouette for one filter's cost. Radii follow the measured falloff:
   luminance 121 at the cluster edge, 54 at +40px, 40 at +90px. */
.stage-card{aspect-ratio:800/505;max-width:32rem}
.stage-card img{position:absolute;inset:0;width:100%;height:100%;
  object-fit:contain;
  filter:drop-shadow(0 0 1px rgba(190,230,220,.40))
         drop-shadow(0 26px 40px rgba(0,0,0,.55))}
/* The card is a black object on a BRIGHT ground now, which is the opposite of
   the problem the rim was added for. The 1px cool rim stays — it still traces
   the alpha edge — but the shadow underneath is what does the separating, and it
   is deepened here because the board behind is luminous. */
#card .stage-card img{filter:drop-shadow(0 0 1px rgba(220,245,238,.55))
         drop-shadow(0 18px 34px rgba(0,14,16,.62))
         drop-shadow(0 2px 6px rgba(0,14,16,.55))}

/* THE CARD MAKES ROOM FOR THE BOARD, and here it is not a preference. The board
   is a fixed ratio of the card box — 1.2895 wide and 2.2745 tall — so the card's
   width sets the board's height, and the BAND is what runs out first: at 445 the
   board would be 639px tall in a 599px band. The card therefore has to come down
   to about 380 for the construction to exist at all. Fluid, so there is no width
   where the card jumps size. */
#card .visual{padding-inline:clamp(20px,3.2vw,40px)}
/* AND THE SECTION GROWS, NOT THE VISUAL, in the two-column range. Padding on
   the visual was the obvious move and it did nothing: .split is align-items
   center and the COPY column is the taller of the two (343px against the
   visual's 304 at 1150), so the row height — and therefore the section, and
   therefore the room the board has above and below — is set by the station. Only
   padding on .split itself moves it. Tapers to nothing by 1330, where the wrap
   stops being viewport-wide and the band is tall enough on its own. */
#card .split{padding-block:clamp(0px,(1330px - 100vw) * .16,26px)}
/* The 20px of block padding is not decoration either. Between 1001 and 1279 the
   wrap is viewport-wide, so the section is shorter than at 1440 while the card
   and board are almost the same size, and the board's 63.7% overhang crossed the
   band edge into the seam by 10px at 1150. Padding on the visual grows the
   section symmetrically, which is the only thing that buys clearance at BOTH
   ends at once — so it tapers from 40px at the pinch to 20px once the wrap stops
   being viewport-wide and the band is tall enough on its own.

   Below 1001 the copy sits ABOVE the card, so the overhang lands on the
   compliance paragraph rather than on the band edge, and the requirement jumps
   from 20px to about 90. The section's own padding was never the constraint —
   the copy is. */
@media (max-width:1000px){
  #card .visual{padding-block:clamp(88px,11vw,104px)}
}

#card .cb-board{position:absolute;z-index:-3;pointer-events:none;
  left:-14.4735%;right:-14.4735%;top:-63.7247%;bottom:-63.7247%}
#card .cb-board svg{width:100%;height:100%;display:block;overflow:visible}
/* ELEVEN CELLS ARE LIT, NOT SIXTY-ONE. Measured against the card in the board's
   own units (the card is 1085.7 x 685.4 there): 20 cells are fully exposed, 16
   partly, and 25 sit behind the card. A fully lit board is therefore 25 bright
   cells nobody can see, and loud enough to take the band over. So the default
   cell is DIM — same geometry, dark fill, faint rim, enough to state the hexagon
   — and eleven are lit: both apexes, both side vertices (the widest points,
   where the silhouette is hardest to infer), and an asymmetric scatter across
   the flanks so it reads as incident light rather than as a rule.

   THE BLOOM IS ON THE LIT GROUP, NOT THE SVG. drop-shadow blurs the union ALPHA,
   not luminance, so a filter on the whole svg glows around the dim cells too and
   the distinction disappears. */
#card .cbg-lit{filter:drop-shadow(0 0 9px rgba(74,211,172,.42))
         drop-shadow(0 0 26px rgba(74,211,172,.26))}
#card .cbc-h{fill:url(#cbCham)}
#card .cbc-f{fill:url(#cbFace)}
#card .cbc-s{fill:url(#cbSpec)}
#card .cbd-h{fill:url(#cbDimC);stroke:rgba(140,228,200,.30);stroke-width:2.5}
#card .cbd-f{fill:#195E62}
/* Dust hovering over the board at rest, placed only where the card does not
   cover. The drift is 9s and 6px: any faster reads as flicker, and any larger
   and they stop looking like they are suspended in the same air as the board. */
#card .cbd{fill:#9FE9D0}
/* The base opacity is a custom property set per speck by build_board_svg.py, not
   a presentation attribute: the drift animates opacity as well as position, and a
   CSS animation overrides the attribute outright — every speck would twinkle
   between the same two values and the density variation that reads as depth
   would be gone. */
#card .cbg-dust circle{opacity:var(--do,.5)}
/* FOUR STOPS, NOT TWO. A two-stop drift is a straight line out and back, and 46
   specks all doing that at once read as a shimmer of one texture. The return leg
   goes somewhere else, and the twinkle is out of phase with the movement so a
   speck is rarely at its brightest and its furthest at the same moment.
   Every speck's period is different (4.4-8.4s, set per element) — a shared
   duration with only the delay varied still beats out a visible common rhythm.

   THE PATH IS 10x16px, NOT 4x7. At 4x7 over a 7-13s cycle a speck moved about a
   pixel a second, which is not motion — it is drift you can only prove with a
   pixel diff, and the resting board read as a still photograph on the page even
   though the GIF of it looked alive. The travel roughly doubled and the period
   roughly halved, so a speck now crosses its path in about three seconds. The
   twinkle floor came down to 0.26 as well: specks appearing and fading is more
   legible at this size than specks sliding, because 10px of travel on a 4px dot
   is still a small movement in a 600px band. */
@keyframes cbDrift{
  0%,100%{transform:translate(0,0);        opacity:calc(var(--do,.5) * .26)}
  28%    {                                 opacity:var(--do,.5)}
  50%    {transform:translate(10px,-16px)}
  74%    {transform:translate(-5px,-7px);  opacity:calc(var(--do,.5) * .72)}
}
#card .cbg-dust circle{animation:cbDrift var(--dp,9s) ease-in-out infinite;
  animation-delay:var(--dd,0ms)}

/* ── AT REST THE LIT CELLS BREATHE ────────────────────────────────────────────
   The board's resting state was completely static, so the moment the run ended
   the band died — 6.6 seconds of motion and then a photograph.

   THE DELAYS ARE NEGATIVE. They used to be positive, 0-3.6s, and that made the
   whole thing invisible: the breath starts when `.landing` comes off, so every
   cell sat at its 0% keyframe — full opacity — waiting out its delay, and the
   first dip arrived up to SEVEN SECONDS after the card had landed. Measured, all
   eleven cells were still at exactly 1.00 at +1680ms. A viewer watches the card
   settle, looks for the movement, and sees a still image. A negative delay starts
   the animation part-way through, so every cell is mid-cycle on its first frame.

   The 0%-is-full-opacity trick was there to hide the join, and it is given up
   here on purpose: a cell can now begin as dim as --pmin, a one-frame step. It is
   spent at the busiest frame in the sequence — the card is landing over the board
   at exactly that moment — and against 61 cells plus a card in motion it is not
   findable at 1440 or at 390. An invisible step beats a visible dead beat.

   OPACITY, NOT THE FILTER. The bloom is a drop-shadow on the parent group, and a
   drop-shadow is computed from the union alpha of its children — so dipping a
   cell's opacity dims its glow with it, for free, and only the eleven lit cells
   are ever touched. Animating the filter instead would re-run two blurs over the
   whole group on every frame for no extra effect.

   Period, phase and depth are all per cell (build_board_svg.py). Eleven cells on
   one clock is a status light blinking, which is the read this must never have. */
@keyframes cbBreathe{
  0%,100%{opacity:1}
  50%    {opacity:var(--pmin,.78)}
}
#card .cb-board:not(.landing) .cbg-lit use{
  animation:cbBreathe var(--pp,7s) ease-in-out infinite;
  animation-delay:var(--pd,0ms)}
@media (prefers-reduced-motion:reduce){
  #card .cbg-dust circle,
  #card .cb-board:not(.landing) .cbg-lit use{animation:none}
}

/* ─────────────── THE ASSEMBLY · cells land, they do not fade in ─────────────
   POLARITY, and it is the whole reason this is a class rather than a plain
   animation. `.landing` is added by script INSIDE the frame that starts drawing
   the particles, and removed again when the run ends. With no script, no WebGL
   or reduced motion the class is never added, so every cell sits at its natural
   full state — which is exactly the board that was verified static. There is no
   "hidden by default" state anywhere in this file.

   transform-box:fill-box is what makes it work at all. A CSS transform on an SVG
   <use> takes its origin from the whole viewBox by default, so `scale(.4)` pulls
   every cell toward the board's centre instead of shrinking it in place — the
   board collapsed into a knot at the middle on the first attempt. fill-box moves
   the origin to the element's own bbox.

   The stagger is set per cell in the style attribute by script, from the cell's
   own x: the board assembles left to right, in the direction the particles come
   from, so a cell lights up where its particles arrive. It is NOT random — a
   random order reads as a fade, not as an assembly.

   The overshoot is small (1.06) and short. The reference's cells arrive and stop;
   a big bounce would read as bubbles, which is the read the whole board spent
   four rounds escaping. */
@keyframes cbLand{
  0%   {opacity:0;transform:scale(.34) translateX(-16px)}
  62%  {opacity:1;transform:scale(1.06) translateX(0)}
  100% {opacity:1;transform:scale(1) translateX(0)}
}
#card .cb-board.landing use{
  transform-box:fill-box;transform-origin:50% 50%;
  animation:cbLand var(--cd,780ms) cubic-bezier(.2,.72,.3,1) both;
  animation-delay:var(--d,0ms)}
/* The dust does not exist until the board does — at rest it belongs to the
   board, so during the assembly it arrives with it rather than hovering over an
   empty band. */
#card .cb-board.landing .cbg-dust{animation:cbDustIn var(--bd,2960ms) ease-out both}
@keyframes cbDustIn{ 0%,45%{opacity:0} 100%{opacity:1} }
/* The bloom ramps with the cell count rather than being on from frame one — at
   full strength over an empty board it reads as a glowing hole before anything
   has arrived. It animates the FILTER, not the element's opacity: the glow is a
   drop-shadow on the svg, so fading the element would fade the cells with it and
   undo the landing animation. Ends on exactly the static rule's value, so the
   fill-mode hold and the no-class state are the same pixels. */
@keyframes cbBloom{
  0%   {filter:drop-shadow(0 0 0 rgba(74,211,172,0))
               drop-shadow(0 0 0 rgba(74,211,172,0))}
  100% {filter:drop-shadow(0 0 9px rgba(74,211,172,.42))
               drop-shadow(0 0 26px rgba(74,211,172,.26))}
}
#card .cb-board.landing .cbg-lit{animation:cbBloom var(--bd,2960ms) linear both}
@media (prefers-reduced-motion:reduce){
  #card .cb-board.landing use,#card .cb-board.landing .cbg-lit,
  #card .cb-board.landing .cbg-dust{animation:none}
}

/* Narrow keeps the same construction — the hexagon is the point, and a
   different silhouette on a phone would be a second motif. What changes is how
   much of it shows: at 390 the card is already most of the viewport, so a board
   at the desktop ratio would be 402px tall against a card of 177 and run into
   the seam. The ratio tightens to 1.16 x 1.72, which still leaves most of a cell
   at the widest point. */
/* STACKED KEEPS THE SAME CONSTRUCTION at a tighter ratio. The hexagon is the
   point, so a different silhouette below 1000 would be a second motif; what
   changes is how much of it shows. The board is 0.898 wide for 1 tall while the
   card is 1.584 wide for 1 — so the vertical overhang is always the large one,
   and stacked is exactly where there is no vertical room. 1.16 x 2.046 keeps the
   viewBox aspect (the pair must satisfy A/B = 0.5669 or the svg letterboxes and
   the hexagon comes out NARROWER than the card) and still shows most of a cell
   at the widest point. */
@media (max-width:1000px){
  #card .cb-board{left:-8%;right:-8%;top:-52.3%;bottom:-52.3%}
}

/* moment 3 · the close bloom. Full bleed over the closing depth, behind the
   copy. No static equivalent is needed: with no canvas the close is simply the
   close, which is how it reads today. */
.bloom{position:absolute;inset:0;z-index:1;pointer-events:none}
.bloom canvas{position:absolute;inset:0;width:100%;height:100%;opacity:0;
  transition:opacity 1.1s ease}
.bloom.gl-on canvas{opacity:1}

/* ═══ WHY BEHIVE — four cells ═══════════════════════════════════════════════
   The four reasons used to be four columns of a `.grid-4`, which at 1440 gave
   each one 261px for a ~100-character paragraph: a measure of roughly 26
   characters against a comfortable 45-75, with every headline wrapping to two
   lines. That — not a missing picture — is why the band read as thin text
   floating in space. It also ran four-across directly below "Inside the app",
   which is also four-across, so the two bands echoed each other.

   2x2 fixes all three: ~54 characters per line, bodies drop from four lines to
   two, the taller two-row block uses the 224px of padding the band was wasting,
   and the rhythm breaks against the band above.

   Everything here is scoped with #why. A media query adds NO specificity — this
   project has been bitten eight times — and an id outranks the `.zone-chamber
   .tile` rule these cells have to override. */
#why .grid-4{grid-template-columns:repeat(2,minmax(0,1fr));
  gap:clamp(16px,1.8vw,26px)}
#why .item{position:relative;overflow:hidden;border-radius:18px;
  padding:clamp(18px,2.2vw,32px);
  background:rgba(12,143,91,.045)}
#why .item .t-body{max-width:48ch}

/* The digits are gone from the MARKUP, not just hidden. They were being masked
   with `color:transparent;font-size:0`, which a screen reader ignores entirely —
   it still announced "1, Nothing to learn first". And 1-2-3-4 promised a sequence
   these four reasons do not have: nothing among them happens first or last.

   An SVG mark rather than the CSS-clipped `.tile`, because this one has to go
   from OUTLINE to FILLED. clip-path gives a silhouette with no stroke, so an
   outline would have meant faking a ring by stacking a smaller hexagon in the
   cell's own wash — a translucent colour composited over the band, which would
   need hard-coding and would break the moment either ground changed. A polygon
   has a real stroke and a real fill. */
#why .hexmark{display:block;width:28px;height:32px;margin-bottom:15px}
#why .hexmark svg{display:block;width:100%;height:100%}
#why .hm-o{fill:none;stroke:var(--green-ink);stroke-width:2}
#why .hm-f{fill:var(--green-ink)}

/* Default is FILLED — the finished state. With no JS, no IntersectionObserver,
   or reduced motion preferred, the marks are simply solid and nothing looks
   half-drawn. Script opts INTO the animation by adding .anim, which is the same
   polarity the WebGL moments use and the reason a failure here degrades to
   "no animation" rather than to "four empty outlines forever".

   The fill rises as a level rather than scaling: translate is on a rect clipped
   to the hexagon, so the boundary between empty and full stays horizontal, like
   a cell filling. Scaling the polygon instead would inflate a squashed hexagon,
   which reads as a shape appearing, not as something filling up.

   32px, not 100%: a percentage translate on an SVG child needs
   transform-box:fill-box to mean anything, and the box is a known 32 user units.

   The clip sits on a wrapping <g>, NOT on the rect. With it on the rect, a CSS
   transform moves the element together with its own clip, so what rose was the
   whole hexagon sliding up from below — the top edge of the fill came to a point
   instead of staying level, which read as a shape arriving rather than a cell
   filling. Clip the parent, move the child inside it. */
/* The digit sits knocked out of the solid hexagon and arrives AFTER its own
   cell has finished filling: fill, then 1; fill, then 2. The numbers came out
   earlier because 1-2-3-4 asserted a sequence the four reasons do not have —
   nothing among them happens first or last. They earn their place back here
   because the reveal itself now establishes that order, so the numbers describe
   something the viewer actually watches rather than inventing a hierarchy.

   They stay out of the accessible name: the whole mark carries aria-hidden, so
   a screen reader still reads "Nothing to learn first" and not "1, Nothing to
   learn first". That was the original fault with them. */
#why .hm-n{font:600 14px Poppins,system-ui,sans-serif;fill:#EAF3EE}

/* 760ms to fill, up from 620 — it was reading as a flick rather than as filling.
   The digit then takes 240ms, its delay set to the fill's duration so it starts
   exactly as the cell tops out. */
#why.anim .hm-f{transform:translateY(32px);
  transition:transform .76s cubic-bezier(.22,.61,.36,1)}
#why.anim .hm-n{opacity:0;transition:opacity .24s linear .76s}
#why.anim .item.on .hm-f{transform:translateY(0)}
#why.anim .item.on .hm-n{opacity:1}

/* Stagger in CSS off nth-child, so no JS batching is needed — and scoped to the
   widths where the cells actually arrive as a BATCH. At one column each cell
   crosses the threshold on its own scroll, and a global stagger would leave the
   fourth mark sitting empty for 2.5s after the reader had already reached it.
   That is the same fault the warp reveal had on mobile. Below 701px there is no
   batch to sequence, so there is no delay. */
@media (min-width:701px){
  #why.anim .item:nth-child(2) .hm-f{transition-delay:.82s}
  #why.anim .item:nth-child(2) .hm-n{transition-delay:1.58s}
  #why.anim .item:nth-child(3) .hm-f{transition-delay:1.64s}
  #why.anim .item:nth-child(3) .hm-n{transition-delay:2.40s}
  #why.anim .item:nth-child(4) .hm-f{transition-delay:2.46s}
  #why.anim .item:nth-child(4) .hm-n{transition-delay:3.22s}
}

/* MOBILE gets a different composition, not the same one reflowed. The stack was
   1103px of four near-identical blocks. Here the mark is promoted from a small
   badge to a large ghost cell-corner that bleeds off the card, and it alternates
   side down the run, so the scroll has a rhythm instead of four identical
   rectangles. Same element, different role per breakpoint.

   It sits BEHIND the text at 7% rather than reserving space beside it: padding
   wide enough to clear a 150px hexagon would have cut the mobile measure to
   ~24 characters, which is the very fault this change exists to fix. */
@media (max-width:820px){
  #why .item::before{content:"";position:absolute;
    width:clamp(120px,34vw,168px);aspect-ratio:1/1.108;
    bottom:-44px;
    clip-path:polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%);
    background:rgba(12,143,91,.07);pointer-events:none}
  #why .item:nth-child(odd)::before{right:-34px}
  #why .item:nth-child(even)::before{left:-34px}
  #why .item>*{position:relative}
}
@media (max-width:700px){
  #why .grid-4{gap:14px}
  #why .item{padding:18px 20px}
}

@media (max-width:1000px){
  /* ── CENTRED COPY BELOW 1000 ──────────────────────────────────────────────
     Every two-column layout on this page collapses at 1000, and once it does
     the copy is a block of its own with a centred visual above or below it.
     Left-aligned text under a centred image is the same two-axis problem that
     made #what read as "heavier on the left" on desktop — it just shows up
     everywhere at once on a phone.

     WHAT IS CENTRED: the section copy (.station — eyebrow, heading, lede), the
     caption under each phone mockup, the screens disclaimer, and the Bercor
     quote. All of these are free-standing text under or over a centred object.

     WHAT IS DELIBERATELY LEFT ALONE, because centring text inside a bounded
     object weakens it rather than tidying it:
       · #why's four cards — each has its own padded box and its own left edge
       · .faq-row — the label is paired with a +/- control on the right, and
         centring the label floats it away from the thing it operates
       · #join's three points — the hexagon tile anchors the left edge there
         (see the <=700 block), so centring the text would break that pairing

     Say the word if any of those three should follow the rest.

     The max-widths are what make this need more than text-align: .t-lede is
     56ch, .t-body 38ch, .q p 30ch — a capped block still sits hard left unless
     its own margins are auto'd too. And .badges (27rem) and .rally
     (width:fit-content) are flex boxes that ignore text-align entirely; the
     .close-mid rules above already solved this for the closing band and these
     mirror them. Badges are centred, never stretched — the standing rule. */
  .depth .station{text-align:center}
  .depth .station .t-lede,
  .depth .station .t-body,
  /* .t-micro was the miss. It is capped at 48ch inline on the card band, so
     text-align:center centred the WORDS inside a 384px box that was itself
     still hard left — at 768 the box ended at x=422 of 768 and the paragraph
     read as left-aligned under centred copy. A capped block needs its own
     margins auto'd; text-align cannot move the box. */
  .depth .station .t-micro{margin-inline:auto}
  .depth .station .badges{justify-content:center;margin-inline:auto}
  .depth .station .rally{margin-inline:auto}

  #insideRail > div .t-feature,
  #insideRail > div .t-body{text-align:center}
  #insideRail > div .t-body{margin-inline:auto}
  #inside > .wrap > .t-micro{text-align:center}

  #bercor .q{text-align:center}
  #bercor .q p{margin-inline:auto}
  #bercor .q footer{justify-content:center;margin-inline:auto}
  #bercor .attrib{justify-content:center}

  .station{max-width:none}
  .split{grid-template-columns:1fr}
  /* THE CAP NEEDS AUTO MARGINS OR IT IS JUST A LEFT-ALIGNED VISUAL. Only
     .depth.open carried margin-inline:auto, so the hero and the close centred
     and the CARD BAND did not — #card is a plain .depth. It was invisible at 390
     because 22rem exceeds the grid cell there, so the visual filled the row and
     looked centred by accident; at 768 the cell is 691px and a 352px visual sat
     340px off centre. Centre it on the generic rule, not per-section, so the
     next collapsed .split inherits the fix instead of repeating the bug. */
  /* width:100% IS LOAD-BEARING, do not trim it. Auto margins on a grid item
     stop it stretching to the track, so it falls back to fit-content — and
     .stage-card sizes itself from aspect-ratio with no intrinsic width, so the
     visual collapsed to 49px and the card and board to ZERO. The existing
     .depth.open rule had width:100% for exactly this reason; the generic rule
     needs it too. */
  .split .visual{max-width:22rem;width:100%;margin-inline:auto}
  /* MOBILE HERO. The mark-as-ground experiment is gone. Pushing the visual
     behind the headline at 40% under a scrim cost it so much contrast that the
     phone hero read as having no visual at all — and it also moved the box the
     shader measures to place the stage, so the swarm no longer landed where
     the fallback did.

     It gets its own space below the copy instead. The point shader already
     knows how to behave here: on narrow widths v_side keeps the swarm BELOW
     the copy rather than beside it, so a full-bleed canvas and a stacked
     layout do not fight each other. Hero padding is tightened so a real amount
     of the visual sits above the fold rather than a sliver of it. */
  .depth.open{padding-block:clamp(48px,6vw,80px) clamp(24px,4vw,40px)}
  .depth.open .split{gap:clamp(14px,3vw,26px)}
  .depth.open .split .visual{max-width:22rem;width:100%;margin-inline:auto}
  /* A PHONE MOCKUP MUST FIT IN ONE SCREEN, WITH ITS CAPTION. Measured at 390:
     each .phone came out 350 x 758 on an 844px viewport — 90% of the screen for
     one mockup before its own text, so reading a single item meant scrolling
     through it. The rail is 3,482px tall for four of them.

     Width-driven rather than height-driven on purpose: aspect-ratio derives the
     height from the width reliably, whereas width:auto on a block in a grid
     stretches to the container and the ratio never gets to apply. 62vw keeps it
     proportional on a 360 handset (223px wide) and the 240px cap stops a tablet
     in the same breakpoint blowing it back up. At 390 that is 240 x 520 — the
     mockup and its caption inside one screen. */
  #insideRail .phone{width:min(240px,62vw);margin-inline:auto}
  .grid-4{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (max-width:700px){
  .grid-3,.grid-4{grid-template-columns:1fr}
  /* THE CLOSING THREE POINTS GO ICON-BESIDE-TEXT ON NARROW. Measured at 390,
     each point was 151px tall — tile 51, its 16px margin, heading 26, 9, body
     49 — so three of them plus gaps came to 473px inside a 1034px band, on an
     844px screen. Nearly half the closing section was three short sentences.

     The tile is what costs it: on its own line it adds 67px per point and says
     nothing the heading does not. Beside the text it still marks the item and
     the point collapses to the height of its own words. Explicit grid placement
     rather than auto-flow — with three children and two columns, auto-placement
     puts the body text UNDERNEATH the tile in column 1 rather than beside it.

     THIS IS THE <=700 BLOCK, not the <=1000 one, and that is deliberate: 700 is
     exactly where .grid-3 collapses to a single column. Between 700 and 1000 the
     three still sit side by side, and a stacked tile above a heading is correct
     in a column that narrow. Moving these rules up to the 1000 block would
     flatten the tablet layout for no reason. */
  #join .grid-3{gap:16px}
  #join .grid-3 .item{display:grid;
    grid-template-columns:38px minmax(0,1fr);
    column-gap:14px;align-items:start}
  #join .grid-3 .item .tile{grid-column:1;grid-row:1 / span 2;
    width:38px;height:42px;margin-bottom:0;font-size:.82rem;align-self:start}
  #join .grid-3 .item .t-feature{grid-column:2;grid-row:1;margin-bottom:5px}
  #join .grid-3 .item .t-body{grid-column:2;grid-row:2}
  /* The why band's 2x2 is set with an id, so it outranks the line above and
     stayed two-up at 390 — two 168px columns, a 17-character measure, which is
     worse than the fault the 2x2 was introduced to fix. Restated at the same
     specificity here. A media query adds none; that is the ninth time. */
  #why .grid-4{grid-template-columns:1fr}
  .seam{height:130px}
  .field{opacity:.72}
}
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  .faq-row i{transition:none}
}

/* ───────────────────────────── THE FOOTER ─────────────────────────────────
   This page had NO footer at all — `</main>` ran straight into the script tag.
   That is not a missing nicety: the not-a-bank disclaimer is a standing
   requirement and it appeared nowhere on the page. Ported from the signed-off
   page, whose own note records that the volatility sentence was moved here
   precisely because its only other home was a collapsed FAQ panel that JS-off
   users could not open.

   Background is --d8, not --navy. The closing zone already ends at --d8, and
   --navy (#0A202B) is LIGHTER than that, so a navy footer would step the page
   back up in tone at the very bottom and read as a mistake. The hairline does
   the separating instead.

   The legal URLs are still [TBD] — marked in the markup, one comment each, so
   they cannot be mistaken for finished links at handover. */
/* .site-foot, NOT a bare `footer` selector. The executive quote's attribution
   is marked up as <footer> inside its <blockquote>, so a bare rule painted a
   --d8 plate behind black text in the middle of a LIGHT band — contrast
   reported 'Name to come' at 1.10:1 and it was real. Same family as the
   .cell/.cf collision already in the notes; an element selector can collide
   just as silently as a class. */
/* position:relative and a z-index on the content, because the footer now hosts
   a .field. The lattice used to stop dead at the border-top above: textured
   ground on one side of a 1px hairline and flat ground on the other, which read
   as the page ending twice. The two grounds are the SAME colour — .zone-return's
   gradient lands exactly on --d8 — so the only thing making that line a seam was
   the texture quitting at it. Carrying the field across and fading it out inside
   the footer turns the hairline back into what it is: a rule under the ask. */
/* NO border-top. It was there to separate the footer from the ask when the two
   were flat ground meeting flat ground. They are the same colour — .zone-return's
   gradient lands exactly on --d8 — and the comb field now runs across the join
   and fades out inside the footer, so the texture already carries the transition.
   The rule was the last thing making a seam out of a continuation. */
.site-foot{background:var(--d8);color:#fff;position:relative;
  padding-block:clamp(48px,6vw,72px) 34px}
.site-foot > .wrap{position:relative;z-index:1}
.foot-grid{display:grid;
  grid-template-columns:minmax(0,1.5fr) repeat(3,minmax(0,1fr));
  gap:clamp(28px,4vw,48px)}
.logo-foot{height:62px;width:auto;display:block}
.foot-brand p{color:var(--lit-40);font-size:.9rem;margin:18px 0 0;max-width:34ch}
.byline{display:inline-flex;align-items:center;gap:9px;margin-top:18px!important;
  font-size:.8rem;color:var(--lit-40)}
.bar{width:1px;height:15px;background:rgba(255,255,255,.24)}
.foot-col h4{font-size:.74rem;letter-spacing:.15em;text-transform:uppercase;
  color:var(--teal);margin:0 0 16px;font-weight:600}
.foot-col ul{list-style:none;margin:0;padding:0}
.foot-col li+li{margin-top:11px}
.foot-col a{text-decoration:none;font-size:.9rem;color:var(--lit-40);
  transition:color .16s}
.foot-col a:hover,.foot-col a:focus-visible{color:#fff}
.disclaimer{margin-top:clamp(38px,5vw,56px);padding-top:26px;
  border-top:1px solid rgba(255,255,255,.11);font-size:.78rem;line-height:1.65;
  color:rgba(255,255,255,.62);max-width:96ch}
.legal{margin-top:20px;display:flex;flex-wrap:wrap;gap:8px 20px;
  justify-content:space-between;font-size:.78rem;color:rgba(255,255,255,.52)}
@media (max-width:820px){
  .foot-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (max-width:560px){
  .foot-grid{grid-template-columns:1fr}
  .legal{flex-direction:column}
}
