/* NextWell Lab — home page.
   Reskinned onto the section shell language (shared/viewer.css): near-black
   shell, one fg alpha ladder, rounded "window" surfaces with faint 1px borders,
   rounded uppercase eyebrows, 36px icon-button chrome. */

/* Unbounded 600 — the display face of the gallery welcome slides, reused here
   for the brand marks and the CTA labels so the landing and the sections speak
   with the same voice. gf.css does not carry it (only the interactive section's
   sheet does), so the three cuts are declared locally; the paths are
   root-absolute because this page is served from the site root and the same
   files back the section covers. Split by unicode-range exactly as upstream
   ships it, so a latin visitor never downloads the cyrillic cut. There is no
   CJK cut in this family — zh/ja/ko fall through to the system stack rather
   than to a synthesised bold. */
@font-face {
  font-family: 'Unbounded';
  font-style: normal; font-weight: 600; font-display: swap;
  src: url('/shared/fonts/unbounded/unbounded-latin-600-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: 'Unbounded';
  font-style: normal; font-weight: 600; font-display: swap;
  src: url('/shared/fonts/unbounded/unbounded-latin-ext-600-normal.woff2') format('woff2');
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face {
  font-family: 'Unbounded';
  font-style: normal; font-weight: 600; font-display: swap;
  src: url('/shared/fonts/unbounded/unbounded-cyrillic-600-normal.woff2') format('woff2');
  unicode-range: U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116;
}

/* Nunito — the label voice: every small uppercase eyebrow, tile caption, tag
   and counter on the page. It replaces JetBrains Mono, which was a typewriter
   in a skin that has nothing else typed about it. Round, humanist, generously
   curved, and — unlike the usual "Gilroy-ish" free stand-ins (Manrope ships one
   weight here, Poppins has no cyrillic at all) — it carries latin, latin-ext
   AND a real cyrillic cut in two weights, so ru/uk/pl labels are the same face
   rather than a system substitute. Nothing here is monospaced any more, so
   wherever digits have to line up the rule asks for tabular-nums explicitly.
   Declared exactly like Unbounded above: root-absolute paths (this page is
   served from the site root), split by unicode-range as upstream ships it, so a
   latin visitor never downloads the cyrillic cut. Both cuts are declared and
   only the weights a rule actually asks for get fetched. No CJK cut exists —
   zh/ja/ko fall through to the system stack. */
@font-face {
  font-family: 'Nunito';
  font-style: normal; font-weight: 400; font-display: swap;
  src: url('/shared/fonts/nunito/nunito-latin-400-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: 'Nunito';
  font-style: normal; font-weight: 400; font-display: swap;
  src: url('/shared/fonts/nunito/nunito-latin-ext-400-normal.woff2') format('woff2');
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face {
  font-family: 'Nunito';
  font-style: normal; font-weight: 400; font-display: swap;
  src: url('/shared/fonts/nunito/nunito-cyrillic-400-normal.woff2') format('woff2');
  unicode-range: U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116;
}
@font-face {
  font-family: 'Nunito';
  font-style: normal; font-weight: 700; font-display: swap;
  src: url('/shared/fonts/nunito/nunito-latin-700-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: 'Nunito';
  font-style: normal; font-weight: 700; font-display: swap;
  src: url('/shared/fonts/nunito/nunito-latin-ext-700-normal.woff2') format('woff2');
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face {
  font-family: 'Nunito';
  font-style: normal; font-weight: 700; font-display: swap;
  src: url('/shared/fonts/nunito/nunito-cyrillic-700-normal.woff2') format('woff2');
  unicode-range: U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116;
}

/* Comfortaa — the header's own voice. The one geometric face in the tree whose
   bowls really are circles: an "o" drawn with a compass, which is exactly what
   the row of small nav pills wanted and what neither the body face nor the
   display face can give. Only the 600 cut is declared, because that is the only
   weight that ships all three subsets here (latin, latin-ext AND cyrillic) —
   asking for 400 would resolve Russian labels onto a face without a cyrillic
   cut and drop them out of the family altogether. gf.css already declares a
   latin-only 400/700 pair under the same name; these three sit alongside it and
   the weight we ask for is the one that decides. */
@font-face {
  font-family: 'Comfortaa';
  font-style: normal; font-weight: 600; font-display: swap;
  src: url('/shared/fonts/comfortaa/comfortaa-latin-600-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face {
  font-family: 'Comfortaa';
  font-style: normal; font-weight: 600; font-display: swap;
  src: url('/shared/fonts/comfortaa/comfortaa-latin-ext-600-normal.woff2') format('woff2');
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face {
  font-family: 'Comfortaa';
  font-style: normal; font-weight: 600; font-display: swap;
  src: url('/shared/fonts/comfortaa/comfortaa-cyrillic-600-normal.woff2') format('woff2');
  unicode-range: U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116;
}

*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }

/* The accent (--acid & friends) is written inline on <html> by the head script
   (random palette entry per load) and rotated by main.js as PLAIN custom
   properties. Native animation/transition of registered @property colours on
   the root is what used to power this — it left descendants holding stale
   paint, forced a full-document style recalc every frame and leaked memory in
   Chromium, so it is gone for good. */

:root {
  --bg: #0a0b10;
  --fg: #f4f2ff;
  --fg-a05: rgba(244, 242, 255, 0.05);
  --fg-a08: rgba(244, 242, 255, 0.08);
  --fg-a12: rgba(244, 242, 255, 0.12);
  --fg-a25: rgba(244, 242, 255, 0.25);
  --fg-a40: rgba(244, 242, 255, 0.4);
  --fg-a55: rgba(244, 242, 255, 0.55);
  --fg-a75: rgba(244, 242, 255, 0.75);
  /* Slightly lifted, slightly translucent — the aurora tints through it. */
  --card: rgba(17, 18, 27, 0.62);
  --card-solid: #111218;
  --acid: #c8ff2e;       /* overridden per load by inline head script */
  --acid-dark: #9ac700;
  --acid-ink: #0b0b10;
  /* One chevron for the whole page — a bare corner, no shaft. Masked into
     pseudo-elements (::after marks cannot live in the markup: the i18n loader
     rewrites innerHTML on every locale switch) and inlined as <svg> where the
     markup is i18n-free. Rotated -45deg it reads as an "opens elsewhere" mark. */
  --chev: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 6l6 6-6 6'/%3E%3C/svg%3E");
  /* And the same idea for "opens elsewhere": the top-right corner of a frame. */
  --corner: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8 6h10v10'/%3E%3C/svg%3E");
  --radius: 16px;
  --shadow-raise: 0 30px 70px -30px rgba(0, 0, 0, 0.7), 0 2px 12px -6px rgba(0, 0, 0, 0.45);
  --shadow-hover: 0 18px 44px -20px rgba(0, 0, 0, 0.65);
  --display: 'Science Gothic', 'Space Grotesk', 'Inter', system-ui, sans-serif;
  /* Brand voice: the marks and the calls to action only. Unbounded is wide and
     loud — anywhere it touches, size and tracking are retuned for it. */
  --brand: 'Unbounded', 'Space Grotesk', 'Inter', system-ui, sans-serif;
  /* The reading voice. Inter is a fine interface face and a characterless one;
     Nunito puts a circle back into every o, a and e, which is the one thing the
     page's own drawing (rounded plates, pill buttons, circular status dots) was
     asking the copy to echo. It also carries latin, latin-ext and cyrillic in
     both weights, so no locale drops out of the family — and it is already on
     the page as the label face, so the switch costs no extra request. */
  --text: 'Nunito', 'Nunito Sans', 'Inter', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
  /* Header chrome only: the nav pills and the language toggle. A compass-drawn
     geometric with genuinely circular bowls — loud enough to carry three words
     at 12px, far too round to read a paragraph in. */
  --ui: 'Comfortaa', 'Nunito', 'Inter', system-ui, sans-serif;
  /* The label role — tracked uppercase micro-type. Nunito is proportional, so
     every slot that used to lean on a monospace advance for its rhythm was
     re-measured for it: sizes up about half a pixel, tracking down a notch, and
     700 rather than 500 (the family ships 400/700, and 500 would have resolved
     down to 400 and gone thin at 9–11px). */
  --label: 'Nunito', 'Nunito Sans', 'Inter', system-ui, sans-serif;
  --max-w: 1160px;
  --head-h: 64px;
  --ease-out: cubic-bezier(.2, .8, .2, 1);
  color-scheme: dark;
}

html { scroll-behavior: smooth; }

body {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--text);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  line-height: 1.5;
  overflow-x: hidden;
  /* Kills the 300ms double-tap-to-zoom delay on touch without taking panning or
     scrolling away — the pinch-zoom lock itself is the viewport meta. */
  touch-action: manipulation;
}

a { color: inherit; text-decoration: none; }
::selection { background: var(--acid); color: var(--acid-ink); }
a:focus-visible, button:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--acid) 70%, #fff);
  outline-offset: 2px;
}

/* The class name is the page's label hook, not a claim about the face — it is
   kept because the markup carries it and the section galleries use the same
   name. */
.mono {
  font-family: var(--label);
  font-size: 11px; font-weight: 700;
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--fg-a40);
}

/* cursor spotlight — a fixed-size halo moved with a transform, so following
   the pointer costs a composite instead of a full-viewport gradient repaint */
.spotlight {
  position: fixed; left: -560px; top: -560px;
  width: 1120px; height: 1120px;
  pointer-events: none;
  z-index: 1;
  background: radial-gradient(circle closest-side,
              color-mix(in srgb, var(--acid) 6%, transparent), transparent 65%);
  transform: translate3d(var(--mx, 50vw), var(--my, 30vh), 0);
  transition: transform .12s linear;
  will-change: transform;
}

/* reveal on scroll */
.reveal { opacity: 0; transform: translateY(22px); transition: opacity .7s var(--ease-out), transform .7s var(--ease-out); }
.reveal.in { opacity: 1; transform: none; }

/* Anchor jumps must clear the sticky header. */
#featured, #sections { scroll-margin-top: calc(var(--head-h) + 12px); }

/* ===== HEADER ===== */
.site-head {
  position: sticky; top: 0; z-index: 90;
  display: flex; align-items: center; gap: 10px;
  padding: 0 clamp(18px, 4vw, 48px);
  height: var(--head-h);
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: blur(16px) saturate(1.2);
  -webkit-backdrop-filter: blur(16px) saturate(1.2);
  border-bottom: 1px solid var(--fg-a05);
}
.wordmark {
  display: flex; align-items: center; gap: 10px;
  margin-right: auto;
  /* Unbounded runs about a fifth wider than Inter at the same size: at the old
     14.5px "NextWell Lab" pushed the language pill and the burger off a 360px
     screen. Fluid size plus a touch of negative tracking claws that back, and
     the box is pinned so a flex squeeze can never dent it. */
  font-family: var(--brand);
  font-size: clamp(11.5px, 3.1vw, 13px);
  font-weight: 600; letter-spacing: -.02em;
  white-space: nowrap;
}
/* The tilt is the mark's signature — it came from the experimental skin and it
   belongs to the logo, not to a stylesheet. Retuned for this plate: the old one
   was a hard-cornered 30px square, this one is 32px and rounded, and a rounded
   square shows its slant on the corners rather than along the edges, so -4deg
   carries further here than it did there — enough that the angle needs no help,
   and the springy overshoot does the rest of the work on hover. The
   rotation costs nothing in layout (a 4deg tilt grows the painted box by ~1px a
   side against 16px of header air) and the glitch copies inherit it for free —
   they are absolutely positioned inside this box, so they are already in its
   rotated coordinate space and their own translate/clip stays square to the
   plate at any angle. */
.wordmark-box {
  position: relative; /* the glitch copies pin to it */
  display: grid; place-items: center;
  width: 32px; height: 32px; flex: 0 0 32px;
  border-radius: 9px;
  background: var(--acid); color: var(--acid-ink);
  font-family: var(--brand); font-size: 10.5px; font-weight: 600; letter-spacing: -.03em;
  /* The plate centres its two letters itself (display: grid + place-items), so
     the inherited 1.5 line box only ever pushed the glyphs off-centre. */
  line-height: 1;
  box-shadow: 0 4px 18px -6px color-mix(in srgb, var(--acid) 70%, transparent);
  transform: rotate(-4deg);
  transition: transform .25s cubic-bezier(.3, 1.6, .4, 1);
}
.wordmark-text { position: relative; }
/* Through the upright and out the other side — the spring in the easing is what
   makes it read as a flick rather than a slide. */
.wordmark:hover .wordmark-box { transform: rotate(4deg) scale(1.08); }
.wordmark em { font-style: normal; color: var(--acid); }

/* ===== wordmark glitch =====
   The old skin tore the hero heading; the new heading is a background-clipped
   gradient, which a duplicated text layer cannot share, so the effect moved to
   the mark it fits better anyway. Same character, a quarter of the scale.

   Four absolutely positioned copies read `data-text` — the wordmark is
   i18n-free markup, so nothing in main.js ever rewrites them. Every copy is
   out of flow and only ever moves by transform/clip-path, so a tear cannot
   shift the header by a pixel.

   ::before is the fill (the accent poured into the letterforms / the plate
   duplicated); ::after is the chromatic ghost, tinted from --goo3 — a rotating
   palette slot, so no hue is named here either.

   Nothing runs continuously. A tear takes a single band — a fifth of the
   element's height — throws it two or three pixels sideways and puts it back
   inside ~60ms. The four layers share one clock but are staggered a few
   milliseconds apart, so the burst reads as one compound dropout every ~12s
   rather than as four separate tics. */
.wordmark-box::before, .wordmark-box::after,
.wordmark-text::before, .wordmark-text::after {
  content: attr(data-text);
  position: absolute; inset: 0;
  opacity: 0;
  pointer-events: none;
}
/* --o is the per-layer offset inside the burst; it is added to whichever
   animation-delay the ambient or the hover rule below sets. */
.wordmark-text::before { --o: 20ms; }
.wordmark-box::after { --o: 40ms; }
.wordmark-text::after { --o: 60ms; }
/* The plate copies carry the plate; the word copies carry only ink. */
.wordmark-box::before, .wordmark-box::after {
  display: grid; place-items: center;
  border-radius: inherit;
}
.wordmark-box::before { background: var(--acid); color: var(--acid-ink); }
.wordmark-box::after {
  background: color-mix(in srgb, var(--goo3, var(--acid)) 72%, transparent);
  color: var(--acid-ink);
}
.wordmark-text::before { color: var(--acid); }
/* Combed into slivers, so the chromatic layer can never show as a whole second
   copy of the word — only a few scanlines of it ever light up. */
.wordmark-text::after {
  color: var(--goo3, var(--acid));
  -webkit-mask-image: repeating-linear-gradient(180deg, #000 0 1px, transparent 1px 4px);
          mask-image: repeating-linear-gradient(180deg, #000 0 1px, transparent 1px 4px);
}

/* Ambient tearing is the header mark's alone — one logo doing this is a detail,
   two on the same page is a tic. The 6s lead-in also keeps the page from
   glitching in the visitor's first glance. */
.site-head .wordmark-box::before,
.site-head .wordmark-box::after,
.site-head .wordmark-text::before,
.site-head .wordmark-text::after {
  animation: nw-tear 12.4s steps(1, end) calc(6s + var(--o, 0ms)) infinite;
}
/* Hover/focus swaps the animation *name*, which is the only thing that restarts
   a CSS animation — so the mark tears the instant the pointer lands on it
   instead of waiting out the ambient clock. Leaving restarts the ambient one,
   lead-in and all, so waving across the logo stays quiet. */
.wordmark:hover .wordmark-box::before,
.wordmark:hover .wordmark-box::after,
.wordmark:hover .wordmark-text::before,
.wordmark:hover .wordmark-text::after,
.wordmark:focus-visible .wordmark-box::before,
.wordmark:focus-visible .wordmark-box::after,
.wordmark:focus-visible .wordmark-text::before,
.wordmark:focus-visible .wordmark-text::after {
  animation: nw-tear-hi 2.8s steps(1, end) var(--o, 0ms) 1;
}

/* steps(1, end): every frame below holds until the next one, so these values
   are the tears themselves rather than something to interpolate through.
   At 12.4s the three bands land 56ms / 118ms / 180ms in and are gone by 230ms.
   Every band is kept inside 20%–80% of the box: at a 19.5px line height the
   glyphs only occupy the middle ~55%, and a band outside that tears empty air. */
@keyframes nw-tear {
  0%, 100% { opacity: 0; }
  0.45% { opacity: 1; transform: translate3d(-3px, 0, 0); clip-path: inset(24% 0 58% 0); }
  0.95% { opacity: 1; transform: translate3d(3px, 1px, 0); clip-path: inset(52% 0 28% 0); }
  1.45% { opacity: 1; transform: translate3d(-2px, 0, 0); clip-path: inset(36% 0 46% 0); }
  1.85% { opacity: 0; }
}
/* On hover: a 170ms triple tear on arrival, one lone band at ~1.5s, quiet after
   that — enough to answer the pointer, not enough to strobe if it rests there. */
@keyframes nw-tear-hi {
  0%   { opacity: 1; transform: translate3d(-4px, 0, 0);   clip-path: inset(20% 0 60% 0); }
  2%   { opacity: 1; transform: translate3d(4px, -1px, 0); clip-path: inset(46% 0 34% 0); }
  4%   { opacity: 1; transform: translate3d(-2px, 1px, 0); clip-path: inset(62% 0 20% 0); }
  6%   { opacity: 0; }
  52%  { opacity: 1; transform: translate3d(3px, 0, 0);    clip-path: inset(30% 0 52% 0); }
  55%, 100% { opacity: 0; }
}
/* Without motion the copies are just a doubled, smeared logo — there is nothing
   left of the effect to keep, so they stay hidden rather than freezing mid-tear.
   (`html.no-anim` is covered by the global animation kill at the end of the
   file, which leaves them at their opacity: 0 rest state.) */
@media (prefers-reduced-motion: reduce) {
  .site-head .wordmark-box::before,
  .site-head .wordmark-box::after,
  .site-head .wordmark-text::before,
  .site-head .wordmark-text::after,
  .wordmark:hover .wordmark-box::before,
  .wordmark:hover .wordmark-box::after,
  .wordmark:hover .wordmark-text::before,
  .wordmark:hover .wordmark-text::after,
  .wordmark:focus-visible .wordmark-box::before,
  .wordmark:focus-visible .wordmark-box::after,
  .wordmark:focus-visible .wordmark-text::before,
  .wordmark:focus-visible .wordmark-text::after { animation: none; }
}

.site-nav { display: flex; align-items: center; gap: 2px; }
.site-nav a {
  display: inline-flex; align-items: center; gap: 6px;
  height: 36px;
  padding: 0 13px;
  border-radius: 10px;
  /* Comfortaa only ships one weight with a cyrillic cut (600), so the size
     comes down half a point rather than the weight — it sets wider than the
     interface face it replaces and the row has a language pill to clear. */
  font-family: var(--ui);
  font-size: 12px; font-weight: 600; letter-spacing: .005em;
  color: var(--fg-a55);
  transition: color .18s, background .18s;
}
.site-nav a:hover { color: var(--fg); background: var(--fg-a05); }
.site-nav .nav-ext {
  margin-left: 6px;
  background: var(--fg-a05);
  border: 1px solid var(--fg-a05);
}
/* The external-link mark: the top-right corner of a frame — still a corner
   with no shaft, but its apex points out of the pill, where a tipped chevron
   read as a bare right angle. */
.site-nav .nav-ext::after {
  content: '';
  width: 12px; height: 12px; flex: 0 0 12px;
  background-color: var(--fg-a40);
  -webkit-mask: var(--corner) no-repeat center / contain;
          mask: var(--corner) no-repeat center / contain;
  transition: background-color .18s, transform .18s;
}
.site-nav .nav-ext:hover { background: var(--fg-a12); }
.site-nav .nav-ext:hover::after { background-color: var(--fg); transform: translate(1px, -1px); }

/* Lang selector — the same pill + panel the section galleries use. */
.lang-selector { position: relative; }
.lang-toggle {
  display: inline-flex; align-items: center; gap: 7px;
  height: 36px;
  padding: 0 12px;
  background: var(--fg-a05);
  border: 1px solid var(--fg-a05);
  border-radius: 10px;
  color: var(--fg); cursor: pointer;
  /* Same face as the nav pills beside it — the two-letter code is where a
     circular O shows best. */
  font-family: var(--ui);
  font-size: 11.5px; font-weight: 600; letter-spacing: .06em;
  transition: background .2s, border-color .2s;
}
.lang-toggle svg { color: var(--fg-a55); }
.lang-toggle:hover { background: var(--fg-a12); }
.lang-dropdown {
  position: absolute; top: calc(100% + 10px); right: 0;
  width: 200px;
  background: var(--card-solid);
  border: 1px solid var(--fg-a12);
  border-radius: 14px;
  padding: 6px;
  display: flex; flex-direction: column; gap: 1px;
  box-shadow: 0 24px 60px -18px rgba(0, 0, 0, .55);
  z-index: 95;
  /* Kept in the DOM and folded away — so opening/closing can animate. It unfurls
     from the toggle (top-right), and the options below fall in one after another. */
  transform-origin: top right;
  opacity: 0; visibility: hidden; pointer-events: none;
  transform: translateY(-6px) scale(.98);
  transition: opacity .18s ease, transform .22s var(--ease-out), visibility .18s;
}
.lang-selector.open .lang-dropdown {
  opacity: 1; visibility: visible; pointer-events: auto;
  transform: none;
}
.lang-option {
  --d: 0ms; /* per-item reveal delay; set from --i only while open (staircase) */
  display: flex; align-items: center; gap: 9px;
  padding: 7px 10px;
  border-radius: 8px;
  background: none; border: none; cursor: pointer;
  font-family: var(--text); font-size: 13px; color: var(--fg-a75);
  text-align: left;
  opacity: 0; transform: translateY(-4px);
  /* reveal (opacity/transform) is staggered by --d; hover (background/color) never is */
  transition: opacity .22s ease var(--d), transform .22s ease var(--d), background .12s ease, color .12s ease;
}
.lang-selector.open .lang-option {
  --d: calc(var(--i, 0) * 24ms);
  opacity: 1; transform: none;
}
.lang-option:hover { background: var(--fg-a08); color: var(--fg); }
.lang-option.active { color: var(--fg); font-weight: 600; }
.lang-option.active::after {
  content: '✓';
  margin-left: auto;
  color: var(--acid);
  font-weight: 600;
}
.flag-icon { width: 18px; height: 14px; object-fit: cover; border-radius: 2px; }

/* Burger (mobile) — the 36px icon-button chrome; kept square as a flex child. */
.burger {
  display: none;
  width: 36px; height: 36px;
  min-width: 36px; min-height: 36px;
  flex: 0 0 36px;
  padding: 0;
  background: var(--fg-a05);
  border: 1px solid var(--fg-a05);
  border-radius: 10px;
  cursor: pointer;
  flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  transition: background .2s;
}
.burger:hover { background: var(--fg-a12); }
.burger span {
  display: block; width: 14px; height: 1.5px;
  border-radius: 1px;
  background: var(--fg);
  transition: transform .25s, opacity .2s;
}
.burger.open span:nth-child(1) { transform: translateY(5.5px) rotate(45deg); }
.burger.open span:nth-child(2) { opacity: 0; }
.burger.open span:nth-child(3) { transform: translateY(-5.5px) rotate(-45deg); }

.mobile-menu {
  position: fixed; inset: var(--head-h) 0 0 0;
  z-index: 85;
  background: color-mix(in srgb, var(--bg) 94%, transparent);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  padding: 28px clamp(18px, 6vw, 40px) 20px;
  display: flex; flex-direction: column; gap: 30px;
  overflow-y: auto;
}
.mobile-menu[hidden] { display: none; }
.mobile-menu-group { display: flex; flex-direction: column; }
.mobile-menu-group .mono { margin-bottom: 12px; color: var(--acid); }
.mobile-menu-group a {
  font-family: var(--display);
  font-size: 22px; font-weight: 700; letter-spacing: -.01em; text-transform: uppercase;
  padding: 9px 0;
  border-bottom: 1px solid var(--fg-a08);
  transition: color .15s, padding-left .2s;
}
.mobile-menu-group a:hover { color: var(--acid); padding-left: 10px; }
.mobile-menu-foot {
  margin-top: auto;
  display: flex; justify-content: space-between; align-items: center; gap: 12px;
  font-family: var(--label); font-size: 11px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  font-variant-numeric: tabular-nums; /* the year */
  color: var(--fg-a40);
  padding-top: 18px;
  border-top: 1px solid var(--fg-a08);
}
.mobile-menu-foot a { color: var(--acid); }

/* ===== LAYOUT ===== */
main { max-width: var(--max-w); margin: 0 auto; padding: 0 clamp(18px, 4vw, 48px); position: relative; z-index: 2; }

/* ===== HERO =====
   Echoes the sections' welcome slides: mono eyebrow · display title with a
   subtle vertical white→accent gradient · lede · CTA row. */
/* A container, so the heading can be sized against the space it actually has
   rather than against the viewport — see .hero h1. */
.hero { padding: clamp(64px, 11vh, 120px) 0 0; position: relative; container-type: inline-size; }

/* Nunito sets wider than the old monospace did at the same nominal size once
   you count the tracking, so the eyebrow trades .08em of letter-spacing for the
   half pixel of size — same measure on the line, more of the face's shape. */
.hero-eyebrow {
  margin-bottom: 28px;
  display: flex; align-items: center; gap: 12px;
  font-family: var(--label);
  font-size: 11.5px; font-weight: 700;
  letter-spacing: .18em; text-transform: uppercase;
  color: var(--acid);
  opacity: .9;
}
.hero-eyebrow::before { content: ''; width: 22px; height: 1px; background: var(--acid); }

/* Science Gothic rather than the page face: a variable grotesk with a
   technical, lab-report edge — and it ships a cyrillic cut, so ru/uk keep
   the same face. */
.hero h1 {
  font-family: var(--display);
  /* Both lines have to hold one line each, and how much that costs depends
     entirely on the locale: "One lab / 128 experiments" runs 11.3× the font
     size at its longest, "Jedno laboratorium" runs 14.5×. So the size is not a
     number here — it is that measurement, inverted: --h1-fit is 100 divided by
     the longest of the locale's two lines (measured in this face, at this
     tracking, uppercased), with 2% held back for rendering slack. Multiplied by
     1cqi it says "fill the column, and stop".
     Container units rather than viewport units is what makes that hold at every
     width: cqi is a share of the very column the line has to fit inside, so a
     ratio checked at 1440 is the same ratio at 360. The vw and px terms only
     cap it. Anything not listed below keeps the default, which is the tightest
     of the twelve. */
  --h1-fit: 8;
  font-size: min(9.2vw, 118px, calc(var(--h1-fit) * 1cqi));
  font-weight: 700;
  line-height: .96;
  letter-spacing: -.03em;
  text-transform: uppercase;
  margin-bottom: 36px;
  /* The welcome slides' title treatment: white pouring into the rotating
     accent well below the baseline, so only the lower line picks up a tint. */
  background: linear-gradient(180deg, #fff 0%, var(--acid) 135%);
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}
/* One entry per locale, each the inverse of that locale's longest heading line.
   Polish is the widest of the twelve ("Jedno laboratorium"), Cyrillic next;
   Italian and French are the roomiest. zh/ja/ko fall back to a system face
   whose metrics are not ours to measure, so they are given the value the 118px
   cap would allow anyway rather than one tuned to a font this machine happens
   to have. */
html:lang(pl) .hero h1 { --h1-fit: 6.75; }
html:lang(ru) .hero h1 { --h1-fit: 7.1; }
html:lang(uk) .hero h1 { --h1-fit: 7.4; }
html:lang(es) .hero h1 { --h1-fit: 8; }
html:lang(tr) .hero h1 { --h1-fit: 8.25; }
html:lang(de) .hero h1,
html:lang(en) .hero h1 { --h1-fit: 8.7; }
html:lang(fr) .hero h1 { --h1-fit: 8.75; }
html:lang(it) .hero h1 { --h1-fit: 8.9; }
html:lang(zh) .hero h1,
html:lang(ja) .hero h1,
html:lang(ko) .hero h1 { --h1-fit: 11; }
/* The two lines are set against opposite edges: the first flush left, the
   second flush right and carrying the rotating accent solid rather than as the
   tail of the heading's gradient. That is what makes the pair read as one
   composition — a block of type spanning the column — instead of as two
   sentences stacked. (The off-balance itself is the experimental skin's idea,
   brought across; that skin still draws its own second line as an accent
   outline, which is why the fill is written only for this one. It has to be
   written at all because the h1 pours a gradient through the glyphs and hands
   its children a transparent fill.) */
.hero h1 .outline {
  display: block;
  text-align: right;
}
html:not(.theme-legacy) .hero h1 .outline {
  color: var(--acid);
  -webkit-text-fill-color: var(--acid);
}

.hero-row {
  display: flex; gap: clamp(24px, 5vw, 72px);
  align-items: flex-end;
  flex-wrap: wrap;
  margin-bottom: clamp(44px, 7vh, 76px);
  justify-content: space-between;
}
.hero .lede { max-width: 500px; flex: 1 1 380px; font-size: 15px; line-height: 1.75; color: var(--fg-a55); }
.hero-actions { display: flex; gap: 12px; flex-wrap: wrap; }

/* CTA — built in layers rather than one flat fill: a translucent blurred body,
   a 1px edge lit from the top (a gradient painted through a transparent border:
   fill clipped to padding-box, edge to border-box — no extra element), a top
   inner highlight, a low outer shadow that deepens on hover, and the chevron on
   its own small plate. The chevron is a pseudo-element because the i18n loader
   replaces the element's innerHTML wholesale on every locale switch. */
.btn {
  position: relative;
  display: inline-flex; align-items: center; gap: 6px;
  /* One notch more vertical padding than the Inter label needed: the smaller
     Unbounded size shrinks the line box, and the pill has to keep its height.
     The right side carries the chevron's plate plus the air the mark needs to
     read as a separate object rather than as the last letter of the label —
     see the negative offset on ::after below. */
  padding: 14px 26px 14px 18px;
  border: 1px solid transparent;
  border-radius: 14px;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, .085), rgba(255, 255, 255, .02) 58%, rgba(255, 255, 255, .045)) padding-box,
    linear-gradient(180deg, rgba(255, 255, 255, .3), rgba(255, 255, 255, .08) 45%, rgba(255, 255, 255, .05)) border-box;
  background-repeat: no-repeat;
  backdrop-filter: blur(12px) saturate(1.15);
  -webkit-backdrop-filter: blur(12px) saturate(1.15);
  /* The brand face on the calls to action, matched to the wordmark. It is wide
     enough that the old 13px + positive tracking overflowed the pill on the
     longer locales ("Ausgewählte Projekte"), so the label drops to 11.5px and
     the tracking to zero — Unbounded needs no help spreading out. */
  font-family: var(--brand);
  font-size: 11.5px; font-weight: 600; letter-spacing: 0;
  color: var(--fg);
  /* top highlight · an inner hairline that only lights up on hover · contact
     shadow · lift. Only the shadow moves, so hover stays a cheap repaint. */
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .06),
    inset 0 0 0 1px rgba(255, 255, 255, 0),
    0 2px 5px -3px rgba(0, 0, 0, .6),
    0 14px 32px -18px rgba(0, 0, 0, .9);
  transition: box-shadow .3s var(--ease-out), transform .18s var(--ease-out), color .25s;
}
/* the plate the chevron sits on */
.btn::before {
  content: '';
  /* 10px, not 8: the chevron is nudged 12px back out of the widened padding
     (see ::after) and lands with its centre 22px in from the edge — the plate
     has to sit under it, not under where it used to be. */
  position: absolute; top: 50%; right: 10px; z-index: 0;
  width: 24px; height: 24px;
  border-radius: 8px;
  background: rgba(255, 255, 255, .05);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .05), inset 0 1px 0 rgba(255, 255, 255, .07);
  transform: translateY(-50%);
  pointer-events: none;
  transition: background .25s, box-shadow .25s, transform .25s var(--ease-out);
}
/* The chevron itself — a mask, so it inherits the button's text colour. The
   flex gap alone put the mark right up against the label; the pill's right
   padding is what opens that distance, and the negative offset then walks the
   chevron back out into it so the pill does not just grow a bald edge. */
.btn::after {
  content: '';
  position: relative; right: -12px; z-index: 1;
  width: 16px; height: 16px; flex: 0 0 16px;
  background-color: currentColor;
  -webkit-mask: var(--chev) no-repeat center / contain;
          mask: var(--chev) no-repeat center / contain;
  transition: transform .22s var(--ease-out);
}
.btn:hover {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .1),
    inset 0 0 0 1px rgba(255, 255, 255, .09),
    0 3px 8px -4px rgba(0, 0, 0, .6),
    0 20px 44px -20px rgba(0, 0, 0, 1);
}
/* Both shift by the same 2px, or the mark slides off-centre inside its plate. */
.btn:hover::before { background: rgba(255, 255, 255, .1); transform: translate(2px, -50%); }
.btn:hover::after { transform: translateX(2px); }
/* pressed: sink, and pull the shadow in under the pill */
.btn:active {
  transform: scale(.98);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .05),
    0 1px 2px -1px rgba(0, 0, 0, .6),
    0 5px 14px -10px rgba(0, 0, 0, .9);
}
.btn:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--acid) 78%, #fff);
  outline-offset: 3px;
}

