/* ForgeBoard — Defence Forge Industries
   Ported from the ForgeBoard v2 design. Tokens and type scale are the design's. */

:root{
  /* One ground for the whole site. The page was cream with dark panels cut
     into it; it is now charcoal throughout, so `.print` sections and ordinary
     ones are the same surface and nothing bands across a page break. */
  --bg:#141210;
  --surface:#221e1a;
  --ink:#efe6d3;
  --muted:#bfae8d;
  --line:#3a332c;
  --brand:#f39a00;
  --brand-ink:#f7b545;
  /* Amber is bright: text sitting on it stays dark whatever the page does. */
  --on-brand:#141210;
  /* The page ground, as one value, so anything that has to paint its own
     opaque patch of it carries the fabric too. A flat colour cannot pass for
     this: the weave runs #1f..#4f, and a flat patch at its mean still reads
     as a panel sitting on top. The tile is noise, so a patch whose tiling
     does not line up with the body's is still indistinguishable. */
  --page-bg:
    linear-gradient(rgba(0,0,0,.22),rgba(0,0,0,.22)),
    var(--print-bg) url(../assets/fabric-black-tile.webp) center/520px repeat;
  --print-bg:#1a1714;
  --print-ink:#efe6d3;
  --print-muted:#bfae8d;
  --ease:cubic-bezier(.2,.7,.2,1);
  --pad:40px;
  --maxw:1400px;
  --rail:200px;
  --sect:clamp(96px,12vw,180px);
}

*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0;
  /* The same ground as a .print panel — colour, fabric and all — so an inner
     page and the home page are the same surface, and a .print section sits on
     the page without a seam. */
  background:var(--page-bg);
  color:var(--ink);
  font-family:Archivo,"Helvetica Neue",Helvetica,Arial,sans-serif;
  -webkit-font-smoothing:antialiased;
  text-wrap:pretty;
  font-size:16px;
  line-height:1.5;
  overflow-x:hidden;
}
a{color:inherit;text-decoration:none;-webkit-tap-highlight-color:transparent}
a:hover{color:var(--brand-ink)}
h1,h2,h3,p,ul,ol,dl,dd,figure{margin:0}
ul{padding:0;list-style:none}
h1,h2,h3{text-wrap:balance}
img{max-width:100%}
button,input,textarea,select{font:inherit;color:inherit}
button{background:none;border:0;padding:0;cursor:pointer}
:focus-visible{outline:2px solid var(--ink);outline-offset:3px}
/* amber only reaches 1.79:1 on the cream ground; keep it for the dark panels,
   where it is 8:1, and use ink on light where it is 15:1 */
.print :focus-visible,.site-head :focus-visible{outline-color:var(--brand)}
::selection{background:var(--brand);color:var(--on-brand)}
table td,table th{font-variant-numeric:tabular-nums}
::placeholder{color:var(--muted);opacity:1}

/* ---------- dark "print" panels ---------- */
.print{
  position:relative;
  isolation:isolate;
  background:var(--print-bg) url(../assets/fabric-black-tile.webp) center/520px repeat;
  color:var(--print-ink);
}
.print::before{
  content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background:rgba(0,0,0,.22);
}
.print>*{position:relative;z-index:1}
.print a:not(.btn):hover{color:#fff}
.print .link-ul:hover,.print a:not(.btn):hover{color:#fff}

/* ---------- motion primitives ---------- */
/* Sections arrive rather than appear: up, and out of a slight blur, so the
   whole page down the scroll carries the hero's low-to-high language. */
.js .rv{
  opacity:0;transform:translateY(38px) scale(.99);filter:blur(5px);
  transition:opacity .9s var(--ease),transform 1s var(--ease),filter .9s var(--ease);
}
.rv[data-in]{opacity:1;transform:none;filter:blur(0)}

/* ---------- scroll-driven motion ----------
   Where the browser supports it, the reveals stop being a one-shot trigger
   and become genuinely scroll-linked: their progress is the scroll's, so
   they move exactly as fast as you do and run on the compositor rather than
   competing with it.

   Only transform and opacity are animated here. Anything that triggers
   layout or paint — width, filter, blur — stays out of a scroll timeline,
   which is why the blur belongs to the IntersectionObserver path below and
   not to this one.

   Firefox still has this behind a flag, so the observer version stays as the
   baseline for everyone else; this block simply takes over when it can. */
@supports (animation-timeline: view()) {
  .js .rv{
    /* the animation owns these now — the observer's data-in is harmless */
    opacity:1;transform:none;filter:none;
    animation:rv-rise linear both;
    animation-timeline:view();
    animation-range:entry 5% entry 60%;
  }
  @keyframes rv-rise{
    from{opacity:0;transform:translateY(40px) scale(.985)}
    to{opacity:1;transform:none}
  }

  /* The hero hands the page over as it leaves rather than just scrolling off. */
  .hero-scene{
    animation:hero-leave linear both;
    animation-timeline:view();
    animation-range:exit 0% exit 90%;
  }
  @keyframes hero-leave{
    from{opacity:1;transform:none}
    to{opacity:.12;transform:translateY(-7%)}
  }

  /* The video used to drift through its frame on scroll, scaled to 110% so
     no edge showed. A scroll-linked transform on a playing 720p video means
     every scroll tick re-composites the whole frame at 110%, and on an
     integrated GPU that is the stutter people saw. The video now sits still
     and plays; the section around it still moves. */

  /* Nothing scroll-linked for anyone who asked for no motion. */
  @media (prefers-reduced-motion:reduce){
    .js .rv,.hero-scene{animation:none}
    .js .rv{opacity:1;transform:none}
  }
}

.wd{display:inline-block}
.js .wd{opacity:.18;transition:opacity .6s ease}
.wd[data-in]{opacity:1}

@keyframes marquee{from{transform:translateX(0)}to{transform:translateX(-50%)}}
@keyframes pulse{0%,100%{opacity:.35}50%{opacity:1}}

@media (prefers-reduced-motion:reduce){
  /* Reveals must land in their finished state, not just stop moving. */
  .js .rv,.js .wd,.rv,.wd{transition:none!important;opacity:1!important;transform:none!important;filter:none!important}
  /* Everything else: no animation, no transition, no smooth scrolling.
     Covers the pulsing dots, the marquee, and every hover transform. */
  *,*::before,*::after{
    animation-duration:.01ms!important;
    animation-iteration-count:1!important;
    transition-duration:.01ms!important;
    transition-delay:0ms!important;
    animation-delay:0ms!important;
    scroll-behavior:auto!important;
  }
}


/* Cross-document page transitions. The reduced-motion rule must come
   AFTER this one: the last matching @view-transition wins. */
@view-transition{navigation:auto}
@media (prefers-reduced-motion:reduce){@view-transition{navigation:none}}

/* ---------- type ---------- */
.eyebrow{
  font-family:"Geist Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:12px;letter-spacing:.14em;text-transform:uppercase;
}
.h-xl{font-size:clamp(44px,6vw,96px);line-height:.95;font-weight:800;font-stretch:112%;letter-spacing:-.04em}
.h-pg{font-size:clamp(56px,8vw,140px);line-height:.9;font-weight:800;font-stretch:112%;letter-spacing:-.045em}
.lede{font-size:20px;color:var(--muted);max-width:52ch;line-height:1.45}

/* ---------- utilities ---------- */
.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;
}
.skip{
  position:absolute;left:-9999px;top:0;z-index:100;
  background:var(--ink);color:var(--bg);padding:12px 16px;border-radius:0 0 4px 0;
}
.skip:focus{left:0;color:var(--bg)}

/* ---------- layout helpers ---------- */
.wrap{max-width:var(--maxw);margin:0 auto}
.rail{display:grid;grid-template-columns:var(--rail) 1fr;gap:40px}
.rail>*{min-width:0}
.rail-end{align-items:end}
.sect{padding:var(--sect) var(--pad) 0}
.page{max-width:var(--maxw);margin:0 auto;padding:160px var(--pad) 140px}
.stack{display:flex;flex-direction:column}
.mono-muted{color:var(--muted)}

/* ---------- header ----------
   The page is one charcoal ground from the hero to the footer, so the bar has
   one appearance and keeps it. It used to swap between a cream treatment and a
   dark one depending on whether a `.print` panel was under it; on a site that
   is dark throughout that only ever fired as a flash of the wrong colour —
   cream text on a cream slab — so the swap is gone. What is left is depth: the
   bar is part of the page at the top and lifts off it once the page moves. */
.site-head{
  position:fixed;top:0;left:0;right:0;z-index:30;
  color:var(--print-ink);
  padding:16px var(--pad);
  display:flex;align-items:center;justify-content:space-between;gap:24px;
  transition:background .45s var(--ease),border-color .45s var(--ease),
             box-shadow .45s var(--ease),padding .45s var(--ease);
}
/* Once the page moves the bar needs a ground of its own, or the nav runs into
   whatever is scrolling under it. The same charcoal, a little denser and
   blurred, with one hairline: it settles onto the page rather than changing
   colour on it. It condenses slightly at the same time, which is the part you
   actually read as "the page has moved". */
.site-head.scrolled{
  background:rgba(20,18,16,.72);
  backdrop-filter:saturate(1.25) blur(14px);
  -webkit-backdrop-filter:saturate(1.25) blur(14px);
  border-bottom:1px solid rgba(239,230,211,.10);
  box-shadow:0 10px 30px -18px rgba(0,0,0,.9);
  padding-top:11px;padding-bottom:11px;
}
/* No backdrop-filter (Firefox with it off, older Safari): lean on opacity
   instead, so the bar is still a ground and not a see-through strip. */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .site-head.scrolled{background:rgba(20,18,16,.94)}
}
.site-head a:hover{color:var(--brand-ink)}

.brand{
  font-family:Archivo,sans-serif;font-stretch:118%;font-weight:800;
  font-size:17px;letter-spacing:-.02em;text-transform:none;
  display:inline-flex;align-items:center;gap:10px;
}
.brand .mark{width:30px;height:auto;display:block;flex:none}

.site-nav{display:flex;gap:28px}
.site-nav a{opacity:.7}
.site-nav a:hover{opacity:1}
.site-nav a[aria-current]{opacity:1;text-decoration:underline;text-underline-offset:6px}
.order-link{display:inline-flex;align-items:center;gap:8px}
.order-dot{width:8px;height:8px;border-radius:50%;background:currentColor;animation:pulse 2.4s ease-in-out infinite}
.nav-toggle{display:none}
.site-head.menu-open{background:var(--print-bg);color:var(--print-ink)}

/* ---------- home hero ---------- */
.hero{
  position:relative;min-height:100vh;min-height:100svh;overflow:hidden;
  display:flex;flex-direction:column;align-items:center;justify-content:flex-end;
  gap:clamp(12px,2.2vh,26px);padding:112px var(--pad) 56px;
}
.hero-meta{
  position:absolute;top:96px;left:var(--pad);right:var(--pad);
  display:flex;justify-content:space-between;gap:24px;color:var(--print-muted);z-index:3;
}