/* Primary — the rotating accent as a lit pill: --acid over --acid-dark (both
   rewritten at runtime, so nothing here names a hue), a coloured glow in place
   of the black shadow, and a specular band parked off the left edge that sweeps
   across once per hover. */
.btn-primary {
  color: var(--acid-ink);
  /* Its fill is opaque, so the inherited blur buys nothing and would cost a
     backdrop snapshot on every frame of the sheen. */
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  background:
    linear-gradient(100deg, rgba(255, 255, 255, 0) 42%, rgba(255, 255, 255, .4) 50%, rgba(255, 255, 255, 0) 58%) padding-box,
    /* Shallow on purpose: some palettes have a properly dark --acid-dark, and
       the label has to stay readable over the foot of the pill. */
    linear-gradient(180deg, color-mix(in srgb, var(--acid) 92%, #fff), var(--acid) 48%, color-mix(in srgb, var(--acid-dark) 70%, var(--acid))) padding-box,
    linear-gradient(180deg, rgba(255, 255, 255, .6), rgba(255, 255, 255, .18) 50%, rgba(255, 255, 255, .08)) border-box;
  background-size: 220% 100%, auto, auto;
  background-position: 130% 0, 0 0, 0 0;
  background-repeat: no-repeat;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .4),
    0 2px 6px -3px color-mix(in srgb, var(--acid) 45%, transparent),
    0 16px 38px -14px color-mix(in srgb, var(--acid) 55%, transparent);
}
.btn-primary::before {
  background: color-mix(in srgb, var(--acid-ink) 10%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--acid-ink) 8%, transparent);
}
.btn-primary:hover {
  animation: btn-sheen 1s var(--ease-out);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .5),
    0 3px 9px -4px color-mix(in srgb, var(--acid) 50%, transparent),
    0 22px 48px -14px color-mix(in srgb, var(--acid) 68%, transparent);
}
.btn-primary:hover::before { background: color-mix(in srgb, var(--acid-ink) 16%, transparent); }
/* Pressed = the darker cut of the same accent (rotated together with --acid);
   only the images are swapped, so the per-layer size/position/clip lists hold. */