/* ---------- the stage: the whole page ----------
   One canvas across the hero, with the four links held over the boards at
   their projected positions. No frame, no ring, nothing drawn behind them —
   the boards and the dark are the composition. */
.hero-scene{position:absolute;inset:0;z-index:1}
/* The boards stand on the same ground as the rest of the page. There used
   to be a vignette here — a pool of light in the middle, the corners falling
   to 70% black — and its lower edge met the untouched fabric of the next
   section as a visible step. The hero is one piece of the page, not a darker
   panel let into it, so nothing is painted here. */
.hero-scene::before{
  content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
}
.hero-canvas{position:absolute;inset:0;width:100%;height:100%;display:block}
/* The foot of the stage falls into shadow, so the name always has ground to
   sit on and no board is ever read through. */
.hero-scene::after{
  content:"";position:absolute;left:0;right:0;bottom:0;height:32%;z-index:3;pointer-events:none;
  /* Shadow only, never a flat colour: a solid fill here sat lighter than the
     textured ground and drew a line across the page where the hero ended. */
  background:linear-gradient(to top,rgba(0,0,0,.42) 6%,rgba(0,0,0,.22) 44%,transparent 100%);
  opacity:0;transition:opacity .45s var(--ease);
}
.hero.is-showing .hero-scene::after{opacity:1}
@media (max-width:860px){.hero-scene::after{display:none}}   /* the plate sits below the scene on a phone; the shadow only dimmed the board */

.orbit{position:absolute;inset:0;z-index:2;margin:0;padding:0;list-style:none}
.orbit-item{
  position:absolute;left:0;top:0;transform-origin:0 0;
  will-change:transform;
}
.orbit-item a{display:block;width:100%;height:100%;cursor:pointer}
.orbit-item img{
  display:block;width:100%;height:100%;object-fit:contain;
  filter:drop-shadow(0 40px 60px rgba(0,0,0,.55));
  transition:filter .6s var(--ease);
}
/* Once a board's model is up, its flat render steps aside and the link
   becomes a plain hit area over the real thing. */
.orbit-item.has-model img{visibility:hidden}
/* On anything that could run the models the flat renders are never shown —
   not while loading and not as a fallback. They are the phone's hero only. */
@media (min-width:861px){.orbit-item img{display:none}}
/* Only the CSS fallback dims with a filter; in 3D the materials do it. */
.orbit:not(.is-3d).has-focus .orbit-item:not(.is-focus) img{filter:blur(2.5px) brightness(.65)}

/* ---------- board name plate ---------- */
/* Below the boards, in the order the research says a first-time visitor
   reads them: what it is, what it does, what to do about it, why to trust
   it. Everything here has to clear the fold with the boards. */
.hero-plate{
  position:relative;z-index:4;
  display:flex;flex-direction:column;align-items:center;gap:10px;text-align:center;
  /* Nothing but the boards until you point at one. The plate then comes up
     the way a panel powers on — out of focus and nearly dark, resolving to
     sharp and fully lit. Low intensity to high, not a fade. */
  opacity:0;pointer-events:none;
  transform:translateY(18px) scale(.972);
  filter:blur(7px) brightness(.28) saturate(.5);
  transition:opacity .35s var(--ease),transform .55s var(--ease),filter .62s var(--ease);
}
.hero.is-showing .hero-plate{
  opacity:1;pointer-events:auto;
  transform:none;
  filter:blur(0) brightness(1) saturate(1);
}
.hero-id{display:flex;flex-direction:column;align-items:center;gap:8px}
.hero-cta{display:flex;flex-wrap:wrap;justify-content:center;gap:10px}
.hero-cta .btn{height:46px;font-size:14px;justify-content:center}
.btn-quiet{border:1px solid rgba(239,230,211,.34);color:var(--print-ink)}
.print .btn-quiet:hover{border-color:var(--print-ink);color:var(--print-ink)}
.hero-trust{margin-top:0;color:var(--print-muted);opacity:.78}
.hero-plate-name{
  display:flex;align-items:baseline;gap:.4em;
  font-size:clamp(24px,2.3vw,36px);font-weight:800;font-stretch:112%;letter-spacing:-.035em;line-height:1;
  /* the name rises up out of the line once the stage is showing — the same
     move the section headings make, so the hero reads as their first line */
  overflow:hidden;padding-bottom:.12em;margin-bottom:-.12em;
}
.hero-plate-name b{
  display:inline-block;transform:translateY(118%);
  transition:transform 1.05s cubic-bezier(.16,1,.3,1) .12s;
}
.hero.is-showing .hero-plate-name b{transform:none}
/* Touch has no hover to hold a board, so the plate is always up and the
   name with it — nothing there ever sets is-showing. */
.hero-plate-name .sup{font-size:.42em;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--print-muted)}
.hero-plate-spec{color:var(--print-muted)}

/* Specs as an instrument readout: the figure first, big, with its label
   underneath. A visitor who only ever looks at the hero still leaves knowing
   the clock, the pin count and the radio. */
.hero-specs{
  position:relative;
  display:flex;flex-wrap:wrap;justify-content:center;gap:0;margin:0;
  /* A light runs across the strip as it powers up, then leaves. */
  background:linear-gradient(100deg,
    transparent 42%, rgba(239,230,211,.2) 50%, transparent 58%) no-repeat;
  background-size:260% 100%;
  background-position:135% 0;
}
.hero.is-showing .hero-specs{
  background-position:-35% 0;
  transition:background-position 1.15s var(--ease) .1s;
}
/* The two rules draw themselves out from the centre as the readout comes up. */
.hero-specs::before,.hero-specs::after{
  content:"";position:absolute;left:0;right:0;height:1px;
  background:rgba(239,230,211,.16);
  transform:scaleX(0);transition:transform .55s var(--ease);
}
.hero-specs::before{top:0}
.hero-specs::after{bottom:0}
.hero.is-showing .hero-specs::before,
.hero.is-showing .hero-specs::after{transform:scaleX(1)}
.hero-specs>div{
  display:flex;flex-direction:column;align-items:center;gap:1px;
  padding:8px clamp(14px,1.8vw,26px);
}
.hero-specs>div+div{border-left:1px solid rgba(239,230,211,.14)}
/* Each figure rises in its own turn, left to right, so the readout reads as
   four separate measurements landing rather than one block appearing. */
.hero-specs>div{opacity:0;transform:translateY(12px)}
.hero.is-showing .hero-specs>div{
  opacity:1;transform:none;
  transition:opacity .5s var(--ease),transform .5s var(--ease);
}
.hero.is-showing .hero-specs>div:nth-child(1){transition-delay:.06s}
.hero.is-showing .hero-specs>div:nth-child(2){transition-delay:.13s}
.hero.is-showing .hero-specs>div:nth-child(3){transition-delay:.2s}
.hero.is-showing .hero-specs>div:nth-child(4){transition-delay:.27s}
.hero-specs dd{font-variant-numeric:tabular-nums}

/* The buttons follow the figures in. */
.hero-cta,.hero-trust{opacity:0;transform:translateY(10px)}
.hero.is-showing .hero-cta,
.hero.is-showing .hero-trust{
  opacity:1;transform:none;
  transition:opacity .5s var(--ease),transform .5s var(--ease);
}
.hero.is-showing .hero-cta{transition-delay:.36s}
.hero.is-showing .hero-trust{transition-delay:.42s}

/* Touch used to get the whole plate from the start, since nothing could be
   hovered. It now works as the desktop does: the boards have the stage, and
   a tap on one brings it forward and powers its plate up — the tap being the
   phone's hover. A second tap on the board opens its page. */
/* The swap, on touch: the changing parts dip out together, the words change
   while nothing can be seen, and the plate powers up for the new board —
   name, then the four figures one after another, then the button, each
   rising a few pixels into place; the light crosses the strip and the two
   rules draw themselves out again. Transforms and opacity only, so the
   plate's geometry never moves under the eye. */
.hero-plate-name b,.hero-specs dd,.hero-cta .btn-brand{transition:opacity .19s var(--ease)}
.hero-plate.is-swapping .hero-plate-name b,
.hero-plate.is-swapping .hero-specs dd,
.hero-plate.is-swapping .hero-cta .btn-brand{opacity:0}
@keyframes plate-in{from{opacity:0;transform:translateY(9px)}to{opacity:1;transform:none}}
@keyframes plate-sweep{from{background-position:135% 0}to{background-position:-35% 0}}
@keyframes rule-draw{from{transform:scaleX(0)}to{transform:scaleX(1)}}
.hero-plate.is-in .hero-plate-name b{animation:plate-in .55s var(--ease) both}
.hero-plate.is-in .hero-specs dd{animation:plate-in .55s var(--ease) both}
.hero-plate.is-in .hero-specs>div:nth-child(1) dd{animation-delay:.08s}
.hero-plate.is-in .hero-specs>div:nth-child(2) dd{animation-delay:.15s}
.hero-plate.is-in .hero-specs>div:nth-child(3) dd{animation-delay:.22s}
.hero-plate.is-in .hero-specs>div:nth-child(4) dd{animation-delay:.29s}
.hero-plate.is-in .hero-cta .btn-brand{animation:plate-in .55s var(--ease) .34s both}
.hero-plate.is-in .hero-specs{animation:plate-sweep 1.1s var(--ease) .05s both}
.hero-plate.is-in .hero-specs::before,.hero-plate.is-in .hero-specs::after{animation:rule-draw .55s var(--ease) both}
/* The readout on touch, character by character: each letter an inline
   block with its final width from the first frame — the box never moves —
   rising in on its own beat; the figures lit amber, cooling to ink. Words
   stay whole so a figure wraps where it always did. */
.hero-plate .w{display:inline-block;white-space:nowrap}
.hero-plate .ch{display:inline-block}
.hero-plate.is-lit.is-in .hero-plate-name b,
.hero-plate.is-lit.is-in .hero-specs dd{animation:none}
/* the table itself holds still: no rules redrawing, no light crossing it — the text is what refreshes */
.hero-plate.is-lit.is-in .hero-specs,
.hero-plate.is-lit.is-in .hero-specs::before,
.hero-plate.is-lit.is-in .hero-specs::after{animation:none}
.hero-plate.is-lit.is-in .ch{
  animation:ch-rise .5s cubic-bezier(.2,.85,.25,1.15) both;
  animation-delay:calc(var(--d,0ms) + var(--i) * 22ms);
}
.hero-plate.is-lit.is-in .hero-specs dd .ch{animation-name:ch-lit;animation-timing-function:cubic-bezier(.22,1,.36,1)}
@keyframes ch-rise{from{opacity:0;transform:translateY(.55em)}to{opacity:1;transform:none}}
@keyframes ch-lit{
  from{opacity:0;transform:translateY(.6em);color:var(--brand-ink);text-shadow:0 0 12px rgba(243,154,0,.8)}
  40%{opacity:1;transform:none;color:var(--brand-ink);text-shadow:0 0 10px rgba(243,154,0,.6)}
  to{opacity:1;transform:none;color:var(--print-ink);text-shadow:0 0 0 rgba(243,154,0,0)}
}
@media (prefers-reduced-motion:reduce){
  .hero-plate.is-in *,.hero-plate.is-in .hero-specs::before,.hero-plate.is-in .hero-specs::after{animation:none!important}
}