.btn-primary:active {
  background-image:
    none,
    /* Bottomed at the same 70% mix the rest state uses: raw --acid-dark drops
       the ink label under 4.5:1 on the darkest palette rows. */
    linear-gradient(180deg, color-mix(in srgb, var(--acid-dark) 55%, var(--acid)), color-mix(in srgb, var(--acid-dark) 70%, var(--acid))),
    linear-gradient(180deg, rgba(255, 255, 255, .34), rgba(255, 255, 255, .06));
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .22),
    0 2px 9px -6px color-mix(in srgb, var(--acid) 55%, transparent);
}
/* A background percentage slides the oversized layer leftwards, so sweeping the
   band across the pill left-to-right means counting down from 130% to -30%. */
@keyframes btn-sheen {
  from { background-position: 130% 0, 0 0, 0 0; }
  to { background-position: -30% 0, 0 0, 0 0; }
}

/* stats — a mono-labelled tile row inside one window-styled card */
.hero-stats {
  display: grid; grid-template-columns: repeat(4, 1fr);
  background: var(--card);
  border: 1px solid var(--fg-a08);
  border-radius: var(--radius);
  box-shadow: var(--shadow-raise);
  overflow: hidden;
}
.hero-stats > div {
  padding: 22px 24px;
  border-right: 1px solid var(--fg-a05);
  border-bottom: 1px solid var(--fg-a05);
  transition: background .2s;
}
.hero-stats > div:hover { background: var(--fg-a05); }
.hero-stats > div:nth-child(4n) { border-right: none; }
.hero-stats > div:nth-child(n+5) { border-bottom: none; }
@media (max-width: 680px) {
  .hero-stats { grid-template-columns: repeat(2, 1fr); }
  .hero-stats > div:nth-child(2n) { border-right: none; }
  .hero-stats > div:nth-child(n+5) { border-bottom: 1px solid var(--fg-a05); }
  .hero-stats > div:nth-child(n+7) { border-bottom: none; }
}
/* The odometers live here (main.js counts them up on first sight), so the
   figures are asked for in their tabular cut: with proportional digits a 1
   rolling into an 8 shifts everything to its right by a hair, sixty times a
   second. The tiles themselves cannot move — the grid is four 1fr columns and
   the padding is fixed, so nothing outside the number is measured off it. */
.hero-stats dt {
  font-family: var(--display);
  font-size: clamp(26px, 3.6vw, 40px); font-weight: 500; letter-spacing: -.03em;
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
  color: var(--acid);
}
.hero-stats dd {
  font-family: var(--label);
  font-size: 10px; font-weight: 700; color: var(--fg-a40);
  letter-spacing: .14em; text-transform: uppercase;
  margin-top: 8px;
}

/* ===== AURORA =====
   Quiet drifting blobs tinted by the rotating accent (--goo1..4 are the palette
   slots the head script / main.js keep writing). Scoped to the hero box: its
   container-type containment captures this fixed layer, so it scrolls away with
   the hero rather than following the page. */
.hero-aurora {
  position: fixed;
  top: 0; bottom: 0; left: 50%;
  width: 100vw;
  transform: translateX(-50%);
  pointer-events: none;
  overflow: hidden;
  opacity: .5;
  z-index: 0;
  -webkit-mask-image: radial-gradient(75% 80% at 50% 38%, #000 40%, transparent 100%);
          mask-image: radial-gradient(75% 80% at 50% 38%, #000 40%, transparent 100%);
}
/* Laid out at half size and scaled ×2, so the blur runs on a quarter of the
   pixels (main.js maps pointer coordinates to the halved space to match). */
.aurora-field {
  position: absolute; left: 0; top: 0;
  width: 50%; height: 50%;
  transform: scale(2);
  transform-origin: 0 0;
  filter: blur(34px);
}
.aurora-field i {
  position: absolute;
  width: 26vmin; height: 26vmin;
  border-radius: 50%;
  mix-blend-mode: screen;
  opacity: .32;
}
.aurora-field .g1 { background: radial-gradient(circle at center, var(--goo1, var(--acid)) 0, transparent 62%); top: 2%; left: 12%; animation: aurora-a 34s ease-in-out infinite; }
.aurora-field .g2 { background: radial-gradient(circle at center, var(--goo2, var(--acid)) 0, transparent 62%); top: 24%; left: 54%; animation: aurora-b 44s ease-in-out infinite -9s; opacity: .26; }
.aurora-field .g3 { background: radial-gradient(circle at center, var(--goo3, var(--acid)) 0, transparent 62%); top: -10%; left: 64%; animation: aurora-a 52s ease-in-out infinite -21s; opacity: .22; }
.aurora-field .g4 { background: radial-gradient(circle at center, var(--goo4, var(--acid)) 0, transparent 62%); top: 36%; left: -6%; animation: aurora-b 40s ease-in-out infinite -5s; opacity: .18; }
.aurora-field .g-pointer {
  width: 14vmin; height: 14vmin;
  background: radial-gradient(circle at center, var(--acid) 0, transparent 60%);
  opacity: .22;
  top: 0; left: 0;
  will-change: transform;
}
/* travel distances are halved too — the ×2 scale doubles them back */
@keyframes aurora-a {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50% { transform: translate(5vmin, 7vmin) scale(1.12); }
}
@keyframes aurora-b {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50% { transform: translate(-6vmin, -5vmin) scale(.92); }
}

/* ===== MARQUEE ===== */
.tape {
  margin: clamp(8px, 2vh, 20px) calc(-50vw + 50%) 0;
  border-top: 1px solid var(--fg-a05);
  border-bottom: 1px solid var(--fg-a05);
  background: rgba(244, 242, 255, .02);
  overflow: hidden;
  white-space: nowrap;
  padding: 13px 0;
  /* The strip dissolves at the viewport edges instead of hitting them. */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
}
.tape-track { display: inline-block; animation: tape 38s linear infinite; }
.tape .w,
.tape .d {
  /* Outside every Nunito subset: name a face that has it rather than letting
     the fallback chain fetch a family for one glyph. */
  font-family: system-ui, sans-serif;
  display: inline-block;
  font-family: var(--label);
  font-size: 11.5px; font-weight: 700; letter-spacing: .22em; text-transform: uppercase;
  color: var(--fg-a40);
  margin: 0 17px; /* equal gap on both sides of every token */
}
.tape .d { color: var(--acid); letter-spacing: 0; opacity: .7; }
@keyframes tape { from { transform: translateX(0); } to { transform: translateX(-50%); } }

/* ===== BLOCKS =====
   The band between two sections is the only thing separating a stats card from
   an ordinal eyebrow, and at 60–100px the two were reading as one column of
   boxes. Half again as much air, and the numbered headings start behaving like
   chapter openings. */
.block { padding: clamp(88px, 13vh, 150px) 0; }
.block-head { position: relative; display: flex; align-items: baseline; gap: 20px; margin-bottom: 14px; }
/* Ordinal eyebrow — numbers are locale-neutral, so no i18n string needed. */
.block-head::before {
  position: absolute; top: -24px; left: 1px;
  font-family: var(--label);
  font-size: 11px; font-weight: 700; letter-spacing: .22em;
  font-variant-numeric: tabular-nums;
  color: var(--acid);
  opacity: .9;
}
#featured .block-head::before { content: '01'; }
#sections .block-head::before { content: '02'; }
.block-head h2 {
  font-family: var(--display);
  font-size: clamp(28px, 4.2vw, 46px);
  font-weight: 700; letter-spacing: -.02em; text-transform: uppercase;
  line-height: 1;
}
.block-sub { max-width: 560px; font-size: 14px; color: var(--fg-a55); line-height: 1.7; margin-bottom: clamp(28px, 5vh, 48px); }
#featured .block-sub { max-width: 570px; }

/* ===== FEATURED — window cards ===== */
.feature-list { display: flex; flex-direction: column; gap: 12px; }
.feature-row {
  position: relative;
  display: grid;
  /* Two tracks now: the status badge left the flow for the top-right corner. */
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: clamp(14px, 3vw, 36px);
  /* The top padding is floored at the badge's own height, so content always
     starts below the corner tag — no width can make the two meet. */
  padding: clamp(26px, 3.2vh, 30px) clamp(18px, 2.6vw, 28px) clamp(18px, 3vh, 26px);
  background: var(--card);
  border: 1px solid var(--fg-a05);
  border-radius: var(--radius);
  overflow: hidden;
  /* the CTA's top inner highlight, at surface strength */
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .045);
  transition: border-color .25s, transform .25s var(--ease-out), box-shadow .3s;
}
/* accent orb in the corner — lights up on hover */
.feature-row::before {
  content: '';
  position: absolute; top: -80px; right: -50px;
  width: 240px; height: 240px;
  border-radius: 50%;
  background: radial-gradient(circle, var(--acid) 0, transparent 65%);
  filter: blur(40px);
  opacity: 0;
  transition: opacity .35s;
  pointer-events: none;
}
.feature-row:hover {
  border-color: color-mix(in srgb, var(--acid) 20%, var(--fg-a12));
  box-shadow: var(--shadow-hover), inset 0 1px 0 rgba(255, 255, 255, .07);
}
.feature-row:hover::before { opacity: .12; }
/* The orb and the running light are pseudo-elements, i.e. positioned siblings
   that paint after the in-flow text; one layer up keeps the content clean. */
.feature-body, .feature-arrow { position: relative; }
.feature-body, .feature-status, .feature-arrow { z-index: 1; }
.feature-body { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.feature-name {
  font-family: var(--display);
  font-size: clamp(18px, 2.4vw, 24px);
  font-weight: 700; letter-spacing: -.01em; text-transform: uppercase;
}
.feature-desc { font-size: 13px; color: var(--fg-a55); }
/* A corner tag rather than a floating pill: flush into the card's top-right,
   so the only corner it rounds off is the free one (bottom-left) plus the card's
   own at top-right — minus the 1px border, since it sits inside it. The two
   edges that run along the card's borders are square and get the hairline; the
   other two have nothing to sit against. */
.feature-status {
  position: absolute; top: 0; right: 0;
  display: inline-flex; align-items: center; gap: 6px;
  height: 24px;
  padding: 0 12px 0 11px;
  font-family: var(--label);
  font-size: 9.5px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
  color: var(--fg-a55);
  background: var(--fg-a05);
  border: 0;
  border-left: 1px solid var(--fg-a05);
  border-bottom: 1px solid var(--fg-a05);
  border-radius: 0 calc(var(--radius) - 1px) 0 10px;
  white-space: nowrap;
}
.feature-status::before {
  content: '';
  width: 5px; height: 5px; border-radius: 50%;
  background: var(--acid);
  box-shadow: 0 0 8px color-mix(in srgb, var(--acid) 80%, transparent);
  animation: status-pulse 2.4s ease-in-out infinite;
}
@keyframes status-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: .35; }
}
.feature-arrow { display: inline-flex; align-items: center; color: var(--fg-a40); transition: color .18s, transform .3s var(--ease-out); }
.feature-row:hover .feature-arrow { color: var(--acid); transform: translateX(5px); }