/* The plate is always up and reads the board at the front: as the ring
   turns, its name and figures follow — on every screen. A click or tap
   still holds a board, and a second one opens its page. */
.hero-plate,.hero-specs>div,.hero-cta,.hero-trust{opacity:1;transform:none;pointer-events:auto}
.hero-plate{filter:none}
.hero-plate-name b{transform:none;transition:none}
.hero-specs{background-position:-35% 0}
.hero-specs::before,.hero-specs::after{transform:scaleX(1)}

/* And the stage warms under the board being looked at. */
.hero-scene::before{transition:opacity .6s var(--ease),filter .6s var(--ease)}
.hero.is-showing .hero-scene::before{filter:brightness(1.45)}
.hero-specs dd{
  margin:0;font-size:clamp(15px,1.35vw,20px);font-weight:700;letter-spacing:-.02em;
  color:var(--print-ink);white-space:nowrap;
}
.hero-specs dt{color:var(--print-muted);opacity:.8}

/* Loader. The whole screen, in the page's own ground, with the count large
   in the middle and a hairline filling beneath it — the wait is a held
   breath rather than a blank. When the models are in, the sheet lifts
   straight up off the page and the boards are already there under it.

   It is in the markup from the first byte (`.hero-scene` ships with
   `is-loading`), so there is no flash of empty hero before the script
   arrives. The 2D path and phones take it off at once: they have nothing to
   count. */
.hero-load{
  position:fixed;inset:0;z-index:80;pointer-events:none;
  display:none;flex-direction:column;align-items:center;justify-content:center;gap:22px;
  background:var(--page-bg);
  transition:transform .9s cubic-bezier(.76,0,.24,1);
}
.js .hero-scene.is-loading .hero-load,.js .hero-load.is-done{display:flex}
.hero-load.is-done{transform:translateY(-101%)}
.hero-load.is-gone{display:none!important}
.hero-load .load-lbl{color:var(--muted);opacity:.85}
.hero-load b{
  font-size:clamp(88px,16vw,224px);font-weight:800;font-stretch:112%;letter-spacing:-.05em;
  line-height:.85;color:var(--ink);font-variant-numeric:tabular-nums;
}
.hero-load .load-bar{
  display:block;width:min(260px,40vw);height:2px;background:rgba(239,230,211,.14);overflow:hidden;
}
.hero-load .load-bar i{
  display:block;width:100%;height:100%;background:var(--brand);
  transform:scaleX(0);transform-origin:0 50%;transition:transform .25s linear;
}
@media (prefers-reduced-motion:reduce){.hero-load{transition:none}}
.hero-scene.is-loading .orbit{opacity:0}
.hero-canvas{opacity:0;transition:opacity .9s var(--ease)}
.hero-scene.is-3d .hero-canvas{opacity:1}

/* The pointer names what it is over. This replaces a line of instructions in
   the fold, and tells you the board is a link as well as a thing to look at. */
.hero-cursor{
  position:absolute;left:0;top:0;z-index:6;pointer-events:none;
  display:flex;align-items:center;gap:7px;
  margin:18px 0 0 18px;padding:7px 13px;border-radius:999px;
  background:var(--print-ink);color:var(--print-bg);
  font-size:13px;font-weight:700;letter-spacing:-.01em;white-space:nowrap;
  opacity:0;transform-origin:0 0;
  transition:opacity .28s var(--ease),scale .28s var(--ease);
  scale:.8;
}
.hero-cursor.is-on{opacity:1;scale:1}
.hero-cursor i{font-style:normal;opacity:.55}
@media (hover:none){.hero-cursor{display:none}}

/* ---------- scroll cue ---------- */
/* Just a mark: the trust line already fills this space, and the research is
   blunt that every extra thing above the fold costs the CTA attention. */
.hero .scroll-cue{position:absolute;left:50%;bottom:18px;transform:translateX(-50%)}
.hero .scroll-cue i{width:1px;height:26px;background:linear-gradient(var(--print-ink),transparent);display:block;animation:pulse 2s ease-in-out infinite}

/* ---------- the name, revealed on the way down ---------- */
.hero-name{
  display:flex;flex-direction:column;gap:clamp(20px,3vw,34px);
  padding:clamp(84px,12vw,170px) var(--pad) clamp(84px,12vw,170px);
}
.hero-title{
  font-size:clamp(44px,9vw,164px);line-height:.88;font-weight:800;font-stretch:112%;
  letter-spacing:-.045em;display:flex;flex-direction:column;color:var(--print-ink);
}
.hero-title .outline{color:transparent;-webkit-text-stroke:2px var(--print-ink)}
.hero-lede{max-width:44ch;font-size:clamp(16px,1.4vw,21px);line-height:1.5;color:var(--print-muted)}

/* ---------- statement ---------- */
.statement{padding:clamp(96px,14vw,200px) var(--pad)}
.statement p{
  font-size:clamp(30px,4vw,64px);line-height:1.08;font-weight:600;letter-spacing:-.03em;
  max-width:24ch;display:flex;flex-wrap:wrap;gap:0 .26em;
}

/* ---------- lineup ---------- */
.lineup-head{display:grid;grid-template-columns:var(--rail) 1fr auto;gap:40px;align-items:end}
.lineup{display:grid;grid-template-columns:repeat(2,1fr);gap:24px}
.lineup .offset{margin-top:80px}
.card{
  position:relative;
  display:flex;flex-direction:column;background:var(--surface);
  border-radius:4px;overflow:hidden;color:inherit;height:100%;
  transition:transform .55s var(--ease),box-shadow .55s var(--ease);
}
.card:hover{color:inherit;transform:translateY(-6px);box-shadow:0 26px 50px -22px rgba(20,18,16,.55)}
/* A light passes over the card as the pointer arrives — the same gesture the
   spec readout uses in the hero, so the page hovers in one language. */
.card::after{
  content:"";position:absolute;inset:0;z-index:2;pointer-events:none;border-radius:inherit;
  background:linear-gradient(100deg,transparent 40%,rgba(255,255,255,.3) 50%,transparent 60%);
  background-size:250% 100%;background-position:130% 0;opacity:0;
}
.card:hover::after{opacity:1;background-position:-30% 0;transition:background-position .95s var(--ease),opacity .3s}
.card-art{position:relative;aspect-ratio:4/3;display:grid;place-items:center;padding:12%;overflow:hidden}
.card-art .tl{position:absolute;top:20px;left:22px;color:var(--muted)}
.card-art .tr{position:absolute;top:20px;right:22px;color:var(--muted)}
.card-art img{
  display:block;max-width:74%;max-height:78%;width:auto;height:auto;object-fit:contain;
  filter:drop-shadow(0 30px 40px rgba(40,25,5,.4));transition:transform .8s var(--ease);
}
.card:hover .card-art img{transform:scale(1.08) rotate(-4deg)}
.card-foot{display:flex;justify-content:space-between;align-items:end;gap:24px;padding:0 24px 26px}
.card-name{font-size:34px;font-weight:800;font-stretch:112%;letter-spacing:-.035em;line-height:1}
.card-tag{font-size:16px;color:var(--muted)}
.link-ul{border-bottom:1px solid currentColor;padding-bottom:4px;width:max-content}

/* ---------- which one? (board chooser) ---------- */
.pick{display:grid;grid-template-columns:repeat(2,1fr);border-top:1px solid var(--ink)}
.pick li{border-bottom:1px solid var(--ink);border-right:1px solid var(--line)}
.pick li:nth-child(2n){border-right:0}
.pick a{
  display:flex;flex-direction:column;gap:18px;height:100%;padding:32px 28px;
  color:inherit;text-decoration:none;
}
.pick li:nth-child(2n+1) a{padding-left:0}
.pick li:nth-child(2n) a{padding-right:0}
.pick a:hover{color:inherit}
.pick b{
  display:flex;align-items:baseline;gap:.22em;
  font-size:clamp(40px,4.6vw,76px);line-height:.95;font-weight:800;font-stretch:112%;letter-spacing:-.04em;
}
.pick b i{font-style:normal;color:var(--muted);transition:transform .6s var(--ease),color .6s var(--ease)}
.pick a:hover b i,.pick a:focus-visible b i{transform:translateX(.3em);color:var(--brand)}
.pick p{font-size:18px;line-height:1.45;color:var(--muted);max-width:32ch}

/* ---------- ticker ---------- */
.ticker:hover .ticker-track,.ticker:focus-within .ticker-track{animation-play-state:paused}
.ticker{
  overflow:hidden;margin-top:var(--sect);
  border-top:1px solid var(--ink);border-bottom:1px solid var(--ink);
  padding:18px 0;white-space:nowrap;
}
.ticker-track{
  display:inline-flex;gap:56px;animation:marquee 40s linear infinite;
  font-size:clamp(20px,2.4vw,34px);font-weight:700;font-stretch:112%;letter-spacing:-.02em;
}
.ticker-track span{display:inline-flex;align-items:center;gap:56px}
.ticker-track i{width:10px;height:10px;border-radius:50%;background:var(--brand);display:block}