/* Running light along the rows. The old skin sent it down the hairline between
   rows; the rows are separate cards now, so it runs along each card's own
   bottom rail instead — same read, and the rounded corners clip the ends where
   the gradient has already faded out. One 7s pass, quartered between the four
   rows by negative delays, so at most one light is ever crossing. It steps
   aside on hover: the row lights its own edge then. */
.feature-row::after {
  transition: opacity .25s;
  content: '';
  position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background-image: linear-gradient(90deg, transparent, color-mix(in srgb, var(--acid) 80%, #fff) 50%, transparent);
  background-size: 45% 100%;
  background-repeat: no-repeat;
  background-position: -100% 0;
  opacity: .45;
  animation: sep-run 7s linear infinite;
  pointer-events: none;
}
.feature-row:nth-child(2)::after { animation-delay: -1.75s; }
.feature-row:nth-child(3)::after { animation-delay: -3.5s; }
.feature-row:nth-child(4)::after { animation-delay: -5.25s; }
.feature-row:hover::after { opacity: 0; }
/* A background percentage slides the oversized layer, so a left-to-right pass
   means counting up from -100% (fully off the left) to 200%. */
@keyframes sep-run {
  from { background-position: -100% 0; }
  to { background-position: 200% 0; }
}

/* ===== SECTIONS GRID — window cards, one per gallery, each carrying its
   section's own accent hue (--ca, set inline in the markup) ===== */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr));
  gap: 14px;
}
.card {
  --ca: var(--acid); /* fallback; every card sets its section hue inline */
  position: relative;
  display: flex; flex-direction: column;
  gap: 10px;
  padding: clamp(20px, 2.6vw, 26px);
  min-height: 230px;
  background: var(--card);
  border: 1px solid var(--fg-a08);
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .045);
  /* No transform in the list: the card does not move on hover (see :hover). */
  transition: border-color .25s, box-shadow .3s;
}
/* section-tinted orb in the corner */
.card::before {
  content: '';
  position: absolute; top: -70px; right: -60px;
  width: 220px; height: 220px;
  border-radius: 50%;
  background: radial-gradient(circle, var(--ca) 0, transparent 65%);
  filter: blur(36px);
  opacity: .09;
  transition: opacity .35s;
  pointer-events: none;
}
/* Pointer-tracked wash in the section's own hue. --px/--py are two percentages
   written by one rAF-throttled listener in main.js; they default to the card's
   centre, so with a coarse pointer — or with motion off, when main.js does not
   listen at all — this degrades to a plain centred glow that still only appears
   on hover. The old skin flooded the whole card with the accent and inverted
   the text; that belonged to the flat-panel look. This is the same "the card
   lights up under your hand" idea rebuilt for a translucent window surface. */
.card::after {
  content: '';
  position: absolute; inset: 0;
  border-radius: inherit;
  background: radial-gradient(circle 220px at var(--px, 50%) var(--py, 50%),
              color-mix(in srgb, var(--ca) 20%, transparent), transparent 72%);
  opacity: 0;
  transition: opacity .3s;
  pointer-events: none;
}
.card:hover::after { opacity: 1; }
/* Both card pseudo-elements are positioned siblings and paint after the in-flow
   text, so the content is lifted one layer clear of the wash. */
.card h3, .card p, .card-tech { position: relative; }
.card h3, .card p, .card-tech, .card-count, .card-arrow { z-index: 1; }
/* The card stays exactly where it is. Hover is light, not motion: the wash
   above follows the pointer, the top hairline picks up the section hue, the two
   marks brighten and the corner chip charges — nothing rises. The border only
   leans a fifth of the way into the section hue; further than that and six
   cards in six different colours turn the grid into a swatch chart. */
.card:hover {
  border-color: color-mix(in srgb, var(--ca) 20%, var(--fg-a12));
  box-shadow: var(--shadow-hover), inset 0 1px 0 color-mix(in srgb, var(--ca) 55%, rgba(255, 255, 255, .07));
}
.card:hover::before { opacity: .22; }
.card-count {
  position: absolute; top: 20px; right: 20px;
  font-family: var(--label);
  font-size: 10.5px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  /* the scene count sits at a fixed corner — tabular digits keep 4 and 56 on
     the same rhythm down the grid */
  font-variant-numeric: tabular-nums;
  color: var(--fg-a40);
  transition: color .25s;
}
.card-count b { font-weight: 700; color: var(--ca); }
/* The two marks come up out of the dim end of the ladder on hover. They used to
   rise two pixels with the card as well; the card does not rise any more, and a
   label drifting off a surface that stays put reads as a wobble. */