/* ---------- software block on home ---------- */
.sw-grid{display:grid;grid-template-columns:1.4fr 1fr;gap:24px;align-items:start}
.sw-shot{border-radius:4px;overflow:hidden;border:1px solid rgba(239,230,211,.18);background:#0f0d0b}
/* the IDE frame until there is an IDE to show: logo + "Coming soon" */
.soon{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;text-align:center;color:var(--ink)}
.soon img{width:clamp(72px,9vw,128px);height:auto;margin-bottom:10px}
.soon .eyebrow{color:var(--print-muted)}
.soon b{font-size:clamp(28px,3.4vw,52px);line-height:1;font-weight:800;font-stretch:112%;letter-spacing:-.04em}
.sw-side{display:flex;flex-direction:column;gap:24px;justify-content:space-between}
.code{
  background:#0f0d0b;border-radius:4px;
  font-family:"Geist Mono",ui-monospace,monospace;font-size:13px;line-height:1.7;overflow:hidden;
}
.code-bar{
  display:flex;justify-content:space-between;padding:10px 18px;
  border-bottom:1px solid rgba(239,230,211,.12);font-size:11px;color:var(--print-muted);
}
.code pre{margin:0;padding:18px;white-space:pre-wrap}
.code .amber{color:var(--brand)}

/* ---------- buttons ---------- */
.btn{
  height:56px;display:inline-flex;align-items:center;justify-content:space-between;gap:16px;
  padding:0 22px;border-radius:999px;font-weight:700;font-size:15px;
  transition:transform .3s var(--ease),background .3s var(--ease),filter .3s var(--ease);
}
.btn-brand{background:var(--brand);color:var(--on-brand)}
.btn-brand:hover{color:var(--on-brand);filter:brightness(1.06)}
.btn-ink{height:52px;background:var(--ink);color:var(--bg)}
.btn-ink:hover{color:var(--on-brand);background:var(--brand)}
.btn-ghost{height:52px;border:1px solid var(--ink)}
.btn-ghost:hover{border-color:var(--brand)}

/* ---------- photos in fixed-ratio frames ---------- */
.photo{width:100%;height:100%;display:block;object-fit:cover}
/* the IDE screenshot is shown zoomed: anchored top-left at 220%, so the title bar,
   sidebar and editor read at a usable size and the rest runs off the frame */
.photo-zoom{width:220%;height:auto;max-width:none;object-fit:fill}

/* ---------- placeholder slots ---------- */
.slot{
  width:100%;height:100%;display:grid;place-items:center;text-align:center;padding:24px;
  background:var(--surface);color:var(--muted);
  background-image:repeating-linear-gradient(45deg,rgba(107,94,75,.09) 0 10px,transparent 10px 20px);
}
.print .slot{background:#221e19;color:var(--print-muted);
  background-image:repeating-linear-gradient(45deg,rgba(239,230,211,.06) 0 10px,transparent 10px 20px)}

/* ---------- made in india ---------- */
.mii-grid{display:grid;grid-template-columns:1.25fr .75fr;gap:clamp(20px,2.4vw,36px);align-items:start}
.mii-side{display:flex;flex-direction:column;gap:clamp(18px,2vw,28px)}
.mii-side .shot{aspect-ratio:4/3;border-radius:4px;overflow:hidden;border:1px solid rgba(239,230,211,.14)}

/* The clip is 1280x720. The frame takes the same ratio and the video fills it
   edge to edge, so the whole line is on screen — it used to sit in a 16:10
   frame at 1.2x under object-fit:cover, which cropped it twice over. */
.mii-main{
  aspect-ratio:16/9;border-radius:4px;overflow:hidden;background:#0f0d0b;
  border:1px solid rgba(239,230,211,.14);
}
.mii-main video{width:100%;height:100%;display:block;object-fit:contain;background:#0f0d0b}
.mii-col{display:flex;flex-direction:column;min-width:0}
.mii-cap{
  display:flex;justify-content:space-between;gap:16px;
  margin-top:12px;color:var(--print-muted);opacity:.75;
}
/* the duration must not break in half when the location wraps */
.mii-cap span+span{white-space:nowrap}

/* ---------- hover: quiet, and always about where it goes ----------
   The rule the research is blunt about is restraint: a hover earns its place
   by saying "this is a way through", not by performing. So the two frames
   behave differently on purpose — the photo is a door and announces itself,
   the video is not and only warms slightly. */

.shot-go{position:relative;display:block;overflow:hidden;color:inherit}
.shot-go .photo{transition:transform .9s var(--ease),filter .6s var(--ease)}
.shot-go::after{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(to top,rgba(10,9,8,.82),rgba(10,9,8,.15) 46%,transparent 72%);
  opacity:0;transition:opacity .45s var(--ease);
}
.shot-label{
  position:absolute;left:16px;right:16px;bottom:14px;z-index:2;
  display:flex;align-items:center;gap:8px;color:var(--print-ink);
  opacity:0;transform:translateY(10px);
  transition:opacity .4s var(--ease),transform .45s var(--ease);
}
.shot-label i{font-style:normal;transition:transform .45s var(--ease)}
.shot-go:hover .photo,.shot-go:focus-visible .photo{transform:scale(1.06)}
.shot-go:hover::after,.shot-go:focus-visible::after{opacity:1}
.shot-go:hover .shot-label,.shot-go:focus-visible .shot-label{opacity:1;transform:none}
.shot-go:hover .shot-label i{transform:translateX(5px)}

/* The video is not a link, so it does no more than come up out of the dark. */
.mii-main{transition:border-color .5s var(--ease)}
.mii-col:hover .mii-main{border-color:rgba(239,230,211,.34)}
.mii-col:hover .mii-cap{opacity:1}

/* A text link that moves the way the arrow suggests. */
.link-go{display:inline-flex;align-items:center;gap:8px;width:max-content;color:var(--print-muted)}
.link-go i{font-style:normal;transition:transform .4s var(--ease)}
.print .link-go:hover{color:var(--print-ink)}
.link-go:hover i{transform:translateX(5px)}

/* The code panel lifts a touch — it is the thing the section is about. */
.code{transition:border-color .5s var(--ease),transform .5s var(--ease)}
.sw-side:hover .code{transform:translateY(-3px)}
.sw-shot{transition:border-color .5s var(--ease)}
.sw-grid:hover .sw-shot{border-color:rgba(239,230,211,.32)}

@media (hover:none){
  /* nothing to reveal without a pointer: the label is simply there */
  .shot-label{opacity:1;transform:none}
  .shot-go::after{opacity:1}
}

/* Now that the sections share a ground, a hairline does the work the colour
   change used to: it marks where one ends and the next begins, without
   breaking the page into bands. */
.band+.band{border-top:1px solid rgba(239,230,211,.1)}
.band{padding:clamp(76px,9vw,132px) var(--pad)}

/* ---------- home CTA ---------- */
.cta{padding:clamp(120px,16vw,240px) var(--pad) var(--sect)}
.cta a{max-width:var(--maxw);margin:0 auto;display:flex;flex-direction:column;gap:32px;color:inherit}
.cta a:hover{color:inherit}
.cta .h-pg span{color:var(--brand-ink)}
.cta .go{display:inline-flex;align-items:center;gap:16px;border-bottom:1px solid currentColor;padding-bottom:6px;width:max-content}

/* ---------- boards index ---------- */
.board-rows{display:flex;flex-direction:column;border-top:1px solid var(--ink)}
.board-rows>li{border-bottom:1px solid var(--ink)}
.board-row{
  display:grid;grid-template-columns:60px minmax(0,1fr) minmax(0,1fr) minmax(0,260px);
  gap:32px;align-items:center;padding:40px 0;color:inherit;
}
.board-row:hover{color:inherit}
.board-row h2{display:flex;flex-direction:column;gap:6px}
.board-row .nm{font-size:clamp(40px,4.4vw,64px);font-weight:800;font-stretch:112%;letter-spacing:-.04em;line-height:.95}
.board-row .tg{font-size:20px;color:var(--muted)}
/* The board is the reason anyone is on this row, so it gets the room. The
   cap has to stay a percentage of a slot with a fixed height: an <img> with
   srcset takes its intrinsic size from `sizes`, not from the file, so the
   only thing holding the picture to a predictable size is this box. */
/* Sat a little left of where the cell centres it, toward the name it
   belongs to, which leaves the gap before the spec list doing the
   separating. justify-items rather than a transform, so the picture is
   still laid out in the cell and nothing overlaps at narrow widths. */
.board-row .art{height:300px;display:grid;align-items:center;justify-items:center}
.board-row .art img{transform:translateX(-26px)}
.board-row:hover .art img{
  transform:translateX(-26px) translateY(-8px) scale(1.06);
  filter:drop-shadow(0 26px 34px rgba(0,0,0,.5));
}
.board-row .art img{
  /* height, not max-height. With `auto` the picture takes its intrinsic size
     from the `sizes` attribute — 24vw — rather than from this box, so the
     board's size on the page was being decided by a resource-selection hint
     and neither cap was reached. Driving the height off the slot makes it
     deterministic, and the width follows the 3:2 frame. */
  display:block;height:90%;width:auto;max-width:92%;object-fit:contain;
  filter:drop-shadow(0 14px 24px rgba(40,25,5,.35));
  transition:transform .6s var(--ease),filter .6s var(--ease);
}

.board-row .hl{
  display:flex;flex-direction:column;gap:8px;font-size:15px;color:var(--muted);
  border-left:1px solid var(--line);padding-left:24px;
  transition:color .45s var(--ease),border-color .45s var(--ease);
}

/* ---------- row hover ----------
   The whole row is one link, so the whole row answers to the pointer rather
   than the board alone doing it in isolation. Four things move together and
   none of them move far: the row takes a faint ground, the name leans in and
   grows an arrow, the board lifts off the page, and the points beside it
   come up out of grey. All transform, opacity and colour, so none of it
   touches the layout. */
.board-rows>li{
  transition:background-color .45s var(--ease),opacity .45s var(--ease);
}
.board-rows>li:hover{background-color:rgba(239,230,211,.03)}
/* The rest of the list steps back so the row under the pointer is the one
   being read. :has needs no JavaScript and no class bookkeeping; where it is
   unsupported nothing happens, which is a fine outcome. */
.board-rows:has(>li:hover)>li:not(:hover){opacity:.5}

.board-row .nm{
  /* align-self, because h2 is a column flex box and a stretched item would
     put `left:100%` out at the edge of the column rather than at the end of
     the word. The box has to be the width of the name for the arrow to sit
     against it. */
  position:relative;display:inline-block;align-self:flex-start;
  transition:transform .5s var(--ease),color .3s var(--ease);
}
/* The arrow is a pseudo-element: no markup, and nothing a screen reader
   will read out as a word. */
.board-row .nm::after{
  content:"→";position:absolute;left:100%;bottom:.1em;margin-left:.22em;
  font-size:.42em;font-weight:600;color:var(--brand-ink);
  opacity:0;transform:translateX(-10px);
  transition:opacity .35s var(--ease),transform .5s var(--ease);
}
.board-row:hover .nm{transform:translateX(5px)}
.board-row:hover .nm::after{opacity:1;transform:none}
.board-row .tg{transition:color .35s var(--ease)}
.board-row:hover .tg{color:var(--ink)}
.board-row:hover .hl{color:var(--ink);border-left-color:var(--brand)}

@media (prefers-reduced-motion:reduce){
  .board-row:hover .nm{transform:none}
  .board-row .nm::after{transition:opacity .01s linear}
}

/* ---------- product page ---------- */
.pdp-hero{
  position:relative;min-height:100vh;min-height:100svh;display:grid;grid-template-rows:1fr auto;
  overflow:hidden;padding-top:150px;
}
.pdp-stage{display:grid;place-items:center;min-height:0}
/* The board no longer fills the stage: the callout columns live in the
   margins either side of it, the way the design lays them out. */
.pdp-board{position:relative;width:min(92vw,1180px);display:grid;place-items:center}
.pdp-stage fb-board{width:min(42vw,545px);height:min(48vh,540px)}

/* ---------- board callouts ----------
   The labels are HTML held over the board, not geometry in it: text that
   stays crisp at any zoom, selects, translates, and reaches a screen reader
   as a list. Until the model is up that is all it is — a plain list of what
   is on the board, which is also what a phone without WebGL gets.

   Live, the list becomes two columns either side of the board, each label
   wired to its part. No markers on the board itself: the wire lands on the
   component, which is what a technical callout has always done. */
.hs.is-pending{display:none}
/* Top and Bottom show the board on its own: no callouts, no list of them
   either. They are the 3D view's furniture. */
.hs.is-flat{display:none}
.hs-lede{display:block;margin:22px 0 10px;text-align:center}
.hs-list{display:flex;flex-wrap:wrap;gap:8px;justify-content:center;
  list-style:none;padding:0;margin:0}
.hs-wires{display:none}
.hs-pt{display:flex;align-items:baseline;gap:8px;text-align:left;
  border:1px solid var(--line);border-radius:999px;padding:7px 14px;
  color:var(--muted);transition:color .25s var(--ease),border-color .25s var(--ease)}
.hs-pt:hover,.hs-pt:focus-visible{color:var(--ink);border-color:var(--ink)}
.hs-txt{display:flex;align-items:baseline;gap:8px}
.hs-pt b{font-weight:700;color:var(--ink)}
.hs-pt i{font-style:normal}

/* ---- live ----
   Drawing convention, not decoration: the leader arrives at a level landing
   which carries on under the text as a reference line, and the text sits on
   that rule rather than beside a bare line end. The gap between the two is
   what stops it reading as an underline. */
.hs.is-live{position:absolute;inset:0;pointer-events:none;z-index:2}
.hs.is-live .hs-lede{display:none}
.hs.is-live .hs-list{display:block}
.hs.is-live .hs-wires{
  display:block;position:absolute;inset:0;width:100%;height:100%;overflow:visible;
}
.hs-wire{
  fill:none;stroke-width:1.25;vector-effect:non-scaling-stroke;
  transition:stroke-width .2s var(--ease),stroke .2s var(--ease);
}
/* Pointing at a label takes its own line off the gradient and on to the
   brand amber, full strength end to end. That is what answers "which line
   goes to which part": one line lights up all the way from the text to the
   component it lands on, while every other line and label drops back. */
.hs-wire.is-on{stroke:var(--brand);stroke-width:2}
/* Turned to its back, the board has nothing to point at: every leader and
   label is off, whatever their own fades are doing. */
.hs.is-away .hs-wires,.hs.is-away .hs-pt{visibility:hidden}
.hs.is-live .hs-pt{
  position:absolute;left:0;top:0;padding:0;border:0;border-radius:0;
  background:none;pointer-events:auto;white-space:nowrap;display:block;
  will-change:transform,opacity;transition:color .2s var(--ease);
}
/* The transform puts the origin on the reference line, so the text is lifted
   clear of it by its own height plus the standoff. */
.hs.is-live .hs-txt{display:block;transform:translateY(-100%) translateY(-6px)}
.hs.is-live .hs-pt.hs-l .hs-txt{text-align:right}
.hs.is-live .hs-pt.hs-r .hs-txt{text-align:left}
.hs.is-live .hs-pt b{
  display:block;font-weight:500;font-size:clamp(14px,1.12vw,17px);
  letter-spacing:-.005em;color:var(--ink);line-height:1.2;
}
/* The second line is the site's own small-print voice — mono, tracked out,
   upper — so a callout reads as instrumentation rather than as a caption. */
.hs.is-live .hs-pt i{
  display:block;margin-top:4px;
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:clamp(11px,.82vw,12.5px);line-height:1.3;
  letter-spacing:.07em;text-transform:uppercase;color:var(--muted);
}
.hs.is-live .hs-pt:hover b,
.hs.is-live .hs-pt:focus-visible b,
.hs.is-live .hs-pt.is-on b{color:#fff}
.hs.is-live .hs-pt:hover i,
.hs.is-live .hs-pt:focus-visible i,
.hs.is-live .hs-pt.is-on i{color:var(--brand-ink)}
.hs.is-live.has-open .hs-pt:not(:hover):not(:focus-visible):not(.is-on){
  opacity:.4 !important;
}
.hs.is-live.has-open .hs-wire:not(.is-on){opacity:.25 !important}

/* The spec row that belongs to the open callout. */
.specs div[data-spec].is-on{background:rgba(243,154,0,.10);border-radius:8px}
.specs div[data-spec].is-on dt{color:var(--brand-ink)}
@media (prefers-reduced-motion:reduce){
  .hs.is-live .hs-dot,.hs.is-live .hs-card,.hs.is-live .hs-pt::before{transition:none}
}
/* 3D · Top · Bottom — a segmented control under the stage */
.pdp-views{
  display:flex;gap:2px;margin:18px auto 0;width:max-content;
  border:1px solid var(--line);border-radius:999px;padding:3px;background:var(--surface);
}
.pdp-views button{
  padding:8px 18px;border-radius:999px;color:var(--muted);
  transition:background .3s var(--ease),color .3s var(--ease);
}
.pdp-views button:hover{color:var(--ink)}
.pdp-views button[aria-pressed="true"]{background:var(--ink);color:var(--bg)}
.crumb{position:absolute;top:96px;left:var(--pad);color:var(--muted)}
.pdp-title{
  position:relative;z-index:2;align-self:end;padding:24px 32px 32px;
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,420px);align-items:end;gap:40px;
}
.pdp-title h1{
  font-size:clamp(64px,11vw,200px);line-height:.88;font-weight:800;font-stretch:112%;
  letter-spacing:-.045em;display:flex;flex-direction:column;
}
.pdp-title h1 .sup{
  font-size:clamp(20px,1.6vw,28px);font-weight:600;font-stretch:100%;letter-spacing:-.01em;
  color:var(--muted);margin-bottom:12px;
}
.pdp-intro{display:flex;flex-direction:column;gap:18px;min-width:0}
.pdp-intro .tag{font-size:22px;font-weight:600;letter-spacing:-.01em;line-height:1.2}
.pdp-intro .tagline{font-size:16px;line-height:1.5;color:var(--muted)}
.pdp-cta{display:flex;gap:10px;flex-wrap:wrap}
.pdp-cta>*{padding:0 22px;font-size:15px;font-weight:700;border-radius:999px;display:inline-flex;align-items:center}

.hl-grid{
  max-width:var(--maxw);margin:0 auto;display:grid;grid-template-columns:repeat(3,1fr);
  gap:1px;background:rgba(239,230,211,.14);
}
.hl-grid li{
  background:var(--print-bg);padding:32px 28px;font-size:22px;font-weight:600;
  letter-spacing:-.02em;line-height:1.2;min-height:160px;
  display:flex;flex-direction:column;justify-content:space-between;gap:24px;
}
.hl-grid .eyebrow{color:var(--print-muted)}

.specs-aside{position:sticky;top:100px;align-self:start;display:flex;flex-direction:column;gap:12px}
.specs-aside p{font-size:14px;color:var(--muted);line-height:1.5}
.specs{border-top:1px solid var(--ink)}
.specs>div{
  display:grid;grid-template-columns:260px 1fr;gap:24px;padding:18px 0;
  border-bottom:1px solid var(--line);font-size:17px;line-height:1.45;
  position:relative;
  transition:background-color .3s var(--ease),color .3s var(--ease);
}
.specs dt{color:var(--muted);transition:color .3s var(--ease)}

/* ---------- spec rows under the pointer ----------
   Thirteen rows of small type is a lot to hold your place in, so the row
   being read takes a ground and the term beside it comes up out of grey.
   The amber tick in the margin is the same mark a callout puts on a row it
   is linked to, so the two mean the same thing wherever you meet them. */
.specs>div:hover{background-color:rgba(239,230,211,.04)}
.specs>div:hover dt{color:var(--ink)}
.specs>div::before{
  content:"";position:absolute;left:-14px;top:18px;bottom:18px;width:2px;
  background:var(--brand);opacity:0;transform:scaleY(.3);transform-origin:50% 50%;
  transition:opacity .3s var(--ease),transform .3s var(--ease);
}
.specs>div:hover::before,.specs>div.is-on::before{opacity:1;transform:none}

.works{display:grid;grid-template-columns:var(--rail) 1fr 1fr;gap:40px}
.chips{display:flex;flex-wrap:wrap;gap:8px;align-content:flex-start}
.chips li{font-size:15px;font-weight:600;padding:10px 16px;border:1px solid var(--ink);border-radius:999px;
  transition:border-color .3s var(--ease),color .3s var(--ease)}
/* These are labels, not links, so the response stays quiet: the border
   warms and the word brightens. Anything more would promise a click that
   is not there. */
.chips li:hover{border-color:var(--brand);color:#fff}

.dl-list{border-top:1px solid var(--ink)}
.dl-list li{border-bottom:1px solid var(--line)}
.dl-list>li>a{
  display:flex;justify-content:space-between;align-items:center;gap:8px;
  padding:14px 0;font-size:16px;font-weight:600;position:relative;
  transition:padding-left .35s var(--ease),color .3s var(--ease);
}
/* These are links, so they may say so. The row steps in from the left and
   an arrow takes the place of the gap it leaves. */
.dl-list>li>a::before{
  content:"→";position:absolute;left:0;color:var(--brand-ink);
  opacity:0;transform:translateX(-6px);
  transition:opacity .3s var(--ease),transform .35s var(--ease);
}
.dl-list>li>a:hover{padding-left:26px;color:#fff}
.dl-list>li>a:hover::before{opacity:1;transform:none}
.dl-list>li>a:hover .mono-muted{color:var(--brand-ink)}

@media (prefers-reduced-motion:reduce){
  .dl-list>li>a:hover{padding-left:0}
  .dl-list>li>a::before{display:none}
  .specs>div::before{transition:opacity .01s linear}
}

/* ---------- a download that actually exists ----------
   These rows sit in a list whose other rows are one line of 15px text with a
   quiet mono "SOON" against the right edge. A download row has more in it —
   a size and two actions — but it is still one row of that list, so it keeps
   that height and that rhythm: the actions are set to the mono eyebrow the
   "SOON" they replace was set in, and sized so the pill's box matches the
   label's line box rather than towering over it.

   The row wraps as a last resort, not as a habit: `margin-left:auto` holds
   the actions against the right edge, and if a narrow card leaves them no
   room they drop to their own line still right-aligned, instead of pushing
   out past the card. */
.dl-row{
  display:flex;flex-wrap:wrap;align-items:center;gap:2px 10px;
  padding:14px 0;font-size:16px;font-weight:600;
}
.docs-grid ul .dl-row{padding:12px 0;font-size:15px}
.dl-name{min-width:0}
.dl-size{margin-left:10px}
.dl-acts{display:flex;align-items:center;gap:6px;flex:none;margin-left:auto}
.dl-acts .act{
  display:inline-block;padding:4px 10px;border-radius:999px;
  border:1px solid var(--line);
  font-family:"Geist Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:11px;letter-spacing:.1em;text-transform:uppercase;
  font-weight:500;line-height:1.2;white-space:nowrap;color:var(--muted);
  transition:background-color .25s var(--ease),border-color .25s var(--ease),
             color .25s var(--ease);
}
.dl-acts .act-view:hover{border-color:var(--brand-ink);color:var(--brand-ink)}
/* the amber marks the one action that is the point of the row */
.dl-acts .act-get{border-color:var(--brand);color:var(--brand-ink)}
.dl-acts .act-get:hover{background:var(--brand);border-color:var(--brand);color:var(--on-brand)}
.dl-acts .act:focus-visible{outline:2px solid var(--brand-ink);outline-offset:2px}

.others{display:grid;grid-template-columns:repeat(3,1fr);gap:24px}
.others a{display:flex;flex-direction:column;gap:14px;color:inherit}
.others a:hover{color:inherit}
.others .art{background:var(--surface);border-radius:4px;aspect-ratio:4/3;display:grid;place-items:center;padding:24px;overflow:hidden}
.others .art img{
  display:block;max-width:82%;max-height:82%;width:auto;height:auto;object-fit:contain;
  filter:drop-shadow(0 14px 24px rgba(40,25,5,.35));transition:transform .6s var(--ease);
}
.others a:hover .art img{transform:scale(1.06)}
.others .row{display:flex;justify-content:space-between;align-items:baseline}
.others .nm{font-size:24px;font-weight:800;font-stretch:112%;letter-spacing:-.03em}
.others .tg{font-size:14px;color:var(--muted)}

/* ---------- software page ---------- */
/* ---------- about: the figures ----------
   A row of numbers, not a chart: four headline values whose job is identity
   and scale, which a bar chart would only make harder to read. Proportional
   figures, not tabular — tabular-nums gives every digit the width of a zero,
   which at this size leaves a number like 113 looking gappy. That belongs in
   columns of figures, and these are not in a column. */
.stat-row{
  display:grid;grid-template-columns:repeat(4,1fr);gap:0;
  border-top:1px solid var(--ink);list-style:none;padding:0;margin:0;
}
.stat-row li{
  display:flex;flex-direction:column;gap:6px;
  padding:30px 28px 32px;border-right:1px solid var(--line);
  transition:background-color .35s var(--ease);
}
/* The figures respond, quietly. They are statements, not controls, so the
   ground shifts and the number warms and that is all. */
.stat-row li:hover{background-color:rgba(239,230,211,.03)}
.stat-v{transition:color .35s var(--ease)}
.stat-row li:hover .stat-v{color:#fff}
.stat-row li:last-child{border-right:0}
.stat-v{
  font-size:clamp(38px,4.4vw,62px);line-height:1;font-weight:800;
  font-stretch:112%;letter-spacing:-.04em;color:var(--ink);
}
.stat-l{font-size:16px;font-weight:600;line-height:1.3;margin-top:4px}
.stat-n{display:block;margin-top:2px;line-height:1.5}

/* ---------- about: protection, per board ---------- */
.prot-dl{display:grid;gap:0;border-top:1px solid var(--line)}
.prot-dl>div{
  display:grid;grid-template-columns:110px 1fr;gap:24px;position:relative;
  padding:16px 0;border-bottom:1px solid var(--line);align-items:baseline;
  transition:background-color .3s var(--ease);
}
.prot-dl>div:hover{background-color:rgba(239,230,211,.04)}
.prot-dl dd{transition:color .3s var(--ease)}
.prot-dl>div:hover dd{color:var(--ink)}
.prot-dl>div::before,.cred-list li::before{
  content:"";position:absolute;left:-14px;top:16px;bottom:14px;width:2px;
  background:var(--brand);opacity:0;transform:scaleY(.3);
  transition:opacity .3s var(--ease),transform .3s var(--ease);
}
.prot-dl>div:hover::before,.cred-list li:hover::before{opacity:1;transform:none}
.prot-dl dt{font-weight:700;font-size:17px}
.prot-dl dd{margin:0;color:var(--muted);font-size:16px;line-height:1.45}

/* ---------- about: what we are recognised by ---------- */
.cred-list{display:grid;gap:0;list-style:none;padding:0;margin:0;
  border-top:1px solid var(--line)}
.cred-list li{
  display:grid;grid-template-columns:150px 1fr;gap:24px;position:relative;
  padding:16px 0;border-bottom:1px solid var(--line);align-items:baseline;
  transition:background-color .3s var(--ease),color .3s var(--ease);
}
.cred-list li:hover{background-color:rgba(239,230,211,.04);color:#fff}
.cred-list li>span:last-child{font-size:17px;line-height:1.45}

/* ---------- about: the closing ask ---------- */
.about-cta{
  display:flex;flex-direction:column;gap:18px;align-items:flex-start;
  padding:clamp(36px,5vw,64px);border:1px solid var(--line);border-radius:22px;
  background:var(--surface);
}
.about-cta h2{font-size:clamp(30px,3.6vw,52px);line-height:1.02;font-weight:800;
  font-stretch:112%;letter-spacing:-.03em;text-wrap:balance}
.about-cta p{color:var(--muted);font-size:18px;line-height:1.45;max-width:54ch}

.sw-cols{display:grid;grid-template-columns:repeat(3,1fr);border-top:1px solid var(--ink)}
.sw-cols li{display:flex;flex-direction:column;gap:12px;padding:32px 28px;border-right:1px solid var(--line);
  transition:background-color .35s var(--ease)}
.sw-cols li:hover{background-color:rgba(239,230,211,.03)}
.sw-cols li h2{transition:color .35s var(--ease)}
.sw-cols li:hover h2{color:#fff}
.sw-cols li:first-child{padding-left:0}
.sw-cols li:last-child{padding-right:0;border-right:0}
.sw-cols h2{font-size:28px;font-weight:800;font-stretch:112%;letter-spacing:-.03em}
.sw-cols p{font-size:16px;color:var(--muted);line-height:1.5}
.sw-cols code{
  font-family:"Geist Mono",ui-monospace,monospace;font-size:12px;background:var(--surface);
  padding:12px 14px;border-radius:4px;word-break:break-all;
}

/* ---------- docs ---------- */
.docs-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));border-top:1px solid var(--ink)}
.docs-grid>li{display:flex;flex-direction:column;gap:20px;padding:32px 24px}
.docs-grid>li:first-child{padding-left:0}
.docs-grid>li:last-child{padding-right:0}
.docs-grid .nm{font-size:32px;font-weight:800;font-stretch:112%;letter-spacing:-.03em}
.docs-grid ul{display:flex;flex-direction:column;border-top:1px solid var(--line)}
.docs-grid ul li{border-bottom:1px solid var(--line);padding:0}
.docs-grid ul>li>a{display:flex;justify-content:space-between;gap:8px;padding:12px 0;font-size:15px;font-weight:600}

/* ---------- compare ---------- */
/* Phone structure that the desktop table does not use: section rows and
   the written-out dash. */
.cmp tr.cmp-group{display:none}
.cmp td .na{display:none}
.cmp-scroll{overflow-x:auto;position:relative;-webkit-overflow-scrolling:touch}
.cmp-scroll:focus-visible{outline:2px solid var(--ink);outline-offset:2px}
/* fixed layout: the four board columns stay equal however long a cell runs */
.cmp{width:100%;table-layout:fixed;border-collapse:collapse;font-size:15px;border-top:1px solid var(--ink);min-width:900px}
.cmp thead tr{text-align:left}
.cmp th[scope=col]{
  padding:24px 20px 22px 0;font-weight:800;font-stretch:112%;letter-spacing:-.03em;
  font-size:32px;line-height:1;vertical-align:bottom;border-bottom:1px solid var(--ink);
}
.cmp th[scope=col] .tag{
  display:block;margin-top:10px;font-weight:400;font-stretch:100%;font-size:15px;
  line-height:1.45;letter-spacing:0;color:var(--muted);
}
.cmp th.spacer{width:200px;padding:20px 0;border-bottom:1px solid var(--ink)}
.cmp th[scope=row]{
  /* the tick lives in this padding, so it never lands on the first letter */
  padding:19px 20px 16px 12px;font-weight:400;color:var(--muted);text-align:left;
  border-bottom:1px solid var(--line);vertical-align:top;
  /* stays put while the board columns scroll sideways */
  position:sticky;left:0;z-index:1;background:var(--page-bg);
}
.cmp th.spacer{position:sticky;left:0;z-index:2;background:var(--page-bg)}
.cmp td{padding:16px 20px 16px 0;vertical-align:top;border-bottom:1px solid var(--line);line-height:1.45;
  transition:background-color .25s var(--ease),color .25s var(--ease)}

/* ---------- reading across the compare table ----------
   A comparison table is read along a row, and this one is wide enough to
   need scrolling sideways — which is exactly where the eye loses its line.
   The row under the pointer takes a ground for its whole width and its
   label comes up out of grey, so the line holds while the columns move.

   The label is a sticky cell with its own background, so it has to be told
   about the ground as well; left alone it keeps the page colour and the
   highlight stops dead at the first column. */
.cmp tbody tr{transition:color .25s var(--ease)}
.cmp tbody tr:hover td{background-color:rgba(239,230,211,.045)}
.cmp tbody tr:hover th[scope=row]{
  background-image:linear-gradient(rgba(239,230,211,.045),rgba(239,230,211,.045));
  color:var(--ink);
}
.cmp th[scope=row]{transition:color .25s var(--ease)}
/* The same amber tick the spec rows use, meaning the same thing. */
.cmp th[scope=row]{position:sticky}
.cmp th[scope=row]::before{
  content:"";position:absolute;left:0;top:14px;bottom:12px;width:2px;
  background:var(--brand);opacity:0;transform:scaleY(.3);
  transition:opacity .25s var(--ease),transform .25s var(--ease);
}
.cmp tbody tr:hover th[scope=row]::before{opacity:1;transform:none}

/* ---------- contact ---------- */
.contact-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:48px}
.contact-grid>.eyebrow{grid-column:1/-1;color:var(--muted)}
.contact-dl{display:flex;flex-direction:column;border-top:1px solid var(--ink);font-size:16px}
.contact-dl>div{display:grid;grid-template-columns:120px 1fr;gap:16px;padding:16px 0;border-bottom:1px solid var(--line)}
.contact-dl dd{min-width:0;overflow-wrap:anywhere}
.contact-dl .gst{font-family:"Geist Mono",ui-monospace,monospace;font-size:14px}
.form{display:flex;flex-direction:column;gap:22px;padding-top:12px;min-width:0}
.form label{display:flex;flex-direction:column;gap:8px}
.form input,.form select,.form textarea{
  height:48px;padding:0;border:0;border-bottom:1px solid var(--ink);background:transparent;
  font-size:20px;font-family:Archivo,sans-serif;letter-spacing:0;text-transform:none;border-radius:0;
}
.form textarea{height:auto;padding:12px 0;resize:vertical}
.form .two{display:grid;grid-template-columns:1fr 140px;gap:24px}
.form button{height:56px;background:var(--ink);color:var(--bg);border-radius:999px;font-size:15px;font-weight:700;margin-top:8px;transition:background .3s var(--ease)}
.form button:hover{background:var(--brand);color:var(--on-brand)}
.form-note{color:var(--muted);min-height:20px}

/* ---------- footer ---------- */
.site-foot{padding:clamp(80px,9vw,140px) var(--pad) 32px;overflow:hidden}
.foot-inner{max-width:var(--maxw);margin:0 auto;display:flex;flex-direction:column;gap:72px;container-type:inline-size}
.foot-cols{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:32px;font-size:15px}
.foot-cols>div{display:flex;flex-direction:column;gap:12px;color:var(--print-muted)}
.foot-cols>div>span:first-child{color:var(--print-ink)}
.foot-cols p{color:var(--print-muted);line-height:1.5;max-width:30ch;font-size:15px}
.foot-mark{width:56px;height:auto;display:block;margin-bottom:-24px}
/* The wordmark fills the column edge to edge and never overflows it: measured at
   6.395× the font size in Archivo, so 15.4cqw is just under an exact fit. The
   clamp() is only for browsers without container units. */
.wordmark{
  font-size:clamp(80px,16.5vw,300px);font-size:15.4cqw;line-height:.8;font-weight:800;font-stretch:118%;
  letter-spacing:-.05em;color:var(--print-ink);user-select:none;
}
.foot-legal{
  display:flex;justify-content:space-between;gap:24px;flex-wrap:wrap;color:var(--print-muted);
  border-top:1px solid rgba(239,230,211,.18);padding-top:20px;
}
/* registration numbers: one line with separators where they fit, one per line where they don't */
.foot-reg{display:inline-flex;flex-wrap:wrap;gap:6px 0}
.foot-reg span{white-space:nowrap}
.foot-reg span:not(:last-child)::after{content:"·";margin:0 10px}

/* ---------- fb-board host ---------- */
fb-board{display:block;position:relative}
fb-board.is-3d,fb-board.is-3d *{touch-action:none;-webkit-user-select:none;user-select:none}
.fb-loading{
  position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  padding:8px 14px;border-radius:999px;white-space:nowrap;
  background:rgba(20,18,16,.82);color:var(--print-ink);
  font-variant-numeric:tabular-nums;pointer-events:none;
}
.print .fb-loading{background:rgba(239,230,211,.9);color:var(--ink)}


/* =====================================================================
   Responsive
   ===================================================================== */

@media (max-width:1100px){
  :root{--rail:140px}

  .sw-grid{grid-template-columns:1fr}
  .others{grid-template-columns:repeat(2,1fr)}
}

@media (max-width:860px){
  :root{--pad:22px;--sect:clamp(72px,14vw,120px)}
  .site-head{padding:16px var(--pad);gap:18px}
  /* with the nav collapsed, keep Menu and Order together on the right */
  .site-head .brand{margin-right:auto}
  .order-link{order:3}
  /* thumb-sized targets: grow the hit area without moving the layout */
  .nav-toggle,.order-link{padding:13px 8px;margin:-13px -8px}   /* 44px tall */
  .foot-cols a{display:inline-block;padding:7px 0;margin:-7px 0}  /* a link, not a hairline */
  .site-nav.open a{padding:6px 0}
  .btn{min-height:48px}
  /* half-weight fabric tile on small screens */
  .print{background-image:url(../assets/fabric-black-tile-sm.webp);background-size:520px}
  .js .site-nav{display:none}
  .site-nav.open{
    display:flex;flex-direction:column;gap:18px;
    position:fixed;inset:52px 0 auto 0;padding:20px var(--pad) 24px;
    background:var(--print-bg);color:var(--print-ink);font-size:14px;
  }
  .js .nav-toggle{display:inline-flex}

  .rail,.lineup-head,.works{grid-template-columns:1fr;gap:20px}
  /* page titles: one word like "Documentation." must fit 346px */
  .h-pg{font-size:clamp(34px,10vw,140px);overflow-wrap:anywhere}
  /* the specs sidebar only makes sense pinned beside a tall column */
  .specs-aside{position:static}
  /* footer: two link columns instead of one long one; the blurb spans both */
  .foot-cols{grid-template-columns:1fr 1fr;gap:28px 20px}
  /* company column spans the row so its name never wraps in a half-width column */
  .foot-cols>div:nth-child(3),.foot-cols p{grid-column:1/-1;max-width:none}
  .lineup-head{align-items:start}
  .page{padding:120px var(--pad) 100px}

  /* The whole phone screen, so the one board has a screen's worth of room. */
  .hero{min-height:100svh;padding:112px var(--pad) 44px;gap:8px}
  .hero-meta{top:62px;flex-direction:column;gap:6px;align-items:flex-start}
  .hero-meta span:first-child{display:none}   /* one line of eyebrow on a phone, not two */
  /* The dial takes the width it can get; the chips sit in a tidy 2x2 under
     the plate rather than three-then-one. */
  .hero .scroll-cue{bottom:28px}
  .hero-name{padding:clamp(72px,16vw,110px) var(--pad)}
  /* One compact row of the four figures: half the height of the 2x2 it
     was, and every pixel saved is board. */
  /* an explicit width: shrink-to-fit inside the centred column let the row
     resize with its longest figure and the dividers crept a few px per board */
  /* the row's parent must have a width of its own: centred and shrink-to-fit,
     it sized itself to the longest figure and the row followed it */
  .hero-id{width:100%}
  .hero-specs{display:grid;grid-template-columns:repeat(4,1fr);width:min(100%,360px);max-width:none;flex:none;margin:0 auto}
  .hero-specs>div{padding:4px 3px}
  .hero-specs>div+div{border-left:1px solid rgba(239,230,211,.14)}
  /* Fixed geometry: every figure gets two lines' room whether it uses them
     or not, the name gets the width of the longest name, and the two buttons
     split the row equally — so a new board changes the words and nothing
     else moves. */
  .hero-specs dd{font-size:12px;white-space:normal;line-height:1.1;min-height:2.2em;font-variant-numeric:tabular-nums}
  .hero-specs dt{font-size:8px;margin-top:2px}
  .hero-plate-name{font-size:22px}
  .hero-plate-name b{min-width:6ch;text-align:left}
  .hero-id{gap:4px}
  .hero-plate{gap:6px}
  .hero-cta{display:grid;grid-template-columns:1fr 1fr;width:100%;max-width:360px}
  /* The four figures say more than the sell line does, and on a phone the
     boards need every pixel it would take. */
  .hero-trust{font-size:10px;margin-top:2px}
  .hero-cta{margin-top:4px}
  .hero-cta .btn{height:44px}

  .statement p{max-width:100%;font-size:clamp(24px,6.4vw,40px)}
  .pick b{font-size:34px}
  .lede,.pdp-intro .tagline{max-width:100%}

  .lineup{grid-template-columns:1fr}
  .lineup .offset{margin-top:0}
  .pick{grid-template-columns:1fr}
  .pick li{border-right:0}
  .pick a{padding:24px 0}
  .pick p{font-size:16px}

  /* The comparison, on a phone: no sideways table. Each row of the table
     becomes a block — the figure's name, then the four boards' values under
     it, one per line with the board's name in front. Every row and every
     value is still there, in the same order; only the shape has changed.
     The column headings are hidden from sight but not from the accessibility
     tree, since each value now names its board itself. */
  /* The comparison on a phone. Two boards side by side, each column headed
     by the board itself — its picture, its name in a selector, its one line —
     in a bar that stays put and folds down to the two selectors once you
     are reading. Every figure is a full-width label with the two values
     centred beneath it; the thirteen rows sit in three sections; a row the
     two boards agree on is set back, and a switch shows only the rows they
     don't. Nothing is hidden by the layout — only by that switch, by you. */
  .cmp-scroll{overflow:visible}
  .cmp{min-width:0;display:block;border-top:0}
  .cmp thead{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
  .cmp tbody{display:block}
  /* Lines do the structuring: a rule between the two columns through every
     row, a firmer rule under each row, and a heavier one opening each
     section — so the eye reads down a column and across a row without
     the values running together. */
  .cmp tr{display:grid;grid-template-columns:1fr 1fr;column-gap:16px;padding:16px 0 14px;border-bottom:1px solid rgba(239,230,211,.18);position:relative}
  .cmp tr:not(.cmp-group)::after{content:"";position:absolute;left:50%;top:40px;bottom:12px;width:1px;background:rgba(239,230,211,.18)}
  .cmp th[scope=row]{
    grid-column:1/-1;grid-row:1;display:block;position:static;padding:0 0 8px;border:0;background:none;
    text-align:center;color:var(--muted);font-size:12px;letter-spacing:.14em;
  }
  .cmp td{display:none;padding:2px 0;border:0;text-align:center;font-size:16px;line-height:1.35;color:var(--ink);overflow-wrap:anywhere}
  .cmp td.is-a,.cmp td.is-b{display:block}
  .cmp td.is-a{grid-column:1;grid-row:2}
  .cmp td.is-b{grid-column:2;grid-row:2}
  /* the figures that carry the comparison, set large over their label */
  .cmp tr[data-big] td{font-size:26px;font-weight:800;font-stretch:112%;letter-spacing:-.03em;line-height:1.1;padding:4px 0}
  /* a dash, written out */
  .cmp td .dash{display:none}
  .cmp td .na{display:inline;color:var(--muted);font-size:14px;font-weight:400;letter-spacing:0}
  /* Every value in full ink — a greyed row read as a lesser one. A row
     where the two boards differ carries an amber mark beside its label,
     so the differences are still found at a glance. */
  .cmp tr:not(.is-same):not(.cmp-group) th[scope=row]::after{
    content:"";display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--brand);
    margin-left:8px;vertical-align:middle;position:relative;top:-1px;
  }
  .cmp.diff-only tr.is-same,.cmp.diff-only tr.cmp-group.is-empty{display:none}
  /* sections */
  .cmp tr.cmp-group{display:block;padding:30px 0 6px;border:0;border-top:2px solid rgba(239,230,211,.34);margin-top:14px}
  .cmp tr.cmp-group:first-child{padding-top:22px;border-top:0;margin-top:0}
  .cmp tr:has(+ .cmp-group){border-bottom:0}   /* one rule at a section break, not two */
  .cmp tr.cmp-group th{
    display:block;position:static;padding:0;border:0;background:none;text-align:left;
    font-size:24px;font-weight:800;font-stretch:112%;letter-spacing:-.03em;line-height:1.05;color:var(--ink);
  }
  /* the bar */
  .cmp-sel{
    display:grid;grid-template-columns:1fr 1fr;column-gap:16px;row-gap:0;
    position:sticky;top:var(--head-h,60px);z-index:5;padding:10px 0 0;background:var(--page-bg);
    border-bottom:1px solid var(--line);
  }
  .cmp-sel-col{position:relative;display:flex;flex-direction:column;align-items:stretch;gap:8px;min-width:0}
  .cmp-sel-img{
    display:block;width:100%;height:110px;object-fit:contain;margin:0 auto;
    transition:height .3s var(--ease),opacity .25s var(--ease),margin .3s var(--ease);
  }
  .cmp-sel-col label{position:relative;display:block}
  .cmp-sel select{
    width:100%;min-height:44px;padding:0 36px 0 14px;border-radius:999px;
    border:1px solid var(--muted);background:var(--surface);color:var(--ink);
    font-size:15px;font-weight:700;appearance:none;-webkit-appearance:none;
  }
  .cmp-sel-col label::after{
    content:"";position:absolute;right:16px;top:50%;width:8px;height:8px;pointer-events:none;
    border-right:1.5px solid var(--muted);border-bottom:1.5px solid var(--muted);transform:translateY(-70%) rotate(45deg);
  }
  .cmp-sel select:focus-visible{outline:2px solid var(--brand);outline-offset:2px}
  .cmp-sel-tag{margin:0;text-align:center;font-size:13px;line-height:1.3;color:var(--muted);min-height:2.6em;
    transition:opacity .25s var(--ease)}
  /* folded: the two selectors alone, once you are reading */
  .cmp-sel.is-compact .cmp-sel-img{height:0;opacity:0;margin:0}
  .cmp-sel.is-compact .cmp-sel-tag{height:0;min-height:0;opacity:0;overflow:hidden}
  /* the one control */
  .cmp-diff{
    grid-column:1/-1;display:flex;align-items:center;gap:10px;min-height:48px;padding:4px 0;
    font-size:14px;font-weight:600;color:var(--ink);cursor:pointer;
  }
  .cmp-diff small{margin-left:auto;font-size:12px;font-weight:400;color:var(--muted);font-variant-numeric:tabular-nums}
  .cmp-diff input{
    appearance:none;-webkit-appearance:none;flex:none;width:42px;height:24px;border-radius:12px;margin:0;
    background:var(--line);position:relative;transition:background-color .2s var(--ease);
  }
  .cmp-diff input::after{
    content:"";position:absolute;top:3px;left:3px;width:18px;height:18px;border-radius:50%;background:var(--ink);
    transition:transform .2s var(--ease);
  }
  .cmp-diff input:checked{background:var(--brand)}
  .cmp-diff input:checked::after{transform:translateX(18px);background:var(--on-brand)}
  .cmp-diff input:focus-visible{outline:2px solid var(--brand);outline-offset:2px}
  /* a column's values cross-fade as its board changes */
  .cmp td.is-a,.cmp td.is-b{transition:opacity .18s var(--ease)}
  .cmp.is-swapping td.is-a,.cmp.is-swapping td.is-b{opacity:0}
  .board-row{grid-template-columns:1fr;gap:20px;padding:32px 0}
  .board-row .art{height:230px}
  .board-row .hl{border-left:0;padding-left:0;border-top:1px solid var(--line);padding-top:16px}

  /* Product hero on a phone: the desktop's callout sheet, on a tall screen.
     The board stands upright and takes the middle of the stage; the labels
     take the margins either side, wired to their parts as they are on the
     desktop, and may wrap within a margin's width. The stage and title
     wrappers are dissolved so their children are items of the hero grid,
     in this order: name, the board with its labels, view switch, the line
     and the buttons. One column that cannot grow past the screen. */
  .pdp-hero{--pdp-board-h:min(62vh,600px);padding-top:100px;min-height:auto;grid-template-columns:minmax(0,1fr);grid-template-rows:none;grid-auto-rows:auto;row-gap:0}
  .pdp-stage,.pdp-title{display:contents}
  .pdp-hero h1{order:1;padding:12px var(--pad) 0}
  .pdp-board{order:2;width:100%;min-height:var(--pdp-board-h);justify-self:stretch}
  .pdp-stage fb-board{width:52vw;height:var(--pdp-board-h)}
  /* Top and Bottom on a phone are renders, not the sheet: there are no
     margins to keep for labels, so the board takes the width and the
     stage only the height the render needs. */
  .pdp-hero:has(.hs.is-flat) .pdp-board{min-height:0}
  .pdp-hero:has(.hs.is-flat) .pdp-stage fb-board{width:84vw;height:min(56vh,520px)}
  .pdp-views{order:3;justify-self:center;margin:10px 0 6px}
  .pdp-intro{order:4;padding:20px var(--pad) 0;gap:12px}
  /* The labels at a phone's size: wrapping within the margin, each line
     aligned to its column's side (the desktop's rule), the second line in
     the same small-print voice. */
  .hs.is-live .hs-pt{white-space:normal}
  .hs.is-live .hs-txt{max-width:calc(24vw - 18px)}
  .hs.is-live .hs-pt b{font-size:13px;line-height:1.15}
  .hs.is-live .hs-pt i{font-size:9.5px;letter-spacing:.05em;line-height:1.3;margin-top:3px}
  .stat-row{grid-template-columns:repeat(2,1fr)}
  .stat-row li{padding:24px 20px 26px}
  .stat-row li:nth-child(2n){border-right:0}
  .stat-row li:nth-child(-n+2){border-bottom:1px solid var(--line)}
  .prot-dl>div,.cred-list li{grid-template-columns:1fr;gap:6px}
  .crumb{top:72px}
  .pdp-title{grid-template-columns:1fr;padding:24px var(--pad) 32px}
  .hl-grid{grid-template-columns:1fr}
  /* Tech specs: the term as the small print, the value as the text — a
     column that reads as a datasheet, not as pairs of lines the same size. */
  .specs>div{grid-template-columns:1fr;gap:6px;padding:14px 0;font-size:16px}
  .specs dt{font-family:"Geist Mono",ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;letter-spacing:.12em;text-transform:uppercase;line-height:1.4}
  .sw-cols,.docs-grid{grid-template-columns:1fr}
  .sw-cols li,.docs-grid>li{padding:24px 0;border-right:0;border-bottom:1px solid var(--line)}
  .others{grid-template-columns:1fr}
  .mii-grid{grid-template-columns:1fr}
  .contact-grid{grid-template-columns:1fr;gap:32px}
  .form .two{grid-template-columns:1fr}
  .foot-legal{flex-direction:column;gap:8px}
  .foot-reg{flex-direction:column;gap:6px}
  .foot-reg span:not(:last-child)::after{content:none}
}

/* Four docs cards across a laptop leave each about 200px of text — not enough
   for a label and two actions on one line. Two columns is. Bounded at both
   ends: below 860 the grid is a single column and these must not apply. */
@media (min-width:861px) and (max-width:1100px){
  .docs-grid{grid-template-columns:repeat(2,1fr)}
  /* the flush edge is now every odd card's left and every even card's right,
     not the first and last card as it is when all four share one row */
  .docs-grid>li:nth-child(odd){padding-left:0;padding-right:24px}
  .docs-grid>li:nth-child(even){padding-left:24px;padding-right:0}
  /* and the second row takes the rule the first one gets from the grid */
  .docs-grid>li:nth-child(n+3){border-top:1px solid var(--line)}
}

/* The comparison across phone shapes: the two columns are the constant; what
   changes with width is the room each has. On a narrow phone the big figures
   come down a size so "80–160 MHz" still sits on one line. */
@media (max-width:399px){
  .cmp tr[data-big] td{font-size:22px}
  .cmp tr{column-gap:12px}
  .cmp-sel{column-gap:12px}
  .cmp-sel select{font-size:14px;padding:0 30px 0 12px}
}
.cmp-sel{display:none}
@media (max-width:860px){.cmp-sel{display:grid}}

/* ---------- touch: no sticky hover states ---------- */
@media (hover:none){
  .card:hover .card-art img,
  .board-row:hover .art img,
  .others a:hover .art img{transform:none}
  .btn:hover{transform:none}
}


@media (prefers-reduced-motion:reduce){
  .cmp th[scope=row]::before,.prot-dl>div::before,.cred-list li::before{
    transition:opacity .01s linear;
  }
}


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

/* ---------- headings that arrive a word at a time ----------
   Each word sits in a box that clips, and rises up out of it a beat after
   the one before — the way a line is set, not switched on. Rising from
   below is the direction that works: the word is only ever cut across, so
   what shows mid-way is the top of the same word, never a shorter word
   that reads as a different one (a wipe from the left did exactly that).
   The box carries a little of the line's leading so descenders are not
   clipped, and the inner span is pulled back up by the same amount. */
.is-split .w{display:inline-block;vertical-align:bottom;overflow:hidden;padding-bottom:.14em;margin-bottom:-.14em}
.is-split .wi{
  display:inline-block;transform:translateY(112%);
  transition:transform 1.05s cubic-bezier(.16,1,.3,1);
  transition-delay:calc(var(--i) * 70ms);
}
.is-split[data-in] .wi{transform:none}

/* ---------- the same words, lit by the scroll ----------
   Where the browser can drive an animation from scroll position, the
   heading stops being a thing that plays on arrival and becomes a thing you
   read: the words come up out of the dark one after another exactly as fast
   as you scroll, and go back down if you scroll up.

   Each word takes its own slice of the section's pass across the screen —
   its index over the total, which is why the splitter writes both — and the
   slices overlap by more than half, so it reads as a wave rather than as
   four separate events.

   The dimming lives inside the @supports, never outside it. A browser with
   no view timeline never sees `opacity:.16`, so the finished, fully legible
   heading is what it renders — the unsupported state is the finished state,
   not a hidden one. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion:no-preference){
    /* Not the page headline: that is in view the moment the page opens, so
       a scroll-driven rise would already be finished — it arrives on load
       with the transition above instead. Headings down the page take the
       scroll. */
    .is-split:not(.h-pg) .wi{
      transform:translateY(112%);transition:none;
      animation:word-lit linear both;
      animation-timeline:view();
      animation-range:
        entry calc(16% + (var(--i) / var(--n)) * 52%)
        entry calc(48% + (var(--i) / var(--n)) * 52%);
    }
  }
}
@keyframes word-lit{
  from{transform:translateY(112%)}
  to{transform:none}
}

/* ---------- scroll-linked drift ----------
   The two halves of the Made-in-India band move against each other as the
   section crosses the screen — the wide frame up, the tall one down — so
   the band has some depth in it rather than sliding past as one flat plate.

   Driven by the scroll position itself rather than by a scroll handler, so
   it runs off the main thread and cannot fight the WebGL hero for frames.
   Where the browser has no view timeline the rule simply does not apply and
   nothing moves, which is a perfectly good version of the page. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion:no-preference) and (min-width:861px){
    .mii-main{
      animation:drift-up linear both;
      animation-timeline:view();
      animation-range:entry 0% exit 100%;
    }
    .mii-side{
      animation:drift-down linear both;
      animation-timeline:view();
      animation-range:entry 0% exit 100%;
    }
    /* The heading holds back a little, which is what sells the rest. */
    .band .h-xl{
      animation:drift-slow linear both;
      animation-timeline:view();
      animation-range:entry 0% exit 100%;
    }
  }
}
@keyframes drift-up{from{transform:translateY(30px)}to{transform:translateY(-30px)}}
@keyframes drift-down{from{transform:translateY(-22px)}to{transform:translateY(22px)}}
@keyframes drift-slow{from{transform:translateY(14px)}to{transform:translateY(-14px)}}