.card:hover .card-count { color: var(--fg-a75); }
.card:hover .card-tech { color: var(--fg-a55); }
.card h3 {
  font-family: var(--display);
  font-size: clamp(20px, 2.4vw, 24px);
  font-weight: 700; letter-spacing: -.01em; text-transform: uppercase;
  margin-top: auto;
}
/* Reserve 3 lines so descriptions of different length keep the bottom-anchored
   title / desc / tech group the same height — otherwise the titles sit at
   different heights across the row. */
.card p { font-size: 13px; color: var(--fg-a55); line-height: 1.65; min-height: 4.95em; }
/* The longest stack line ("Canvas 2D · Three.js · JS") has to clear the arrow
   chip on a 300px card, so this slot takes the tracking cut rather than the
   size bump the other labels got. */
.card-tech {
  font-family: var(--label);
  font-size: 9.5px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: var(--fg-a40);
  margin-top: 6px;
  padding-right: 44px; /* keep clear of the corner arrow chip */
  transition: color .25s;
}
/* Same plate as the CTA chevron, one size up; the mark is tipped 45deg so it
   points out of the card. */
.card-arrow {
  position: absolute; bottom: 18px; right: 18px;
  display: grid; place-items: center;
  width: 30px; height: 30px;
  border-radius: 9px;
  border: 1px solid var(--fg-a08);
  background: var(--fg-a05);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .05);
  color: var(--fg-a55);
  transition: background .25s, color .25s, border-color .25s, box-shadow .3s, transform .25s var(--ease-out);
}
/* Upright, not tipped: a section card leads further in, and two equal arms at
   45deg read as a crop mark rather than a direction. On hover the chip charges:
   it fills with the section hue and throws a short glow of it underneath. */
.card:hover .card-arrow {
  background: color-mix(in srgb, var(--ca) 20%, transparent);
  border-color: color-mix(in srgb, var(--ca) 35%, transparent);
  color: var(--ca);
  transform: translateX(2px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .09),
    0 0 18px -4px color-mix(in srgb, var(--ca) 65%, transparent);
}

/* ===== FOOTER ===== */
.site-foot {
  position: relative; z-index: 2;
  margin-top: clamp(40px, 8vh, 72px);
  background: #08090e;
  overflow: hidden;
}
/* accent hairline along the top edge */
.foot-accent {
  position: relative; z-index: 3;
  height: 1px;
  background: linear-gradient(90deg,
    transparent,
    color-mix(in srgb, var(--acid) 50%, transparent) 30%,
    color-mix(in srgb, var(--acid) 65%, transparent) 50%,
    color-mix(in srgb, var(--acid) 50%, transparent) 70%,
    transparent);
}
/* Fine checkered grid under the top edge — the same 22px lattice the previous
   skin drew, re-tinted onto the fg ladder instead of the retired --ink vars so
   it sits a step under the footer's own hairlines. Two scales: the 22px weave
   at a05 for texture, a 110px lattice at a08 every fifth line to give it the
   read of engineering paper rather than a noise field. The domed radial mask
   keeps it to the lit part of the footer, fading out well before the columns —
   past that it would only make the link lists harder to scan. */
.site-foot::before {
  content: '';
  position: absolute; inset: 0; z-index: 0;
  background-image:
    linear-gradient(rgba(244, 242, 255, .13) 1px, transparent 1px),
    linear-gradient(90deg, rgba(244, 242, 255, .13) 1px, transparent 1px),
    linear-gradient(var(--fg-a08) 1px, transparent 1px),
    linear-gradient(90deg, var(--fg-a08) 1px, transparent 1px);
  background-size: 18px 18px;
  -webkit-mask-image: radial-gradient(85% 78% at 50% -6%, #000 8%, transparent 62%);
          mask-image: radial-gradient(85% 78% at 50% -6%, #000 8%, transparent 62%);
  pointer-events: none;
  opacity: .25;
}
/* soft lamp glow spilling from the top edge (about half visible) */
.foot-glow {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translate(-50%, -50%);
  width: min(1100px, 90vw);
  height: 440px;
  z-index: 1;
  background: radial-gradient(50% 60% at 50% 50%,
  color-mix(in srgb, var(--acid) 20%, transparent), transparent 70%);
  filter: blur(28px);
  opacity: .55;
  pointer-events: none;
}
.foot-inner { position: relative; z-index: 2; max-width: var(--max-w); margin: 0 auto; padding: 0 clamp(18px, 4vw, 48px); }

/* top band: brand on the left, featured chips on the right */
.foot-top {
  display: flex; align-items: center; justify-content: space-between;
  gap: 20px 32px; flex-wrap: wrap;
  padding: clamp(40px, 6vh, 60px) 0 32px;
  border-bottom: 1px solid var(--fg-a08);
}
.foot-word { margin-right: 0; }
.foot-featured { display: flex; gap: 8px; flex-wrap: wrap; }
.foot-featured a {
  display: inline-flex; align-items: center;
  height: 32px; padding: 0 14px;
  border-radius: 10px;
  border: 1px solid var(--fg-a08);
  background: var(--fg-a05);
  font-family: var(--label);
  font-size: 10.5px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: var(--fg-a55);
  transition: background .2s, color .2s, border-color .2s;
}
.foot-featured a:hover { background: var(--fg-a12); color: var(--fg); }

/* section columns */
.foot-grid {
  padding: clamp(28px, 5vh, 44px) 0 24px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 28px 20px;
}
.foot-col { display: flex; flex-direction: column; gap: 8px; }
/* The column heads carry the accent, as they do in the experimental skin: six
   lit markers are what let the eye find the six sections in a wall of grey
   links, and the rotating hue ties the footer to the rest of the page. */
.foot-head {
  font-family: var(--label);
  font-size: 11px; font-weight: 700; letter-spacing: .16em; text-transform: uppercase;
  color: var(--acid);
  margin-bottom: 6px;
  width: fit-content;
  transition: color .15s;
}
/* Already accent at rest, so hover has to go somewhere else: towards white,
   which brightens every palette entry the rotation can land on. */
.foot-head:hover { color: color-mix(in srgb, var(--acid) 60%, #fff); }
.foot-col a:not(.foot-head) {
  font-size: 12px; color: var(--fg-a55);
  width: fit-content;
  transition: color .15s;
}
.foot-col a:not(.foot-head):hover { color: var(--fg); }
/* Five scenes are a taste, not a table of contents — every column ends with the
   way into the rest of its section. Set apart from the picks above it by a
   little air and by the accent, and marked with the page's one chevron. The
   mark is a pseudo-element because the i18n loader rewrites the link's
   innerHTML on every locale switch and would eat anything put in the markup. */
.foot-col a.foot-more {
  display: inline-flex; align-items: center; gap: 6px;
  margin-top: 4px;
  color: var(--acid);
  transition: color .15s, gap .2s var(--ease-out);
}
.foot-col a.foot-more::after {
  content: '';
  width: 11px; height: 11px; flex: 0 0 11px;
  background-color: currentColor;
  -webkit-mask: var(--chev) no-repeat center / contain;
          mask: var(--chev) no-repeat center / contain;
}
.foot-col a.foot-more:hover { color: color-mix(in srgb, var(--acid) 60%, #fff); gap: 10px; }

.foot-bar {
  border-top: 1px solid var(--fg-a08);
  display: flex; justify-content: space-between; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 20px 0 26px;
  font-family: var(--label); font-size: 11px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
  font-variant-numeric: tabular-nums; /* the year */
  color: var(--fg-a40);
}
.foot-bar a { color: var(--acid); transition: opacity .15s; }
.foot-bar a:hover { opacity: .75; }

/* ===== FOOTER CURTAIN =====
   On a desktop screen the footer is not pushed up the page by the content — it
   is parked against the bottom of the viewport and the content is an opaque
   sheet sliding over it. Scrolling the last screen therefore *uncovers* the
   footer from its top edge down, the way a curtain goes up, instead of dragging
   it into view. Nothing animates: it is two boxes and a stacking order, so it
   costs nothing per frame and behaves identically with animations switched off.

   Three parts, and all three are needed:
     · .foot-dock is taken out of the flow and pinned to the bottom, one layer
       down (z-index 0);
     · .page is everything above it — header, hero, blocks — raised one layer up
       and given the page background, because a transparent sheet would show the
       footer through it from the first screen;
     · and the flow still has to reserve the footer's height, or the last section
       would scroll straight past it. That is .foot-space, standing in for the
       box that left the flow, sized from --foot-h — measured by main.js, because
       how tall the footer ends up depends on how its six columns wrap.

   Below 768px none of it applies: the dock is a plain block again and footer
   follows content the ordinary way. */
.page { position: relative; }
.foot-dock { position: relative; }

@media (min-width: 768px) {
  .page {
    z-index: 1;
    background: var(--bg);
  }
  .foot-dock {
    position: fixed; left: 0; right: 0; bottom: 0;
    z-index: 0;
  }
  .foot-space { height: var(--foot-h, 0px); }
  /* The hem of the sheet, lit in the rotating accent. Without it the reveal is
     two near-black fields meeting at an invisible line and the footer looks
     like it is being clipped rather than uncovered; with it there is an edge
     travelling up the screen. It is the footer's own top rule, moved onto the
     thing that actually moves — which is why the real one steps aside below. */
  .page::after {
    content: '';
    position: absolute; left: 0; right: 0; bottom: 0;
    height: 1px;
    background: linear-gradient(90deg,
      transparent,
      color-mix(in srgb, var(--acid) 50%, transparent) 30%,
      color-mix(in srgb, var(--acid) 65%, transparent) 50%,
      color-mix(in srgb, var(--acid) 50%, transparent) 70%,
      transparent);
    pointer-events: none;
  }
  /* Only the property is overridden, not the paint — so the experimental skin's
     own thicker rule stays exactly as it is and simply stops being drawn while
     the curtain owns that job. */
  .foot-dock .foot-accent { visibility: hidden; }
}
/* Escape hatch. A viewport shorter than the footer would clip its top edge off
   the screen, and there is no scrolling inside a fixed box — so main.js
   measures both and puts the footer back in the flow when it will not fit. */
html.foot-static .page { z-index: auto; }
html.foot-static .foot-dock { position: relative; }
html.foot-static .foot-space { height: 0; }
/* …and with it the lit hem, which has nothing to uncover any more, and the
   footer's own top rule, which has its job back. */
html.foot-static .page::after { display: none; }
html.foot-static .foot-dock .foot-accent { visibility: visible; }
/* The sheet is its own layer now, and the switch rail sits outside it — which
   would float the two pills over an open mobile menu. They were always hidden
   behind it; keep them there. */
body:has(.mobile-menu:not([hidden])) .corner-switches { opacity: 0; pointer-events: none; }

/* ===== RESPONSIVE ===== */
/* The 108px indent this replaces was solving a real collision — the switch rail
   is fixed in the bottom-left corner and the footer's last line ends up right
   under it — but it solved it sideways, which knocked the copyright out of
   alignment with the six columns above it at every width, including the ones
   where the rail was nowhere near. The rail reaches into the column only below
   about 1300px (past that the centred 1160 column starts far enough in), so the
   clearance is taken there, and downwards. */
@media (max-width: 1295px) {
  .foot-bar { padding-bottom: 68px; }
}
@media (max-width: 900px) {
  .site-nav { display: none; }
  .burger { display: flex; }
}
@media (max-width: 760px) {
  /* The status badge is in the corner now, so the row is just body + arrow. */
  .feature-row { grid-template-columns: 1fr 32px; }
  .feature-arrow { align-self: center; justify-self: end; }
  .foot-grid { grid-template-columns: repeat(2, 1fr); }
  .foot-featured { flex-direction: column; align-items: flex-start; gap: 10px; }
  .spotlight { display: none; }
}
/* The eyebrow is one long tracked string and the locales stretch it further —
   "NextWell · Laboratorio de experimentos" is 38 characters against the English
   36. It wrapped on a 390px screen once, so below 430px the size and the
   tracking both come down and the rule and its gap shrink with them. Re-checked
   against Nunito, which sets a touch narrower than the monospace it replaced
   (~0.65em a cap against a flat 0.6em advance, but no tracking wasted on the
   spaces): at 10px/.12em the longest locale runs ~295px against the 354px a
   390px screen leaves, and still clears 360px. Nothing is asked to hold a
   second line. */
@media (max-width: 430px) {
  .hero-eyebrow {
    font-size: 10px;
    letter-spacing: .12em;
    gap: 8px;
    margin-bottom: 22px;
  }
  .hero-eyebrow::before { width: 14px; }
}
/* Narrow phones: the CTAs go full width and split, so a long localized label
   can never push a pill past the edge — it wraps inside instead. */
@media (max-width: 420px) {
  .hero-actions { width: 100%; }
  .btn { flex: 1 1 100%; justify-content: space-between; }
}
@media (max-width: 760px), (prefers-reduced-motion: reduce) {
  .hero-aurora { display: none; }
  /* A light crossing a card is a desktop-scale gesture; on a phone the four
     rows fill the screen and it reads as flicker. */
  .feature-row::after { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .tape-track { animation: none; }
  .feature-status::before { animation: none; }
  .btn-primary:hover { animation: none; }
  .reveal { opacity: 1; transform: none; transition: none; }
  *, *::before, *::after { transition-duration: 0.01ms !important; }
}

/* ===== corner switches (bottom-left) =====
   Two independent controls sharing one rail and one chrome: animations on/off
   (html.no-anim, remembered in lab-home-motion) and, to its right, the skin
   switch (html.theme-legacy, lab-home-theme). Each is a 40px square at rest and
   unfurls its own label on hover — the row grows rightwards from the corner,
   which is why the animations switch, the one you reach for in a hurry, holds
   the corner itself. */
.corner-switches {
  position: fixed; left: 16px; bottom: 16px; z-index: 80;
  display: flex; align-items: flex-end; gap: 8px;
  /* An unfurled label plus a second pill must never reach past the viewport;
     the buttons are allowed to shrink and clip their own label instead. */
  max-width: calc(100vw - 32px);
}
.corner-btn {
  display: flex; align-items: center;
  flex: 0 1 auto; min-width: 40px;
  height: 40px;
  padding: 0;
  background: rgba(13, 14, 20, 0.85);
  backdrop-filter: blur(14px) saturate(1.2);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  border: 1px solid var(--fg-a08); border-radius: 12px;
  color: var(--fg-a55); cursor: pointer;
  overflow: hidden;
  transition: color .2s, border-color .2s;
}
.corner-btn:hover,
.corner-btn:focus-visible { color: var(--fg); border-color: var(--fg-a25); }
.motion-icon {
  display: flex; align-items: center; justify-content: center; gap: 3px;
  width: 38px; height: 38px; flex: 0 0 38px;
}
.motion-icon svg { display: block; }
.motion-icon i { width: 2.5px; height: 12px; border-radius: 1px; background: currentColor; }
/* Paused state swaps the two pause bars for a play triangle (see .no-anim
   below). The theme switch carries an svg flask in the same slot, so both the
   bars and the triangle are scoped to the animations button. */
.motion-icon::before {
  content: ""; display: none;
  width: 0; height: 0; margin-left: 3px;
  border: 7px solid transparent;
  border-right: 0;
  border-left: 11px solid var(--acid);
}
.motion-label {
  max-width: 0; opacity: 0;
  overflow: hidden; white-space: nowrap;
  font-family: var(--label);
  font-size: 10.5px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  transition: max-width .35s ease, opacity .25s ease, padding-right .35s ease;
}
.corner-btn:hover .motion-label,
.corner-btn:focus-visible .motion-label { max-width: 260px; opacity: 1; padding-right: 14px; }
.motion-on { display: none; }
/* Each switch reads its own state. The animations one keys off the class it
   sets, so the label is right even when the class arrives from localStorage
   before main.js runs; the theme one keys off aria-pressed, which is the same
   hook theme-legacy.css uses from its side. */
html.no-anim .motion-toggle .motion-on { display: inline; }
html.no-anim .motion-toggle .motion-off { display: none; }
html.no-anim .motion-toggle .motion-icon i { display: none; }
html.no-anim .motion-toggle .motion-icon::before { display: block; }
.theme-toggle[aria-pressed="true"] .motion-on { display: inline; }
.theme-toggle[aria-pressed="true"] .motion-off { display: none; }

/* ===== animations disabled (.no-anim on <html>, toggled by the left button of
   the rail above, and restored from lab-home-motion before the first paint).
   The accent stays on the random colour picked at load; main.js stops the
   rotation. Everything self-moving (marquee, aurora, spotlight, status dots,
   the wordmark glitch, the running lights) is off; hover states stay but snap
   instantly — the global animation/transition kill below is what makes them
   instant. The glitch layers need no rule of their own: killing the animation
   leaves them at their opacity: 0 rest state. main.js checks the same class
   before tracking the pointer across the section cards. ===== */
html.no-anim *,
html.no-anim *::before,
html.no-anim *::after {
  animation: none !important;
  transition: none !important;
}
html.no-anim { scroll-behavior: auto; }
html.no-anim .spotlight,
html.no-anim .hero-aurora,
html.no-anim .feature-row::after { display: none; }
html.no-anim .reveal { opacity: 1; transform: none; }
