/* Astarter landing · pure HTML/CSS/JS */

/* ── Self-hosted fonts: Inter Variable ──
 * Switched from Poppins (5 weights, 5 files) to Inter Variable (1 file,
 * all weights 100-900). Matches what Solana / Sui / Aptos / Optimism /
 * Polygon / Uniswap / Linear / Vercel / Stripe all use · it's the de
 * facto serious-protocol typeface.
 *
 * Single file ~352 KB; one HTTP request covers every weight on the page. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../assets/fonts/InterVariable.woff2") format("woff2-variations");
}

html {
  /* scroll-behavior: smooth REMOVED · on Chromium it also interpolates
   * mouse-wheel scroll, causing the "stuck" feeling. Wheel scroll now
   * uses native instant response. Anchor-link smoothness handled per-link
   * via JS when needed (.scrollIntoView({behavior:'smooth'})). */
  scroll-behavior: auto;
}

:root {
  --purple: #6f00ff;
  --purple-hover: #8807ff;
  --bg: #040404;
  --font: "Inter", "PingFang SC", "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Noto Sans CJK SC", "Noto Sans CJK JP", "Noto Sans CJK KR", "Microsoft YaHei", "Yu Gothic", "Malgun Gothic", system-ui, -apple-system, "Segoe UI", sans-serif;

  /* ── Design-token typography scale ─────────────────────────────────
   * Single source of truth for type sizes across every section.
   * Override per-breakpoint via the @media block below · every rule
   * that uses these tokens automatically scales.
   * ──────────────────────────────────────────────────────────────── */
  --fs-hero:      3rem;       /* hero title (overridden directly in .hero__title) */
  /* v10.291 · section h2 fluid display sizing: 40 → 72 px across the
   * viewport range. Was 3rem flat = 48 px → felt under-scaled vs the
   * 96 px hero h1 above. Now scales proportionally so every section
   * heading reads as display-tier, not body-tier. */
  --fs-section:   clamp(2.5rem, 4.5vw, 4.5rem);
  --fs-subsec:    clamp(1.5rem, 2.2vw, 2.25rem);
  /* display weight + tight leading + letter-spacing tokens · applied to
   * every section h2 via the shared rule + per-section overrides. */
  --fw-display:   800;
  --lh-display:   1.04;
  --ls-display:   -0.022em;
  --fs-card:      1.5rem;     /* card h3 (gateway cards, news cards) */
  --fs-body:      1rem;       /* default paragraph */
  --fs-eyebrow:   0.8rem;     /* uppercase eyebrow above each section title */
  --fs-meta:      0.85rem;    /* dates, descriptions, captions */

  --fw-normal:    400;
  --fw-medium:    500;
  --fw-semibold:  600;
  --fw-bold:      700;
  --fw-extrabold: 800;

  --lh-tight:     1.2;        /* headings */
  --lh-snug:      1.3;
  --lh-normal:    1.5;        /* body */
  --lh-relaxed:   1.75;       /* long-form copy */

  --ls-eyebrow:   0.2em;      /* uppercase eyebrow letter-spacing */
  --ls-tight:    -0.015em;

  /* ── Motion design tokens ──────────────────────────────────────────
   * One source of truth for easing + duration. Use these instead of
   * inline cubic-beziers so the whole site feels like a coherent motion
   * system. Map: standard for general transitions, emph for "this is
   * the thing we want you to notice" reveals, decel for elements
   * entering, accel for elements leaving, spring for tactile pops.
   * ──────────────────────────────────────────────────────────────── */
  --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
  --ease-emph:     cubic-bezier(0.16, 1, 0.3, 1);
  --ease-decel:    cubic-bezier(0, 0, 0.2, 1);
  --ease-accel:    cubic-bezier(0.4, 0, 1, 1);
  --ease-spring:   cubic-bezier(0.5, 1.6, 0.4, 1);
  --dur-fast:    150ms;
  --dur-medium:  250ms;
  --dur-slow:    400ms;
}

/* ────────────────────────────────────────────────────────────────────
 * Global typography rendering polish.
 *
 * - optimizeLegibility enables advanced shaping (kerning, ligatures)
 *   at any zoom. Tiny perf cost (mostly invisible on modern hardware),
 *   big quality lift on serif curves and weighted typefaces.
 * - font-feature-settings opt into kern + standard + contextual ligatures
 *   explicitly, since browsers don't always default to "on" for all
 *   weights of variable fonts.
 * - text-wrap: balance redistributes a heading's words across lines so
 *   no single line is much longer than the rest (eliminates orphan
 *   words at line ends). Pretty does the same for body paragraphs
 *   without the perf cost of balance on long copy.
 * ──────────────────────────────────────────────────────────────────── */
html {
  /* Subpixel AA on non-Retina; grayscale AA on HiDPI · only on html, not body,
   * so it applies before the font swap and avoids a flash on load. */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
h1, h2, h3, h4 {
  /* optimizeLegibility + kern/liga only on headings (few elements, big visual
   * payoff). Applying to html runs expensive text shaping on every character
   * on the page · measurable CPU cost on long-scroll pages. */
  text-rendering: optimizeLegibility;
  font-feature-settings: "kern" 1, "liga" 1, "calt" 1;
  text-wrap: balance;
}
p {
  text-wrap: pretty;
}

/* ── Pre-promote reveal elements before their entrance animation ──
 * will-change is set on all .reveal elements so the browser creates the
 * compositor layer BEFORE the IntersectionObserver fires. Without this,
 * layer promotion happens mid-animation causing a visible first-frame hitch.
 * Remove will-change after the animation completes so the layer is freed. */
[class*="reveal"] {
  will-change: transform, opacity;
}
[class*="reveal"].is-visible {
  will-change: auto;
}

/* ── Brand-coloured text selection ──────────────────────────────── */
::selection {
  background: var(--purple);
  color: #fff;
}

/* ── Custom scrollbar · thin, on-brand, fades in on hover ────────────
 * WebKit (Chrome/Safari/Edge) + Firefox both supported. Track stays
 * transparent so it doesn't add visual noise. Thumb is subtle white
 * at rest, brighter on hover. */
/* v10.67 · Premium brand-styled scrollbar.
 * Previous: 10 px wide, transparent track, white-alpha thumb (8% → 18% on
 * hover). Functional but generic.
 * Now: dark track with subtle hairline border, brand-violet gradient thumb,
 * 2 px transparent padding inside thumb so the gradient appears to "float"
 * inside the track. Brighter gradient on hover. */
::-webkit-scrollbar {
  width: 12px;
  height: 12px;
}
::-webkit-scrollbar-track {
  /* v10.114: matched to page bg (#040404). Previously was
   * rgba(10,10,14,0.6) · a slightly lighter dark that read as a
   * visible vertical strip on the right side of the page. User
   * flagged it as "side colour different". Solid #040404 makes
   * the scrollbar track invisible against the page. */
  background: #040404;
  border-left: 1px solid rgba(255, 255, 255, 0.04);
}
::-webkit-scrollbar-thumb {
  background: linear-gradient(180deg, rgba(157, 92, 255, 0.75), rgba(111, 0, 255, 0.75));
  border-radius: 8px;
  border: 2px solid transparent;
  background-clip: padding-box;
  transition: background var(--dur-fast) var(--ease-standard);
}
::-webkit-scrollbar-thumb:hover {
  background: linear-gradient(180deg, rgba(176, 127, 255, 0.95), rgba(139, 58, 255, 0.95));
  background-clip: padding-box;
}
::-webkit-scrollbar-corner {
  background: #040404;
}
/* Firefox uses a different API */
html {
  scrollbar-width: thin;
  /* v10.114: Firefox track matched to page bg #040404 same as WebKit. */
  scrollbar-color: rgba(111, 0, 255, 0.7) #040404;
}

/* ── Kill the iOS/Android default blue tap-highlight flash ──────────
 * Default `-webkit-tap-highlight-color` is a 26% opaque dark-blue
 * rectangle that flashes around any tapped <a>, <button>, or anything
 * with cursor:pointer. Reads as "draft web" · every premium app and
 * top-tier site disables it. Replaced with our own :active states
 * where wanted. */
a, button, [role="button"], input, textarea, select,
.btn-launch, [tabindex] {
  -webkit-tap-highlight-color: transparent;
}

/* Responsive scale: titles shrink on mobile to avoid hero-text wrap-orphans */
@media (max-width: 768px) {
  :root {
    --fs-hero:    2rem;
    --fs-section: 2rem;
    --fs-subsec:  1.5rem;
  }
}

*,
*::before,
*::after {
  box-sizing: border-box;
}
*:focus {
  outline: none;
}
*:focus-visible {
  outline: 2px solid var(--purple);
  outline-offset: 2px;
}

/* Accessible skip-link: hidden until keyboard focus
 * Uses transform (composited) NOT top (layout-trigger) · Prompt 1 fix. */
.skip-link {
  position: fixed;
  top: 16px;
  left: 16px;
  z-index: 10001;
  padding: 12px 16px;
  background: var(--purple);
  color: #fff;
  font-weight: 600;
  border-radius: 6px;
  text-decoration: none;
  transform: translateY(-200%);
  transition: transform 0.2s ease;
}
.skip-link:focus,
.skip-link:focus-visible {
  transform: translateY(0);
  outline: 2px solid #fff;
}

/* ── Film grain overlay (signature hand-crafted detail) ──
 * A fine ~3% opacity noise pattern fixed across the viewport. Breaks the
 * "too perfect digital" feel that's the telltale sign of an AI-generated
 * page. Pure CSS · uses inline SVG fractalNoise via data: URL, no asset
 * required. Pointer-events:none so it never intercepts interaction.
 *
 * Position:fixed + transform:translateZ(0) → composited on its own GPU
 * layer, never repaints on scroll. Honors prefers-reduced-motion by
 * fading further out · the grain stays static, but defensively dimmed.
 *
 * v10.444-pending · scroll-lag perf pass: REMOVED `mix-blend-mode: overlay`
 * and `will-change: transform`. The overlay blend mode forced the compositor
 * to re-blend this full-viewport noise layer against every pixel under it
 * on every scroll-driven repaint of any section · a documented major source
 * of scroll jank (mix-blend-mode breaks compositor layer isolation and
 * forces software-blended composites). will-change was permanently allocating
 * a GPU layer for an element that never animates · wasted VRAM
 * (per github.com/zakky8/web-optimization · 18-css-performance/
 * compositor-thread.md, "each layer costs VRAM ... overuse causes worse
 * performance than not using it"). The same 3.5% film-grain texture reads
 * correctly with plain alpha · slightly subtler in dark areas but
 * indistinguishable to the eye at this opacity level. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 9999;
  pointer-events: none;
  opacity: 0.035;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  background-size: 200px 200px;
}
@media (prefers-reduced-motion: reduce) {
  body::before { opacity: 0.02; }
}

/* Visually hide an element while keeping it accessible to screen readers.
 * Standard WCAG-recommended pattern (no clip-path: inset(50%) since that
 * has a Safari focus-trap bug). */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Honor user OS-level reduced-motion preference everywhere */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ────────────────────────────────────────────────────────────────────
 * .perf-mode · automatic ultra-light fallback
 *
 * Engaged by JS when:
 *   • WebGL detected as SwiftShader / software renderer (= Chrome hardware
 *     accel is broken or disabled on this machine), OR
 *   • Sustained <30 fps for 2 seconds during normal use
 *
 * Strips every GPU-expensive effect so the page stays usable on the
 * slowest machines. Visual fidelity drops slightly (no animations, no
 * blur), but the page is responsive instead of stuttering at 5-10 fps.
 *
 * The user's OS-level prefers-reduced-motion preference is honored above;
 * this rule is for involuntary perf collapse on the machine side.
 * ──────────────────────────────────────────────────────────────────── */
html.perf-mode *,
html.perf-mode *::before,
html.perf-mode *::after {
  animation: none !important;
  transition: none !important;
  will-change: auto !important;
}
html.perf-mode .site-nav,
html.perf-mode .site-nav--scrolled {
  /* v10.124: perf-mode nav now matches the white-glass PC theme
   * (was rgba(4,4,4,.95) near-solid black). Dark bar in perf-mode
   * was the "still bug" user saw · kicks in whenever fps<15. */
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
  background: #ffffff !important;
}
/* v10.196 · perf-mode partner-marquee handling removed.
 *
 * Previously perf-mode killed --left and --right animations but NOT
 * --left-slow, leaving the third row still scrolling while the other
 * two froze. User report: "this not moving sometimes" · that was
 * perf-mode auto-engaging at fps<45 and producing the inconsistent
 * 2-of-3-frozen state.
 *
 * CSS marquee animations are compositor-only (transform only) · they
 * cost ~0 CPU/GPU on the main thread. Killing them in perf-mode saved
 * nothing while breaking the UX. The more severe lite-mode (fps<15)
 * can still suspend the section via .lite-mode display:none rule
 * (line 361+ block) if the device is truly struggling.
 *
 * Also removed the overflow-x:auto fallback because hiding the
 * scrollbar (rule below in .partner-marquee__viewport) made that
 * fallback's scrollbar invisible · same visual outcome as the
 * default overflow:hidden. */
html.perf-mode .hero::after {
  animation: none !important;
  transform: none !important;
}
html.perf-mode .gateway-core--active {
  box-shadow: 0 0 24px rgba(111, 0, 255, 0.4) !important;
}
html.perf-mode #abox-canvas-wrap,
html.perf-mode .nodes__visual {
  /* Hide the 3D scenes entirely · they're the heaviest elements and
   * pointless to keep alive on software-rendered Chrome */
  visibility: hidden;
}

/* ────────────────────────────────────────────────────────────────────
 * .astarter-no-3d · PROACTIVE mobile 3D-skip (v10.269).
 *
 * Set imperatively on <html> by app.js when matchMedia (max-width: 900px)
 * matches, BEFORE any 3D init runs. Unlike .perf-mode/.lite-mode which
 * engage REACTIVELY after FPS drops, this gate prevents the lag in the
 * first place · no Spline runtime mount, no Three.js boot, no DarkVeil
 * shader on /solution.html.
 *
 * textura.agency parity: their site has zero 3D, which is why it feels
 * effortlessly smooth on phones. We keep the 3D for desktop + landscape
 * tablet (matchMedia goes false above 900px) so the brand identity
 * stays where users can appreciate it.
 *
 * `display: none` (not visibility: hidden) so the column collapses and
 * the copy panels expand to fill · mobile layout already stacks single-
 * column, so removing the canvas slot is visually clean.
 * ──────────────────────────────────────────────────────────────────── */
/* v10.300 · ABox 3D stays on mobile (cheap single-GLB scene, already
 * tuned for low-power: DPR 1, no AA, paused out-of-view). Only the
 * heavy scenes are gated off: the Spline globe (continuous WebGL +
 * ~150 KB runtime) and the DarkVeil fragment shader on /solution.html. */
html.astarter-no-3d .nodes__visual,
html.astarter-no-3d #dv-bg {
  display: none !important;
}

/* ────────────────────────────────────────────────────────────────────
 * .lite-mode · NUCLEAR fallback for catastrophic lag (<20 fps)
 *
 * Layered on top of .perf-mode (both classes are set together).
 * JS has already disposed Three.js + removed Spline from DOM by the
 * time this CSS applies. This rule hides every decorative element
 * that's even remotely expensive · leaves only text + clean layout.
 *
 * Triggers: sustained <20 fps, or ?lite URL param, or click on the
 * corner indicator twice.
 * ──────────────────────────────────────────────────────────────────── */
html.lite-mode .hero::after,
html.lite-mode .hero::before {
  display: none !important;
}
html.lite-mode .hero {
  background: linear-gradient(180deg, #0a0420 0%, #040404 100%) !important;
}
html.lite-mode #abox-canvas-wrap,
html.lite-mode .nodes__visual,
html.lite-mode .abox__dots,
html.lite-mode .gateway-core__decor,
html.lite-mode .gateway-core__overlay,
html.lite-mode .gateway-core__circuit,
/* Below moved out of imperative JS DOM-removal (which was irreversible).
 * CSS hide is reversible: when lite-mode class clears on FPS recovery,
 * these elements come back without a page reload. */
html.lite-mode #partners-track-1,
html.lite-mode #partners-track-2,
html.lite-mode #partners-track-3,
html.lite-mode .partner-marquee,
html.lite-mode #tokenomics-pie {
  display: none !important;
}
/* v10.x · guard infinite animations on lite-mode and mobile.
 * heroKenBurns (24s alternate) and roadmap-status-pulse (2.4s) run
 * forever and consume GPU compositor budget. Kill them on weak devices.
 * (v10.405 · flywheel-loop-spin removed · loop pill deleted with the
 *  Option F flywheel rebuild.) */
html.lite-mode .hero::after,
html.lite-mode .roadmap-item.is-current,
html.lite-mode .flywheel-step::before,
html.lite-mode .use-case-card::before {
  animation: none !important;
}
@media (max-width: 767px) {
  .hero::after,
  .roadmap-item.is-current,
  /* v10.430 · sparkle animation killed on mobile · 8 always-active
   * compositor layers caused scroll lag on weak GPUs (Adreno 5xx,
   * Mali, integrated Intel). Sparkles stay VISIBLE (static) ·
   * just the drift animation pauses. */
  .flywheel-step::before,
  .use-case-card::before {
    animation: none !important;
  }
}

/* v10.441 · pause continuous CSS animations when their section is off-screen.
 *
 * User reports PC scroll lag (even on gaming PC) after today's sweep.
 * 5 infinite CSS animations were running CONTINUOUSLY regardless of
 * viewport: hero KenBurns (24s), gatewayCoreAmbient (6s), and 3x
 * partner marquee tracks (45s / 38s / 56s). Each creates a permanent
 * compositor layer with active transform animation. Combined GPU
 * compositor load was eating frame budget even when the sections were
 * far off-screen.
 *
 * Fix · use the existing `.in-view` class (toggled by initInViewSections
 * with 200px rootMargin) to pause animations on parent sections that
 * aren't visible. `animation-play-state: paused` is GPU-cheap · the
 * layer stays promoted but the transform interpolation stops.
 *
 * Hero KenBurns and roadmap are exempt because they're either always
 * near top-of-page (hero) or already gated elsewhere (roadmap). */
.partner-marquee:not(.in-view) .partner-marquee__track--left,
.partner-marquee:not(.in-view) .partner-marquee__track--right,
.partner-marquee:not(.in-view) .partner-marquee__track--left-slow {
  animation-play-state: paused;
}
.gateway:not(.in-view) .gateway-core::before {
  animation-play-state: paused;
}
/* v10.96: .hero__video REMOVED from the lite-mode hide list.
 *
 * Bug: scroll bottom → top → hero video stays hidden (`display: none`)
 * even though the user is back at the hero. Reason: once lite-mode
 * trips, the FPS-measuring rAF loop has almost nothing to measure
 * (Three.js and Spline are already paused, video is hidden), so the
 * recovery condition (FPS > 55 sustained) never re-evaluates and the
 * lite-mode class is never cleared.
 *
 * The hero video isn't the GPU-heavy element · Three.js (Nodes globe)
 * and the Spline canvas are. The IntersectionObserver at js/app.js:394
 * already pauses the video when off-screen, so it costs zero GPU when
 * the user is below the fold. There's no perf justification for also
 * setting `display: none` on it. Keep the visual continuity. */
html.lite-mode .gateway-card {
  background: #0d0d0d !important;
}
html.lite-mode .gateway-core {
  border: 1px solid rgba(111, 0, 255, 0.4) !important;
  box-shadow: none !important;
}
html.lite-mode .partner-marquee__rows::before,
html.lite-mode .partner-marquee__rows::after {
  display: none !important;
}
html.lite-mode .news-card,
html.lite-mode .gateway-card,
html.lite-mode .roadmap-item {
  /* Strip the .reveal staging · content always visible (no animation) */
  opacity: 1 !important;
  transform: none !important;
}
html.lite-mode * {
  /* Disable text-shadow on all elements · text-shadow is a paint cost */
  text-shadow: none !important;
}

body {
  margin: 0;
  min-width: 320px;
  /* v10.161: body gradient removed. It was creating a visible step
   * wherever an adjacent section had a solid bg of its own. Root
   * cause of every "still visible line" report. Flat dark across
   * the whole page now. Identity comes from typography + content. */
  background: var(--bg);
  color: rgba(255, 255, 255, 0.87);
  font-family: var(--font);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  /* Tell browser scroll is the dominant gesture · skips click-tracking delay */
  touch-action: manipulation;
  /* Hint compositor to keep document on its own layer for smoother scroll */
  overflow-x: hidden;
}

/* v10.469 · Inner-page premium backdrop.
 * Every non-home page (body.inner-page · added to all 52 non-home HTML
 * files) gets ONE coherent brand-violet ambient glow behind its hero —
 * the "lit from the top" studio look already used on /architecture —
 * replacing the flat #040404 that read as cheap/"vibecoded". This selector
 * (specificity 0,1,1) beats the inline `body{background:...}` each page
 * ships (0,0,1), so it wins regardless of source order. ONE light source,
 * alpha <= 0.17 · no stacked glows (anti-pattern #8). The gradient is
 * anchored to the TOP of the page (scrolls away with the hero) so content
 * sections below stay clean. The homepage is intentionally excluded · it
 * keeps its hero video untouched. Static gradient · zero scroll-repaint,
 * no perf cost on low-end. */
body.inner-page {
  background-color: var(--bg);
  background-image:
    radial-gradient(58% 46% at 50% 0%, rgba(111, 0, 255, 0.17) 0%, rgba(111, 0, 255, 0.055) 40%, transparent 72%),
    radial-gradient(38% 30% at 99% 3%, rgba(157, 92, 255, 0.06) 0%, transparent 60%);
  background-repeat: no-repeat, no-repeat;
  background-position: center top, right top;
  background-size: 100% 820px, 60% 460px;
}

/* Promote ONLY elements actively animating to a compositor layer.
 * The previous universal `img, video, canvas { translateZ(0) }` was a mistake ·
 * it promoted 150+ partner/blog/asset images, exploding GPU memory and
 * compositor frame time. The hero video gets its own promotion below. */
.hero__video {
  /* set in .hero__video block; this is just documentation */
}

a {
  color: inherit;
  text-decoration: none;
}

.container {
  max-width: 1600px;
  margin: 0 auto;
  padding: 0 24px;
}
@media (max-width: 768px) {
  .container {
    padding: 0 16px;
  }
}

/* ---- Nav ----
 * v10.128: deleted the pre-v10.73 edge-to-edge .site-nav rule that
 * lived here. It set top:0/left:0/width:100%/height:80px/background:
 * transparent · all overridden by the floating-pill rule below at
 * `.site-nav { position:fixed; top:calc(16px+...); left:50%; ... }`,
 * but during cascade evaluation the dead rule was still parsed every
 * paint. Its only useful properties (will-change:background and
 * contain:style) have been moved into the active rule. */
.site-nav__inner {
  max-width: 1600px;
  height: 100%;
  margin: 0 auto;
  padding: 0 24px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.site-nav__brand {
  display: flex;
  align-items: center;
  /* v10.282 · Pharos-style refined: 10px gap, tighter pairing. */
  gap: 10px;
  cursor: pointer;
}
/* Desktop nav logo · 40px height, natural aspect ratio. In lockstep with
 * the inline critical CSS so no visible resize on load. The mobile media
 * query below overrides to 32px with !important for smaller viewports. */
.site-nav__logo-img {
  height: 22px;
  width: auto;
}

/* v10.128: deleted the pre-v10.77 rule (48x48 white silhouette via
 * filter:brightness(0) invert(1)) · it overrode the inline critical-CSS
 * values and caused a visible resize + colour loss on every page refresh.
 * The pre-v10.79 .site-nav__title { color:#fff } rule was also deleted;
 * the active rule below sets the title to #0a0a0a !important. */
.site-nav__menu {
  display: flex;
  list-style: none;
  margin: 0;
  padding: 0;
}
/* ════════════════════════════════════════════════════════════════════
 * v10.73 · Floating-card light nav (Pharos pattern)
 *
 * Combines v10.66 floating-pill positioning + v10.72 light theme:
 *   - Floating card (inset 16 px from top/sides, max-width 1456 px)
 *   - Rounded rectangle 20 px corners (Pharos shape)
 *   - Frosted-white background with backdrop blur
 *   - Dark text + brand-purple logo + brand-violet CTA
 *   - Full 1 px black-alpha border (not just bottom)
 *   - Soft drop shadow lifts it off the dark page
 * ════════════════════════════════════════════════════════════════════ */
.site-nav {
  position: fixed;
  top: calc(16px + env(safe-area-inset-top));
  left: 50%;
  transform: translateX(-50%);
  width: calc(100% - 32px);
  max-width: 1456px;
  height: auto;
  z-index: 1000;
  padding: 0;
  border: 1px solid rgba(0, 0, 0, 0.08);
  border-radius: 20px;
  /* v10.91: tuned for content-color stability.
   *   v10.76 dropped alpha 0.92 → 0.70 + saturate(180%) for a glassier
   *   look. Side effect: the saturate(180%) AMPLIFIES whatever is behind
   *   the nav, so when the user scrolls past the tokenomics donut
   *   (#6f00ff / #9d5cff / #e040fb), those colours bled through as a
   *   visible purple tint. The bar appeared to "change colour" between
   *   sections.
   *   v10.91 lifts alpha 0.70 → 0.86 and softens saturate 180% → 120%.
   *   Still reads as glass (you can see depth behind it) but content
   *   colours no longer push through. Blur kept at 20 px · enough for
   *   the frosted look without being a perf liability on weaker GPUs. */
  /* v10.117: PERMANENT fix for "bar changes colour" · root cause is
   * that ANY combination of translucency + saturate >100% will
   * amplify content colours through the bar. v10.91 reduced (180→120),
   * v10.112 added blur, but small bleed still possible against
   * hard-saturated content (tokenomics donut).
   *
   * Final values:
   *   alpha 0.86 → 0.94 (just below the v10.76 anti-pattern threshold
   *                      where glass effect collapses to solid white)
   *   saturate 120% → 100% (zero amplification · content cannot be
   *                          made more colourful through the bar)
   *   blur 28px kept (softens whatever DOES show through to a fuzz)
   *
   * Result: bar colour is now effectively independent of what's
   * behind it. Glass effect still reads because of the blur, but
   * saturation can never tint it. */
  background: rgba(255, 255, 255, 0.94);
  /* v10.505 · blur 28px -> 10px. The bar bg is already 0.94-0.97 opaque,
   * so a 28px backdrop-blur was nearly invisible yet re-sampled the whole
   * region behind the fixed nav EVERY scroll frame · a top scroll-jank
   * cause on mid/low GPUs, on every page. 10px keeps the glass hint at a
   * fraction of the per-frame cost. */
  backdrop-filter: blur(10px) saturate(100%);
  -webkit-backdrop-filter: blur(10px) saturate(100%);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.7) inset,
    0 10px 36px rgba(0, 0, 0, 0.18),
    0 2px 8px rgba(0, 0, 0, 0.08);
  font-family: var(--font);
  transition:
    background 0.3s cubic-bezier(0.16, 1, 0.3, 1),
    border-color 0.3s cubic-bezier(0.16, 1, 0.3, 1),
    box-shadow 0.3s cubic-bezier(0.16, 1, 0.3, 1);
  /* v10.128: will-change:background pre-promotes the nav to its own
   * compositor layer so the scrolled-state background swap doesn't
   * trigger a paint of the whole bar. DO NOT use will-change:transform
   * or backdrop-filter here · both create a containing block that
   * clips the mobile drawer (.site-nav__drawer's fixed positioning
   * needs the viewport, not the nav box). See anti-pattern #13. */
  will-change: background;
  contain: style;
}
.site-nav--scrolled {
  /* v10.117: bumped 0.94 → 0.97 for the scrolled state · content
   * behind the bar is densest here (tokenomics donut, partner
   * marquee). At 0.97 alpha the bar is effectively opaque so no
   * content colour can leak through at all. */
  background: rgba(255, 255, 255, 0.97);
  border-color: rgba(0, 0, 0, 0.1);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.7) inset,
    0 14px 40px rgba(0, 0, 0, 0.22),
    0 3px 12px rgba(0, 0, 0, 0.1);
}
.site-nav--solid {
  background: #ffffff;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
.site-nav__inner {
  max-width: 100%;
  /* v10.273 · REVERTED: v10.272 bumped pill 60→72 to give the logo
   * room, but Zakky wanted the LOGO bigger, not the bar. Bar stays
   * at the compact 60 px. The logo image itself is bumped to 56 px
   * (height: 56px below) so it fills 93 % of the 60 px pill · large
   * brand block without changing the pill chrome. */
  height: 60px;
  margin: 0;
  padding: 0 12px 0 22px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
/* Logo wordmark · dark, matches the new light bar.
 * v10.264 · bumped PC size: font 18→22px, weight 600→700, letter-spacing
 * 0.05→0.04em. Matches the larger logo icon (40→48px) for stronger brand
 * presence on PC. Mobile rule (line ~1411) overrides back to 16px,
 * so this change is desktop-only · mobile unaffected. */
.site-nav__title {
  color: #0a0a0a !important;
  /* v10.282 · Pharos-style refined wordmark: smaller + tighter. */
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.02em;
}
.site-nav__item {
  position: relative;
  margin: 0 14px;
  list-style: none;
}
.site-nav__trigger {
  display: inline-flex;
  align-items: center;
  /* v10.294 · text-only (icons removed per user). Compact padding
   * gives the pill-hover bg breathing room without enlarging the bar. */
  gap: 4px;
  padding: 8px 12px;
  margin: -8px -12px;  /* negate the padding from layout flow */
  border: none;
  background: transparent;
  color: rgba(10, 10, 14, 0.78);
  font-family: inherit;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.005em;
  cursor: pointer;
  position: relative;
  border-radius: 10px;
  transition:
    color 0.25s cubic-bezier(0.16, 1, 0.3, 1),
    background-color 0.25s cubic-bezier(0.16, 1, 0.3, 1);
}
/* v10.294 · refined hover state: subtle pill background fades in
 * behind the label (Linear / Vercel pattern). Removes the v10.283
 * underline accent + icon-scale recipe in favour of a single
 * cohesive cue. Active dropdown gets a slightly stronger bg + dark
 * text so the user always knows which menu is open. */
/* v10.330 · sliding pill indicator pattern (Pharos brand-kit nav).
 * Per-trigger bg dropped: a single shared pill now glides under the
 * hovered/open trigger via JS-set CSS custom properties. Triggers
 * only change text color on hover; the bg work is the indicator's job. */
@media (hover: hover) {
  .site-nav__trigger:hover,
  .site-nav__item:hover .site-nav__trigger {
    color: #0a0a0a;
  }
}
.site-nav__item.is-open .site-nav__trigger {
  color: #0a0a0a;
}

/* The sliding indicator. Injected as first child of .site-nav__menu by
 * initNavIndicator() in app.js. Position + width are driven by CSS
 * custom props the JS sets on .site-nav__menu (--ind-x / --ind-w). */
.site-nav__menu{position:relative;}
.site-nav__indicator{
  position:absolute;
  top:50%;
  left:0;
  height:34px;
  width:var(--ind-w,0px);
  transform:translate(var(--ind-x,0px), -50%);
  background-color:rgba(10,10,14,0.06);
  border-radius:10px;
  opacity:0;
  pointer-events:none;
  transition:
    transform .35s cubic-bezier(.22,1,.36,1),
    width .35s cubic-bezier(.22,1,.36,1),
    opacity .25s cubic-bezier(.22,1,.36,1),
    background-color .25s cubic-bezier(.22,1,.36,1);
  z-index:0;
  will-change:transform, width;
}
.site-nav__menu.has-indicator-active .site-nav__indicator{opacity:1;}
.site-nav__menu.has-indicator-open .site-nav__indicator{
  background-color:rgba(10,10,14,0.085);
}
.site-nav__item{z-index:1;}
@media (prefers-reduced-motion: reduce){
  .site-nav__indicator{transition:opacity .15s ease;}
}
.site-nav__trigger:focus-visible {
  outline: 2px solid var(--purple);
  outline-offset: 2px;
}
.site-nav__chev {
  font-size: 10px;
  opacity: 0.6;
  margin-left: 2px;
  transition:
    transform 0.4s cubic-bezier(0.16, 1, 0.3, 1),
    opacity 0.4s cubic-bezier(0.16, 1, 0.3, 1);
  display: inline-block;
}
.site-nav__trigger:hover .site-nav__chev,
.site-nav__item:hover .site-nav__chev,
.site-nav__item.is-open .site-nav__chev {
  opacity: 1;
  transform: rotate(180deg);
}
.site-nav__sub {
  position: absolute;
  /* Start RIGHT below the trigger (no dead zone) · the 12px visual gap
   * is now padding INSIDE the dropdown's hover area, so mouse can move
   * from trigger to items without losing :hover and closing the menu. */
  top: 100%;
  left: 50%;
  transform: translateX(-50%) translateY(-4px);
  min-width: 220px;
  margin: 0;
  /* Top padding creates the visual breathing room; the bg is below it */
  padding: 12px 0 8px 0;
  list-style: none;
  background: transparent;
  border: none;
  box-shadow: none;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  /* v10.114: pre-promote to compositor layer so the open/close
   * animation doesn't trigger layout/paint on the first frame
   * (was causing a perceived 1-frame lag on click). Animate
   * transform + opacity together for a smoother reveal than
   * pure opacity fade. Visibility transition kept so the
   * dropdown becomes non-hit-testable at the END of the close. */
  will-change: transform, opacity;
  transition:
    opacity 0.18s cubic-bezier(0.16, 1, 0.3, 1),
    transform 0.22s cubic-bezier(0.16, 1, 0.3, 1),
    visibility 0.18s linear;
  z-index: 1002;
}
/* The actual visible menu surface is a child wrapper · bg only there,
 * leaving the 12px padding above invisible-but-hoverable. */
.site-nav__sub::before {
  content: "";
  position: absolute;
  top: 12px;
  left: 0;
  right: 0;
  bottom: 0;
  /* v10.83: glassy dropdown · semi-transparent + backdrop blur,
   * matching the v10.76 nav surface.
   * v10.114: saturate 180 → 120 (anti-pattern #15 · colour
   * amplification, same fix as v10.91/v10.112 on the nav).
   * Blur 24 → 20 to match nav blur and reduce GPU cost on open. */
  /* v10.331 · bg opacity 0.72 → 0.92 so the dropdown surface stays
   * white-glass even on dark pages (DarkVeil bg, dark hero) instead
   * of bleeding through and washing the link text. */
  background: rgba(255, 255, 255, 0.92);
  backdrop-filter: blur(20px) saturate(120%);
  -webkit-backdrop-filter: blur(20px) saturate(120%);
  border: 1px solid rgba(0, 0, 0, 0.08);
  border-radius: 12px;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.7),
    0 12px 36px rgba(0, 0, 0, 0.14),
    0 2px 8px rgba(0, 0, 0, 0.06);
  z-index: -1;
}
.site-nav__item:hover .site-nav__sub,
.site-nav__item:focus-within .site-nav__sub,
.site-nav__item.is-open .site-nav__sub {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: translateX(-50%) translateY(0);
  /* Slight opening delay = "hover intent" · prevents accidental opens
   * when sweeping mouse across the nav bar */
  transition-delay: 0.06s;
}
.site-nav__item .site-nav__sub {
  /* v10.114: closing delay halved 0.15s → 0.08s. The longer delay
   * created the "laggy" feel users reported · dropdown hung around
   * after the cursor moved away. 0.08s is still enough grace period
   * to let the cursor cross from trigger to items without closing. */
  transition-delay: 0.08s;
}
.site-nav__sub li {
  margin: 0;
}
.site-nav__sub a {
  display: block;
  padding: 10px 18px;
  color: rgba(10, 10, 14, 0.78);
  font-size: 14px;
  font-weight: 500;
  white-space: nowrap;
  transition: color 0.3s ease, background 0.3s ease;
}
.site-nav__sub a:hover {
  color: #0a0a0a;
  background: rgba(111, 0, 255, 0.08);
}

/* v10.217 two-column Resources dropdown removed in v10.220 per user
 * direction. The dropdown is back to a single-column 9-item list. */
.site-nav__menu a {
  color: rgba(10, 10, 14, 0.78);
  font-size: 16px;
  font-weight: 500;
  transition: color 0.3s ease;
}
.site-nav__menu a:hover {
  color: #0a0a0a;
}
.site-nav__right {
  display: flex;
  align-items: center;
  gap: 20px;
}
/* v10.102: compact 3-icon community row in the desktop nav, left of
 * Launch APP. Polygon / Solana pattern. Hidden under 900px along with
 * the rest of .site-nav__right (the drawer carries the full set). */
.site-nav__socials {
  display: flex;
  align-items: center;
  /* v10.286 · plain icons (no chip bg) per Zakky's pharos.xyz ref */
  gap: 14px;
  padding-right: 10px;
  margin-right: 4px;
  border-right: 1px solid rgba(0, 0, 0, 0.08);
}
.site-nav__socials a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  /* v10.286 · no background, no border-radius; just the glyph */
  color: rgba(10, 10, 14, 0.7);
  transition:
    color 0.3s cubic-bezier(0.16, 1, 0.3, 1),
    transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
@media (hover: hover) {
  .site-nav__socials a:hover {
    color: var(--purple);
    transform: translateY(-1px);
  }
}
.site-nav__socials a:focus-visible {
  outline: 2px solid var(--purple);
  outline-offset: 2px;
}
/* v10.167: deleted the v10.74 iPad-specific override that flipped
 * the icons to white for the dark-glass nav theme. v10.123 reverted
 * the iPad nav back to white-glass; this override survived and made
 * the 3 social icons invisible (white 0.62 alpha on the white bar)
 * across the entire 768-1099px viewport range. */
/* ── .btn-launch · Top-1% recipe (v10.84) ──
 *
 * The Stripe/Apple sweet spot between "flat solid" (v10.68) and
 * "AI-maximalist gradient-with-sheen-and-4-shadows" (v10.63):
 *
 *   - Subtle 2-stop gradient (#7a14ff → #6f00ff) · slight top-to-
 *     bottom darken creates dimension without looking candy. The
 *     range is tiny (~10% brightness) so it reads as "lit", not
 *     "ornamented".
 *   - ONE inner top highlight (inset 1 px rgba(255,255,255,0.18))
 *     · simulates light hitting from above, gives the button mass.
 *   - ONE outer drop shadow with brand-violet glow.
 *   - Border-radius 10 px (rounded rect = professional, not pill).
 *   - Refined typography: 13 px / weight 600 / letter-spacing 0.04em.
 *   - Hover: bg brightens + 1 px lift + glow intensifies.
 *   - Active: bg darkens, transform → 0, shadow shrinks.
 *
 * Three layers (gradient + inner highlight + outer glow). No sheen
 * overlay, no border, no inset bottom shadow. */
.btn-launch {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* v10.283 · tighter gap, arrow icon sits 6 px from label */
  gap: 6px;
  /* Apple-HIG 44 px tap target via min-height + inline-flex.
   * v10.312 · was 40 px (WCAG 2.5.5 fail flagged by audit). 44 px
   * matches every other primary button on the site (nodes__cta,
   * subscribe__submit) and gives a fingertip-comfortable hit area. */
  min-height: 44px;
  padding: 0 16px 0 14px;
  /* Override the critical-CSS inline polygon */
  clip-path: none;
  border: none;
  border-radius: 10px;
  /* v10.298 · flat solid bg + tighter shadow. Was a 7a14ff→6500e6
   * gradient + deep glow that looked uneven on the chunky hero CTA. */
  background: #6f00ff;
  color: #fff !important;
  font-weight: 600;
  font-size: 13px;
  letter-spacing: 0.02em;
  text-decoration: none;
  cursor: pointer;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.16),
    0 2px 8px -1px rgba(111, 0, 255, 0.28);
  /* v10.85: longer duration + GPU pre-promotion → smoother feel */
  will-change: transform;
  transition:
    background 0.5s cubic-bezier(0.22, 1, 0.36, 1),
    box-shadow 0.5s cubic-bezier(0.22, 1, 0.36, 1),
    transform 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}
@media (hover: hover) {
  .btn-launch:hover {
    background: #7a14ff;
    color: #fff !important;
    transform: translateY(-1px);
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.2),
      0 4px 14px -2px rgba(111, 0, 255, 0.38);
  }
}
.btn-launch:active {
  background: #5a00d6;
  transform: translateY(0);
  box-shadow:
    inset 0 1px 2px rgba(0, 0, 0, 0.18),
    0 2px 8px -2px rgba(111, 0, 255, 0.3);
}
/* v10.283 · Pharos-style CTA: small ↗ arrow indicator + subtle Beta tag.
 * Arrow scales+translates on hover for an active "go" cue. */
.btn-launch__arrow {
  flex-shrink: 0;
  opacity: 0.85;
  transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1),
              opacity 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
@media (hover: hover) {
  .btn-launch:hover .btn-launch__arrow {
    opacity: 1;
    transform: translate(2px, -2px);
  }
}
.btn-launch__tag {
  display: inline-flex;
  align-items: center;
  margin-left: 4px;
  padding: 2px 6px;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.18);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  line-height: 1;
}
.btn-launch:focus-visible {
  outline: 2px solid #b07fff;
  outline-offset: 2px;
}
.site-nav__right .btn-launch {
  margin-left: 20px;
}
/* v10.287 · Hero primary CTA upgrade.
 *
 * The Buy Node button is the page's main action. Inherits .btn-launch
 * recipe but pushed bigger + more substantial:
 *   - height 40 → 52 px
 *   - padding 14/16 → 18/22 px
 *   - font 13 → 15 px
 *   - 1.5x shadow + glow on hover
 *   - inline server-rack icon (lhs) + arrow (rhs) · both shift on hover
 *     for an active "go" cue
 * Apple/Linear/Vercel primary-button playbook. */
.hero__cta.btn-launch {
  margin: 0;
  min-height: 52px;
  padding: 0 22px 0 18px;
  gap: 10px;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.01em;
  border-radius: 12px;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    0 2px 8px -2px rgba(0, 0, 0, 0.25);
}
@media (hover: hover) {
  .hero__cta.btn-launch:hover {
    transform: translateY(-1px);
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.22),
      0 4px 12px -3px rgba(0, 0, 0, 0.3);
  }
}
.hero__cta-icon,
.hero__cta-arrow {
  flex-shrink: 0;
  opacity: 0.92;
  transition: transform 0.45s cubic-bezier(0.22, 1, 0.36, 1),
              opacity 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
@media (hover: hover) {
  .hero__cta.btn-launch:hover .hero__cta-icon { opacity: 1; transform: scale(1.05); }
  .hero__cta.btn-launch:hover .hero__cta-arrow { opacity: 1; transform: translateX(3px); }
}
/* Burger menu · refined "top-tier" treatment.
 *
 * Premium reference points (Linear, Vercel, Stripe): thin lines
 * (1.5–1.8px), shorter than the tap-target (20–24px wide inside a
 * 44px button), rounded ends, asymmetric subtle hover where the
 * middle line shifts. Smooth cubic-bezier on the X transform.
 *
 * The .site-nav__burger button itself stays 44×44 for Apple HIG tap
 * target; the visible icon (3 spans, 20px wide × 14px tall total) is
 * absolutely positioned at the center of the button. */
.site-nav__burger {
  display: none;
  position: relative;
  /* 44×44 tap target · invisible button padding around the icon */
  width: 44px;
  height: 44px;
  cursor: pointer;
  z-index: 1001;
  background: none;
  border: none;
  padding: 0;
  border-radius: 50%;
  transition: background 0.2s ease;
}
@media (hover: hover) {
  .site-nav__burger:hover {
    background: rgba(255, 255, 255, 0.06);
  }
  .site-nav__burger:hover span:nth-child(1) { transform: translateX(-2px); }
  .site-nav__burger:hover span:nth-child(3) { transform: translateX(2px); }
}
.site-nav__burger:focus-visible {
  outline: 2px solid var(--purple);
  outline-offset: 2px;
}
.site-nav__burger span {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 20px;
  height: 1.6px;
  /* v10.125: burger lines dark for the white-glass bar (was #fff for the
   * previous dark-glass nav). Matches the inline critical CSS. */
  background: #0a0a0a;
  border-radius: 1px;
  margin-left: -10px;
  /* Symmetric stagger around the button centre: spans at y -5px, 0, +5px */
  transition:
    transform 0.35s cubic-bezier(0.65, 0, 0.35, 1),
    opacity   0.2s cubic-bezier(0.65, 0, 0.35, 1);
  transform-origin: center;
}
.site-nav__burger span:nth-child(1) { transform: translateY(-5px); }
.site-nav__burger span:nth-child(2) { transform: translateY(0); }
.site-nav__burger span:nth-child(3) { transform: translateY(5px); }

/* Open state · clean X via 45°/-45° rotation on top + bottom lines,
 * middle line fades to 0. Translations resolved to center (0px) so
 * both rotated lines pivot through the same point. */
.site-nav__burger.is-open span:nth-child(1) {
  transform: translateY(0) rotate(45deg);
}
.site-nav__burger.is-open span:nth-child(2) {
  transform: translateY(0) scaleX(0);
  opacity: 0;
}
.site-nav__burger.is-open span:nth-child(3) {
  transform: translateY(0) rotate(-45deg);
}

/* ── Mobile drawer: StaggeredMenu pattern ──
 * Slide-from-right panel with 2 coloured pre-layers that slide in
 * first (the "staggered" reveal), then large numbered category items
 * pop up from below one after another. Vanilla CSS port of the React
 * Bits StaggeredMenu · no GSAP, just CSS transitions with
 * transition-delay driven by --sm-i (item index, set inline in HTML).
 *
 * Wrapper .site-nav__drawer is the fixed full-viewport container.
 * Its visual layers slide INDEPENDENTLY from right:
 *   - .site-nav__drawer-prelayers (purple bars, behind panel)
 *   - .site-nav__drawer-panel (the dark menu surface)
 * When .is-open is added, all three slide to xPercent 0 with a small
 * stagger between them. */
.site-nav__drawer {
  position: fixed;
  inset: 0;
  pointer-events: none;
  /* v10.92: raised from 999 → 1001 so the open drawer sits ABOVE the
   * nav bar (z-index 1000). With the drawer at 999, the transparent
   * mobile nav (position:fixed, z-index 1000) was intercepting every
   * tap aimed at the in-drawer close X · the button was visible but
   * never fired. pointer-events:none in the closed state means this
   * z-index change has zero effect until the drawer is actually open. */
  z-index: 1001;
}
.site-nav__drawer.is-open {
  pointer-events: auto;
}
.site-nav__drawer-prelayers {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: row-reverse;
  pointer-events: none;
  /* v10.94: hide the brand-purple slide-bars to match the cleaner
   * anthropic.com mobile-menu pattern (single dark panel, no dramatic
   * pre-layer reveal). Markup kept in place for back-compat. */
  display: none;
}
.site-nav__drawer-prelayer {
  position: absolute;
  inset: 0;
  transform: translateX(100%);
  transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
  will-change: transform;
}
.site-nav__drawer-prelayer:nth-child(1) {
  /* outer-most layer slides in first */
  transition-delay: 0s;
}
.site-nav__drawer-prelayer:nth-child(2) {
  transition-delay: 0.07s;
}
.site-nav__drawer.is-open .site-nav__drawer-prelayer {
  transform: translateX(0);
}
/* v10.392 · `hidden` attribute on the drawer-panel pre-paints it as
 * display:none during the FOUC window (before site.css loads). Once
 * site.css is in, this rule wins via specificity and restores the
 * panel's intended display:flex. Without this, mobile users briefly
 * saw the entire drawer (including the EN/中文/日本語/한국어 lang
 * switcher) flash in document flow before the panel slid off-screen. */
.site-nav__drawer-panel[hidden] { display: flex !important; }
.site-nav__drawer-panel {
  position: absolute;
  inset: 0;
  background: #040404;
  display: flex;
  flex-direction: column;
  /* v10.412 · top padding ZEROED. Was max(80px, safe-area + 64px)
   * which dated from when the close X was absolutely positioned at
   * top:16px and needed 64-80px of clearance. The new flex header
   * (v10.410) provides its own padding · the panel-level 80px was
   * stacking with the header padding and pushing the logo + close
   * X 80px below the top of the panel. Horizontal + bottom padding
   * preserved. */
  padding: 0 24px max(24px, env(safe-area-inset-bottom));
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  transform: translateX(100%);
  transition: transform 0.55s cubic-bezier(0.16, 1, 0.3, 1);
  transition-delay: 0.14s;
  will-change: transform;
}
.site-nav__drawer.is-open .site-nav__drawer-panel {
  transform: translateX(0);
}
/* Drawer close button · paired refinement to the burger icon.
 * 44×44 tap target, circular border, thin 1.5px stroke X icon.
 * Hover rotates the X 90° (signature close-button feedback) +
 * subtle background/border brightening + ring brightens.
 * Active press scales 0.94 for tactile feel.
 * focus-visible gets a purple keyboard ring. */
/* v10.411 · drawer header · tightened proportions per Pharos reference.
 * v10.410 overcooked it · big padding above logo, oversized close
 * circle, visible divider. This rewrite matches Pharos's compact
 * header: logo tight against the top safe-area inset, plain X glyph
 * (no circle border), no bottom divider · the spacing alone separates
 * the header from the section list. */
.site-nav__drawer-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  /* v10.412 · horizontal padding removed · the panel parent already
   * provides 24px left/right via .site-nav__drawer-panel padding.
   * Only top (safe-area + 14px) and bottom (14px) padding here. */
  padding: calc(env(safe-area-inset-top) + 14px) 0 14px;
  margin: 0;
}
.site-nav__drawer-brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  color: #fff;
  opacity: 0;
  transition: opacity 0.3s cubic-bezier(0.65, 0, 0.35, 1);
}
.site-nav__drawer.is-open .site-nav__drawer-brand {
  opacity: 1;
  transition-delay: 0.2s;
}
.site-nav__drawer-brand img {
  width: auto;
  height: 26px;
  display: block;
  flex-shrink: 0;
  image-rendering: -webkit-optimize-contrast;
  filter: none;
}
.site-nav__drawer-brand-title {
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: #fff;
}

.site-nav__drawer-close {
  position: relative;
  /* v10.421 · 32→44 px (Apple HIG / WCAG 2.5.5 tap target). Icon
   * stays 18 px via the SVG rule below; the extra padding is the
   * invisible hit area around it. Negative margin keeps the visible
   * icon optically aligned where the 32 px button used to sit so the
   * drawer header geometry doesn't shift. */
  width: 44px;
  height: 44px;
  margin: -6px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  border-radius: 0;
  color: rgba(255, 255, 255, 0.85);
  cursor: pointer;
  z-index: 1001;
  padding: 0;
  flex-shrink: 0;
  /* v10.75: explicit guard · invisible + non-interactive until the
   * drawer is open. Prevents the close icon from leaking through
   * before the user taps the burger. The panel's translateX(100%)
   * SHOULD keep it off-screen, but the safety belt of opacity +
   * pointer-events ensures it's truly hidden in every viewport. */
  opacity: 0;
  pointer-events: none;
  transition:
    opacity     0.3s  cubic-bezier(0.65, 0, 0.35, 1),
    background   0.25s cubic-bezier(0.65, 0, 0.35, 1),
    border-color 0.25s cubic-bezier(0.65, 0, 0.35, 1),
    transform    0.2s  cubic-bezier(0.65, 0, 0.35, 1);
}
.site-nav__drawer.is-open .site-nav__drawer-close {
  opacity: 1;
  pointer-events: auto;
  transition-delay: 0.2s; /* wait for panel slide-in to complete */
}
.site-nav__drawer-close svg {
  width: 18px;
  height: 18px;
  /* Override the HTML stroke-width="2" attribute for a thinner stroke
   * that matches the refined burger lines (1.6px). */
  stroke-width: 1.5;
  transition: transform 0.4s cubic-bezier(0.65, 0, 0.35, 1);
}
@media (hover: hover) {
  .site-nav__drawer-close:hover { color: #fff; }
}
.site-nav__drawer-close:active { opacity: 0.65; }
.site-nav__drawer-close:focus-visible {
  outline: 2px solid var(--purple);
  outline-offset: 2px;
  border-radius: 4px;
}

/* v10.411 · drawer section list · tightened spacing per Pharos
 * reference. Was margin 8 0 24, border-top hairline · now sits
 * flush against the header with no divider since the header itself
 * has padding · spacing alone separates them. */
.site-nav__drawer-items {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0;
}
.site-nav__drawer-item {
  display: flex;
  flex-direction: column;
  /* v10.398 · tighter section rhythm. Label sits close to its links
   * (4px gap from .drawer-label margin-bottom) so it reads as one
   * grouped block, not floating header + separate link list. */
  gap: 0;
  /* v10.411 · tightened spacing · was padding 28 0 + visible bottom
   * hairline. Now compact rows with no divider · spacing alone
   * separates sections, matching the Pharos reference. */
  padding: 4px 0;
  /* Each item entrance: translates up + rotates slightly into place.
   * Delay driven by --sm-i inline custom property so we can have N
   * items without re-writing CSS each time. */
  opacity: 0;
  /* v10.94: dropped the rotate(6deg) entrance and tightened the
   * translation. Plain fade + small lift matches anthropic.com's
   * understated menu reveal · the previous rotate read as theatrical. */
  transform: translateY(16px);
  transition:
    opacity 0.45s cubic-bezier(0.16, 1, 0.3, 1),
    transform 0.45s cubic-bezier(0.16, 1, 0.3, 1);
  transition-delay: calc(0.25s + var(--sm-i, 0) * 0.06s);
}
.site-nav__drawer.is-open .site-nav__drawer-item {
  opacity: 1;
  transform: translateY(0);
}
/* v10.408 · drawer · Pharos-style collapsible sections.
 *
 * Replaces the flat always-visible label + links list with native
 * <details>/<summary> disclosure widgets. Tap a section label →
 * sub-links expand smoothly underneath (grid-template-rows 0fr→1fr
 * transition · compositor-friendly · no JS needed for the animation).
 *
 * Kept dark per user instruction "use this pattern and animation our
 * page not use the bg color" · Pharos uses white surfaces, we keep
 * the existing drawer dark surface and only add subtle dark cards
 * for the expanded sub-link rows.
 */
.site-nav__drawer-section {
  display: block;
}
/* Hide the default disclosure marker (▶) across browsers · we
 * provide our own chevron icon. */
.site-nav__drawer-section > summary { list-style: none; }
.site-nav__drawer-section > summary::-webkit-details-marker { display: none; }
.site-nav__drawer-section > summary::marker { display: none; content: ""; }

.site-nav__drawer-label {
  /* v10.411 · Pharos reference is 1.05-1.1rem weight 600 · we matched
   * 1.35rem 700 which read as display-tier and too dramatic next to
   * Pharos's compact menu. Dialed down to match the reference. */
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 4px;
  margin: 0;
  font-size: 1.1rem;
  font-weight: 600;
  letter-spacing: -0.012em;
  line-height: 1.2;
  color: #fff;
  text-transform: none;
  outline: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
}
/* v10.458 · the <summary> accordion triggers set outline:none above but
   had no focus-visible replacement · keyboard users couldn't see focus
   (WCAG 2.4.7). Restore a visible ring for keyboard nav only. */
.site-nav__drawer-label:focus-visible {
  outline: 2px solid var(--purple, #6f00ff);
  outline-offset: 2px;
  border-radius: 6px;
}
.site-nav__drawer-label > span:first-child { flex: 1; min-width: 0; }
.site-nav__drawer-chev {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px; height: 28px;
  color: rgba(255, 255, 255, 0.55);
  transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1),
              color 0.3s ease;
  flex-shrink: 0;
}
.site-nav__drawer-section[open] .site-nav__drawer-chev {
  transform: rotate(180deg);
  color: rgba(255, 255, 255, 0.85);
}

/* v10.413 · expand animation tightened to 0.22s ease-out · the
 * 0.38s spring was drawing too much attention to itself. Pharos
 * uses a near-instant CSS display swap · this is a compromise
 * between hard-snap and smooth (fast enough that the eye registers
 * the expand as immediate, slow enough that it doesn't feel jarring). */
.site-nav__drawer-sublinks-wrap {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.22s ease-out;
}
.site-nav__drawer-section[open] .site-nav__drawer-sublinks-wrap {
  grid-template-rows: 1fr;
}
@media (prefers-reduced-motion: reduce) {
  .site-nav__drawer-sublinks-wrap { transition: none; }
  .site-nav__drawer-chev { transition: none; }
}

/* v10.413 · sub-link rows · card chrome STRIPPED. The bordered dark
 * rounded boxes (v10.408 → v10.411) read as generic SaaS template
 * "tap card" pattern · same AI tell the user keeps flagging. Now:
 * plain text rows on the panel surface, indented under the section
 * header with a left hairline border that brightens on hover.
 * Pattern modeled on Stripe Docs / GitHub mobile nav · text-led,
 * boxless, scannable. */
.site-nav__drawer-sublinks {
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: 0;
  padding: 4px 0 10px 14px;
  margin: 0 0 0 4px;
  list-style: none;
  border-left: 1px solid rgba(255, 255, 255, 0.06);
}
.site-nav__drawer-sublinks a {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: 10px 0;
  background: transparent;
  border: none;
  border-radius: 0;
  color: rgba(255, 255, 255, 0.72);
  font-size: 0.98rem;
  font-weight: 500;
  letter-spacing: -0.003em;
  text-decoration: none;
  transition: color 0.18s ease;
}
.site-nav__drawer-sublinks a:hover,
.site-nav__drawer-sublinks a:focus-visible {
  color: #fff;
  outline: none;
}
.site-nav__drawer-sublinks a:active {
  color: #b07fff;
}

/* Footer block: socials row + Launch APP CTA pinned at the bottom of
 * the drawer. Sits below the numbered items list with margin-top:auto
 * pushing the whole block down regardless of how many items above. */
.site-nav__drawer-footer {
  margin-top: auto;
  display: flex;
  flex-direction: column;
  gap: 24px;
  padding-top: 32px;
  /* Subtle divider between the items list and the footer block */
  border-top: 1px solid rgba(255, 255, 255, 0.06);
  /* Both children fade up together as the last entrance step */
  opacity: 0;
  transform: translateY(20px);
  transition:
    opacity 0.5s ease-out,
    transform 0.5s ease-out;
  transition-delay: 0.85s;
}
.site-nav__drawer.is-open .site-nav__drawer-footer {
  opacity: 1;
  transform: translateY(0);
}

/* Social icon row · centered, generous gap, 40×40 buttons. */
.site-nav__drawer-socials {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 10px;
}
.site-nav__drawer-socials a {
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  /* v10.139: bumped circle bg + border from 0.06/0.08 -> 0.12/0.18
   * so the icon circles stand out more clearly against the drawer's
   * dark-glass panel. */
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid rgba(255, 255, 255, 0.18);
  color: #fff;
  transition: background 0.2s, border-color 0.2s, transform 0.2s;
}
.site-nav__drawer-socials a:hover,
.site-nav__drawer-socials a:focus-visible {
  background: var(--purple);
  border-color: var(--purple);
  outline: none;
}

/* Launch APP CTA · clean pill, no clip-path notch. Subtle border-on-fill
 * style to match the minimal staggered-menu aesthetic. */
.site-nav__drawer-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  align-self: center;
  width: 100%;
  max-width: 320px;
  min-height: 52px;
  padding: 14px 32px;
  background: var(--purple);
  color: #fff;
  font-family: var(--font);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-align: center;
  text-decoration: none;
  border: none;
  border-radius: 999px;
  cursor: pointer;
  box-shadow: 0 8px 24px rgba(111, 0, 255, 0.35);
  transition: background 0.2s, transform 0.2s, box-shadow 0.2s;
}
.site-nav__drawer-cta svg {
  transition: transform 0.2s;
}
@media (hover: hover) {
  .site-nav__drawer-cta:hover {
    background: var(--purple-hover);
    transform: translateY(-2px);
    box-shadow: 0 12px 28px rgba(111, 0, 255, 0.45);
  }
  .site-nav__drawer-cta:hover svg {
    transform: translateX(3px);
  }
}
.site-nav__drawer-cta:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 3px;
}

/* Hide the burger when the drawer is open so the two close affordances
 * (animated-X burger AND drawer's circled-X) don't overlap visually.
 * Drawer's close button + Esc key + click-outside (later if needed)
 * all still close cleanly. */
.site-nav__burger.is-open {
  visibility: hidden;
  pointer-events: none;
}

/* ── Toast notification ──────────────────────────────────────────────
 * Lazy-created by JS at the bottom of the viewport. Used for
 * click-to-copy email feedback. Self-dismisses after ~2.2s. */
.astarter-toast {
  position: fixed;
  bottom: max(24px, env(safe-area-inset-bottom));
  left: 50%;
  transform: translate(-50%, 20px);
  z-index: 10002;
  padding: 12px 20px;
  background: rgba(10, 4, 25, 0.95);
  color: #fff;
  font-family: var(--font);
  font-size: 14px;
  font-weight: 500;
  border: 1px solid rgba(111, 0, 255, 0.5);
  border-radius: 999px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);
  max-width: calc(100vw - 32px);
  text-align: center;
}
.astarter-toast--visible {
  opacity: 1;
  transform: translate(-50%, 0);
}

/* Respect reduced motion: collapse all transitions to near-instant
 * so the menu still works but doesn't animate. */
@media (prefers-reduced-motion: reduce) {
  .site-nav__drawer-prelayer,
  .site-nav__drawer-panel,
  .site-nav__drawer-item,
  .site-nav__drawer-cta {
    transition-duration: 0.01s !important;
    transition-delay: 0s !important;
  }
}
@media (max-width: 900px) {
  /* Tablet+: tighter margins, smaller height, hide desktop menu + CTA */
  .site-nav {
    top: calc(12px + env(safe-area-inset-top));
    width: calc(100% - 20px);
    border-radius: 16px;
  }
  .site-nav__inner {
    height: 54px;
    padding: 0 8px 0 16px;
  }
  .site-nav__menu,
  .site-nav__right {
    display: none;
  }
  .site-nav__burger {
    display: flex;
  }
  /* v10.193: ASTARTER wordmark now visible on mobile/tablet too (was
   * desktop-only). User reference: solid dark bar with logo + ASTARTER
   * text + burger. Title colour overridden to white in the mobile
   * dark-bar rule below.
   * v10.271 · bumped font-size 16 → 22 to match the v10.264/v10.268
   * PC + mobile parity bumps. The old 16px was sized to pair with the
   * old 32px logo; with the 48px logo it looked tiny in proportion.
   * This rule fires at 768–900px (tablet portrait + large phones
   * landscape) · that range was the visible-broken case the user
   * caught. Below 767px the mobile @media block further down with
   * !important pins the same 22px/700/0.04em. */
  .site-nav__title {
    display: inline-block;
    font-size: 17px;
    font-weight: 700;
    letter-spacing: 0.02em;
  }
}

/* ════════════════════════════════════════════════════════════════════
 * v10.74 · Three-state responsive nav theme
 *   Desktop (>= 1100 px): WHITE glassy floating card  (v10.73 default)
 *   iPad / tablet (768–1099 px): DARK glassy floating card
 *   Mobile (< 768 px): NO BAR (transparent, no border/shadow/blur).
 *     Logo + burger float free over the hero.
 * ════════════════════════════════════════════════════════════════════ */
/* v10.123: iPad/tablet nav reverted to the desktop WHITE-GLASS theme
 * per user request ("make bar colour like PC view"). The previous
 * dark-glass override caused FOUC bugs (anti-pattern #14) and didn't
 * match the rest of the site. The desktop .site-nav rules now apply
 * here unchanged · text/burger/dropdown stay dark on white. */

/* v10.169 + v10.170 · mobile (<768px): no bar at all, ever.
 *
 * v10.169 stripped .site-nav to transparent. But the bar kept
 * reappearing on scroll because three OTHER rules were still
 * painting it white:
 *   1. .site-nav--scrolled (JS adds class when user scrolls past
 *      ~50px) -> rgba(255,255,255,0.97)
 *   2. .site-nav--solid (JS adds class on deep scroll) -> #ffffff
 *   3. html.perf-mode .site-nav (active when fps<15) -> #ffffff
 *      with !important (v10.124)
 *
 * v10.170 nukes all three in the mobile media query so the bar
 * stays transparent no matter the scroll state or perf mode. */
/* v10.193 · mobile (<768px): edge-to-edge solid dark bar with logo
 * + ASTARTER wordmark + burger (matches user reference screenshot).
 * Was transparent (v10.169-v10.170). Now: full-width black bar at
 * the very top, no rounded corners, no floating-card margin ·
 * standard mobile app-bar pattern. Inline critical CSS in index.html
 * is updated in lockstep (anti-pattern #14). */
@media (max-width: 767px) {
  .site-nav,
  .site-nav--scrolled,
  .site-nav--solid,
  html.perf-mode .site-nav,
  html.perf-mode .site-nav--scrolled {
    top: 0 !important;
    left: 0 !important;
    transform: none !important;
    width: 100% !important;
    max-width: 100% !important;
    border-radius: 0 !important;
    background: #000000 !important;
    border: none !important;
    border-bottom: 1px solid rgba(255, 255, 255, 0.06) !important;
    box-shadow: none !important;
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
    padding-top: env(safe-area-inset-top) !important;
  }
  /* Burger spans + wordmark + logo all white on the dark bar */
  .site-nav__burger span { background: #fff !important; }
  .site-nav__title { color: #fff !important; font-size: 14px !important; font-weight: 700 !important; letter-spacing: 0.02em !important; }
  /* Mobile logo size history:
   * - pre-v10.195: 40px
   * - v10.195: 28px (~9:1 downscale aliased stripes in "O")
   * - v10.222: 36px (too big per user)
   * - v10.223: 32px ("sweet spot" · predates v10.264 PC bump)
   * - v10.266: 40px (still felt small after v10.264 PC bump to 48px)
   * - v10.268: 48px · full PC parity
   * - v10.273: 56px · bumped image to fill the existing 60 px bar
   *   (was 80 % fill, now 93 %). Pill chrome unchanged · only the
   *   icon grew. Width math on 320 px viewport: bar = 288 px wide,
   *   content = 240 px after padding, brand budget = ~186 px after
   *   burger + gap. Required: 56 px icon + 10 px gap + ~110 px
   *   wordmark = ~176 px. Fits with 10 px headroom on iPhone SE
   *   1st-gen (320 px viewport). 56 / 256 native = 4.6:1 downscale,
   *   still clean stripe rendering. */
  .site-nav__logo-img {
    height: 22px !important;
    width: auto !important;
    image-rendering: -webkit-optimize-contrast;
    image-rendering: crisp-edges;
  }
  /* v10.280 · lockup variant slightly smaller (whole image, not just icon). */
  .site-nav__logo-img--lockup {
    height: 48px !important;
  }
  /* Tighter brand gap on mobile so logo + wordmark feel like a unit.
   * Stays visible on every scroll position (no fade-on-scroll · the
   * bar is a permanent app-bar now, not a hero-only overlay). */
  .site-nav__brand {
    gap: 8px !important;
    opacity: 1 !important;
    pointer-events: auto !important;
  }
}

@media (min-width: 901px) {
  .site-nav__drawer {
    display: none;
  }
}

/* ---- Hero ---- */
.hero {
  position: relative;
  width: 100%;
  /* 90dvh tracks the dynamic viewport · adjusts when iOS Safari shows /
   * hides its URL bar, and when the user rotates the device. min-height
   * is a safety floor so on extremely short viewports (e.g. some Android
   * keyboards open, or unusual split-screen) the hero never collapses
   * to a sliver. */
  height: 90vh;
  height: 90dvh;
  min-height: 480px;
  overflow: hidden;
  background: #040404;
}
/* Sharp WebP poster as the hero background, layered as its own element so
 * we can apply a subtle Ken Burns (slow zoom) animation without affecting
 * children. Single static image = zero decode cost, sharp at any resolution. */
.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  background: #040404 url(../assets/hero-poster.webp) center/cover no-repeat;
  z-index: 0;
  will-change: transform;
  animation: heroKenBurns 24s ease-in-out infinite alternate;
  /* Pause the animation whenever the hero scrolls off-screen. JS adds the
   * .hero--paused class when the hero is no longer in viewport · saves
   * continuous GPU transform calc while user is reading the rest of page. */
}
.hero--paused::after {
  animation-play-state: paused;
}
/* v10.267 · hard-off on weak devices (set by initVideoA11y). Pairs
 * with the inline critical CSS in <head> so the rule applies even
 * before site.css finishes loading. */
.hero--no-anim::after {
  animation: none;
}
@keyframes heroKenBurns {
  from { transform: scale(1.00); }
  to   { transform: scale(1.06); }
}
@media (prefers-reduced-motion: reduce) {
  .hero::after { animation: none; }
}
.hero__wrap {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  z-index: 100;
  overflow: hidden;
}
/* One combined gradient instead of two stacked overlays · half the blend cost */
.hero__wrap::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0.5) 0%,
    rgba(0, 0, 0, 0.5) 60%,
    #040404 100%
  );
  z-index: 1;
  pointer-events: none;
}
.hero__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 0;
  /* v10.87: fade in once the decoder is actually playing frames.
   * Without this, the element shows a transparent frame for ~1-2
   * dropped frames as it transitions from poster → first decoded
   * frame, which reads as a "flash". Poster shows through underneath
   * (CSS bg on .hero::after) so the section is never blank. */
  opacity: 0;
  /* v10.177 · perceived-sharpness pass.
   * Source MP4 is 1280×720 (anti-pattern #9 in CLAUDE.md · the only
   * source we have). On 1080p+ desktops and retina mobile it's
   * upscaled 1.5×–3×, which softens the picture. Three signals
   * help the eye read it as sharper than the raw resolution:
   *   - contrast(1.08): micro-boost the edge differential
   *   - saturate(1.06): richer brand-violet glow on the lit edges
   *   - image-rendering hints: tell the browser to use its highest-
   *     quality scaler instead of the cheap bilinear default
   *   - GPU layer (translate3d + backface-visibility) keeps the
   *     compositor from re-sampling on every paint. */
  filter: contrast(1.08) saturate(1.06);
  image-rendering: -webkit-optimize-contrast;
  image-rendering: crisp-edges;
  image-rendering: high-quality;
  /* v10.417 · deduped duplicate transform + backface-visibility · the
   * trailing `transform: translateZ(0)` was silently overriding the
   * `translate3d(0,0,0)` above (identical GPU-layer hint, just noisy).
   * Kept translateZ(0) as the single canonical form. */
  transform: translateZ(0);
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  will-change: transform, opacity;
  transition: opacity 0.6s cubic-bezier(0.16, 1, 0.3, 1);
}
.hero__video.hero__video--ready {
  opacity: 1;
}
.hero__content {
  position: relative;
  z-index: 2;
  width: 100%;
  max-width: 1600px;
  display: flex;
  flex-direction: column;
  gap: 24px;
  padding: 0 32px;
}
/* v10.245 PERMANENT FIX: hero text entrance is a pure CSS keyframes
 * animation. Previous behaviour:
 *   .hero__head.reveal { opacity: 0 }       (initial state · invisible)
 *   .hero__head.reveal.is-visible { ... 1 } (JS-added class · visible)
 *
 * Problem: ANY JS failure (syntax error, network block, broken SW
 * cache) meant .is-visible never got added → hero stayed invisible
 * forever. Exactly what happened in v10.243 when a stray orphan
 * close-comment token broke app.js parse → entire script never
 * executed → no .is-visible anywhere → hero blank.
 *
 * New behaviour: CSS @keyframes animation runs unconditionally on
 * page load via `animation: heroEnter ... both`. The `both` fill mode
 * holds the final visible state after the animation completes. No JS
 * dependency. If JS fails for ANY reason, hero still renders.
 *
 * Below-the-fold reveals (architecture, problem, nodes, etc.) still
 * use the .reveal → .is-visible JS pattern via initReveal() · they
 * NEED scroll-trigger timing. Only above-the-fold hero changed. */
@keyframes heroEnter {
  from { opacity: 0; transform: translateY(36px); }
  to   { opacity: 1; transform: translateY(0); }
}
.hero__head.reveal {
  opacity: 1; /* default visible if animation is unavailable */
  will-change: transform, opacity;
  animation: heroEnter 0.6s cubic-bezier(0.2, 0.8, 0.2, 1) 0.05s both;
}
.hero__actions.reveal {
  opacity: 1;
  will-change: transform, opacity;
  animation: heroEnter 0.6s cubic-bezier(0.2, 0.8, 0.2, 1) 0.35s both;
}
@media (prefers-reduced-motion: reduce) {
  .hero__head.reveal,
  .hero__actions.reveal {
    animation: none;
    opacity: 1;
    transform: none;
  }
}
.hero__head {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  /* v10.289 · bigger gap between tag-row / h1 / subtitle / CTA for
   * generous display-tier rhythm. Was 16 px. */
  gap: 24px;
}
.hero__title {
  /* v10.289 · Display-tier hero h1 per top-1% reference (Pharos /
   * Linear / Stripe). Was clamp(2.4rem,5vw,4rem) capping at 64 px;
   * now fluid 48 → 96 px so it dominates on wide viewports.
   * Tighter leading + display letter-spacing for editorial feel.
   * Weight 800 (was 700) · heavier display optical sizing. */
  font-size: clamp(3rem, 6.2vw, 6rem);
  font-weight: 800;
  color: #fff;
  margin: 0;
  line-height: 1.02;
  letter-spacing: -0.025em;
  max-width: 18ch;
}
.hero__subtitle {
  /* v10.289 · larger, softer subtitle. Slight opacity makes the h1
   * dominate without competing for attention. */
  font-size: clamp(1.05rem, 1.3vw, 1.25rem);
  color: rgba(255, 255, 255, 0.78);
  margin: 0;
  max-width: 560px;
  line-height: 1.55;
  font-weight: 400;
  letter-spacing: -0.005em;
}
@media (max-width: 768px) {
  /* .hero__title font-size now driven by --fs-hero token (2rem at <768)
   * defined in :root @media block. Hardcoded 2.4rem override removed. */
  .hero__content {
    padding: 0 16px;
  }
  /* v10.468 · Mobile hero "Buy a Node" CTA was inheriting the desktop
   * 52 px / 15 px treatment with no override · read as oversized/chunky
   * against the smaller mobile hero. Refined to a tighter, top-tier
   * scale (Linear/Stripe mobile CTA proportions) while staying a
   * comfortable >=44 px tap target (WCAG 2.5.5). */
  .hero__cta.btn-launch {
    min-height: 46px;
    padding: 0 18px 0 15px;
    gap: 8px;
    font-size: 14px;
    border-radius: 11px;
  }
  .hero__cta-icon {
    width: 16px;
    height: 16px;
  }
}
/* Landscape-phone safety: when a phone is rotated to landscape, the
 * viewport height collapses to ~375px. 70dvh of that = 263px, which is
 * too short to display the hero title + subtitle + CTA. Force a sensible
 * minimum so content never overlaps or clips. */
@media (orientation: landscape) and (max-height: 500px) {
  .hero {
    height: 100vh;
    height: 100dvh;
    min-height: 360px;
  }
  .hero__title {
    font-size: 1.75rem;
  }
  .hero__subtitle {
    font-size: 0.9rem;
  }
  .hero__content {
    gap: 12px;
    padding: 0 16px;
  }
}

/* ---- Story ---- */
/* ════════════════════════════════════════════════════════════════════
 * v10.50 · Story section: editorial manifesto with brand accents
 *
 * The sticky-scroll word-reveal block between Hero and ABox carries
 * the Web4 thesis. Old design: plain white text with grey→white word
 * fade. Upgrade adds:
 *   - Eyebrow label ("The Thesis") with gradient bar accent
 *   - Display-size editorial typography (3.5 rem, -0.02 letter-spacing)
 *   - Subtle radial purple ambient backdrop behind the text
 *   - Brand-gradient text on key terms: "AI agents", "Astarter",
 *     "DePIN-powered" (marked .story__word--accent in HTML)
 *
 * JS scroll logic in initNarrativeWords() unchanged · it still
 * toggles .--lit / .--dim. The accent is purely CSS, layered when
 * a word has both .--lit AND .--accent.
 * ════════════════════════════════════════════════════════════════════ */
.story {
  /* Was 300vh (4320px on 1440p · 3 full viewports just to read one
   * paragraph). 130vh keeps the word-fade effect but ~55% faster to
   * scroll through. Mobile shorter (90vh) below.
   * v10.156: brand-violet radial removed · flat now. */
  height: 130vh;
  background: var(--bg);
  position: relative;
}
.story__sticky {
  /* 100vh on iOS Safari ignores the URL bar · content gets cut off
   * by the bottom chrome. 100dvh tracks the actual visible viewport. */
  height: 100vh;
  height: 100dvh;
  position: sticky;
  top: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  padding: 0 32px;
}
.story__inner {
  max-width: 1500px;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
}
.story__eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  margin: 0 0 36px;
  padding-left: 4px;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.5);
}
/* v10.383 · ornamental gradient dash removed (mobile AI tell #3 / anti-pattern #11). */
.story__text {
  max-width: 92%;
  font-size: 3.5rem;
  font-weight: 700;
  text-align: left;
  line-height: 1.22;
  letter-spacing: -0.02em;
}
.story__word {
  transition:
    color 0.55s cubic-bezier(0.16, 1, 0.3, 1),
    text-shadow 0.55s cubic-bezier(0.16, 1, 0.3, 1);
}
.story__word--dim {
  color: rgba(255, 255, 255, 0.08);
}
.story__word--lit {
  color: #fff;
  text-shadow: 0 0 24px rgba(255, 255, 255, 0.12);
}
/* v10.383 · gradient text-clip on accent story words REMOVED
 * (mobile AI tell · gradient rainbow words inside body copy
 * reads crypto-template). Solid white --lit carries the emphasis. */
@media (max-width: 768px) {
  .story__text {
    font-size: 1.85rem;
    max-width: 96%;
    line-height: 1.3;
  }
  .story__eyebrow {
    font-size: 0.7rem;
    margin-bottom: 22px;
  }
  .story__eyebrow::before {
    width: 28px;
  }
}

/* ---- ABOX ---- */
.abox-anchor {
  position: relative;
  /* 550vh originally → 280vh (too fast) → 400vh → 450vh.
   * Extra 50vh slows the camera lerp slightly between the 6 ABOX shots
   * · smoother transitions, no other side effects. Mobile stays 280vh. */
  height: 450vh;
}
/* ════════════════════════════════════════════════════════════════════
 * v10.62 · ABox section: top-1% polish
 *
 * Was: 3D device on the left, panel text on the right, both sitting
 * in pure black void. Functional but visually flat.
 *
 * Adds (CSS-only, no HTML/JS touched):
 *   - Soft purple radial ambient behind the device (no more void)
 *   - Subtle dot-grid pattern across the section bg, masked to fade
 *     at the edges (tech/data feel without competing for attention)
 *   - "01 · OF · 05" eyebrow above each panel title using CSS counter
 *     (gives the viewer a "where am I" signal in the feature flow)
 *   - Vertical brand-violet hairline between the canvas + copy columns
 *     (visually ties the device to its description)
 *   - Tighter typography on panel titles
 * ════════════════════════════════════════════════════════════════════ */
.abox {
  position: sticky;
  top: 0;
  min-height: 100vh;
  min-height: 100dvh;
  background: var(--bg);
  color: #fff;
  overflow: hidden;
  isolation: isolate;
}
/* v10.156: v10.62's dot-grid + brand-violet radial ambients removed.
 * The 3D ABox device + scroll-driven panels carry the section. No
 * decorative chrome behind them. */
.abox__layout,
.abox__canvas-col,
.abox__copy {
  position: relative;
  z-index: 1;
}
/* v10.362 · reverted to the pre-v10.355 baseline. The grid rebuild
 * + paginator landed awkwardly (small ticks in the middle of the gap
 * read as confused, copy column got narrowed). Back to the original
 * flex 50/45 split with comfortable spacing. */
.abox__layout {
  display: flex;
  align-items: center;
  gap: 48px;
  max-width: 1600px;
  margin: 0 auto;
  height: 100%;
  min-height: 100vh;
  min-height: 100dvh;
  padding: 0;
  box-sizing: border-box;
}
.abox__canvas-col {
  flex: 0 0 50%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
/* ABOX canvas wrapper (Three.js scene) · fully contained.
 * `contain: strict` (size + layout + style + paint) isolates the canvas
 * from the rest of the page's render pipeline. Without this, Chrome
 * recomputes the canvas's layout context on every parent change. */
.abox__canvas-wrap {
  width: 100%;
  aspect-ratio: 856 / 686;
  max-height: 80vh;
  position: relative;
  background: #040404;
  overflow: hidden;
  contain: strict;
}
.abox__dots {
  display: none;
}
.abox__copy {
  flex: 0 0 45%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 0 24px 0 48px;
  box-sizing: border-box;
  opacity: 0;
  transform: translateY(36px);
  transition:
    opacity 0.6s ease-out 0.2s,
    transform 0.6s cubic-bezier(0.2, 0.8, 0.2, 1) 0.2s;
  will-change: opacity, transform;
  position: relative;
}
/* Vertical brand-violet hairline that visually ties the copy column
 * back to the device on the left. Fades at top + bottom so it reads
 * as a subtle accent, not a divider. */
.abox__copy::before {
  content: "";
  position: absolute;
  left: 0;
  top: 20%;
  bottom: 20%;
  width: 1px;
  background: linear-gradient(180deg,
    transparent,
    rgba(157, 92, 255, 0.4) 30%,
    rgba(157, 92, 255, 0.4) 70%,
    transparent);
}
.abox__copy--visible {
  opacity: 1;
  transform: translateY(0);
}
.abox__stack {
  position: relative;
  min-height: 200px;
  /* Counter scope for the panel eyebrow (01 · OF · 05) */
  counter-reset: abox-num;
}
.abox-panel {
  position: absolute;
  inset: 0 auto auto 0;
  right: 0;
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.45s ease, transform 0.45s ease;
  pointer-events: none;
  /* Counter increment runs even on inactive panels, but only the
   * active one is visible · so the count shown is always correct
   * for the visible panel. */
  counter-increment: abox-num;
}
.abox-panel--active {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}
/* v10.365 · restored pre-v10.354 ABox state. v10.62's "top-1% polish"
 * pass (counter eyebrow + violet hairline + 48px padding + fw 800
 * typography) was the preferred design · bringing it all back. */
/* v10.366 · eyebrow refined ONLY (no other changes).
 *   - "01 · OF · 05" → "01 / 05" (Apple/Linear pagination convention)
 *   - Monospace for the numerals (tabular data feel, premium)
 *   - fw 800 → fw 500 (heavy weight on a tiny eyebrow reads AI)
 *   - letter-spacing 0.22em → 0.14em (was screaming)
 *   - Color de-saturated to rgba(.85) so it doesn't punch above the
 *     panel title
 *   - inline-block + 8px padding-bottom + 1px violet-alpha border-
 *     bottom: quiet editorial hairline under the eyebrow that ends
 *     at the eyebrow's right edge, not full column width. */
.abox-panel::before {
  content: counter(abox-num, decimal-leading-zero) " / 05";
  display: inline-block;
  font-family: ui-monospace, "SF Mono", Menlo, Monaco, monospace;
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(176, 127, 255, 0.85);
  margin-bottom: 22px;
  line-height: 1;
  padding-bottom: 8px;
  border-bottom: 1px solid rgba(157, 92, 255, 0.22);
}
.abox-panel h2 {
  font-size: 3.1rem;
  font-weight: 800;
  line-height: 1.1;
  letter-spacing: -0.02em;
  margin: 0 0 20px;
}
.abox-panel h3 {
  font-size: 2.1rem;
  font-weight: 800;
  line-height: 1.15;
  letter-spacing: -0.015em;
  margin: 0 0 14px;
}
.abox-panel p {
  font-size: 1.02rem;
  line-height: 1.7;
  margin: 0;
  max-width: 460px;
  color: rgba(255, 255, 255, 0.78);
}
@media (prefers-reduced-motion: reduce) {
  .abox__copy {
    opacity: 1;
    transform: none;
    transition: none;
  }
  .abox-panel {
    transition: none;
  }
}
@media (max-width: 900px) {
  /* Mobile: stick at 100dvh so the section anchors to a viewport-sized
   * frame, but center the stacked content (canvas + dots + copy) inside
   * that frame. Previous v6.4 tried to collapse the section instead ·
   * that just left the abox-anchor scroll runway showing black behind
   * the now-shorter section, which is the same visual problem from the
   * other direction. Centering keeps the empty space symmetrical
   * (top and bottom of the stack) rather than dumping it all at the
   * bottom. */
  .abox,
  .abox__layout {
    min-height: 100vh;
    min-height: 100dvh;
  }
  /* v10.93: kill the desktop left-side ambient on mobile.
   * .abox::after is positioned at 25% 50% (left of the device on desktop).
   * On mobile the device stacks above the copy, so the ambient lands in
   * the upper-left of the panel area and reads as a dark smudge behind
   * the title. Drop it; the dot-grid (::before) keeps the section from
   * looking flat. */
  .abox::after {
    display: none;
  }
  .abox__layout {
    flex-direction: column;
    justify-content: center;
    gap: 0;
    padding: 0;
  }
  .abox__canvas-col {
    flex: 0 0 auto;
    width: 100%;
    height: auto;
  }
  .abox__canvas-wrap {
    /* v10.385 · was `width: 100vw`. On mobile Chrome/Safari, 100vw
     * can be off-by-a-fractional-pixel from the actual layout-viewport
     * (subpixel rounding + safe-area + dynamic toolbar), causing a
     * 1px horizontal overflow → browser auto-zooms out, rendering
     * the whole page in "desktop view" on refresh. Switching to
     * `width: 100%` constrains to the actual parent box; no overflow. */
    max-height: 40vh;
    max-height: 40dvh;
    width: 100%;
  }
  .abox__dots {
    display: flex;
    justify-content: center;
    gap: 8px;
    padding: 10px 0 4px;
  }
  .abox__dot {
    /* Apple HIG 44×44 tap target via pseudo-element; visible dot stays 8×8 */
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.3);
    cursor: pointer;
    border: none;
    padding: 0;
    position: relative;
  }
  .abox__dot::before {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 44px;
    height: 44px;
    transform: translate(-50%, -50%);
  }
  .abox__dot--active {
    background: #fff;
    transform: scale(1.25);
  }
  .abox__copy {
    flex: 1;
    width: 100%;
    height: auto;
    /* Center vertically in the remaining space so the empty area sits
     * symmetrically around the text instead of all dumped at the bottom
     * (user reported "too much black space" in the lower viewport). */
    justify-content: center;
    margin-top: 24px;
    padding: 0 16px 24px;
  }
  /* Hairline accent doesn't make sense on mobile (vertical stack, not
   * side-by-side columns). Hide it. */
  .abox__copy::before {
    display: none;
  }
  .abox-panel::before {
    font-size: 0.62rem;
    letter-spacing: 0.18em;
    margin-bottom: 12px;
  }
  .abox__stack {
    min-height: 170px;
  }
  .abox-panel h2 {
    font-size: 2rem;
  }
  .abox-panel h3 {
    font-size: 1.5rem;
  }
  .abox-panel p {
    font-size: 0.88rem;
    line-height: 1.65;
  }
}

/* ---- Gateway features ---- */
.gateway {
  background: var(--bg);
  padding: 100px 0;
  position: relative;
  overflow: clip;
  /* content-visibility removed: SVGs are now 347 KB optimized + lazy-loaded.
   * Keeping `contain` for compositing isolation without paint-skip-on-rerender. */
  contain: layout style;
}
/* v10.103: subtle section-level brand-violet ambient. Sits below the
 * cards and leads the eye toward the A-Core block underneath. Static
 * (no animation here · the A-Core block carries the breathing motion).
 * Doesn't compete with the core's stronger ambient because it's
 * positioned in the lower 60% of the section only. */
.gateway::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background:
    radial-gradient(ellipse 60% 45% at 50% 75%, rgba(111, 0, 255, 0.08), transparent 70%);
}
.gateway > .container { position: relative; z-index: 1; }
.gateway .container {
  position: relative;
  max-width: 1600px;
  padding: 0 32px;
  margin: 0 auto;
  z-index: 5;
  --stack-gap: clamp(8px, 1.25vw, 20px);
  --stack-size: calc((100% - (var(--stack-gap) + var(--stack-gap))) / 3);
}
.gateway__title {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  color: #fff;
  margin-bottom: 32px;
}
.gateway__title h2 {
  text-align: center;
  font-size: clamp(1.75rem, 5vw, 3.5rem);
  max-width: 22ch;
  color: #fff;
  line-height: 1.2;
  margin: 0 0 16px;
  white-space: normal;
}
@media (max-width: 991px) {
  .gateway {
    overflow-x: clip;
    overflow-y: visible;
  }
  .gateway__title h2 {
    font-size: clamp(1.5rem, 6vw, 2.75rem);
    margin-bottom: 0;
  }
}
@media (max-width: 768px) {
  .gateway .container {
    padding: 0 16px;
  }
}
.gateway__grid {
  display: grid;
  width: 100%;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-template-rows: 1fr;
  gap: var(--stack-gap);
  justify-content: center;
  /* Use dynamic viewport height · fixes iOS Safari URL bar cutoff */
  height: 100vh;
  height: 100dvh;
  position: sticky;
  top: 0;
  z-index: 1;
}
.gateway-card {
  position: relative;
  aspect-ratio: 1;
  /* v10.485 · sharp-cornered faint-border boxes read as flat/unfinished.
     Rounded + a touch more border definition = premium card, restrained
     (no extra glow · the hover state already carries the violet accent). */
  border: 1px solid rgba(255, 255, 255, 0.13);
  border-radius: 20px;
  background: linear-gradient(160deg, #1a1a1a, #0d0d0d 40%, #040404);
  transform-origin: center center;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  /* v10.107: inner top-light highlight (Stripe/Linear depth pattern).
   * Subtle 1px inset white-alpha across the top edge gives the card
   * a sense of being "lit from above" · reads as a real surface
   * with mass instead of a flat rectangle. Paired with a thin inset
   * dark shadow at the bottom for additional depth. */
  box-shadow:
    0 1px 0 rgba(255, 255, 255, 0.06) inset,
    0 -1px 0 rgba(0, 0, 0, 0.4) inset,
    0 1px 2px rgba(0, 0, 0, 0.3);
  transition: transform 0.4s cubic-bezier(0.2, 0.8, 0.2, 1),
              box-shadow 0.4s cubic-bezier(0.2, 0.8, 0.2, 1),
              border-color 0.4s ease;
}
/* Hover lift + zoom · gated by hover:hover so touch devices don't
 * get stuck-hover artifacts after taps. */
@media (hover: hover) {
  .gateway-card:hover {
    transform: translateY(-8px) translateZ(0);
    /* v10.107: punchier hover glow. Was 0.15 alpha · too faint to
     * read as a real signal. Now 0.32 alpha + preserved inset
     * highlight so the depth doesn't collapse when hovered. */
    box-shadow:
      0 1px 0 rgba(255, 255, 255, 0.08) inset,
      0 -1px 0 rgba(0, 0, 0, 0.4) inset,
      0 16px 36px rgba(111, 0, 255, 0.32),
      0 6px 14px rgba(0, 0, 0, 0.45);
    border-color: rgba(111, 0, 255, 0.55);
  }
  .gateway-card:hover .gateway-card__media,
  .gateway-card:hover .gateway-card__icon svg {
    transform: scale(1.08) translateZ(0);
  }
}
.gateway-card__icon {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  /* v10.199: center the icon horizontally (was flex-start = left-aligned).
   * With the new 3D renders, all 3 icons need to be horizontally centered
   * inside their box instead of pinned to the left edge. */
  justify-content: center;
  padding: 16px;
}
.gateway-card__icon svg,
.gateway-card__icon .gateway-card__media {
  width: 100%;
  height: 100%;
  max-width: 60%;
  max-height: 60%;
  display: block;
  pointer-events: none;
  border: none;
  transition: transform 0.6s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.gateway-card__body {
  flex-shrink: 0;
  padding: 20px;
  color: #fff;
  /* v10.205: center-align title + description for the 3-card row.
   * Matches the centered icon above (v10.199) so each card reads as
   * a vertically-stacked composition: icon -> title -> description,
   * all aligned to the card's central axis. */
  text-align: center;
}
.gateway-card__body h3 {
  margin: 0 0 6px;
  font-size: 16px;
  font-weight: 600;
}
.gateway-card__body p {
  margin: 0;
  font-size: 14px;
  line-height: 1.5;
  color: rgba(255, 255, 255, 0.7);
  transition: color 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
@media (hover: hover) {
  .gateway-card:hover .gateway-card__body p {
    color: #9d5cff;
  }
}
.gateway-core {
  position: relative;
  width: var(--stack-size);
  aspect-ratio: 1;
  margin: 0 auto 64px;
  z-index: 2;
  background: var(--bg);
  /* backdrop-filter removed · element has a solid bg, so there was
   * nothing behind it to blur. Was wasting GPU work every frame. */
  border: 1px solid rgba(255, 255, 255, 0.25);
  transition: border-color 0.45s, box-shadow 0.45s, background 0.45s;
  overflow: visible;
  /* v10.103: brand-violet breathing ambient. Soft radial sits BEHIND
   * the block (negative z-index of pseudo) and pulses slowly. Gives
   * the A-Core block the "powered on" feel without changing markup
   * or text. Compositor-only (opacity + transform), respects
   * prefers-reduced-motion. */
  isolation: isolate;
}
.gateway-core::before {
  content: "";
  position: absolute;
  inset: -25%;
  z-index: -1;
  pointer-events: none;
  /* v10.108: ambient halved AGAIN per user · the centre still read
   * too bright after v10.106. Alphas 0.12/0.10 → 0.06/0.04, base
   * opacity 0.5 → 0.3, keyframe peak 0.7 → 0.4. Just a hint of
   * "powered on" now, no center hotspot.
   *
   * v10.444-pending · scroll-lag perf pass: dropped `filter: blur(12px)`
   * from the animated layer. The radial gradients here are already very
   * soft (60%/35% ellipses with low-alpha colour stops), so the blur
   * added almost no visible difference but forced a full re-rasterise
   * of the layer EVERY frame of the 6s pulse animation while the
   * Gateway section was in view · documented compositor cost
   * (see github.com/zakky8/web-optimization · 18-css-performance/
   * compositor-thread.md, "each layer costs VRAM" + filter+animation
   * = re-raster per frame).
   * Compositor-only properties (opacity + transform) remain animated. */
  background:
    radial-gradient(ellipse 60% 55% at 50% 50%, rgba(157, 92, 255, 0.06), transparent 70%),
    radial-gradient(ellipse 35% 45% at 50% 50%, rgba(111, 0, 255, 0.04), transparent 60%);
  opacity: 0.3;
  animation: gatewayCoreAmbient 6s ease-in-out infinite;
  will-change: transform, opacity;
}
/* v10.103: faint scan-grid texture inside the A-Core block · tech
 * "powered chip" feel. Masked via radial so it fades at edges. */
.gateway-core::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background-image:
    linear-gradient(rgba(157, 92, 255, 0.05) 1px, transparent 1px),
    linear-gradient(90deg, rgba(157, 92, 255, 0.05) 1px, transparent 1px);
  background-size: 28px 28px;
  mask: radial-gradient(ellipse 70% 65% at 50% 50%, #000 35%, transparent 80%);
  -webkit-mask: radial-gradient(ellipse 70% 65% at 50% 50%, #000 35%, transparent 80%);
  opacity: 0.6;
}
.gateway-core__inner {
  position: relative;
  z-index: 1;
}
@keyframes gatewayCoreAmbient {
  0%, 100% { opacity: 0.25; transform: scale(0.96); }
  50%      { opacity: 0.4;  transform: scale(1.04); }
}
@media (prefers-reduced-motion: reduce) {
  .gateway-core::before { animation: none; opacity: 0.75; }
}
.gateway-core__decor {
  position: absolute;
  width: 300%;
  height: 300%;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  opacity: 0;
  transition: opacity 2s ease 0.2s;
  pointer-events: none;
  z-index: 1;
}
.gateway-core__circuit {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: none;
  pointer-events: none;
  object-fit: cover;
  display: block;
}
.gateway-core__overlay {
  position: absolute;
  width: 300%;
  height: 300%;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: linear-gradient(to bottom, transparent 70%, #040404 95%);
  pointer-events: none;
  z-index: 2;
  opacity: 0;
  transition: opacity 1s ease 0.2s;
}
.gateway-core--active .gateway-core__decor,
.gateway-core--active .gateway-core__overlay {
  opacity: 1;
}
.gateway-core__inner {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 12%;
  z-index: 3;
}
.gateway-core__logo-img {
  width: 70%;
  max-width: 280px;
  height: auto;
  flex-shrink: 0;
  display: block;
  object-fit: contain;
  opacity: 0;
  transition: opacity 0.8s ease;
  filter: drop-shadow(0 1px 6px rgba(255, 255, 255, 0.04));
}
.gateway-core--active .gateway-core__logo-img {
  opacity: 1;
}
.gateway-core--active {
  border: none;
  /* Reduced from 5 stacked shadows (max blur 168px) to 2 · paint cost
   * scales with blur radius, this is ~70% cheaper for near-identical look. */
  box-shadow:
    0 0 80px rgba(111, 0, 255, 0.55),
    0 12px 24px rgba(0, 0, 0, 0.45);
}
.gateway-core__text {
  text-align: center;
  max-width: 72ch;
  margin: 0 auto;
  color: rgba(255, 255, 255, 0.88);
  line-height: 1.6;
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.8s ease, transform 0.8s ease;
}
.gateway-core__text.is-visible {
  opacity: 1;
  transform: translateY(0);
}
.gateway-core__text h3 {
  margin: 0 0 10px;
  color: #fff;
  font-size: 2rem;
  font-weight: 700;
}
.gateway-core__text p {
  margin: 0;
}
.gateway-core__cta {
  margin-top: 32px;
  display: flex;
  justify-content: center;
}
.gateway-core__cta a {
  background: #fff;
  color: #000;
  border: none;
  width: 18.5vw;
  max-width: 280px;
  padding: 12px 20px;
  border-radius: 100px;
  font-size: 14px;
  font-weight: 600;
  font-family: var(--font);
  text-align: center;
  /* v10.103: full top-1% button transition · was just `opacity 0.2s`.
   * Now: subtle lift + brand-violet glow on hover, pressed-in feel on
   * :active. Matches the polish of the primary CTAs sitewide. */
  transition:
    transform 0.35s cubic-bezier(0.16, 1, 0.3, 1),
    box-shadow 0.35s cubic-bezier(0.16, 1, 0.3, 1),
    background 0.25s ease;
  display: inline-block;
  will-change: transform;
}
@media (hover: hover) {
  .gateway-core__cta a:hover {
    background: #f4f1ea;
    transform: translateY(-2px);
    box-shadow:
      0 10px 28px rgba(111, 0, 255, 0.32),
      0 2px 8px rgba(0, 0, 0, 0.3);
  }
}
.gateway-core__cta a:active {
  transform: translateY(0);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25) inset;
}
@media (max-width: 1024px) {
  .gateway .container {
    --stack-gap: 10px;
    /* v10.458 · was calc((100vw - 32px - ...)/3). 100vw includes the
       scrollbar width (horizontal-clip risk) and 32px didn't match the
       container's actual 48px padding. .gateway__grid is width:100% of
       the container content box, so size columns against 100% with no
       padding subtraction · 3 columns + 2 gaps fill the grid exactly. */
    --stack-size: calc((100% - 2 * var(--stack-gap)) / 3);
  }
  .gateway__grid {
    grid-template-columns: repeat(3, var(--stack-size));
    grid-template-rows: 1fr;
    justify-content: center;
    padding-top: 5vh;
    /* On portrait tablets the 100dvh grid leaves a huge void below
     * square cards. Cap so the grid hugs its content. */
    height: auto;
    min-height: calc(var(--stack-size) + 5vh);
  }
  /* gateway-core's width was inheriting --stack-size (now narrow ~120px),
   * which crushed the inner A-Core logo/text/CTA into an unreadable column.
   * Give it its own comfortable mobile width independent of card sizing. */
  .gateway-core {
    width: min(80vw, 360px);
    aspect-ratio: 1;
    margin-top: 48px;
  }
  .gateway-card__icon {
    justify-content: center;
  }
  .gateway-card__icon svg,
  .gateway-card__icon .gateway-card__media {
    max-width: 70%;
    max-height: 50%;
  }
  .gateway-card__body p {
    display: none;
  }
  .gateway-core__cta a {
    width: 200px;
    max-width: none;
  }
}
@media (max-width: 768px) {
  .gateway {
    padding: 40px 0;
  }
  /* v10.99: hide the gateway-circuit decoration on mobile.
   *
   * .gateway-core__decor is a 300%×300% SVG centered on the A-Core
   * block that traces circuit lines outward · on desktop it reads as
   * a clean topology connecting the cards above to A-Core below. On
   * narrow viewports the same SVG gets compressed into chaotic
   * tendrils that don't trace any meaningful path; they just add
   * visual noise around the title and cards.
   *
   * Top-1% pattern: hide desktop decorations on mobile rather than
   * trying to redraw them (Apple / Stripe / Linear all do this). The
   * cards + A-Core block carry the section without the connector. */
  .gateway-core__decor,
  .gateway-core__circuit {
    display: none !important;
  }
  /* PHONE-ONLY card fix. At ~108px wide the cards are too narrow to
   * survive aspect-ratio:1 · title wraps to 2 lines, eats all the
   * vertical space, and the icon container collapses to 0 (icons
   * literally don't render). Drop aspect-ratio, let cards size from
   * content, and give the icon a guaranteed minimum. Tablet (769-991px)
   * keeps the existing square-card layout untouched. */
  .gateway__grid {
    grid-template-rows: auto;
    height: auto;
    padding: 40px 0 32px;
  }
  .gateway-card {
    aspect-ratio: auto;
    min-height: 144px;
  }
  .gateway-card__icon {
    min-height: 72px;
    padding: 8px;
  }
  .gateway-card__icon svg,
  .gateway-card__icon .gateway-card__media {
    /* Phone: ditch the base rule's `width: 100%; height: 100%` (which
     * stretches the SVG box and lets paths drawn outside the viewBox
     * · like icon limbs reaching past x=20→x=0 · bleed
     * above the card border). Use intrinsic sizing capped at 65% of
     * the icon container so the icon has breathing room. */
    width: auto;
    height: auto;
    max-width: 65%;
    max-height: 65%;
    object-fit: contain;
  }
  .gateway-card {
    /* Belt-and-suspenders clip against any SVG paths that draw past
     * their viewBox. The card border-radius already implies clipping,
     * but explicit overflow:hidden makes it deterministic. */
    overflow: hidden;
  }
  .gateway-card__body {
    padding: 10px 6px 12px;
  }
  .gateway-card__body h3 {
    /* v10.204: shrink + balance for "AI Agent Gateway" on mobile.
     * Previous 12px + default text-wrap made the 17-char title break
     * as "AI Agent" / "Gateway" · readable but cramped. 11px gives
     * "AI Agent Gateway" room to fit on a single line at typical
     * widths (>=375px), and `text-wrap: balance` makes any wrap on
     * narrower widths (320-360px) split as "AI" / "Agent Gateway"
     * which feels more deliberate. letter-spacing tightened too. */
    font-size: 11px;
    line-height: 1.2;
    letter-spacing: -0.01em;
    text-wrap: balance;
  }
  .gateway-core {
    margin-bottom: 32px;
    /* The 20vh margin-top from the 991px rule pushes the core too far
     * down on phones where the grid is now auto-height. Reset it. */
    margin-top: 32px;
    /* Drop the aspect-ratio square on phones. Inner content stack ·
     * logo (159px) + h3 + 6-line description + CTA · is ~420px tall and
     * doesn't fit in a 300×300 square, so the logo overflows out the top
     * of the bordered area and the CTA risks bleeding out the bottom.
     * PC keeps the intentional "logo spills out" effect · it's a design
     * choice on a comfortably-sized square. On phones it just looks
     * broken. Auto-height lets the box wrap the content cleanly. */
    aspect-ratio: auto;
    padding-bottom: 24px;
  }
  .gateway-core__inner {
    /* Inner stack on phones uses padding instead of inset:0+padding%
     * so the content doesn't bleed past the visible border. */
    position: relative;
    padding: 24px 16px;
  }
  .gateway-core__logo-img {
    /* On the auto-height phone box, the logo can take its full natural
     * proportion without being pushed off the top edge. */
    width: 50%;
    max-width: 180px;
  }
}

/* ---- Nodes ---- */
.nodes {
  display: flex;
  background: var(--bg);
  padding: 100px 0;
  overflow: hidden;
}
.nodes__layout {
  display: flex;
  align-items: center;
  width: 100%;
  max-width: 1600px;
  margin: 0 auto;
  padding: 0 32px;
  gap: 48px;
}
.nodes__visual {
  flex: 1 1 50%;
  aspect-ratio: 1 / 1;
  max-width: min(100%, 480px);
  margin-inline: auto;
  min-width: 0;
  position: relative;
  /* Background removed (was #000000) · the Spline scene now renders on
   * the section's own --bg color so the globe blends seamlessly with
   * the rest of the page instead of sitting on a hard black square. */
  background: transparent;
  overflow: hidden;
  /* content-visibility removed · was redundant with contain:strict AND
   * caused a paint hitch on reverse scroll (the "skipped → render"
   * transition). Spline is already paused via visibility:hidden in JS
   * when far offscreen; contain:strict isolates its rendering from the
   * rest of the page. */
  contain: strict;
}
.nodes__visual .nodes__spline,
.nodes__visual spline-viewer {
  display: block;
  width: 100%;
  height: 100%;
  min-height: 240px;
  /* Force the system's default cursor over the Spline viewer. By
   * default the Spline runtime injects grab/grabbing cursors when the
   * user hovers/drags the 3D canvas. Since our scene isn't meant to
   * be user-rotated, the grab cursor was misleading. Default cursor
   * everywhere keeps the experience consistent across all devices. */
  cursor: default;
}
.nodes__visual spline-viewer canvas,
.nodes__visual spline-viewer * {
  cursor: default !important;
}
.nodes__content {
  flex: 1 1 50%;
  display: flex;
  flex-direction: column;
  gap: 48px;
}
.nodes__head.reveal {
  opacity: 0;
  transform: translateY(36px);
  transition:
    opacity 0.6s ease-out,
    transform 0.6s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.nodes__head.reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}
/* Unified section eyebrow · purple uppercase tag above each section title. */
.nodes__eyebrow,
.tokenomics__eyebrow,
.partner-marquee__eyebrow,
.roadmap__eyebrow,
.news__eyebrow {
  font-size: var(--fs-eyebrow);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--purple);
  margin: 0 0 12px;
}
.nodes__eyebrow {
  /* nodes layout handles spacing on the parent .nodes__head, so no
   * margin-bottom needed here (would double up otherwise) */
  margin: 0;
}
/* Unified section-title baseline. Each section's __title only adds
 * section-specific overrides (e.g. tokenomics adds margin-bottom). */
.nodes__title,
.tokenomics__title,
.partner-marquee__title,
.roadmap__title,
.news__title {
  font-size: var(--fs-section);
  /* v10.291 · display weight + tight leading + display letter-spacing,
   * matching the new 96 px hero h1 treatment. */
  font-weight: var(--fw-display);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
  margin: 0;
}
/* v10.291 · extend the display recipe to the rest of the section H2s
 * that had their own per-section rules (problem, architecture,
 * flywheel, use-cases, advisors, gateway). Keeps everything cohesive
 * without rewriting each block. */
.architecture__title,
.flywheel__title,
.use-cases__title,
.advisors__title,
.gateway__title h2 {
  font-size: var(--fs-section);
  font-weight: var(--fw-display);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
  margin: 0;
}
.nodes__title {
  /* section-specific overrides go here, if any */
}
.nodes__subtitle {
  font-size: 1rem;
  color: #fff;
  max-width: 520px;
  line-height: 1.6;
  margin: 0;
}
/* Nodes "Buy Nodes" CTA · same v10.84 recipe as .btn-launch so all
 * primary CTAs share one button language. */
.nodes__cta a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 44px;
  padding: 0 22px;
  background: linear-gradient(180deg, #7a14ff 0%, #6500e6 100%);
  color: #fff;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-decoration: none;
  border: none;
  border-radius: 10px;
  clip-path: none;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    0 4px 14px -2px rgba(111, 0, 255, 0.32);
  will-change: transform;
  transition:
    background 0.5s cubic-bezier(0.22, 1, 0.36, 1),
    box-shadow 0.5s cubic-bezier(0.22, 1, 0.36, 1),
    transform 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}
@media (hover: hover) {
  .nodes__cta a:hover {
    background: linear-gradient(180deg, #9233ff 0%, #7a14ff 100%);
    transform: translateY(-1px);
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.22),
      0 8px 22px -2px rgba(111, 0, 255, 0.42);
  }
}
.nodes__cta a:active {
  background: linear-gradient(180deg, #5a00d6 0%, #4a00b8 100%);
  transform: translateY(0);
  box-shadow:
    inset 0 1px 2px rgba(0, 0, 0, 0.18),
    0 2px 8px -2px rgba(111, 0, 255, 0.3);
}
.nodes__cta a:focus-visible {
  outline: 2px solid #b07fff;
  outline-offset: 2px;
}
@media (max-width: 768px) {
  .nodes {
    padding: 40px 0;
  }
  /* v10.163: mobile reflow · head, then 3D globe, then tier cards,
   * then revenue chips. Was column-reverse which put the visual
   * AFTER all the content (head + tiers + revenue), so users had to
   * scroll past every tier just to see the 3D scene. The visual
   * should sit RIGHT AFTER the headline so the section reads:
   * 'here's what nodes are -> here's what they look like -> here are
   * the pricing tiers'.
   *
   * Technique: display:contents on .nodes__content promotes its
   * children (.nodes__head, .nodes__tiers, .nodes__revenue) to
   * direct flex children of .nodes__layout. Then `order` interleaves
   * .nodes__visual between head and tiers. */
  .nodes__layout {
    flex-direction: column;
    padding: 0 16px;
    gap: 28px;
    align-items: stretch;
  }
  .nodes__content {
    display: contents;
  }
  .nodes__head    { order: 1; width: 100%; }
  .nodes__visual  {
    order: 2;
    flex: 0 0 auto;
    width: min(80vw, 320px);
    margin: 0 auto;
  }
  .nodes__cta-block { order: 3; }
  .nodes__revenue { order: 4; }
}

/* ---- Tokenomics ---- */
.tokenomics {
  background: var(--bg);
  padding: 100px 0;
  overflow: hidden;
  contain: layout style;
  /* content-visibility removed here · duplicated in the desktop-only
   * @media (min-width: 900px) block at the end of the file. Inline
   * here caused the v10.262 fast-scroll mobile lag regression. */
}
.tokenomics .container {
  max-width: 1600px;
  margin: 0 auto;
  padding: 0 16px;
}
.tokenomics__head.reveal {
  text-align: center;
  opacity: 0;
  transform: translateY(36px);
  transition:
    opacity 0.6s ease-out,
    transform 0.6s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.tokenomics__head.reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}
.tokenomics__eyebrow {
  /* inherits from the consolidated eyebrow rule */
}
.tokenomics__title {
  /* base font-size/weight/lh/margin from the consolidated rule above */
  margin: 0 0 12px;  /* leaves 12px before .tokenomics__sub */
}
.tokenomics__sub {
  font-size: 1rem;
  /* WCAG AA 4.5:1 · #a6a6a6 on #040404 = 9.04:1 (was #666 = 3.41:1, fail) */
  color: #a6a6a6;
  margin: 0;
}
.tokenomics__sub strong {
  color: #aaa;
}
/* ── Tokenomics · two-band layout ──────────────────────────────────
 * BAND 1 (.tokenomics__breakdown): donut chart + structured legend.
 *   On desktop: chart left (40%), legend right (60%) · a row.
 *   On mobile: stacked · chart on top, legend rows below.
 * BAND 2 (.tokenomics__mech-section): 5 distribution-mechanic cards
 *   in a responsive grid (3 cols on desktop, 1 col on mobile).
 *
 * No more floating labels around the chart. Pattern matches the
 * production-grade approach used by Lido / Aave / Optimism.
 * ──────────────────────────────────────────────────────────────── */

/* BAND 1 · chart + legend.
 * Larger chart (440px max) with a radial-gradient glow behind it for
 * depth. Hero supply number sits in the donut centre at 2.4rem bold.
 * Legend rows are interactive: hover one and the matching donut slice
 * dims its siblings (via .is-focused class set by JS). */
.tokenomics__breakdown {
  display: grid;
  grid-template-columns: minmax(320px, 0.9fr) 1.1fr;
  gap: 64px;
  align-items: center;
  margin-top: 48px;
}
.tokenomics__chart-wrap {
  position: relative;
  width: min(100%, 440px);
  aspect-ratio: 1 / 1;
  margin: 0 auto;
  /* Allow callout leader lines + labels to extend beyond the donut
   * radius without clipping. The 2-col layout's gap (64px) gives them
   * room to breathe on desktop without colliding with the legend. */
  overflow: visible;
  /* No background gradient · the transparent logo PNG was picking
   * up a subtle purple tint from the previous radial glow. Pure
   * dark surface (inherited from the .tokenomics section's #040404)
   * keeps the logo + slices reading cleanly. */
  background: transparent;
}
.tokenomics__chart-wrap svg {
  overflow: visible;
}

/* Centre logo · no disc, no backdrop. Just the transparent SVG mark
 * floating in the donut hole. pointer-events:none so the chart-wrap
 * mousemove handler still receives all events through this layer. */
.tokenomics__center-logo {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 120px;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  z-index: 2;
}
.tokenomics__center-logo-img {
  /* v10.131: PNG canvas is 732×562 but the CO ink occupies bbox
   * (68,104,697,455) · ink centre x=382.5, canvas centre x=366,
   * so the ink itself is offset 16.5px (2.25%) to the right of
   * the image centre. Without compensation, the rendered logo
   * appears right-of-centre on the donut. Shift the IMG left by
   * the exact measured offset to land ink-centre on hole-centre. */
  width: 100%;
  height: auto;
  display: block;
  transform: translateX(-2.25%);
}
@media (max-width: 1024px) {
  .tokenomics__center-logo {
    width: 100px;
    /* v10.132: the chart-wrap has `padding: 12px 0` on mobile/iPad (added
     * in v10.118 for sticky breathing room). The SVG fills the wrap's
     * full width and overflows the padding box, so the donut visual
     * centre ends up 12px below the wrap centre. The logo (positioned
     * via top:50% relative to wrap) was therefore sitting 12px ABOVE
     * the donut hole. Shift the logo down by 12px to land on the SVG
     * centre, not the wrap centre. */
    top: calc(50% + 12px);
  }
}
@media (max-width: 768px) {
  .tokenomics__center-logo { width: 88px; }
}
.tokenomics__chart-wrap #tokenomics-pie {
  /* No min-height. Was 380px which forced taller-than-wide on
   * mobile/tablet, breaking the chart-wrap's aspect-ratio: 1/1.
   * The SVG now uses a square viewBox so it scales correctly with
   * the wrap's natural square dimensions. */
  width: 100%;
  height: 100%;
}
/* Focus state · slice extraction pattern.
 *
 * No opacity dimming on non-focused slices. Instead, the focused
 * slice translates radially outward (~14 SVG units) along its
 * midpoint vector, like a wedge being pulled out of the donut. The
 * translate is applied via the SVG `transform` attribute (set by JS
 * in setFocus); CSS `transform` on bare <path> elements is
 * unreliable across browsers, so we use the attribute path which
 * transitions cleanly in modern browsers.
 *
 * Pattern used by Polkadot, Worldcoin, Linear, Aave. Keeps the full
 * picture readable while clearly elevating the focused slice. */
.tokenomics__chart-wrap svg path {
  /* All slice transitions unified on the brand emphasized curve (was
   * mixing 0.3s/0.2s `ease` with the cubic-bezier · that inconsistency
   * is what felt "not smooth"). Single curve, paired durations.
   * Pre-promote to compositor with will-change so the first frame of
   * the extract has no GPU-layer-creation hitch. */
  transition:
    transform 0.55s cubic-bezier(0.16, 1, 0.3, 1),
    filter 0.4s cubic-bezier(0.16, 1, 0.3, 1),
    stroke-width 0.3s cubic-bezier(0.16, 1, 0.3, 1);
  will-change: transform;
}
.tokenomics__chart-wrap.is-focused svg path.is-focused {
  /* No coloured drop-shadow · earlier versions bled the slice colour
   * outward as a glow, which read as "spreading colour" and washed
   * out neighbouring slices. The radial slice-extraction (handled by
   * the SVG transform attribute) is the focus indicator on its own. */
  stroke-width: 2;
}

/* SVG-based click-to-pin callout: leader polyline from the slice's
 * outer arc to a label outside the chart. JS draws / hides it. */
.tokenomics__callout-svg {
  pointer-events: none;
}
/* Leader-line geometry transitions.
 * x1/y1/x2/y2 are SVG "Geometry Properties" · animatable in CSS in
 * modern browsers (Chrome 76+, Firefox 80+, Safari 14+). When the
 * line element stays in the DOM and we change attributes via JS,
 * these transitions smoothly slide the endpoints · both when moving
 * between slices on hover, and when the slice extracts on click. */
.tokenomics__callout-line {
  stroke-linecap: round;
  stroke-linejoin: round;
  fill: none;
  opacity: 0;
  /* Opacity and stroke unified onto the same brand curve as the
   * geometry endpoints so fade-in syncs with the line drawing. */
  transition:
    opacity 0.35s cubic-bezier(0.16, 1, 0.3, 1),
    x1 0.45s cubic-bezier(0.16, 1, 0.3, 1),
    y1 0.45s cubic-bezier(0.16, 1, 0.3, 1),
    x2 0.45s cubic-bezier(0.16, 1, 0.3, 1),
    y2 0.45s cubic-bezier(0.16, 1, 0.3, 1),
    stroke 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
.tokenomics__callout-line.is-visible {
  opacity: 1;
}
.tokenomics__callout-label {
  font-family: var(--font);
  font-size: 14px;
  font-weight: 700;
  fill: #fff;
  opacity: 0;
  /* Sync opacity to position so the label drifts into place smoothly
   * instead of snapping in while the line is still moving. */
  transition:
    opacity 0.35s cubic-bezier(0.16, 1, 0.3, 1),
    x 0.45s cubic-bezier(0.16, 1, 0.3, 1),
    y 0.45s cubic-bezier(0.16, 1, 0.3, 1);
  pointer-events: none;
  font-variant-numeric: tabular-nums;
}
.tokenomics__callout-label.is-visible {
  opacity: 1;
}
.tokenomics__callout-label-sub {
  font-size: 11px;
  font-weight: 500;
  fill: #b0b0b0;
  letter-spacing: 0.06em;
}

/* ── v10.498 · slice callout · TWO styles, picked by breakpoint ────────
 * Phones (<768px): neither · the logo stays + the stat-row legend carries
 *   every number.
 * iPad / tablet (768–1024px): the leader-line callout · a line from the
 *   slice up to a "% name" + amount label pinned in the UPPER region
 *   (clear of the legend below). The logo stays in the hole.
 * Normal PC (≥1025px): the centre-of-donut "% + name" overlay · the logo
 *   fades while it shows.
 * JS updates both presentations; CSS reveals only the one for the current
 * width. */

/* Leader-line · tablet band only */
.tokenomics__callout-svg { display: none; }
@media (min-width: 768px) and (max-width: 1024px) {
  .tokenomics__callout-svg { display: block; }
}

/* Centre overlay · normal PC only */
.tokenomics__center-info { display: none; }
@media (min-width: 1025px) {
  .tokenomics__center-logo {
    transition: opacity 0.3s cubic-bezier(0.16, 1, 0.3, 1);
  }
  .tokenomics__chart-wrap.is-info .tokenomics__center-logo { opacity: 0; }
  .tokenomics__center-info {
    display: flex;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 64%;
    max-width: 210px;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    text-align: center;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.3s cubic-bezier(0.16, 1, 0.3, 1);
    z-index: 3;
  }
  .tokenomics__chart-wrap.is-info .tokenomics__center-info { opacity: 1; }
  .tokenomics__center-info-pct {
    font-size: 2.7rem;
    font-weight: 800;
    line-height: 1;
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
  }
  .tokenomics__center-info-name {
    font-size: 0.82rem;
    font-weight: 600;
    line-height: 1.32;
    color: rgba(255, 255, 255, 0.85);
    text-wrap: balance;
  }
}
/* Structured legend · 6 rows. Hover lifts the row and tints the
 * left accent bar; JS pairs it with chart-slice focus. */
/* ── DESKTOP allocation list (≥1025px) · ORIGINAL boxed-card style ──
 * Vertical accent bar on the left, name + colour-keyed % + amount in
 * a 4-column row. Soft card background + subtle border. Hover shifts
 * slightly right for tactile feedback. Sub-description hidden here. */
.tokenomics__legend {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}
/* v10.365 · Tokenomics legend, editorial cap-table pattern.
 * v10.363's boxed cards + colored stripes + colored percentages read
 * as generic-crypto-decoration. Stripped to S-1 / Bloomberg / FT cap-
 * table aesthetic:
 *   - No per-row boxes, no backgrounds, no borders. Hairline dividers
 *     only.
 *   - No colored left stripe. The donut already carries the color-to-
 *     slice mapping; repeating it in the legend is redundant chrome.
 *   - Percentages are PURE WHITE (no slice-color tint). Color isn't
 *     hierarchy · opacity + weight + tracking are.
 *   - Tiny slice-color dot indicator (6px) next to each name · minimal
 *     connection to the donut without screaming.
 *   - Generous 24px row padding, 32px column gap. Whitespace does the
 *     work that decoration was trying to do.
 *   - Tabular numerics throughout for clean numeric alignment. */
.tokenomics__legend-row {
  position: relative;
  display: grid;
  grid-template-columns: 6px 1fr auto auto;
  align-items: center;
  column-gap: 24px;
  padding: 24px 8px;
  border-radius: 0;
  background: transparent;
  border: none;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  cursor: pointer;
  transition: background 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
.tokenomics__legend-row:focus-visible {
  outline: 1px solid rgba(255, 255, 255, 0.4);
  outline-offset: 4px;
}
@media (hover: hover) {
  .tokenomics__legend-row:hover,
  .tokenomics__legend-row.is-focused {
    background: rgba(255, 255, 255, 0.02);
  }
}
.tokenomics__legend-row.is-focused {
  background: rgba(255, 255, 255, 0.02);
}
/* Slice-color dot indicator · minimal mapping to the donut. 6px round
 * dot in the slice color, no glow stack. Single signal. */
.tokenomics__legend-bar {
  display: block;
  width: 6px;
  height: 6px;
  min-height: 0;
  background: var(--clr, #fff);
  border-radius: 999px;
  margin-left: 0;
  box-shadow: none;
}
/* Name + sub as a tight editorial pair. Name slightly heavier; sub at
 * 40% opacity reads as metadata. No color tinting. */
.tokenomics__legend-name {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 1.02rem;
  font-weight: 500;
  color: #fff;
  letter-spacing: -0.014em;
  line-height: 1.2;
}
.tokenomics__legend-sub {
  display: block;
  font-size: 0.8rem;
  font-weight: 400;
  color: rgba(255, 255, 255, 0.4);
  letter-spacing: -0.003em;
  line-height: 1.4;
}
/* Percentages in PURE WHITE · color = data noise. Weight + size carry
 * the hierarchy. Tracking pulled tight for editorial feel. */
.tokenomics__legend-pct {
  font-size: 1.45rem;
  font-weight: 400;
  color: #fff;
  letter-spacing: -0.028em;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  font-feature-settings: "tnum" 1;
}
.tokenomics__legend-amt {
  min-width: 110px;
  text-align: right;
  font-size: 0.78rem;
  font-weight: 400;
  color: rgba(255, 255, 255, 0.35);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
}

/* BAND 2 · distribution mechanics (5 cards).
 * Left accent bar instead of top strip (more elegant, matches the
 * legend style). Hover lifts the card with a coloured glow. */
.tokenomics__mech-section {
  margin-top: 72px;
  padding-top: 48px;
  border-top: 1px solid rgba(255, 255, 255, 0.06);
}
.tokenomics__mech-section-title {
  margin: 0 0 28px;
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--purple);
  text-transform: uppercase;
  letter-spacing: 0.2em;
  text-align: center;
}
.tokenomics__mechanisms {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 16px;
}
.tokenomics__mech {
  position: relative;
  z-index: 1;
  padding: 22px 22px 20px 26px;
  /* v10.486 · Huly rounded-card box · dark-glass surface + whisper violet
   * corner glow (alpha .07) layered over it. The ::before pseudo here is
   * the left accent bar (kept), so the glow rides the background. */
  background:
    radial-gradient(60% 90% at 100% 0%, rgba(111, 0, 255, 0.07), transparent 60%),
    linear-gradient(180deg, rgba(255, 255, 255, 0.055), rgba(255, 255, 255, 0.018));
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 16px;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.06),
    0 20px 44px -28px rgba(0, 0, 0, 0.7);
  overflow: hidden;
  transition: border-color 0.25s, transform 0.25s, box-shadow 0.25s;
}
.tokenomics__mech::before {
  /* Left accent bar · thinner and more elegant than the v7.9 top strip */
  content: "";
  position: absolute;
  top: 22px;
  bottom: 20px;
  left: 0;
  width: 3px;
  background: var(--clr, var(--purple));
  border-radius: 0 3px 3px 0;
  box-shadow: 0 0 16px color-mix(in srgb, var(--clr, var(--purple)) 55%, transparent);
}
@media (hover: hover) {
  .tokenomics__mech:hover {
    border-color: color-mix(in srgb, var(--clr, var(--purple)) 35%, transparent);
    transform: translateY(-2px);
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.07),
      0 22px 48px -26px rgba(0, 0, 0, 0.72);
  }
}
.tokenomics__mech-name {
  margin: 0 0 10px;
  font-size: 1.02rem;
  font-weight: 700;
  color: #fff;
  letter-spacing: -0.005em;
  line-height: 1.2;
}
.tokenomics__mech-desc {
  margin: 0;
  font-size: 0.86rem;
  color: #b8b8b8;
  line-height: 1.5;
}

/* ── iPad + MOBILE (≤1024px): donut chart + horizontal progress bars ──
 * DUAL visualisation per GK's brief: the donut (with brand logo
 * centred) at the top for the angular view, then a vertical list
 * of progress-bar stats · one per allocation · below it. Same
 * pattern Coinbase / Sui / Aptos use on small screens.
 *
 *   ┌──────────────┐
 *   │   [donut]    │  ← angular viz
 *   └──────────────┘
 *
 *   Ecosystem & Community                                42%
 *   ████████████████████████████████████░░░░░░░░░░░░░░░    ← length viz
 *   Early user incentives, marketing, eco rewards
 *   420,000,000 $AST
 *
 * Each bar fills to its `--proportion` (set inline on the <li>),
 * tinted in the slice colour. Two complementary views of the same data.
 *
 * Desktop (>1024px) keeps the donut + boxed-card legend untouched. */
@media (max-width: 1024px) {
  /* Section breakdown stacks vertically: donut on top, list below */
  .tokenomics__breakdown {
    grid-template-columns: 1fr;
    gap: 32px;
    max-width: 720px;
    margin-left: auto;
    margin-right: auto;
  }
  /* v10.109: donut sticks while the legend scrolls beneath it (Apple
   * data-vis pattern). User stays oriented · the chart they're reading
   * about remains in view as they tab through the slices. Sticky to
   * the breakdown container; falls out of sticky once the user scrolls
   * past the whole tokenomics section.
   *
   * Soft brand-violet ambient behind the donut via `::before` lifts
   * it from the section bg without competing with the slice colours.
   * Same restraint pattern as the A-Core ambient in v10.103/108. */
  .tokenomics__chart-wrap {
    width: min(100%, 300px);
    margin: 0 auto;
    position: sticky;
    top: 80px;
    z-index: 2;
    /* v10.118: padding was 16px 0 8px (asymmetric) · pushed the SVG
     * down 8px relative to the chart-wrap centre, but the absolute-
     * positioned .tokenomics__center-logo at top:50% stayed centred
     * on the chart-wrap padding box. Result: logo sat 4px ABOVE the
     * donut centre on mobile/iPad.
     * Symmetric padding (12 0) keeps the same sticky breathing room
     * but aligns the logo + donut centre exactly. */
    padding: 12px 0;
  }
  .tokenomics__chart-wrap::before {
    content: "";
    position: absolute;
    inset: -10%;
    z-index: -1;
    pointer-events: none;
    background: radial-gradient(ellipse 55% 55% at 50% 50%, rgba(157, 92, 255, 0.08), transparent 70%);
    filter: blur(20px);
  }
  /* v10.109: percentage typography pushed up · was 1.6rem on every
   * breakpoint, feels small in the new sticky-donut + scroll-legend
   * layout. 2rem on mobile reads as the primary anchor of each row. */
  .tokenomics__legend-pct {
    font-size: 2rem;
  }

  /* Allocation list */
  .tokenomics__legend { gap: 0; }

  /* Each allocation = 3-row grid:
   *   Row 1: Name (left) + Percentage (right, colour-matched)
   *   Row 2: Progress bar (full width)
   *   Row 3: Sub-description (left) + Amount (right) */
  .tokenomics__legend-row {
    grid-template-columns: 1fr auto;
    grid-template-rows: auto auto auto;
    align-items: baseline;
    column-gap: 16px;
    row-gap: 8px;
    padding: 20px 0;
    border: 0;
    background: transparent;
    border-radius: 0;
    border-bottom: 1px solid rgba(255, 255, 255, 0.07);
  }
  .tokenomics__legend-row:last-child { border-bottom: 0; }
  @media (hover: hover) {
    .tokenomics__legend-row:hover,
    .tokenomics__legend-row.is-focused {
      transform: none;
      border-color: transparent;
      background: linear-gradient(
        to right,
        color-mix(in srgb, var(--clr, var(--purple)) 6%, transparent),
        transparent 60%
      );
    }
  }
  /* v10.491 · no lit gradient box behind the tapped row · the user flagged
   * that "lighting" as off on mobile. Row stays clean; the bar fill + the
   * colour-keyed % already signal which allocation it is. */
  .tokenomics__legend-row.is-focused {
    background: transparent;
  }

  /* Bar element = track. The colored FILL is a ::before pseudo so it
   * can animate in smoothly with transform: scaleX (compositor-only,
   * never triggers layout · buttery on every device). */
  .tokenomics__legend-bar {
    display: block;
    grid-column: 1 / -1;
    grid-row: 2;
    width: 100%;
    height: 8px;
    min-height: 8px;
    border-radius: 999px;
    margin-left: 0;
    box-shadow: none;
    background: rgba(255, 255, 255, 0.06);
    position: relative;
    overflow: hidden;
  }
  .tokenomics__legend-bar::before {
    content: "";
    position: absolute;
    inset: 0 auto 0 0;
    /* Fill is exactly --proportion wide; scaleX then animates its
     * visible length 0 → 1. Final width = --proportion of the parent,
     * regardless of how far scaleX has progressed. Compositor-only.
     *
     * v10.407 · color-mix with page bg (#0a0a0c @ 32%) deepens bright
     * slice colors on mobile · #5dffe6 mint and #00c2ff cyan were
     * reading as candy-neon against the dark mobile bg. The blend
     * keeps each slice color recognizable but tones down the
     * saturation so it sits with the rest of the dark palette. */
    width: var(--proportion, 50%);
    height: 100%;
    background: color-mix(in srgb, var(--clr, var(--purple)) 78%, #0a0a0c);
    border-radius: inherit;
    transform-origin: left center;
    transform: scaleX(0);
    will-change: transform;
    transition: transform 1.1s cubic-bezier(0.16, 1, 0.3, 1);
  }
  /* Row enters viewport (JS adds .is-visible) → fill smoothly grows
   * from 0 to its full --proportion width. */
  .tokenomics__legend-row.is-visible .tokenomics__legend-bar::before {
    transform: scaleX(1);
  }
  /* Stagger the entries so bars cascade in as a wave instead of all
   * firing at once · premium motion timing. */
  .tokenomics__legend-row:nth-child(1).is-visible .tokenomics__legend-bar::before { transition-delay: 0.08s; }
  .tokenomics__legend-row:nth-child(2).is-visible .tokenomics__legend-bar::before { transition-delay: 0.16s; }
  .tokenomics__legend-row:nth-child(3).is-visible .tokenomics__legend-bar::before { transition-delay: 0.24s; }
  .tokenomics__legend-row:nth-child(4).is-visible .tokenomics__legend-bar::before { transition-delay: 0.32s; }
  .tokenomics__legend-row:nth-child(5).is-visible .tokenomics__legend-bar::before { transition-delay: 0.40s; }
  .tokenomics__legend-row:nth-child(6).is-visible .tokenomics__legend-bar::before { transition-delay: 0.48s; }
  /* Honour reduced-motion: snap to full instantly. */
  @media (prefers-reduced-motion: reduce) {
    .tokenomics__legend-bar::before {
      transform: scaleX(1);
      transition: none;
    }
  }
  .tokenomics__legend-name {
    grid-column: 1;
    grid-row: 1;
    font-size: 1rem;
    font-weight: 600;
  }
  .tokenomics__legend-sub {
    display: block;
    grid-column: 1;
    grid-row: 3;
    font-size: 0.82rem;
    font-weight: 400;
    color: rgba(255, 255, 255, 0.5);
    line-height: 1.4;
  }
  .tokenomics__legend-pct {
    grid-column: 2;
    grid-row: 1;
    /* v10.110: 1.5rem → 1.85rem for tablet range. Parallels the
     * mobile bump to 1.7rem so the % stays primary across both. */
    font-size: 1.85rem;
    font-weight: 800;
    letter-spacing: -0.025em;
    text-align: right;
    align-self: center;
  }
  .tokenomics__legend-amt {
    display: block;
    grid-column: 2;
    grid-row: 3;
    min-width: unset;
    text-align: right;
    font-size: 0.78rem;
    color: rgba(255, 255, 255, 0.42);
    letter-spacing: 0.05em;
    align-self: end;
  }

  /* Callout (leader line + label) shows on iPad/tablet too (v10.497) ·
   * its label is pinned to the upper region so it stays clear of the
   * legend. Only phones (<768px) hide it · see the @media (max-width:767px)
   * rule above. */
}

/* MOBILE (≤768): single column for both bands */
@media (max-width: 768px) {
  .tokenomics { padding: 40px 0; }
  .tokenomics__breakdown {
    grid-template-columns: 1fr;
    gap: 28px;
    margin-top: 24px;
  }
  .tokenomics__chart-wrap {
    width: min(100%, 280px);
  }
  .tokenomics__center-total {
    /* Scale down so it fits inside the smaller donut hole */
    font-size: 1.5rem;
  }
  /* Mobile-only tightening for the horizontal-bar layout from the
   * ≤1024px block above. Scales typography slightly down on phones. */
  .tokenomics__legend-row {
    padding: 18px 0;
    row-gap: 6px;
  }
  .tokenomics__legend-name {
    font-size: 0.95rem;
  }
  .tokenomics__legend-sub {
    font-size: 0.78rem;
  }
  .tokenomics__legend-pct {
    /* v10.110: bumped from 1.35rem → 1.7rem to make the % the
     * primary anchor of each row, matching the sticky-donut +
     * scroll-legend pattern shipped in v10.109. */
    font-size: 1.7rem;
  }
  .tokenomics__legend-amt {
    font-size: 0.72rem;
  }
  .tokenomics__legend-bar {
    height: 7px;
  }
  .tokenomics__mech-section {
    margin-top: 48px;
    padding-top: 36px;
  }
  .tokenomics__mechanisms {
    grid-template-columns: 1fr;
    gap: 12px;
  }
  .tokenomics__mech {
    padding: 18px 18px 16px 22px;
  }
  .tokenomics__mech::before {
    top: 18px;
    bottom: 16px;
  }
}

/* ---- Partners marquee ---- */
.partner-marquee {
  background: var(--bg);
  padding: 100px 0;
  /* contain: layout style paint · strict compositor isolation.
   * The marquee section has 152 image elements + 2 perpetually-animating
   * tracks. Without `paint` containment, Chrome was painting their tiles
   * during page scroll (the "Trusted by the Best 10 fps" lag root cause).
   * The JS pauses the marquee during active scroll; this CSS tells Chrome
   * NOT to paint marquee tiles outside the section's bounds during scroll. */
  contain: layout style paint;
}
.partner-marquee__head.reveal {
  text-align: center;
  opacity: 0;
  transform: translateY(36px);
  transition:
    opacity 0.6s ease-out,
    transform 0.6s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.partner-marquee__head.reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}
.partner-marquee__eyebrow {
  /* inherits from the consolidated eyebrow rule */
}
.partner-marquee__title {
  /* inherits from the consolidated section-title rule above */
}
.partner-marquee__rows {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin-top: 40px;
  overflow: hidden;
  position: relative;
}
.partner-marquee__rows::before,
.partner-marquee__rows::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: 160px;
  z-index: 2;
  pointer-events: none;
}
.partner-marquee__rows::before {
  left: 0;
  background: linear-gradient(to right, #040404, transparent);
}
.partner-marquee__rows::after {
  right: 0;
  background: linear-gradient(to left, #040404, transparent);
}
.partner-marquee__viewport {
  width: 100%;
  overflow: hidden;
  /* v10.196: hide scrollbar unconditionally. When mobile/perf-mode
   * apply overflow-x:auto as a fallback, the global brand-violet
   * thumb gradient (lines 149-150) becomes visible under each row
   * as 3 short purple bars · user-reported visual bug. With these
   * rules the scrollbar is invisible in every state, while native
   * touch-swipe still works on mobile. */
  -ms-overflow-style: none;
  scrollbar-width: none;
}
.partner-marquee__viewport::-webkit-scrollbar { display: none; }
.partner-marquee__track {
  display: flex;
  /* v10.240 · `gap: 16px` REMOVED. With duplicated content
   * (buildPartnerTrack calls addImgs() twice for the seamless loop),
   * flex `gap` places a gap between EVERY adjacent pair including
   * the join between copy-1's last card and copy-2's first card ·
   * but NOT before copy-1's first card. So the total track width is
   *   2 * N * card_width + (2N - 1) * 16px
   * and `translate3d(-50%)` lands the loop ~8 px short of one
   * logical copy width. Every 45/38/56 s the marquee visibly "ticks"
   * back by ~16 px which reads to users as the loop sticking.
   *
   * Fix: move the 16 px spacing onto each card as `margin-right` so
   * every card owns its trailing space. Track width becomes
   *   2 * N * (card_width + 16)
   * and `-50%` is exactly N * (card_width + 16) · a perfect match,
   * so the wrap-around is invisible. The very last card of copy-2
   * still has a 16 px margin-right but it falls outside the viewport
   * when the loop restarts; harmless. */
  width: max-content;
  /* translate3d + backface-visibility forces compositor-only updates,
   * no main-thread involvement on every frame. */
  transform: translate3d(0, 0, 0);
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}
.partner-marquee__track > .partner-marquee__card {
  margin-right: 16px;
}
@keyframes partnerScrollLeft {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}
@keyframes partnerScrollRight {
  from { transform: translate3d(-50%, 0, 0); }
  to   { transform: translate3d(0, 0, 0); }
}
.partner-marquee__track--left {
  animation: partnerScrollLeft 45s linear infinite;
}
.partner-marquee__track--right {
  animation: partnerScrollRight 38s linear infinite;
}
/* Third row scrolls left at a slower pace · staggered durations across
 * the three rows give a more organic "river of logos" feel instead of
 * the rigid lock-step you get when every row runs the same speed. */
.partner-marquee__track--left-slow {
  animation: partnerScrollLeft 56s linear infinite;
}
/* v10.197 · .is-paused / .is-active classes no longer toggled.
 * The JS IO that drove them was the source of intermittent "marquee
 * stuck" (race condition when scrolling past during deferUntilNear's
 * pre-roll). Tracks now animate continuously; will-change applied
 * directly on the track classes below since the GPU promotion is
 * always needed when the animation is always running. */
.partner-marquee__track--left,
.partner-marquee__track--right,
.partner-marquee__track--left-slow {
  will-change: transform;
}
@media (prefers-reduced-motion: reduce) {
  .partner-marquee__track--left,
  .partner-marquee__track--right,
  .partner-marquee__track--left-slow {
    animation: none;
  }
}
.partner-marquee__card {
  flex-shrink: 0;
  width: 180px;
  height: 80px;
  background: #fff;
  border-radius: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 12px 20px;
  /* Cards are now <a> elements when a partner has an external URL. Kill
   * the default underline + reset color so the white tile looks identical
   * to the old <div> version. Pointer + subtle press feedback for affordance. */
  text-decoration: none;
  color: inherit;
  cursor: pointer;
  transition: transform var(--dur-fast, .15s) var(--ease-standard, ease);
}
a.partner-marquee__card:hover,
a.partner-marquee__card:focus-visible {
  transform: scale(1.04);
  outline: none;
}
a.partner-marquee__card:focus-visible {
  box-shadow: 0 0 0 2px var(--purple);
}
.partner-marquee__card img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}
@media (max-width: 768px) {
  .partner-marquee {
    padding: 40px 0;
  }
  /* v10.395 · restored all 3 marquee rows on mobile per user request.
   * v10.262 had hidden rows 2 & 3 to drop 282 → 94 composited cards
   * for mobile GPU savings. User wants the full marquee. Mitigations
   * to keep mobile cost reasonable:
   *   - .lite-mode kill switch (html.lite-mode #partners-track-{1,2,3})
   *     still hides them on FPS-detected weak devices
   *   - prefers-reduced-motion gate already in place
   *   - row 3 (slow 56s) is the lowest GPU cost · keep
   *   - cards have contain:strict from earlier opt */
  /* .partner-marquee__title mobile size now via --fs-section in :root */
}

/* ---- Roadmap ---- */
.roadmap {
  background: var(--bg);
  padding: 100px 0;
  /* Removed content-visibility · roadmap is mostly text + scaleY fill bar.
   * One-time paint, then sticks in GPU layer. Smooth in both directions. */
  contain: layout style;
}
.roadmap__layout {
  max-width: 1600px;
  margin: 0 auto;
  padding: 0 32px;
  display: grid;
  grid-template-columns: 0.85fr 1.35fr;
  gap: 80px;
  align-items: start;
}
.roadmap__head.reveal {
  position: sticky;
  top: 120px;
  opacity: 0;
  transform: translateY(36px);
  transition:
    opacity 0.6s ease-out,
    transform 0.6s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.roadmap__head.reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}
.roadmap__eyebrow {
  /* inherits from the consolidated eyebrow rule */
}
.roadmap__title {
  /* inherits from the consolidated section-title rule above
   * (was line-height: 1.3, normalised to 1.2 to match other sections) */
}
.roadmap__timeline {
  display: block;
}
/* Old dot-and-line timeline hidden · v10.46 uses self-contained
 * phase cards (.roadmap-item) that carry their own visual weight.
 * Element kept in DOM so the scroll-driven fill animation infra
 * (@supports block below) doesn't error; just visually inert. */
.roadmap__line-track {
  display: none;
}
.roadmap__line-fill {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: var(--purple);
  border-radius: 2px;
  /* GPU-only transform animation · was animating `height` which triggers
   * a layout pass on EVERY scroll-driven update, causing reflow + repaint
   * of the entire roadmap section. scaleY is composited only. */
  transform-origin: top center;
  transform: scaleY(0);
  transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
  will-change: transform;
}

/* ── MODERN: CSS scroll-driven animation (Chrome 115+) ──
 * Native compositor-thread scroll animation. The browser computes the
 * roadmap fill scaleY directly from the section's scroll position · NO
 * main-thread JS work, NO scroll listener, NO rAF dispatch. Pure GPU.
 *
 * Falls back gracefully: older browsers run the JS handler in app.js.
 * JS feature-detects CSS.supports("animation-timeline", "scroll()") and
 * skips its handler when CSS works, avoiding double-animation conflicts. */
@supports (animation-timeline: view()) {
  #roadmap-section {
    view-timeline-name: --roadmap-view;
    view-timeline-axis: block;
  }
  .roadmap__line-fill {
    animation: roadmap-fill-anim linear forwards;
    animation-timeline: --roadmap-view;
    animation-range: cover 5% cover 75%;
    /* Override the transition so the CSS-driven animation is direct */
    transition: none;
  }
  @keyframes roadmap-fill-anim {
    from { transform: scaleY(0); }
    to   { transform: scaleY(1); }
  }
}
/* ════════════════════════════════════════════════════════════════════
 * v10.46 · Roadmap section: top-1% redesign
 *
 * Each phase is a self-contained PREMIUM CARD with:
 *   - Huge gradient phase number (anchor, left on desktop / above on mobile)
 *   - Status badge top-right: "✓ Shipped" / "● In Progress" / "● Upcoming"
 *   - Display-size period text (gradient on current phase)
 *   - Items list with chevron accent (no more chip-in-chip nesting)
 *   - Hover lift + brand-violet border accent
 *
 * Status classes set in HTML on .roadmap-item:
 *   .is-done    → cyan ✓ Shipped, dimmed number
 *   .is-current → magenta ● In Progress (pulses), gradient period, glow
 *   (default)   → grey ● Upcoming
 * ════════════════════════════════════════════════════════════════════ */
.roadmap__items {
  display: flex;
  flex-direction: column;
  gap: 28px;
  flex: 1;
}
.roadmap-item {
  position: relative;
  z-index: 1;
  padding: 34px 32px 30px 100px;
  /* v10.486 · Huly rounded-card box · whisper violet corner glow layered
   * OVER the dark base gradient (this card's ::before/::after are already
   * the phase number + status badge, so the glow rides the background here
   * instead of a pseudo-element · alpha .07 to match .arch-layer v10.485). */
  background:
    radial-gradient(60% 90% at 100% 0%, rgba(111, 0, 255, 0.07), transparent 60%),
    linear-gradient(160deg, #0a0a0a, #060606 60%, #040404);
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 18px;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.06),
    0 20px 44px -28px rgba(0, 0, 0, 0.7);
  transition:
    transform 0.5s cubic-bezier(0.16, 1, 0.3, 1),
    border-color 0.5s cubic-bezier(0.16, 1, 0.3, 1),
    box-shadow 0.5s cubic-bezier(0.16, 1, 0.3, 1);
  will-change: transform;
}
/* Big gradient phase number · top-left anchor (replaces the dot/line) */
.roadmap-item::before {
  content: attr(data-phase);
  position: absolute;
  left: 28px;
  top: 32px;
  font-size: 2.6rem;
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.03em;
  /* v10.488 · dropped the purple→magenta gradient text-clip (gradient-on-
   * numbers reads as crypto-template · same tell removed from the period
   * + status badge in v10.383). One calm brand violet, quiet opacity. */
  color: #9d5cff;
  opacity: 0.55;
}
/* Old dot/line elements no longer carry visual weight · hide them */
.roadmap-item__dot { display: none; }

/* ── Status badge (top-right pill, class-driven via ::after) ── */
.roadmap-item::after {
  position: absolute;
  right: 28px;
  top: 34px;
  display: inline-flex;
  align-items: center;
  padding: 5px 12px 4px;
  border-radius: 999px;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  content: "● Upcoming";
  color: rgba(255, 255, 255, 0.55);
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.08);
}
.roadmap-item.is-done::after {
  content: "✓ Shipped";
  color: #5dffe6;
  background: rgba(93, 255, 230, 0.06);
  border-color: rgba(93, 255, 230, 0.32);
}
/* v10.383 · pulsing dot + pulse animation REMOVED (anti-pattern #11,
 * #1 mobile AI tell). Solid violet caption alone marks the current
 * phase; no animation, no dot. */
.roadmap-item.is-current::after {
  content: "In Progress";
  color: #b07fff;
  background: rgba(157, 92, 255, 0.08);
  border-color: rgba(157, 92, 255, 0.4);
}

/* ── Period date (display-size text, no more chip) ── */
.roadmap-item__period {
  display: block;
  background: none;
  color: rgba(255, 255, 255, 0.92);
  font-size: 1.65rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  padding: 0;
  margin: 0 0 22px;
  box-shadow: none;
}
/* v10.383 · gradient text-clip on current-phase period REMOVED
 * (mobile AI tell · gradient text on data values reads crypto-
 * template). Solid white reads as the current phase via context
 * alone (status caption + accent line). */
.roadmap-item.is-current .roadmap-item__period {
  color: #fff;
  padding: 0;
  box-shadow: none;
}
.roadmap-item.is-current .roadmap-item__period::after {
  content: none; /* status badge in card corner now owns the NOW signal */
}
.roadmap-item.is-done .roadmap-item__period {
  color: rgba(255, 255, 255, 0.55);
}

/* ── Items list (clean two-line, chevron accent, no chip nesting) ── */
.roadmap-item__pills {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
/* v10.488 · milestone rows are title-only now (the echo descriptions that
 * just restated each title were the section's loudest AI tell · removed in
 * the HTML). A quiet 5px violet dot marks each row · no decorative chevron
 * (anti-pattern #11) and no arrow-slide hover. */
.roadmap-pill {
  display: flex;
  align-items: baseline;
  padding-left: 20px;
  position: relative;
}
.roadmap-pill::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 0.62em;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: #9d5cff;
  opacity: 0.7;
  transition: opacity 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
.roadmap-pill__tag {
  display: block;
  padding: 0;
  font-size: 1rem;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.92);
  background: transparent;
  border: none;
  border-radius: 0;
  line-height: 1.45;
  letter-spacing: -0.005em;
}

/* ── Hover on the whole card (quiet · dot brightens, card lifts 2px) ── */
@media (hover: hover) {
  .roadmap-item:hover {
    transform: translateY(-2px);
    border-color: rgba(157, 92, 255, 0.32);
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.07),
      0 22px 48px -26px rgba(0, 0, 0, 0.72);
  }
  .roadmap-item:hover .roadmap-pill::before {
    opacity: 1;
  }
}

/* ── Current phase: always-on emphasis ──
 * IMPORTANT: keep this QUIET. The status-badge pulse is the primary
 * "live now" signal · everything else here just edge-accents the
 * card. Previous versions stacked four glow layers (border + radial
 * corner glow + drop-shadow + 30px text-shadow on the number) which
 * piled colour into the top-left and produced a "spreading" haze.
 *
 * Permanent rule for .is-current: edge-only emphasis. No fills, no
 * radials, no large blurs. */
.roadmap-item.is-current {
  border-color: rgba(157, 92, 255, 0.4);
  background:
    radial-gradient(60% 90% at 100% 0%, rgba(111, 0, 255, 0.07), transparent 60%),
    linear-gradient(160deg, #0a0a0a, #060606 60%, #040404);
}
.roadmap-item.is-current::before {
  opacity: 1;
}

/* ── Done phase: muted ── */
.roadmap-item.is-done::before {
  opacity: 0.35;
}
.roadmap-item.is-done .roadmap-pill__tag {
  color: rgba(255, 255, 255, 0.78);
}
/* ════════════════════════════════════════════════════════════════════
 * MOBILE · v10.46 phase-card layout
 *
 * Same card structure as desktop but:
 *   - Phase number stacks ABOVE the period (no left padding)
 *   - Status badge stays anchored top-right (smaller text)
 *   - Tighter type sizes throughout
 * ════════════════════════════════════════════════════════════════════ */
@media (max-width: 768px) {
  .roadmap {
    padding: 72px 0;
  }
  .roadmap__layout {
    grid-template-columns: 1fr;
    gap: 36px;
    padding: 0 16px;
  }
  .roadmap__head.reveal {
    position: static;
  }
  .roadmap__head {
    text-align: center;
  }
  .roadmap__items {
    gap: 20px;
  }
  /* Card: drop left-padding (number stacks above, not beside) */
  .roadmap-item {
    padding: 24px 22px 22px;
    border-radius: 16px;
  }
  /* Phase number flows above the period on mobile */
  .roadmap-item::before {
    position: static;
    display: block;
    font-size: 2.4rem;
    margin: 0 0 8px;
    line-height: 0.9;
  }
  /* Status badge: stays top-right, smaller */
  .roadmap-item::after {
    right: 18px;
    top: 22px;
    font-size: 0.58rem;
    padding: 4px 10px 3px;
    letter-spacing: 0.14em;
  }
  /* Period: smaller display text on mobile */
  .roadmap-item__period {
    font-size: 1.3rem;
    margin-bottom: 18px;
  }
  /* Tighter pills */
  .roadmap-item__pills {
    gap: 14px;
  }
  .roadmap-pill {
    padding-left: 18px;
  }
  .roadmap-pill__tag {
    font-size: 0.95rem;
  }
}

/* ---- News ---- */
.news {
  /* v10.156: stripped v10.149's radial ambients + scan-line pattern.
   * User: 'every page bg look lik ai'. Now flat · photos in the card
   * track carry the visual weight.
   *
   * v10.263 · REVERTED v10.262's content-visibility:auto. It made
   * mobile fast-swipe scroll WORSE: each section entering/leaving the
   * viewport triggered a full layout recalc on the mobile main thread,
   * which costs more than just painting the cards once. Layout is
   * expensive on mobile; paint is cheap. Wrong tool for this job. */
  background: var(--bg);
  padding: 100px 0;
  contain: layout style;
  position: relative;
}
.news .container {
  max-width: 1600px;
  margin: 0 auto;
  padding: 0 32px;
  display: flex;
  flex-direction: column;
  gap: 40px;
}
/* v10.146 · head row: title LEFT, prev/next controls RIGHT.
 * Pharos carousel pattern. */
.news__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  text-align: left;
}
.news__head.reveal {
  opacity: 0;
  transform: translateY(36px);
  transition:
    opacity 0.6s ease-out,
    transform 0.6s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.news__head.reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}
.news__head-text { flex: 1; }
.news__eyebrow { /* inherits from consolidated eyebrow rule */ }
.news__title { /* inherits from consolidated section-title rule */ }

/* "View all on Medium →" link below the carousel.
 * Gives users a discoverable path to fresher content when the curated
 * cards are older · without forcing us to keep manually editing the
 * BLOG[] array in app.js every time a new post drops. */
.news__footer {
  display: flex;
  justify-content: center;
  margin-top: 32px;
}
.news__more {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-height: 48px;
  padding: 13px 26px;
  color: rgba(255, 255, 255, 0.92);
  font-size: 0.88rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-decoration: none;
  background: linear-gradient(160deg, #0a0a0a, #060606);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 999px;
  transition:
    border-color 0.45s cubic-bezier(0.16, 1, 0.3, 1),
    background 0.45s cubic-bezier(0.16, 1, 0.3, 1),
    color 0.45s cubic-bezier(0.16, 1, 0.3, 1),
    transform 0.45s cubic-bezier(0.16, 1, 0.3, 1),
    box-shadow 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}
@media (hover: hover) {
  .news__more:hover {
    border-color: rgba(157, 92, 255, 0.5);
    color: #fff;
    transform: translateY(-2px);
    box-shadow: 0 10px 32px rgba(111, 0, 255, 0.18);
  }
}
.news__more:focus-visible {
  outline: 2px solid var(--purple);
  outline-offset: 3px;
}
/* v10.146 · horizontal carousel viewport + track (Pharos pattern). */
.news__viewport {
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none; /* Firefox */
  margin: 0 -32px; /* bleed edges so peek cards aren't clipped by container padding */
  padding: 8px 32px;
}
.news__viewport::-webkit-scrollbar { display: none; }
.news__track {
  display: flex;
  gap: 24px;
}

/* v10.146 · Pharos-style press card: thumbnail on top, body below.
 * All cards are equal-size, scroll-snap-aligned. */
.news-card {
  flex: 0 0 calc((100% - 48px) / 3); /* 3 visible on desktop with 24px gap */
  scroll-snap-align: start;
  /* v10.178: force snap on every touch / wheel · never land mid-card.
   * Default scroll-snap-stop is 'normal' which allows fast scrolls to
   * skip past snap points. 'always' makes each gesture commit to one
   * card. */
  scroll-snap-stop: always;
  display: flex;
  flex-direction: column;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.015));
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 20px;
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  position: relative;
  transition:
    transform 0.45s cubic-bezier(0.16, 1, 0.3, 1),
    border-color 0.45s cubic-bezier(0.16, 1, 0.3, 1),
    box-shadow 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}
.news-card__thumb {
  width: 100%;
  /* v10.179: match Medium OG image ratio (16:9) so cover doesn't crop the
   * left/right edges of source images that have built-in title text. */
  aspect-ratio: 16 / 9;
  overflow: hidden;
  position: relative;
  background: #0a0a0a;
}
.news-card__thumb::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, transparent 55%, rgba(0, 0, 0, 0.35));
  pointer-events: none;
}
.news-card__thumb img {
  width: 100%;
  height: 100%;
  /* v10.180: Medium banner images are wider than 16:9 (custom 2:1-ish).
   * `cover` cropped the left/right edges (titles like "ASTARTER" + dates
   * got chopped). `contain` shows the full image; card bg fills any
   * residual space · matches the source image's own dark background. */
  object-fit: contain;
  display: block;
}
/* Source badge · glass pill, top-left of thumbnail */
.news-card__source {
  position: absolute;
  left: 14px;
  top: 14px;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  padding: 4px 11px 3px;
  background: rgba(0, 0, 0, 0.55);
  /* backdrop-filter removed · small badge uses solid bg; blur created 6 GPU
   * offscreen surfaces on every frame for no visible benefit at this scale */
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 999px;
  color: #fff;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  line-height: 1.4;
}
.news-card__body {
  padding: 22px 24px 24px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  flex: 1;
}
.news-card__date {
  font-size: 0.7rem;
  color: rgba(255, 255, 255, 0.5);
  margin: 0;
  letter-spacing: 0.08em;
  font-weight: 600;
  text-transform: uppercase;
  line-height: 1;
}
.news-card__title {
  font-size: 1.1rem;
  font-weight: 700;
  color: #fff;
  margin: 0;
  line-height: 1.35;
  letter-spacing: -0.005em;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  transition: color 0.4s cubic-bezier(0.16, 1, 0.3, 1);
  flex: 1;
}
.news-card__cta {
  display: inline-flex;
  margin-top: 8px;
  font-size: 0.68rem;
  font-weight: 800;
  color: rgba(157, 92, 255, 0.85);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  transition: color 0.3s ease;
}

@media (hover: hover) {
  .news-card:hover {
    border-color: rgba(157, 92, 255, 0.4);
    box-shadow: 0 14px 36px rgba(111, 0, 255, 0.14);
    transform: translateY(-4px);
  }
  .news-card:hover .news-card__cta { color: #fff; }
}

/* Prev/next arrow buttons. White-on-dark pill style matching Pharos.
 * Active = white bg + dark icon; inactive = transparent bg + white icon.
 * Disabled state at start/end of track. */
.news__controls {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-shrink: 0;
}
.news__nav {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.12);
  color: rgba(255, 255, 255, 0.88);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  transition:
    background 0.3s cubic-bezier(0.16, 1, 0.3, 1),
    border-color 0.3s cubic-bezier(0.16, 1, 0.3, 1),
    color 0.3s cubic-bezier(0.16, 1, 0.3, 1),
    transform 0.3s cubic-bezier(0.16, 1, 0.3, 1),
    opacity 0.3s ease;
}
.news__nav svg { width: 20px; height: 20px; }
.news__nav:hover:not(:disabled) {
  background: #fff;
  border-color: #fff;
  color: #0a0a0a;
  transform: scale(1.04);
}
.news__nav:disabled {
  opacity: 0.32;
  cursor: not-allowed;
}
.news__nav:focus-visible {
  outline: 2px solid var(--purple);
  outline-offset: 3px;
}

/* Responsive: 3 -> 2 -> 1 cards visible. */
@media (max-width: 1099px) {
  /* v10.178: tablet viewport gets matching tighter bleed so first card
   * sits flush at the container's left padding, no mid-snap drift. */
  .news .container { padding: 0 24px; }
  .news__viewport { margin: 0 -24px; padding: 8px 24px; }
  .news-card {
    flex-basis: calc((100% - 24px) / 2);
  }
}
@media (max-width: 768px) {
  .news { padding: 80px 0; }
  .news .container { padding: 0 16px; gap: 32px; }
  .news__head { flex-direction: column; align-items: flex-start; }
  .news__viewport { margin: 0 -16px; padding: 8px 16px; }
  .news-card {
    flex-basis: 82%; /* one card + small peek of the next */
  }
  .news-card__source {
    font-size: 0.58rem;
    padding: 3px 9px 2px;
    left: 12px;
    top: 12px;
  }
  .news__footer { margin-top: 8px; }
}

/* ---- Newsletter ---- */
/* ════════════════════════════════════════════════════════════════════
 * v10.55 · Subscribe section: dark editorial newsletter
 *
 * Old design: solid #6f00ff promo banner, white input, black square
 * button. Felt like a Mailchimp signup form on the wrong site.
 * Replaced with the dark-with-purple-ambient pattern that matches
 * the story section and the rest of the page:
 *   - Centered narrow column (720 px max) · no more wasted right side
 *   - Eyebrow pill with pulsing brand-violet dot
 *   - Display-size headline
 *   - Glass-pill form with backdrop blur
 *   - Brand-gradient submit button with hover lift
 * ════════════════════════════════════════════════════════════════════ */
/* v10.168 · Subscribe: Pharos hero gradient adapted to our brand.
 * Pharos uses radial-gradient(120% 140% at 10% -10%, #4187ff 0%,
 * #6da2fd 18%, #9abdfa 35%, #c6d9f8 52%, #f2f4f5 75%) · a saturated
 * brand-color radial in the top-left fading to off-white. Replicated
 * with our brand violet → lavender → cream stops. Creates real
 * dimensional light without looking AI-template. */
.subscribe {
  padding: 140px 0 120px;
  position: relative;
  /* v10.263 · REVERTED v10.262's content-visibility:auto. Made mobile
   * worse: layout recalc on fast swipes cost more than the one-time
   * radial-gradient paint it was trying to avoid. */
  background: radial-gradient(
    120% 140% at 10% -10%,
    #7a14ff 0%,
    #9d5cff 14%,
    #c2a3f5 32%,
    #e0d2f0 50%,
    #ede5f2 68%,
    #f4eff8 85%
  );
  color: #0a0a0a;
  overflow: hidden;
  contain: layout style;
  isolation: isolate;
}
/* Subtle dot grid on the lighter portion of the gradient so the
 * lower-right doesn't read as a flat wall. */
.subscribe::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    radial-gradient(circle at 1px 1px, rgba(10, 10, 14, 0.06) 1px, transparent 0);
  background-size: 26px 26px;
  mask: radial-gradient(ellipse 90% 90% at 100% 100%, #000 30%, transparent 70%);
  -webkit-mask: radial-gradient(ellipse 90% 90% at 100% 100%, #000 30%, transparent 70%);
  pointer-events: none;
}
.subscribe .container {
  max-width: 720px;
  margin: 0 auto;
  padding: 0 32px;
  position: relative;
  z-index: 1;
  text-align: center;
}.subscribe__form-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
}
.subscribe__form-wrap.reveal {
  opacity: 0;
  transform: translateY(36px);
  transition:
    opacity 0.6s ease-out,
    transform 0.6s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.subscribe__form-wrap.reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}
.subscribe__title {
  /* v10.166: dark text on light bg */
  font-size: 2.6rem;
  font-weight: 800;
  margin: 0;
  color: #0a0a0a;
  letter-spacing: -0.025em;
  line-height: 1.12;
  max-width: 620px;
}
.subscribe__text {
  font-size: 1.05rem;
  line-height: 1.55;
  color: rgba(10, 10, 14, 0.75);
  margin: 0;
  max-width: 540px;
}
.subscribe__form {
  display: flex;
  gap: 6px;
  padding: 6px;
  /* v10.166: white pill with subtle dark border + soft elevation
   * shadow, replacing the dark glass-pill from v10.58. Stripe-style
   * floating input on light bg. */
  border: 1px solid rgba(10, 10, 14, 0.1);
  background: #ffffff;
  border-radius: 999px;
  width: 100%;
  max-width: 500px;
  margin-top: 8px;
  box-shadow: 0 6px 22px -8px rgba(10, 10, 14, 0.18);
  transition:
    border-color 0.45s cubic-bezier(0.16, 1, 0.3, 1),
    box-shadow 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}
.subscribe__form:focus-within {
  border-color: rgba(111, 0, 255, 0.45);
  box-shadow:
    0 6px 22px -8px rgba(10, 10, 14, 0.18),
    0 0 0 4px rgba(157, 92, 255, 0.18);
}
.subscribe__input {
  flex: 1;
  min-width: 0;
  padding: 0 18px;
  border: none;
  background: transparent;
  color: #0a0a0a;
  font-size: 1rem;
  font-family: inherit;
  outline: none;
}
.subscribe__input:focus,
.subscribe__input:focus-visible {
  /* v10.523 · no inner rectangle. The parent .subscribe__form:focus-within
   * already draws a soft violet ring around the whole pill, which IS the
   * focus indicator · the input's own 2px hard outline stacked a second,
   * ugly box inside the pill (user screenshot). Keep the input outline-free;
   * accessibility is covered by the pill ring. */
  outline: none;
}
.subscribe__input::placeholder {
  color: rgba(10, 10, 14, 0.42);
}
/* Subscribe submit · same v10.84 recipe as .btn-launch.
 * Pill shape (border-radius 999) to match form pill perimeter,
 * but otherwise identical to the other primary CTAs. */
.subscribe__submit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 0 24px;
  height: 46px;
  color: #fff;
  border: none;
  font-weight: 600;
  font-size: 0.88rem;
  letter-spacing: 0.04em;
  cursor: pointer;
  border-radius: 999px;
  background: linear-gradient(180deg, #7a14ff 0%, #6500e6 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.18),
    0 4px 14px -2px rgba(111, 0, 255, 0.32);
  will-change: transform;
  transition:
    background 0.5s cubic-bezier(0.22, 1, 0.36, 1),
    box-shadow 0.5s cubic-bezier(0.22, 1, 0.36, 1),
    transform 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}
.subscribe__submit:hover {
  background: linear-gradient(180deg, #9233ff 0%, #7a14ff 100%);
  transform: translateY(-1px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.22),
    0 8px 22px -2px rgba(111, 0, 255, 0.42);
}
.subscribe__submit:active {
  background: linear-gradient(180deg, #5a00d6 0%, #4a00b8 100%);
  transform: translateY(0);
  box-shadow:
    inset 0 1px 2px rgba(0, 0, 0, 0.18),
    0 2px 8px -2px rgba(111, 0, 255, 0.3);
}
.subscribe__submit:disabled {
  opacity: 0.6;
  cursor: wait;
}
@media (max-width: 576px) {
  .subscribe {
    padding: 72px 0;
  }
  .subscribe .container {
    padding: 0 20px;
  }
  .subscribe__title {
    font-size: 1.85rem;
  }
  .subscribe__text {
    font-size: 0.95rem;
  }
  .subscribe__form {
    flex-direction: column;
    border-radius: 22px;
    padding: 8px;
    gap: 8px;
  }
  .subscribe__input {
    padding: 12px 16px;
  }
  .subscribe__submit {
    width: 100%;
    justify-content: center;
  }
}

/* ════════════════════════════════════════════════════════════════════
 * v10.55 · Footer: editorial premium
 *
 * Old footer: flat #1a1a1a, "Follow Us" label, generic columns, links
 * that just turned purple on hover, tiny centered copyright.
 *
 * Upgrades:
 *   - Subtle gradient backdrop with a top brand-violet hairline accent
 *   - Brand wordmark with subtle white→violet gradient text-clip
 *   - Cleaner social-icon hover (violet tint, brand-correct)
 *   - Column titles as uppercase tracked captions
 *   - Link hover: slide right + reveal a violet "›" chevron
 *   - Animated status pill in the bottom strip ("● Live · 2026 Q3")
 *   - Refined typography hierarchy and breathing room throughout
 * ════════════════════════════════════════════════════════════════════ */
.site-footer {
  position: relative;
  background: linear-gradient(180deg, #0a0a0a, #060606);
  color: rgba(255, 255, 255, 0.9);
  padding: 80px 0 32px;
  font-family: var(--font);
  contain: layout style;
  overflow: hidden;
  isolation: isolate;
}
/* Top accent: thin brand-gradient hairline that fades out on either
 * end. Anchors the footer as a deliberate transition from the dark
 * content, not just "the bg colour changed". */
.site-footer::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(157, 92, 255, 0.4), transparent);
  pointer-events: none;
  z-index: 1;
}
/* v10.65 · subtle X watermark on the right side (Pharos-style).
 * Two wide diagonal bands overlap to form an X. Kept extremely faint
 * (rgba 0.025–0.04) so it reads as a texture, not a graphic. */
/* v10.147 · giant outlined ASTARTER wordmark as the footer watermark.
 * Pharos uses a diagonal-X texture (user reference screenshot); rather
 * than copying their X, we use the brand wordmark itself as a massive
 * outline-only signature that bleeds off the bottom edge. Top-tier
 * pattern: Stripe, Linear, Resend, Vercel all use the company name
 * as a footer signature watermark. Replaces the v10.65 X-watermark. */
.site-footer::after {
  content: "ASTARTER";
  position: absolute;
  left: 50%;
  bottom: -0.22em;
  transform: translateX(-50%);
  font-family: var(--font);
  font-size: clamp(7rem, 22vw, 20rem);
  font-weight: 900;
  letter-spacing: -0.05em;
  line-height: 0.85;
  color: transparent;
  /* v10.470 · stroke lifted 0.07 → 0.12 · the giant ASTARTER signature
     watermark was too faint to read (user flagged). Still an outline-only
     texture, just a touch more present. */
  -webkit-text-stroke: 1px rgba(255, 255, 255, 0.12);
  white-space: nowrap;
  pointer-events: none;
  user-select: none;
  z-index: 0;
  /* Mask the top edge so the wordmark fades into the footer instead
   * of cutting off abruptly above the content. */
  mask-image: linear-gradient(180deg, transparent 0%, #000 35%, #000 100%);
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 35%, #000 100%);
}
.site-footer .container {
  position: relative;
  z-index: 1;
}
.site-footer__top {
  display: grid;
  grid-template-columns: 1.3fr 3fr;
  gap: 80px;
  margin-bottom: 48px;
}
.site-footer__brand {
  /* v10.111: max-width: 380px only kicks in on desktop. Below 992px
   * the footer stacks (.site-footer__top → 1fr) and the brand block
   * sits alone in its row · capping it at 380px creates dead space
   * on the right at viewports 430–768px, which user flagged as the
   * footer looking broken / cramped. Full width on tablet + mobile
   * lets the social row and tagline breathe. */
  max-width: 380px;
  margin-bottom: 0;
}
/* Footer logo · reverted to the prior styling per user (v10.61).
 * 32 px logo height + 22 px bold wordmark, plain white (not the
 * gradient text-clip that was here originally · user previously
 * preferred plain white). */
.site-footer__logo-row {
  display: flex;
  align-items: center;
  margin-bottom: 18px;
}
.site-footer__logo {
  height: 48px;
  width: auto;
  object-fit: contain;
  /* v10.81: drop-shadow glow removed per user · logo renders as pure
   * brand purple #6f00ff from the PNG, no halo. */
}
.site-footer__logo-text {
  color: #fff;
  font-size: 22px;
  font-weight: 800;
  letter-spacing: -0.005em;
  margin-left: 12px;
}
.site-footer__tagline {
  font-size: 0.95rem;
  line-height: 1.55;
  margin: 0 0 28px;
  color: rgba(255, 255, 255, 0.55);
  max-width: 320px;
}.site-footer__social {
  display: flex;
  gap: 8px;
}
.site-footer__social a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  /* v10.139: bumped from 0.65 -> 0.88 per user feedback. Footer icons
   * sit on a near-black background; the previous opacity made them
   * read as a gray smudge. */
  color: rgba(255, 255, 255, 0.88);
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.08);
  transition:
    background 0.45s cubic-bezier(0.16, 1, 0.3, 1),
    color 0.45s cubic-bezier(0.16, 1, 0.3, 1),
    border-color 0.45s cubic-bezier(0.16, 1, 0.3, 1),
    transform 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}
.site-footer__social a svg {
  display: block;
  width: 17px;
  height: 17px;
}
.site-footer__social a:focus-visible {
  color: #fff;
  background: rgba(157, 92, 255, 0.14);
  border-color: rgba(157, 92, 255, 0.45);
  outline: none;
}
@media (hover: hover) {
  .site-footer__social a:hover {
    color: #fff;
    background: rgba(157, 92, 255, 0.14);
    border-color: rgba(157, 92, 255, 0.45);
    transform: translateY(-2px);
  }
}

/* ── Column grid ──
 * v10.100: restructured from 4 columns (Education/Products/ISPO/Community)
 * to 3 (Product/Token/Resources). Community is gone · its channels live
 * as icons in the brand block instead.
 *
 * v10.113: switched from fixed `repeat(3, 1fr)` + breakpoint overrides
 * to `repeat(auto-fit, minmax(180px, 1fr))`. Columns naturally fill
 * the row width at every viewport:
 *   ≥600px container  → 3 columns (180px min each)
 *   400-599px        → 2 columns
 *   <400px           → 1 column
 * No more arbitrary breakpoint stairsteps, no dead space, no need
 * for the special nth-child(3) span rule that was bracing the old
 * 2-col layout. */
.site-footer__columns {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 40px;
  margin-left: 0;
}
.site-footer__col {
  flex: initial;
  margin: 0;
}
/* v10.65 · softer column titles. Was loud uppercase tracked all-caps;
 * Pharos / Linear use a muted normal-case title that reads like a
 * label, not a banner. Lets the actual links underneath carry the
 * weight. */
.site-footer__col-title {
  display: block;
  font-size: 0.95rem;
  font-weight: 500;
  letter-spacing: -0.005em;
  text-transform: none;
  color: rgba(255, 255, 255, 0.35);
  margin-bottom: 22px;
}
.site-footer__links {
  list-style: none;
  padding: 0;
  margin: 0;
}
.site-footer__links li {
  margin-bottom: 12px;
}
/* Footer links · clean, static column. No chevron, no slide. The
 * previous hover (slide-right + reveal ›) was making the column
 * shift around as the mouse moved, which read as sloppy alignment.
 * A simple colour-brighten on hover is the right amount of feedback. */
.site-footer__links a {
  display: inline-block;
  color: rgba(255, 255, 255, 0.72);
  font-size: 0.92rem;
  font-weight: 500;
  text-decoration: none;
  letter-spacing: -0.005em;
  transition: color 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
@media (hover: hover) {
  .site-footer__links a:hover {
    color: #fff;
  }
}

/* v10.218 footer 6+3 split was rejected · rules removed in v10.219.
 * Footer Resources stays a single-column 9-item list (the previous
 * v10.209 layout). The drawer Solution link addition from v10.218
 * is kept (separate improvement, not related to the footer layout). */

/* ── Divider + bottom strip ── */
.site-footer__divider {
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.08) 20%, rgba(255, 255, 255, 0.08) 80%, transparent);
  margin: 32px 0 24px;
}
/* v10.381 · footer bottom strip: was a flex row that caused "Media Kit"
 * and "Cookie settings" to wrap to 2 lines on narrow viewports.
 * Restructured to stack copyright on top, then a single flex-wrap row
 * for legal links (centered, gap 8/16, white-space:nowrap on each item
 * so multi-word labels stay intact), email on its own line below. */
.site-footer__bottom {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
}
.site-footer__bottom .site-footer__legal {
  flex-wrap: wrap;
  justify-content: center;
  row-gap: 6px;
}
.site-footer__bottom .site-footer__legal a {
  white-space: nowrap;
}
.site-footer__copy {
  margin: 0;
  font-size: 0.82rem;
  font-weight: 500;
  /* v10.469 · was rgba(...,0.4) · 40% white on the near-black footer
     rendered almost invisible (user flagged vs the crisp Pharos
     reference). Lifted to 0.7 · clearly legible while still reading as
     a quiet, secondary line (the legal links beside it sit at 0.55). */
  color: rgba(255, 255, 255, 0.7);
  letter-spacing: 0.05em;
  text-align: center;
  /* v10.464 · keep "© 2026 ASTARTER · All rights reserved" on a single
     line · it's short enough to fit even at 320px, and wrapping it to
     two lines looked broken. */
  white-space: nowrap;
}
/* v10.225 · Privacy / Terms nav in the footer bottom strip. Quiet
 * dot-separated text links matching the copyright line treatment. */
.site-footer__legal {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 0.82rem;
  color: rgba(255, 255, 255, 0.4);
  letter-spacing: 0.05em;
}
.site-footer__legal a {
  color: rgba(255, 255, 255, 0.7);
  text-decoration: none;
  transition: color 0.3s ease;
}
.site-footer__legal a:hover { color: #fff; }
.site-footer__legal span { color: rgba(255, 255, 255, 0.25); }

/* v10.100: contact email in the bottom strip · second item next to
 * the copyright line. Subdued by default, brand-violet on hover. */
.site-footer__contact {
  font-size: 0.82rem;
  font-weight: 500;
  color: rgba(255, 255, 255, 0.4);
  letter-spacing: 0.05em;
  text-decoration: none;
  transition: color 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
@media (hover: hover) {
  .site-footer__contact:hover {
    color: var(--purple);
  }
}

@media (max-width: 992px) {
  .site-footer__top {
    grid-template-columns: 1fr;
    gap: 48px;
  }
  .site-footer__columns {
    margin-left: 0;
  }
  /* v10.111: drop the desktop 380px cap below tablet so the brand
   * block fills the row, eliminating the dead space on the right. */
  .site-footer__brand {
    max-width: none;
  }
}
@media (max-width: 768px) {
  .site-footer {
    padding: 64px 0 28px;
  }
  /* v10.147: ASTARTER wordmark watermark · the clamp() already scales
   * it down for mobile (min 7rem), but tighten the stroke + push it
   * a little further off the bottom so it doesn't compete with the
   * stacked single-column content. */
  .site-footer::after {
    bottom: -0.3em;
    -webkit-text-stroke-width: 0.5px;
    opacity: 0.85;
  }
  /* v10.113: tighter gap on tablet/mobile. The grid-template-columns
   * is handled by the auto-fit rule above (180px min each column);
   * the special nth-child(3) span rule from v10.100 is no longer
   * needed because auto-fit naturally lays out 1-3 columns based on
   * available width, never leaving an orphan. */
  /* v10.121: 2-col layout on tablet/mobile so Resources fills the empty
   * right side of Product. Token spans the full width on row 2.
   *
   * v10.221: that earlier rule left a big gap below Product because
   * row 1 stretched to match Resources' height (9 items). Token then
   * dropped to row 2, leaving Product → empty space → Token visually.
   * New arrangement:
   *   col 1 row 1 = Product
   *   col 1 row 2 = Token         (slides up right under Product)
   *   col 2 rows 1-2 = Resources  (tall column on the right)
   * No gap below Product · Token sits directly beneath it. */
  .site-footer__columns {
    grid-template-columns: 1fr 1fr;
    grid-template-rows: auto auto;
    gap: 32px 24px;
  }
  .site-footer__col:nth-child(1) { grid-column: 1; grid-row: 1; }              /* Product   */
  .site-footer__col:nth-child(2) { grid-column: 1; grid-row: 2; }              /* Token     */
  .site-footer__col:nth-child(3) { grid-column: 2; grid-row: 1 / span 2; align-self: start; } /* Resources */
  /* v10.100: bottom strip · stack copyright above contact on mobile
   * so neither line gets squeezed. Keep both centered. */
  .site-footer__bottom {
    flex-direction: column;
    gap: 6px;
  }
}

/* ---- Toast ---- */
#toast-root {
  position: fixed;
  top: 16px;
  right: 16px;
  z-index: 10000;
  display: flex;
  flex-direction: column;
  gap: 8px;
  pointer-events: none;
}
.toast-msg {
  pointer-events: auto;
  min-width: 280px;
  padding: 14px 16px;
  border-radius: 6px;
  background: #121212;
  color: #fff;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
  font-size: 14px;
  animation: toastIn 0.35s ease;
}
@keyframes toastIn {
  from {
    opacity: 0;
    transform: translateX(20px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

#abox-canvas-wrap {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
#abox-canvas-wrap canvas {
  display: block;
  width: 100% !important;
  height: 100% !important;
}

/* ─────────────────────────────────────────────────────────────────
 * MOBILE PERFORMANCE OVERRIDES (≤ 900px)
 * Targets the GPU/CPU bottlenecks that mobile devices struggle with:
 *   1. backdrop-filter blur (very expensive on mobile GPU)
 *   2. Large sticky sections forcing constant repaint
 *   3. Infinite CSS keyframe animations
 *   4. Long transition durations
 * Keeps every visual feature · just renders cheaper.
 * ───────────────────────────────────────────────────────────────── */
@media (max-width: 900px) {
  /* v10.125: scrolled-state nav background flipped from near-solid
   * black to white to match the PC theme (user: "make bar colour like
   * PC view"). Backdrop-filter still removed for mobile perf. */
  .site-nav--scrolled {
    background: #ffffff;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }

  /* Sticky sections: shorter than desktop, but enough to read the 4
   * ABOX panels of copy at mobile reading pace. */
  /* dvh tracks the visible viewport (iOS Safari URL bar safe) */
  .story { height: 100vh; height: 100dvh; }
  .abox-anchor { height: 280vh; }

  /* DO NOT add `content-visibility: auto` on .nodes__visual.
   * Spline viewer runs its own IntersectionObserver to decide when to
   * unhide its canvas; if the parent has content-visibility:auto the
   * canvas's bounding rect appears off-screen forever to Spline's IO
   * and the scene never finishes initialising → user sees an empty
   * black box. The IntersectionObserver pause in js/app.js already
   * gives us the off-screen perf benefit without this side effect. */

  /* Partner marquee: keep the auto-scroll animation running on mobile
   * too (user feedback: static row looked broken). The animation is
   * cheap (transform-only, 45s linear), and the perfAutoFallback will
   * pause it via .perf-mode if FPS actually drops. Native horizontal
   * swipe is preserved as an alternative input via overflow-x:auto. */
  .partner-marquee__viewport {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x proximity;
  }
  .partner-marquee__rows::before,
  .partner-marquee__rows::after { display: none; }

  /* Cap reveal animation cost on slow mobile GPUs + snappier easing */
  .reveal {
    transition-duration: 0.45s !important;
    /* Force a GPU layer so the animation runs entirely on the
     * compositor thread (avoids first-frame hitches when the layer
     * is created mid-animation). */
    transform: translateZ(0);
    backface-visibility: hidden;
  }

  /* Mobile-only smoothness pass · pre-promote the headline animated
   * elements to GPU layers so their transitions never stutter:
   *   - Roadmap pill cards (entrance + hover)
   *   - Tokenomics legend rows (bar fill, hover state)
   *   - Gradient phase badges (already promoted via will-change in
   *     their own rules, listed here for completeness) */
  .roadmap-item,
  .tokenomics__legend-row {
    transform: translateZ(0);
    backface-visibility: hidden;
  }

  /* Reduce hero-overlay gradient complexity */
  .hero { height: 70vh; height: 70dvh; }

  /* No expensive purple-glow ring on the gateway-core box on mobile */
  .gateway-core--active {
    box-shadow: 0 0 40px rgba(111, 0, 255, 0.4);
  }
}

/* Honor user OS-level data saver preference (Chrome / Edge)
 * v10.433 · expanded gate: also kill the hero Ken Burns transform and
 * any tap-flash on body. The Ken Burns runs a 24s GPU transform loop
 * forever · pointless on a saveData session. */
@media (prefers-reduced-data: reduce) {
  .hero__video { display: none; }
  .hero::after { animation: none; }
  .partner-marquee__track--left,
  .partner-marquee__track--right,
  .partner-marquee__track--left-slow { animation: none; }
}

/* v10.486 · pull-to-refresh RESTORED on mobile.
 * v10.433 set `overscroll-behavior-y: none` to kill the iOS rubber-band
 * grey-flash · but that ALSO disables Chrome/Android pull-to-refresh (the
 * pull-down-to-reload gesture), which the user wants back. There's no way
 * to keep both: pull-to-refresh requires native overscroll. We choose the
 * gesture · `auto` re-enables pull-to-refresh + the standard bounce. The
 * minor iOS scroll-end flash returns, an acceptable trade for the expected
 * refresh gesture. */
@media (max-width: 767px) {
  html, body { overscroll-behavior-y: auto; }
}

/* ════════════════════════════════════════════════════════════════════
 * v10.136 · Top-1% polish: pharos-inspired radial ambients, react-bits
 * inspired ShinyText sheep + ElectricBorder + GlareHover. All
 * compositor-only (transform / opacity / filter / background-position).
 * No new JS · pure CSS where possible. prefers-reduced-motion respected.
 * ════════════════════════════════════════════════════════════════════ */

/* v10.156: v10.136 hero ambient-drift radial removed. The video and
 * Ken Burns poster carry the hero · no additional brand-violet glow
 * stacked on top. */

/* v10.383 · ShinyText sheen on hero H1 REMOVED (mobile AI tell #1,
 * crypto-template hero pattern). Linear/Vercel/Stripe/Anthropic
 * never decorate the LCP heading. Solid white h1 carries the brand. */

/* v10.383 · GlareHover diagonal sheen on primary CTAs REMOVED
 * (anti-pattern #8 stacked sheen; mobile audit flagged as pure
 * desktop decoration cost · hover isn't observable on touch
 * anyway). Solid hover-lift from v10.68 already carries button
 * language. */

/* v10.156: v10.136 brand-violet section-top hairlines removed. They
 * added a generic-AI 'pharos-style decoration' on every section
 * boundary. Sections now blend naturally without the violet rule. */

/* v10.162: card-depth inset rule removed from .advisor-card. After
 * v10.153 stripped the advisor cards to a bgless / borderless
 * editorial layout, this rule kept drawing a 1px inset white hairline
 * at the top of each card. Three cards side-by-side aligned their
 * three top-insets into ONE perceptible horizontal line across the
 * section · the "line" the user kept reporting in v10.157-v10.161.
 * Found via preview_eval DOM inspection.
 *
 * .arch-layer, .problem-card removed earlier (those sections are
 * hairline-rows, not cards). */
/* v10.407 · stale .use-case-card box-shadow inset rule removed.
 * It added a 1px top white-alpha + 1px bottom black-alpha designed
 * for dark cards · on the new light Option F cards (white-ish bg)
 * those insets created a barely-visible weird seam. Light cards
 * carry their own depth via the hairline border + radius. */

/* v10.150: ElectricBorder removed (was bleeding outside card bounds).
 * The featured PRO tier keeps the brand-violet drop-shadow defined
 * later · don't reset it here. */

/* ════════════════════════════════════════════════════════════════════
 * v10.137 · micro-polish layer:
 *   - Smooth scroll for in-page anchors (with safe-area offset)
 *   - Hero hashtag chips (deck p.1: #Web4 #AI #DePIN)
 *   - Stagger reveal cascade on every card grid (uses the existing
 *     inline --pb-i / --uc-i / --av-i / --nt-i / --fn-i / --lr-i
 *     indexes set in HTML · no extra JS)
 * ════════════════════════════════════════════════════════════════════ */

/* v10.238 · apply scroll-behavior + scroll-padding-top to BOTH html and
 * body. Why: `body { overflow-x: hidden }` (defined above for hero/3D
 * overscroll containment) forces the browser to compute `overflow-y:
 * auto` on body · which makes BODY the document's scroll container
 * instead of <html>. With the scroll rules only on <html>, anchor links
 * to #problem etc. neither smooth-scrolled nor honoured the 96px nav
 * offset; users landed at scrollY=0 or with the section header covered
 * by the floating nav. Replicating on body fixes both. The :is(html,
 * body) selector keeps things terse + future-proof if the scroll root
 * ever shifts again. */
:is(html, body) {
  scroll-behavior: smooth;
  scroll-padding-top: 96px;
}
@media (prefers-reduced-motion: reduce) {
  :is(html, body) { scroll-behavior: auto; }
}

/* Hero hashtag chip row · pharos-style tag pills above the title.
 * Same recipe as the drawer sublinks but smaller + brand-violet-on-black
 * instead of dim white. */
/* v10.383 · .hero__tags entirely removed. Eyebrow chips (Web4 / AI /
 * DePIN) above the hero title were a top-rated mobile AI tell. The H1
 * is identity enough; Linear/Stripe/Vercel never decorate above the
 * LCP heading. */

/* Stagger reveal cascade · each card in a section grid waits its turn.
 * The existing inline `--*-i` indexes are reused: when the parent gets
 * .reveal.is-visible, child cards lift in sequence rather than all at
 * once. Compositor-only (transform + opacity). */
.reveal[style*="--pb-i"],
.reveal[style*="--uc-i"],
.reveal[style*="--av-i"],
.reveal[style*="--nt-i"],
.reveal[style*="--fn-i"],
.reveal[style*="--lr-i"] {
  transition-delay: calc(
    (var(--pb-i, 0) + var(--uc-i, 0) + var(--av-i, 0) + var(--nt-i, 0) + var(--fn-i, 0) + var(--lr-i, 0)) * 0.08s
  );
}

/* ════════════════════════════════════════════════════════════════════
 * v10.135 · deck-deep sections: Problem (p.3) + Flywheel (p.7).
 * Plus shared text-animation primitives (SplitText letter reveal,
 * CountUp number ramp). Top-1% polish patterns adapted from react-bits
 * (BlurText, CountUp) as vanilla CSS/JS · no React dependency.
 * ════════════════════════════════════════════════════════════════════ */

/* ───────── Problem section (deck p.3 · v10.141 editorial rebuild) ─────────
 * Stripe / Anthropic / Linear pattern: 2-column narrative layout
 * with a sticky title on the left and three problem rows on the
 * right separated by hairlines. Pure typography hierarchy, no card
 * chrome, no glyph squares, no gradient numbers, no per-card hue.
 * v10.140 had all of those and the user (correctly) called the
 * result "agentic" · the AI-template look.
 *
 * The premium feel here comes from:
 *   - generous breathing room (120px padding, large gaps)
 *   - display-grade typography (clamped down to phone)
 *   - oversize muted numbers as captions, not anchors
 *   - hairline dividers (no boxed surfaces)
 *   - sentence-case title (less shouty than Title Case) */
.problem {
  /* v10.161: flat dark, no bg. Body provides #040404 and Problem
   * sits on it. */
  padding: 140px 0;
  position: relative;
}

/* v10.344 · Twin Problem + Solution strip, top-1% editorial pass.
 * Stripped the pill+arrow CTA chrome (anti-patterns #11 + #12).
 * Title is now the hero · display weight, large size, tighter LH.
 * The whole card is the link; "Read the X" is a quiet caption with
 * an animated underline that grows from the left on hover. No
 * decorative chrome · typography + space + a single hover signal. */
.ctas-strip {
  padding: 120px 0 100px;
  position: relative;
}
.ctas-strip__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1px;                          /* hairline between cards */
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 24px;
  overflow: hidden;
  isolation: isolate;
}
/* v10.353 · projection-beam stripped. Reading as AI-generic chrome
 * (anti-pattern #8 + #11: glow stacks + decorative atmospherics).
 * Back to a single quiet signal: a flat near-black surface with the
 * v10.344 corner ambient. No backdrop-blur (nothing meaningful to
 * blur through on a dark page), no inset highlight rim, no conic
 * beam, no hotspot. Restraint over decoration. */
.cta-strip {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 32px;
  padding: 56px 52px 52px;
  background: #060606;
  text-decoration: none;
  color: inherit;
  overflow: hidden;
  isolation: isolate;
  min-height: 320px;
  transition: background-color 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}
/* v10.394 · corner radial ambients REMOVED on desktop CTA strips too.
 * The subtle violet/magenta gradient corners read as AI-generated
 * decorative atmospherics (anti-pattern #11). Pure flat #060606
 * surfaces communicate the brand without decorative chrome.
 * Mobile rules in the @media block also already strip ::before. */
.cta-strip::before { display: none; }
.cta-strip__eyebrow {
  font-family: ui-monospace, "SF Mono", Menlo, Monaco, monospace;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: rgba(157, 92, 255, 0.95);
  display: inline-flex;
  align-items: center;
  gap: 14px;
}
/* v10.383 · ornamental gradient dash REMOVED (anti-pattern #11). */
.cta-strip__title {
  font-size: clamp(1.45rem, 2.1vw, 1.95rem);
  font-weight: 500;
  letter-spacing: -0.022em;
  line-height: 1.18;
  color: #fff;
  margin: 0;
  flex: 1;
  text-wrap: balance;
  max-width: 22ch;
}
/* Quiet caption · small mono uppercase, underline that grows from
 * the left on hover. No arrow, no pill, no extra surface. */
.cta-strip__caption {
  position: relative;
  align-self: flex-start;
  font-family: ui-monospace, "SF Mono", Menlo, Monaco, monospace;
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.78);
  padding-bottom: 6px;
  transition: color 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
.cta-strip__caption::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: linear-gradient(90deg, rgba(157, 92, 255, 0.9), rgba(157, 92, 255, 0.5));
  transform: scaleX(0.32);
  transform-origin: left;
  transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}
@media (hover: hover) {
  .cta-strip:hover { background-color: #0a0a0c; }
  /* v10.394 · ::before is display:none now; hover opacity is moot. */
  .cta-strip:hover .cta-strip__caption { color: #fff; }
  .cta-strip:hover .cta-strip__caption::after { transform: scaleX(1); }
}
.cta-strip:focus-visible {
  outline: 2px solid var(--purple);
  outline-offset: -8px;
}
@media (max-width: 900px) {
  .ctas-strip { padding: 72px 0 64px; }
  .ctas-strip__grid {
    grid-template-columns: 1fr;
    gap: 16px;
    background: transparent;
    border: none;
    border-radius: 0;
  }
  .cta-strip {
    padding: 36px 28px 32px;
    min-height: 0;
    gap: 22px;
    border-radius: 20px;
  }
}

/* v10.399 · Mobile Problem/Solution: refined narrative pair.
 *
 * v10.391 used dark+white contrast · read jarring on a dark page,
 * not editorial. Vercel / Linear / Stripe pair narrative sections via
 * SHARED dark surface + DIFFERENTIATED single accent. Adopted.
 *
 * Both cards now: deep refined dark surface #0a0a0c with subtle inset
 * highlight + 1 thin top-edge accent line (per-card color) signaling
 * the semantic difference. Cohesive surface, clear semantic split.
 *
 * Problem accent: magenta #e040fb (the "weight / locked" state · pulls
 * from the existing tokenomics palette).
 * Solution accent: brand-violet #6f00ff (the "answer / unlock" state). */
@media (max-width: 768px) {
  .cta-strip {
    overflow: hidden;
    position: relative;
    border-radius: 18px;
  }
  .cta-strip::before { display: none; }

  /* Shared dark surface · both cards */
  .cta-strip,
  .cta-strip--solution {
    background: #0a0a0c !important;
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.05),
      inset 0 -1px 0 rgba(0, 0, 0, 0.4),
      0 14px 36px -16px rgba(0, 0, 0, 0.55) !important;
  }

  /* Per-card top-edge accent · single signal differentiator.
   * 2px line fading at both ends for editorial restraint. */
  .cta-strip::after {
    content: "";
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 2px;
    pointer-events: none;
    background: linear-gradient(90deg, transparent, #e040fb 30%, #e040fb 70%, transparent);
    opacity: 0.7;
  }
  .cta-strip--solution::after {
    background: linear-gradient(90deg, transparent, #6f00ff 30%, #6f00ff 70%, transparent);
  }

  /* Typography · refined editorial ramp */
  .cta-strip__eyebrow {
    font-size: 0.72rem;
    letter-spacing: 0.22em;
    color: #b07fff;
  }
  .cta-strip--solution .cta-strip__eyebrow {
    color: #d4b3ff;
  }
  .cta-strip__eyebrow::before { display: none !important; }

  .cta-strip__title {
    color: #fff;
    font-size: clamp(1.55rem, 5vw, 1.95rem);
    font-weight: 700;
    letter-spacing: -0.022em;
    line-height: 1.15;
  }

  .cta-strip__caption {
    color: rgba(255, 255, 255, 0.65);
  }
  .cta-strip__caption::after {
    background: linear-gradient(90deg, #e040fb, rgba(224, 64, 251, 0.4));
  }
  .cta-strip--solution .cta-strip__caption::after {
    background: linear-gradient(90deg, #6f00ff, rgba(111, 0, 255, 0.4));
  }

  /* Tap state · subtle violet wash per accent */
  .cta-strip:active { background: #0e0e12 !important; }
  .cta-strip--solution:active { background: #0e0a18 !important; }
}
@media (max-width: 768px) {
  .problem { padding: 88px 0; }
}

/* ───────── Flywheel section (deck p.7) ───────── */
/* ───────── Flywheel (deck p.7 · v10.145 horizontal flow) ─────────
 * Distinct layout signature: horizontal 4-step process flow, left
 * to right, with thin connector lines between steps. Different from
 * Problem (editorial 2-col) and Architecture (vertical pyramid).
 * Loop indicator below wraps the cycle back to step 01. */
.flywheel {
  padding: 140px 0;
  background: #040404;
  position: relative;
}
.flywheel__head {
  text-align: center;
  max-width: 720px;
  margin: 0 auto 80px;
}
.flywheel__eyebrow {
  margin: 0 0 18px;
  font-size: 0.78rem; font-weight: 700; letter-spacing: 0.24em;
  text-transform: uppercase; color: var(--purple);
}
.flywheel__title {
  /* v10.291 · display tokens; was clamp(2rem,3.4vw,2.8rem)/700/-.02em/1.15 */
  margin: 0 0 16px;
  font-size: var(--fs-section);
  font-weight: var(--fw-display);
  letter-spacing: var(--ls-display);
  line-height: var(--lh-display);
  color: #fff;
}
.flywheel__sub {
  margin: 0;
  font-size: 1rem; line-height: 1.65;
  color: rgba(255, 255, 255, 0.58);
}

/* v10.405 · Option F · Pharos-style light cards.
 *
 * Reverse-engineered from pharos.xyz section-4 ("From Real Assets to
 * Open Access"). Each step is a self-contained light card on the dark
 * page · big numeral anchor, concept icon top-right, label · title ·
 * description. No connector hairlines, no glow stack, no rotating
 * "cycle restarts" pill (the visual repetition of 4 identical cards
 * already implies the loop · saying it explicitly is anti-pattern #11).
 *
 * Tokens measured from Pharos CSS:
 *   card bg     #f2f4f5
 *   card border #d7dade @ 1px
 *   radius      12px
 *   padding     32px 24px desktop · 22px 18px mobile
 *   numeral     64px weight 700 JetBrains-mono-like (mono)
 *   icon chip   48px square 10px radius 1px hairline
 *   brand accent rgba(111,0,255,1) used ONLY on label + icon stroke
 */
.flywheel__flow {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
}
/* v10.422/424/425/426 · purple gradient cards with DRIFTING starfield.
 *
 * v10.422 · 10 stacked radial-gradients (visible but heavy paint)
 * v10.424 · fractalNoise SVG (cheap but not visible as dots)
 * v10.425 · single SVG with explicit circles (visible AND cheap, but STATIC)
 * v10.426 · same circles but on a pseudo-element layer that translates
 *           with a slow GPU-only transform · sparkles drift diagonally
 *           across the card · seamless infinite loop because the
 *           background tiles and translates by exactly one tile.
 *
 * Compositor-only · transform on a pseudo-element runs on the GPU.
 * The card body itself stays static (linear-gradient + contain:layout).
 * Honors prefers-reduced-motion · animation: none for those users.
 * Position:relative + overflow:hidden on the card so the drifting
 * pseudo-element doesn't bleed outside the card boundary. */
/* v10.431 · REVERTED back to clean dark text-only treatment per user
 * screenshot. The purple gradient + sparkle animation (v10.422-v10.430)
 * was rejected · "use old one current onr not fit".
 *
 * Now: transparent card with subtle right-edge hairline divider, mono
 * numeral at the top, violet uppercase label, white title, muted
 * description. Matches the editorial/spec-sheet look in the
 * reference screenshot. No card bg, no icons, no animation. */
.flywheel-step,
.use-case-card {
  position: relative;
  z-index: 1;
  /* v10.486 · upgraded from flat text-only columns (with a right-edge
   * hairline divider) to the Huly rounded-card box · matches the
   * .roadmap-item / .tokenomics__mech / .arch-layer treatment. */
  padding: 28px 26px 26px;
  display: flex;
  flex-direction: column;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.055), rgba(255, 255, 255, 0.018));
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 16px;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.06),
    0 20px 44px -28px rgba(0, 0, 0, 0.7);
  overflow: hidden;
  color: #fff;
  transition: border-color 0.35s cubic-bezier(.2, .7, .2, 1), transform 0.35s cubic-bezier(.2, .7, .2, 1);
  /* v10.435 · contain:layout style isolates each card's recalc scope.
   * Cheap win for text-only cards · sibling card hover/state
   * changes don't propagate layout/style work outside the card. */
  contain: layout style;
}
/* v10.486 · whisper violet corner glow (alpha .07 · matches .arch-layer
 * v10.485). Sits behind the card content (z-index:1 above). The old
 * right-edge hairline ::after divider is dropped · a boxed card carries
 * its own edge via the border. */
.flywheel-step::before,
.use-case-card::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(60% 90% at 100% 0%, rgba(111, 0, 255, 0.07), transparent 60%);
}
.flywheel-step > *,
.use-case-card > * { position: relative; z-index: 1; }
@media (hover: hover) {
  .flywheel-step:hover,
  .use-case-card:hover {
    transform: translateY(-2px);
    border-color: rgba(157, 92, 255, 0.32);
  }
}
/* Mono numeral · 01 / 02 / 03 / 04 (flywheel only · use-cases skip this) */
.flywheel-step__num {
  font-size: 0.9rem;
  font-weight: 500;
  letter-spacing: 0.08em;
  font-feature-settings: "tnum" 1;
  color: rgba(255, 255, 255, 0.32);
  margin-bottom: 22px;
  font-family: ui-monospace, "SF Mono", Menlo, Monaco, monospace;
}
.flywheel-step__label,
.use-case-card__label {
  margin: 0 0 10px;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: rgba(157, 92, 255, 0.78);
}
.flywheel-step__title,
.use-case-card__title {
  margin: 0 0 12px;
  font-size: clamp(1.15rem, 1.4vw, 1.32rem);
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.3;
  color: #fff;
  min-height: 2.6em;
}
.flywheel-step__desc,
.use-case-card__desc {
  margin: 0;
  font-size: 0.94rem;
  line-height: 1.6;
  color: rgba(255, 255, 255, 0.6);
}
.use-case-card__title { min-height: auto; }

/* v10.431 · loop indicator pill restored · "THE CYCLE RESTARTS" badge
 * at the bottom of the flywheel section. Glass pill with rotating
 * cycle icon (subtle 12s linear · respects prefers-reduced-motion). */
.flywheel__loop {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 56px auto 0;
  padding: 10px 18px 10px 12px;
  background: linear-gradient(180deg, rgba(255,255,255,0.045), rgba(255,255,255,0.015));
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: 999px;
  color: rgba(255,255,255,0.78);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  width: fit-content;
  box-shadow: 0 8px 22px rgba(111,0,255,0.10), inset 0 1px 0 rgba(255,255,255,0.05);
}
.flywheel__loop-icon {
  width: 28px; height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 50%, rgba(157,92,255,0.22), rgba(111,0,255,0.06) 70%);
  color: #b07fff;
  flex-shrink: 0;
}
.flywheel__loop-icon svg { width: 16px; height: 16px; }
.flywheel__loop-label {
  background: linear-gradient(90deg, #fff 0%, rgba(176,127,255,0.85) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}

@media (max-width: 1023px) {
  .flywheel__flow,
  .use-cases__grid {
    grid-template-columns: repeat(2, 1fr);
  }
  /* v10.486 · boxed cards carry their own border at every breakpoint ·
   * the old inter-card hairline dividers (border-bottom on the top row)
   * are no longer needed. */
}
@media (max-width: 640px) {
  .flywheel { padding: 88px 0; }
  .flywheel__head { margin-bottom: 56px; }
  .flywheel__flow,
  .use-cases__grid {
    grid-template-columns: 1fr;
  }
  /* v10.486 · keep the boxed card on mobile · just trim the inner padding
   * (NOT to 0 · that would collapse the box edge against the text). */
  .flywheel-step,
  .use-case-card {
    padding: 24px 20px;
  }
  .flywheel-step__title,
  .use-case-card__title { min-height: auto; font-size: 1.1rem; }
}

/* v10.465 · dead `.split-text*` + `[data-countup]` CSS removed.
 * The matching initSplitText()/initCountUp() JS was removed in v10.418;
 * these rules had zero call sites (verified across all HTML + JS). */

/* ───────── Responsive ───────── */
@media (max-width: 1024px) {
  .problem, .flywheel { padding: 88px 0; }
}
@media (max-width: 768px) {
  .problem, .flywheel { padding: 72px 0; }
  .flywheel__title { font-size: 1.75rem; }
}

/* ════════════════════════════════════════════════════════════════════
 * v10.133 · deck-sync sections: Architecture, Use Cases, Node Tiers,
 * Advisors. Each new block mirrors an existing card/section pattern so
 * the visual language stays consistent across the page.
 * ════════════════════════════════════════════════════════════════════ */

/* Shared eyebrow + title typography (matches tokenomics / news / roadmap) */
/* v10.161: all section backgrounds removed. Body is #040404, all
 * sections inherit. No boundaries to draw lines at. */
.architecture, .advisors, .use-cases {
  padding: 120px 0;
  position: relative;
}
.architecture__eyebrow, .use-cases__eyebrow, .advisors__eyebrow {
  margin: 0 0 12px;
  font-size: 0.75rem; font-weight: 700; letter-spacing: 0.22em;
  text-transform: uppercase; color: var(--purple);
}
.architecture__title, .use-cases__title, .advisors__title {
  /* v10.291 · display tokens; was clamp(2rem,3.6vw,3rem)/800/-.02em/1.1 */
  margin: 0 0 18px;
  font-size: var(--fs-section);
  font-weight: var(--fw-display);
  letter-spacing: var(--ls-display);
  line-height: var(--lh-display);
  color: #fff;
}
.architecture__sub {
  margin: 0 0 56px;
  font-size: 1.05rem; line-height: 1.55;
  color: rgba(255, 255, 255, 0.62);
  max-width: 640px;
}
.architecture__head, .use-cases__head, .advisors__head { margin-bottom: 12px; }

/* ───────── Architecture: inverted-pyramid stack (v10.145) ─────────
 * Distinct layout signature from Problem (2-col narrative) and
 * Flywheel (horizontal flow). Each layer band's width grows as you
 * scroll down so L1 (Infrastructure) is the widest physical band ·
 * the foundation everything else sits on. Layout itself communicates
 * the hierarchy; no decorative chrome.
 *
 * Width by data-w:
 *   L4 (data-w="4") - 56% width  <- narrowest, sits at top
 *   L3 (data-w="3") - 70% width
 *   L2 (data-w="2") - 84% width
 *   L1 (data-w="1") - 98% width  <- widest, the foundation
 * Bands are centred horizontally, separated by a 14px gap so each
 * one reads as its own slab. */
.architecture__head {
  text-align: center;
  max-width: 720px;
  margin: 0 auto 80px;
}
.architecture__title {
  /* v10.292 · display tokens (override the more-specific old rule). */
  margin: 0 0 16px;
  font-size: var(--fs-section);
  font-weight: var(--fw-display);
  letter-spacing: var(--ls-display);
  line-height: var(--lh-display);
  color: #fff;
}
.architecture__sub {
  margin: 0;
  font-size: 1rem; line-height: 1.6;
  color: rgba(255, 255, 255, 0.58);
}

.architecture__stack {
  list-style: none;
  margin: 0 auto;
  padding: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
}
.arch-layer {
  /* Pyramid widths · set per layer via data-w. */
  width: 100%;
  max-width: 1200px;
  display: grid;
  grid-template-columns: 56px 1fr auto;
  align-items: center;
  gap: 32px;
  padding: 28px 36px;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.045), rgba(255, 255, 255, 0.015));
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 14px;
  transition:
    transform 0.45s cubic-bezier(0.16, 1, 0.3, 1),
    border-color 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}
.arch-layer[data-w="4"] { width: 56%; }
.arch-layer[data-w="3"] { width: 70%; }
.arch-layer[data-w="2"] { width: 84%; }
.arch-layer[data-w="1"] { width: 98%; }
@media (hover: hover) {
  .arch-layer:hover {
    border-color: rgba(157, 92, 255, 0.32);
  }
  .arch-layer:hover .arch-layer__num { color: #fff; }
}

.arch-layer__num {
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: rgba(157, 92, 255, 0.78);
  font-feature-settings: "tnum" 1;
  line-height: 1;
  transition: color 0.3s ease;
}
.arch-layer__body { min-width: 0; }
.arch-layer__name {
  margin: 0 0 6px;
  font-size: 1.15rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.25;
  color: #fff;
}
.arch-layer__desc {
  margin: 0;
  font-size: 0.93rem;
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.62);
}
.arch-layer__chips {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 6px 10px;
  max-width: 320px;
}
.arch-layer__chips span {
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.05em;
  color: rgba(255, 255, 255, 0.55);
  white-space: nowrap;
}
.arch-layer__chips span:not(:last-child)::after {
  content: "·";
  margin-left: 10px;
  color: rgba(255, 255, 255, 0.22);
}

@media (max-width: 1023px) {
  /* Below ~1024 the pyramid widths read as oddly cramped · switch to
   * full-width slabs, but keep the vertical stack visual. */
  .arch-layer[data-w="1"],
  .arch-layer[data-w="2"],
  .arch-layer[data-w="3"],
  .arch-layer[data-w="4"] { width: 100%; }
  .arch-layer {
    grid-template-columns: 48px 1fr;
    padding: 24px 24px;
  }
  .arch-layer__chips {
    grid-column: 1 / -1;
    justify-content: flex-start;
    max-width: 100%;
  }
}
@media (max-width: 768px) {
  .architecture__head { margin-bottom: 56px; }

  /* v10.401 · Mobile architecture: top-1% refinement pass.
   * Reference: Apple iPhone product spec stacks, Stripe Atlas chapter
   * cards, Pharos brandkit.
   * Layered improvements over v10.389:
   *   - Display-weight L# numerals (2.5rem → 3.25rem, tighter tracking)
   *   - Subtle paper-tone gradient on container (#f9f8fb → #f3f2f5)
   *     instead of flat #f7f6f9 · adds depth without chrome
   *   - Capability meta switched to JetBrains Mono · data-sheet feel
   *   - Tighter editorial line-height on name (1.22 → 1.15)
   *   - Mono "Layer 04" caption above L# adds chapter context
   *   - Thin vertical hairline on the left rail visually connects the
   *     4 L# numerals as a stack (Stripe Atlas chapter-rail pattern) */
  /* v10.389 · base pass (see below for current rules). */
  /* v10.389 ORIGINAL · Mobile architecture: Pharos-tone editorial restraint.
   *
   * Reference: pharos.xyz token palette + restraint principles.
   *   - Surface: #f7f6f9 (warm off-white, NOT pure #fff · pure white
   *     reads sterile; warm off-white is the Pharos / Linear / Stripe
   *     Press editorial tone)
   *   - Text: #0b0b0b primary (off-black, warmer than #000), #667085
   *     secondary (Pharos's exact gray · clean, calm, no rgba())
   *   - One brand accent (not 4 cycling colors). Pharos uses #0012b8
   *     deep blue confidently and singularly. We use #6f00ff brand
   *     violet the same way.
   *   - Bigger display L# numerals · Pharos's display ramp goes to
   *     80-120px. Scaling our L# to 2.5rem captures that confident
   *     editorial weight on a tiny mobile card.
   *   - No chip pill chrome · Pharos uses text-only meta lines with
   *     #667085 color. Pills here would re-introduce the chrome we
   *     just removed.
   *   - Generous breathing · 28px vertical padding, 22px horizontal,
   *     real gap between L# and body content.
   *   - One soft shadow (no stack), one internal hairline per seam,
   *     one tap state (compositor color shift only). */

  /* Container: one connected panel, subtle paper-tone gradient.
   * v10.401 · gradient adds depth without chrome; thin inset top
   * highlight + 2-layer shadow for true editorial card lift. */
  .architecture__stack {
    /* v10.476 · mobile was a LIGHT editorial panel · looked out of place
       on the dark homepage. Now Huly-style dark-glass bento: transparent
       container, each layer is its own glass card with a gap between. */
    background: transparent;
    border-radius: 0;
    overflow: visible;
    box-shadow: none;
    display: flex;
    flex-direction: column;
    gap: 14px;
    list-style: none;
    padding: 0;
    position: relative;
  }
  /* v10.409 · v10.401 vertical rail REMOVED. User feedback: the
   * thin chapter-rail running through the L# column read as visual
   * noise · the horizontal hairline between rows already groups
   * the stack, and the big L# numerals are anchors enough. */

  .arch-layer {
    /* v10.476 · dark-glass card (Huly bento) · was a transparent row in a
       light panel. */
    background: linear-gradient(180deg, rgba(255,255,255,.055) 0%, rgba(255,255,255,.018) 100%) !important;
    border: 1px solid rgba(255,255,255,.09) !important;
    border-radius: 18px !important;
    box-shadow: inset 0 1px 0 rgba(255,255,255,.06), 0 20px 44px -26px rgba(0,0,0,.7) !important;
    width: 100% !important;
    grid-template-columns: 66px 1fr !important;
    padding: 26px 22px !important;
    gap: 18px !important;
    position: relative;
    overflow: hidden;
    transition: border-color 0.25s ease, transform 0.35s cubic-bezier(.2,.7,.2,1);
  }
  /* v10.485 · the violet corner glow was too strong (rgba .18) · read as
     "AI-generated"/overcooked. Dialed back to a restrained whisper so the
     card reads as a clean professional surface, not a purple wash. */
  .arch-layer::before {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: radial-gradient(60% 90% at 100% 0%, rgba(111,0,255,.07), transparent 60%);
  }
  .arch-layer__body { position: relative; z-index: 1; }
  .arch-layer + .arch-layer {
    border-top: 1px solid rgba(255,255,255,.09) !important;
  }
  .arch-layer:active {
    border-color: rgba(157,92,255,.42) !important;
  }

  /* L# numeral: display-grade editorial weight.
   * v10.401 · scaled up 2.5 → 3.25rem with tighter tracking.
   * Confident, anchors each row visually. */
  .arch-layer__num {
    font-size: 3rem;
    font-weight: 800;
    letter-spacing: -0.045em;
    line-height: 0.9;
    align-self: start;
    margin-top: 2px;
    /* v10.476 · brand-violet gradient numeral on the dark card (was solid
       purple over a light panel). */
    background: linear-gradient(180deg, #b07fff 0%, #6f00ff 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    position: relative;
    z-index: 1;
    padding: 0;
  }
  .arch-layer:nth-child(1) .arch-layer__num,
  .arch-layer:nth-child(2) .arch-layer__num,
  .arch-layer:nth-child(3) .arch-layer__num,
  .arch-layer:nth-child(4) .arch-layer__num {
    color: transparent;
  }

  /* Body typography · refined editorial ramp.
   * v10.401 · name tightened (1.18 → 1.22rem, line-height 1.22 → 1.15
   * for tighter stacking on multi-line names). */
  .arch-layer__name {
    font-size: 1.22rem;
    font-weight: 700;
    letter-spacing: -0.022em;
    line-height: 1.15;
    color: #fff;
    margin: 0 0 8px;
  }
  .arch-layer__desc {
    font-size: 0.95rem;
    line-height: 1.55;
    color: rgba(255, 255, 255, 0.72);
    margin: 0 0 16px;
  }

  /* Capability meta: monospace data-sheet feel.
   * v10.401 · switched from sans (.82rem) to JetBrains Mono (.72rem
   * uppercase tracked). Reads as spec datasheet caption, not editorial
   * body text. Stripe Atlas spec-row pattern. */
  .arch-layer__chips {
    display: block !important;
    flex-wrap: unset !important;
    gap: 0 !important;
    grid-column: 1 / -1;
    max-width: 100%;
    font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace !important;
    font-size: 0.7rem !important;
    letter-spacing: 0.06em !important;
    text-transform: uppercase !important;
    line-height: 1.55;
    color: rgba(255, 255, 255, 0.5);
  }
  .arch-layer__chips span {
    font-family: inherit !important;
    font-size: inherit !important;
    font-weight: 500 !important;
    letter-spacing: inherit !important;
    text-transform: inherit !important;
    color: inherit;
    background: none !important;
    border: none !important;
    padding: 0 !important;
    border-radius: 0 !important;
    white-space: normal;
  }
  .arch-layer__chips span:not(:last-child)::after {
    content: "·" !important;
    margin: 0 8px !important;
    color: rgba(255, 255, 255, 0.3) !important;
  }
}

/* ───────── Use Cases: 4-card grid · Option F Pharos light cards ─────────
 * v10.407 · matches the flywheel treatment for visual consistency.
 * Same recipe: light surface #f2f4f5, hairline #d7dade, 12px radius,
 * 32x24 padding, icon chip top-left with violet stroke.
 * Use Cases skip the label row (the title IS the category here, unlike
 * the flywheel where the label is a separate step name). */
.use-cases__grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
}
/* v10.422 · .use-case-card styling now shared with .flywheel-step
 * (see purple gradient + starfield rule above near .flywheel-step). */

/* ───────── v10.348 · Homepage Nodes CTA block ─────────
 * Routes to /a-core.html for full tier detail. No pricing repeated here;
 * the homepage teases the structure and hands off to the product page. */
.nodes__cta-block {
  margin: 36px 0 28px;
  padding: 40px 44px;
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 20px;
  background:
    radial-gradient(120% 100% at 0% 0%, rgba(111, 0, 255, 0.10) 0%, rgba(111, 0, 255, 0) 55%),
    linear-gradient(180deg, rgba(255, 255, 255, 0.025), rgba(255, 255, 255, 0.004));
  position: relative;
  overflow: hidden;
}
.nodes__cta-eyebrow {
  margin: 0 0 14px;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.5);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}
.nodes__cta-eyebrow span {
  display: inline-flex;
  align-items: center;
  padding: 4px 10px;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  color: rgba(255, 255, 255, 0.78);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 999px;
}
.nodes__cta-eyebrow span:nth-of-type(2) {
  color: #fff;
  border-color: rgba(157, 92, 255, 0.55);
  background: rgba(111, 0, 255, 0.08);
}
.nodes__cta-line {
  margin: 0 0 26px;
  font-size: clamp(1.05rem, 1.4vw, 1.2rem);
  font-weight: 500;
  letter-spacing: -0.012em;
  color: rgba(255, 255, 255, 0.82);
  max-width: 56ch;
}
.nodes__cta-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
}
.nodes__cta-primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 46px;
  padding: 0 26px;
  background: #6f00ff;
  color: #fff !important;
  border: 1px solid #6f00ff;
  border-radius: 10px;
  font-size: 0.94rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  text-decoration: none;
  box-shadow: 0 4px 14px -2px rgba(111, 0, 255, 0.32);
  transition: background 0.3s, border-color 0.3s, transform 0.3s, box-shadow 0.3s;
}
.nodes__cta-secondary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 46px;
  padding: 0 22px;
  background: transparent;
  color: rgba(255, 255, 255, 0.78) !important;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 10px;
  font-size: 0.94rem;
  font-weight: 500;
  letter-spacing: 0.005em;
  text-decoration: none;
  transition: color 0.3s, border-color 0.3s, background 0.3s;
}
@media (hover: hover) {
  .nodes__cta-primary:hover {
    background: #8b3aff;
    border-color: #8b3aff;
    transform: translateY(-1px);
    box-shadow: 0 6px 18px -2px rgba(111, 0, 255, 0.48);
  }
  .nodes__cta-secondary:hover {
    color: #fff !important;
    border-color: rgba(157, 92, 255, 0.55);
    background: rgba(111, 0, 255, 0.06);
  }
}
.nodes__cta-primary:active { background: #5a00d6; transform: translateY(0); }
@media (max-width: 720px) {
  /* v10.377 · mobile: buttons stack vertically full-width single-line
   * instead of sharing a row 50/50 (which made "View Node Tiers" wrap
   * to 2 lines and looked clumsy). Tightened padding + line-height too. */
  .nodes__cta-block { padding: 24px 20px; }
  .nodes__cta-eyebrow { gap: 8px; font-size: 0.68rem; letter-spacing: 0.18em; }
  .nodes__cta-eyebrow span { padding: 3px 8px; font-size: 0.58rem; }
  .nodes__cta-line { font-size: 0.98rem; line-height: 1.45; margin-bottom: 22px; }
  .nodes__cta-actions {
    width: 100%;
    flex-direction: column;
    gap: 10px;
  }
  .nodes__cta-primary,
  .nodes__cta-secondary {
    flex: none;
    width: 100%;
    min-height: 48px;
    white-space: nowrap;
    line-height: 1;
  }
}

/* ───────── Nodes: revenue chips + benefit row ───────── */
.nodes__benefits {
  margin: 18px 0 0;
  display: flex; flex-wrap: wrap; gap: 6px 14px;
  font-size: 0.85rem; color: rgba(255, 255, 255, 0.55);
}
.nodes__benefits span { position: relative; }
.nodes__benefits span:not(:last-child)::after {
  content: "·"; margin-left: 14px; color: rgba(255, 255, 255, 0.3);
}
.nodes__revenue {
  margin: 8px 0 28px;
  padding-top: 24px;
  border-top: 1px solid rgba(255, 255, 255, 0.06);
}
.nodes__revenue-label {
  margin: 0 0 12px;
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.2em;
  text-transform: uppercase; color: rgba(255, 255, 255, 0.42);
}
.nodes__revenue-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.nodes__revenue-chips span {
  font-size: 0.82rem; font-weight: 500; color: rgba(255, 255, 255, 0.82);
  padding: 7px 16px;
  border: 1px solid rgba(157, 92, 255, 0.22);
  border-radius: 999px;
  background: linear-gradient(180deg, rgba(157, 92, 255, 0.1), rgba(157, 92, 255, 0.04));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
  transition: color 0.2s, background 0.2s, border-color 0.2s, transform 0.2s;
}
@media (hover: hover) {
  .nodes__revenue-chips span:hover {
    color: #fff;
    background: linear-gradient(180deg, rgba(111, 0, 255, 0.28), rgba(111, 0, 255, 0.16));
    border-color: rgba(157, 92, 255, 0.6);
    transform: translateY(-1px);
  }
}
/* v10.505 · PC: airier, more deliberate chips (desktop-only per request;
 * mobile layout was fine). Even gap + larger hit area read as premium. */
@media (min-width: 1025px) {
  .nodes__revenue-chips { gap: 10px; }
  .nodes__revenue-chips span { font-size: 0.85rem; padding: 8px 18px; }
}

/* ───────── Advisors (v10.153 + v10.154 bg): editorial photo grid.
 * Avatar size LOCKED at 96px per user. */

/* v10.156 · Advisors BG stripped to flat. User: 'every page bg look
 * lik ai'. Removed the CO watermark + every other radial-ambient
 * attempt. Section now reads as a quiet container; the photos and
 * typography carry it. */

.advisors__head {
  text-align: center;
  max-width: 720px;
  margin: 0 auto;
}
.advisors__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 48px 32px;
  margin-top: 72px;
  max-width: 960px;
  margin-left: auto;
  margin-right: auto;
}
.advisor-card {
  text-align: center;
  /* No background, border, or shadow. The photo + typography carry it. */
}
.advisor-card__avatar {
  width: 96px; height: 96px;
  margin: 0 auto 20px;
  border-radius: 50%;
  overflow: hidden;
  /* Subtle 1px white-alpha rim · just enough to define the edge against
   * the dark page, no gradient ring, no glow. */
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08);
}
.advisor-card__avatar img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}
.advisor-card__name {
  margin: 0 0 6px;
  font-size: 1.15rem; font-weight: 600; color: #fff;
  letter-spacing: -0.01em;
}
.advisor-card__role {
  margin: 0;
  font-size: 0.92rem; line-height: 1.5;
  color: rgba(255, 255, 255, 0.55);
  max-width: 22ch;
  margin-left: auto;
  margin-right: auto;
}

/* ───────── Responsive ───────── */
@media (max-width: 1024px) {
  .architecture, .use-cases, .advisors { padding: 88px 0; }
  .use-cases__grid { grid-template-columns: repeat(2, 1fr); }
  .advisors__grid { grid-template-columns: 1fr; max-width: 480px; margin-left: auto; margin-right: auto; }
  .arch-layer { grid-template-columns: 72px 1fr; gap: 20px; padding: 22px 24px; }
  .arch-layer__num { font-size: 2.6rem; }
}
@media (max-width: 768px) {
  .architecture, .use-cases, .advisors { padding: 72px 0; }
  .arch-layer { grid-template-columns: 56px 1fr; gap: 16px; padding: 20px; }
  .arch-layer__num { font-size: 2.1rem; }
  .arch-layer__name { font-size: 1.15rem; }
  .architecture__title, .use-cases__title, .advisors__title { font-size: 1.85rem; }

  /* v10.400 · Mobile Use Cases: 4 generic icon+title+body cards →
   * editorial text list (Linear / Vercel / Stripe / Anthropic pattern).
   * Icons stripped (anti-pattern #11 · decorative chrome on small
   * mobile cards adds no signal). Card chrome stripped. Each vertical
   * becomes a hairline-separated editorial row inside one connected
   * panel · same restraint principles as v10.389 architecture stack. */
  /* v10.407 · Use Cases mobile · same Option F light cards stack as 1col.
   * Removed the v10.400 "editorial row inside one panel" override · the
   * new light card recipe reads cleanly at every viewport, so the dark
   * panel override is no longer needed. */
  .use-cases__grid {
    grid-template-columns: 1fr;
    gap: 12px;
  }
  .use-case-card {
    padding: 22px 18px;
  }
  /* v10.431 · .use-case-card__icon rules removed · icon HTML deleted
   * with the revert to text-only treatment. */
  .use-case-card__title { font-size: 1.05rem; }
}

/* ──────────────────────────────────────────────────────────────────────
 * v10.211 · Solution page (Stripe Press / Anthropic editorial style).
 *
 * v10.209 used a 3-card numbered grid + breadcrumb chevrons + a "Ready
 * to join..." CTA header. User feedback: "looks AI generated." All
 * three were textbook AI/SaaS template patterns called out in
 * anti-patterns #2 (editorial section numbers) and #11 (AI-template
 * decorations on premium sections).
 *
 * Rewrite uses typography as the only signal:
 *   .sol-hero     · generous top space + giant display title + lede
 *   .sol-thesis   · 3 editorial rows, 2-col layout, hairline dividers
 *                   only · NO cards, NO numbers, NO surfaces
 *   .sol-vision   · full-bleed pull quote with giant decorative "
 *                   glyph behind it (Anthropic / NYT magazine pattern)
 *   .sol-foot     · single CTA + quiet "Back home" link, no header
 *
 * Old .page-hero / .solution-pillars / .page-cta / .btn-ghost rules
 * intentionally removed · nothing on the page references them anymore,
 * and leaving them creates dead CSS that confuses future edits.
 * ────────────────────────────────────────────────────────────────────── */

/* ── Hero ───────────────────────────────────────────────────────── */
.sol-hero {
  padding: 200px 0 80px;          /* top clears the floating nav */
  /* v10.469 · was solid #040404 · made transparent so the body.inner-page
     brand-ambient glow reads through the hero (solution / problem pages). */
  background: transparent;
  position: relative;
  overflow: hidden;               /* contain the v10.510 aurora */
}
/* v10.510 · richer hero · ONE soft brand-violet aurora gives the hero depth
 * and a "lit" premium feel instead of flat black. Pure CSS (zero perf cost),
 * single light source (anti-pattern #8), sits behind the content (z-index 0). */
.sol-hero::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(58% 52% at 50% 8%, rgba(157, 92, 255, 0.22), transparent 70%),
    radial-gradient(42% 38% at 15% 2%, rgba(111, 0, 255, 0.14), transparent 60%);
  pointer-events: none;
  z-index: 0;
}
.sol-hero > .container { position: relative; z-index: 1; }
/* v10.343 · eyebrow now carries a brand-violet leader line on the left,
 * matching the .cta-strip eyebrow pattern from index.html. Visually
 * anchors the eyebrow as a "stamp" rather than free-floating caption. */
.sol-hero__eyebrow {
  margin: 0 0 28px;
  font-size: 0.74rem;
  letter-spacing: 0.32em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--purple-2);
  display: inline-flex;
  align-items: center;
  gap: 14px;
}
.sol-hero__eyebrow::before {
  content: "";
  width: 36px;
  height: 1px;
  background: linear-gradient(90deg, rgba(157, 92, 255, 0.0), rgba(157, 92, 255, 0.9));
  display: inline-block;
}
.sol-hero__title {
  margin: 0 0 36px;
  /* Magazine-display sizing · bigger than any other H1 on the site.
   * The hero's job is to land a single sentence with weight. */
  font-size: clamp(2.6rem, 6.5vw, 5.4rem);
  font-weight: 600;
  letter-spacing: -0.035em;
  line-height: 1.02;
  color: #fff;
  max-width: 17ch;
}
.sol-hero__lede {
  margin: 0;
  font-size: clamp(1.08rem, 1.5vw, 1.3rem);
  line-height: 1.55;
  font-weight: 400;
  color: rgba(255, 255, 255, 0.62);
  max-width: 56ch;
}

/* ── Thesis (3 editorial rows) ──────────────────────────────────── */
.sol-thesis {
  padding: 100px 0 60px;
  background: #040404;
}
.sol-thesis > .container { max-width: 1120px; }
.sol-thesis__row {
  /* Stripe Press 2-col: name left, body right. Aligned to baseline so
   * the row reads as a single typographic statement. */
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.25fr);
  gap: 80px;
  align-items: baseline;
  padding: 76px 24px 76px 28px;
  margin: 0 -28px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  transition: background-color 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}
.sol-thesis__row:last-child {
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
/* v10.343 · brand-violet left-edge accent that fades in on hover.
 * Single signal: just the bar lights up + a very soft surface tint.
 * No transform / lift · keeps the editorial-row rhythm intact. */
.sol-thesis__row::before {
  content: "";
  position: absolute;
  left: 0;
  top: 22%;
  bottom: 22%;
  width: 2px;
  background: linear-gradient(180deg, transparent, rgba(157, 92, 255, 0.85), transparent);
  opacity: 0;
  transition: opacity 0.45s cubic-bezier(0.16, 1, 0.3, 1);
  pointer-events: none;
}
@media (hover: hover) {
  .sol-thesis__row:hover { background-color: rgba(157, 92, 255, 0.035); }
  .sol-thesis__row:hover::before { opacity: 1; }
}
.sol-thesis__name {
  margin: 0;
  font-size: clamp(1.7rem, 3.2vw, 2.6rem);
  font-weight: 600;
  letter-spacing: -0.028em;
  line-height: 1.08;
  color: #fff;
}
.sol-thesis__body {
  margin: 0;
  font-size: 1.08rem;
  line-height: 1.62;
  color: rgba(255, 255, 255, 0.72);
  max-width: 52ch;
  font-weight: 400;
}

/* ── Vision pull quote (deck p.9) ───────────────────────────────── */
.sol-vision {
  padding: 160px 0 140px;
  background: #040404;
  position: relative;
  overflow: hidden;
}
.sol-vision::before {
  /* Giant decorative " glyph behind the quote · NYT / Anthropic
   * pull-quote pattern. One signal, oversized, very low alpha so it
   * doesn't compete with the text. */
  content: "\201C";
  position: absolute;
  top: 30px;
  left: 50%;
  transform: translateX(-50%);
  font-family: Georgia, "Times New Roman", serif;
  font-size: clamp(18rem, 28vw, 28rem);
  line-height: 0.78;
  color: rgba(157, 92, 255, 0.07);
  pointer-events: none;
  z-index: 0;
  font-style: italic;
  font-weight: 400;
}
.sol-vision > .container { position: relative; z-index: 1; }
.sol-vision__quote {
  margin: 0 auto;
  max-width: 28ch;
  font-size: clamp(1.8rem, 3.4vw, 3rem);
  font-weight: 400;
  line-height: 1.18;
  letter-spacing: -0.026em;
  text-align: center;
  color: rgba(255, 255, 255, 0.96);
}
/* v10.343 · optional brand-violet emphasis on a key word inside the
 * pull quote (use <em>…</em> in the markup). */
.sol-vision__quote em {
  font-style: normal;
  color: var(--purple-2);
  font-weight: 500;
}

/* ── Closing strip · single CTA + quiet home link.
 *    v10.212: switched from inline-block overrides (which broke
 *    .btn-launch's native inline-flex vertical centering, leaving
 *    the button rendering ~70 px tall with text pinned to the top)
 *    to a proper flex container. The button keeps its default
 *    inline-flex + min-height: 44px, and the home link sits beside
 *    it baseline-aligned. ───────────────────────────────────────── */
.sol-foot {
  padding: 60px 0 140px;
  background: #040404;
}
.sol-foot > .container {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 28px;
  flex-wrap: wrap;
}
.sol-foot__home {
  font-size: 0.92rem;
  letter-spacing: 0.02em;
  color: rgba(255, 255, 255, 0.5);
  transition: color 0.3s ease;
}
.sol-foot__home:hover { color: rgba(255, 255, 255, 0.92); }

/* ── Responsive (mobile) ─────────────────────────────────────────── */
@media (max-width: 900px) {
  .sol-hero { padding: 150px 0 50px; }
  .sol-hero__title { max-width: 100%; }
  .sol-thesis { padding: 60px 0 30px; }
  .sol-thesis__row {
    grid-template-columns: 1fr;
    gap: 18px;
    padding: 56px 0;
  }
  .sol-vision { padding: 110px 0 100px; }
  .sol-foot { padding: 50px 0 110px; }
  .sol-foot > .container {
    /* Stack vertically on mobile · button above, home link below. */
    flex-direction: column;
    gap: 18px;
  }
}

/* v10.396 · Problem/Solution pages: top-1% mobile refinements.
 *
 * Below 768px specifically · tighter typography, generous tap targets,
 * remove decorative chrome that doesn't translate at small viewport,
 * Pharos-tone restraint (no ornamental dashes, no hover-only states
 * since hover doesn't exist on touch, single signal per state).
 *
 * Apply to BOTH /problem.html and /solution.html (they share the
 * .sol-* class namespace per the v10.342 builder decision). */
@media (max-width: 768px) {
  .sol-hero {
    padding: 124px 0 28px;
  }
  /* v10.396 · decorative gradient leader line removed on mobile
   * (anti-pattern #11 · too small to read, just visual noise). */
  .sol-hero__eyebrow {
    gap: 0;
    margin-bottom: 22px;
    font-size: 0.7rem;
    letter-spacing: 0.26em;
  }
  .sol-hero__eyebrow::before { display: none; }
  .sol-hero__title {
    /* Tighter on mobile · clamp ceiling at 2.6rem so it doesn't
     * overflow narrow viewports, line-height bumped to 1.06 for
     * cleaner stacking. */
    font-size: clamp(2.1rem, 8vw, 2.6rem);
    line-height: 1.06;
    letter-spacing: -0.028em;
    margin-bottom: 28px;
  }
  .sol-hero__lede {
    font-size: 1.02rem;
    line-height: 1.55;
    color: rgba(255, 255, 255, 0.66);
  }

  /* Thesis rows: full-width with hairline divider rhythm */
  .sol-thesis { padding: 24px 0 20px; }
  .sol-thesis__row {
    /* v10.511 · On problem/solution these rows are CARDS (styled in the page's
     * inline <style>). The old editorial-grid override here was `padding:36px 0`
     * (NO horizontal padding) which jammed the icon tile + text flush against
     * the card's left edge · looked broken. Restore card-correct inner padding,
     * card-to-card spacing, and a comfortable icon/text gap. */
    padding: 24px 20px !important;
    margin: 0 0 16px !important;
    gap: 16px !important;
  }
  .sol-thesis__row::before { display: none !important; }
  .sol-thesis__name {
    font-size: clamp(1.4rem, 6vw, 1.85rem);
    letter-spacing: -0.022em;
    line-height: 1.1;
  }
  .sol-thesis__body {
    font-size: 0.98rem;
    line-height: 1.6;
    color: rgba(255, 255, 255, 0.7);
    max-width: 100%;
  }

  /* Vision pull quote: scale down giant decorative " glyph; tighten
   * quote typography for narrow viewport. */
  .sol-vision {
    padding: 80px 0 70px;
  }
  .sol-vision::before {
    /* Decorative quote-mark scaled way down · was up to 28rem which
     * blew out the mobile viewport horizontally. */
    font-size: clamp(8rem, 32vw, 12rem);
    top: 12px;
  }
  .sol-vision__quote {
    font-size: clamp(1.45rem, 6vw, 1.85rem);
    max-width: 22ch;
    line-height: 1.2;
    letter-spacing: -0.02em;
  }

  /* Closing foot: button full-width, home link below */
  .sol-foot {
    padding: 30px 0 80px;
  }
  .sol-foot > .container {
    flex-direction: column;
    gap: 16px;
    align-items: stretch;
  }
  .sol-foot .btn-launch {
    width: 100%;
    justify-content: center;
    min-height: 52px;
  }
  .sol-foot__home {
    text-align: center;
    font-size: 0.88rem;
  }
}

/* End of v10.211 Solution page CSS. */

/* ──────────────────────────────────────────────────────────────────────
 * v10.213 · DarkVeil generative-art background (Solution page only).
 *
 * <canvas id="dv-bg" class="dv-bg"> is the FIRST element in <body> on
 * /solution.html. It's position:fixed and fills the viewport. By making
 * the solution-page sections transparent (they previously painted
 * #040404), the veil shows through behind the typography.
 *
 * Body keeps #040404 as a fallback color in case the GL context fails
 * to initialise or the user's browser doesn't support WebGL · the page
 * still reads correctly on the existing dark surface.
 *
 * The veil DOES NOT appear on index.html. Index keeps its own hero
 * video + section treatments untouched.
 * ────────────────────────────────────────────────────────────────────── */
.dv-bg {
  /* v10.215 · applied as the React Bits spec describes. The component
   * CSS in the spec is just `width: 100%; height: 100%; display: block;`.
   * We add position: fixed + inset: 0 to fill the viewport behind page
   * content. Desktop: no filter, no vignette overlay · the veil renders
   * raw as the spec describes.
   *
   * v10.226: dropped `width: 100vw / height: 100vh`. On Windows + macOS
   * `100vw` includes the 12px scrollbar gutter, so the canvas was
   * painting OVER the custom violet-gradient scrollbar (v10.67),
   * making it invisible on /solution.html. `inset: 0` alone defines
   * width/height from the four inset values and stops at the right
   * edge of the visible viewport (excluding the scrollbar gutter). */
  position: fixed;
  inset: 0;
  display: block;
  pointer-events: none;
  z-index: 0;
}

/* v10.224 · mobile-only veil dim. On desktop the bright violet blobs
 * spread across a wide canvas so each section sees only a slice. On
 * mobile the canvas is the same SHADER output but covers a smaller
 * viewport, so a single bright peak can fill the whole hero behind
 * the text · killing contrast. Filter + a soft top-vignette darkens
 * the area behind the typography while leaving the veil itself
 * visible. Desktop view is unchanged. */
@media (max-width: 900px) {
  .dv-bg {
    filter: brightness(0.55) contrast(1.15) saturate(1.05);
  }
  /* Darken behind the page text via the section background · keeps
   * the violet ribbons visible at edges, lifts text contrast. */
  body:has(.dv-bg) .sol-hero,
  body:has(.dv-bg) .sol-thesis,
  body:has(.dv-bg) .sol-vision,
  body:has(.dv-bg) .sol-foot {
    background: rgba(4, 4, 4, 0.55);
  }
}

/* Make the Solution page section backgrounds transparent so the veil
 * shows through. Each was set to #040404 in v10.211. */
.sol-hero,
.sol-thesis,
.sol-vision,
.sol-foot {
  background: transparent;
}

/* The footer on the Solution page sits over the veil too · give it a
 * subtle solid backing so the partner/links columns stay readable. */
body:has(.dv-bg) .site-footer {
  background: linear-gradient(180deg, rgba(4, 4, 4, 0) 0%, rgba(4, 4, 4, 0.85) 30%, #060606 100%);
  position: relative;
  z-index: 1;
}

/* All sections sit above the canvas via DOM order, but force a
 * stacking context just in case (some browsers don't promote
 * `position: fixed` siblings predictably). */
body:has(.dv-bg) > main { position: relative; z-index: 1; }
body:has(.dv-bg) > nav.site-nav { z-index: 1000; /* already at 1000 · restated for clarity */ }

/* ─────────────────────────────────────────────────────────────────────────
 * v10.233 · i18n language switcher + beta banner
 * ─────────────────────────────────────────────────────────────────────────
 * The switcher lives inside .site-nav__right between socials and the
 * Launch APP CTA on desktop. Uses native <details>/<summary> so the
 * open/close state is browser-handled (zero JS dependency). On mobile,
 * a horizontal row of compact locale links sits above the drawer
 * socials. Visual treatment mirrors the existing pill-nav language ·
 * white-glass background, hairline border, violet accent on the active
 * locale.
 */

/* Hide the default <summary> marker (the disclosure triangle) · we
 * provide our own chevron span to keep visual control. */
.site-nav__lang > summary { list-style: none; cursor: pointer; }
.site-nav__lang > summary::-webkit-details-marker { display: none; }

.site-nav__lang {
  position: relative;
  display: inline-flex;
  align-items: center;
}
.site-nav__lang-trigger {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  height: 32px;
  border-radius: 10px;
  font: 600 12px/1 var(--font, system-ui);
  letter-spacing: 0.04em;
  color: #0a0a0a;
  background: rgba(255, 255, 255, 0.5);
  border: 1px solid rgba(0, 0, 0, 0.08);
  transition: background 0.2s, border-color 0.2s, transform 0.2s;
}
.site-nav__lang-trigger:hover {
  background: rgba(255, 255, 255, 0.85);
  border-color: rgba(0, 0, 0, 0.18);
}
.site-nav__lang-trigger svg { opacity: 0.7; }
.site-nav__lang-current { font-weight: 700; }
.site-nav__lang-chev { font-size: 10px; opacity: 0.6; transition: transform 0.2s; }
.site-nav__lang[open] .site-nav__lang-chev { transform: rotate(180deg); }

.site-nav__lang-menu {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  min-width: 160px;
  margin: 0;
  padding: 6px;
  list-style: none;
  background: rgba(255, 255, 255, 0.96);
  border: 1px solid rgba(0, 0, 0, 0.08);
  border-radius: 12px;
  backdrop-filter: blur(20px) saturate(120%);
  -webkit-backdrop-filter: blur(20px) saturate(120%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.6),
    0 10px 28px rgba(0, 0, 0, 0.18),
    0 2px 6px rgba(0, 0, 0, 0.08);
  z-index: 1001;
  transform-origin: top right;
}
@keyframes site-nav-lang-in {
  from {
    opacity: 0;
    transform: translateY(-6px) scale(0.97);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}
/* v10.311 · gate the entrance animation on [open] so it actually fires
 * when the user opens the menu (not silently against a display:none
 * element at page load, which was the v10.310 bug). */
.site-nav__lang[open] .site-nav__lang-menu {
  animation: site-nav-lang-in 0.22s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.site-nav__lang[open] .site-nav__lang-menu li {
  animation: site-nav-lang-item-in 0.32s cubic-bezier(0.22, 1, 0.36, 1) both;
  animation-delay: calc(60ms + var(--li-i, 0) * 35ms);
}
.site-nav__lang-menu li:nth-child(1) { --li-i: 0; }
.site-nav__lang-menu li:nth-child(2) { --li-i: 1; }
.site-nav__lang-menu li:nth-child(3) { --li-i: 2; }
.site-nav__lang-menu li:nth-child(4) { --li-i: 3; }
@keyframes site-nav-lang-item-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .site-nav__lang-menu,
  .site-nav__lang-menu li {
    animation: none;
  }
}
.site-nav__lang-menu li { margin: 0; }
.site-nav__lang-menu a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 12px;
  border-radius: 8px;
  font: 500 14px/1.2 var(--font, system-ui);
  color: #1a1a1a;
  transition: background 0.15s, color 0.15s;
}
.site-nav__lang-menu a:hover {
  background: rgba(111, 0, 255, 0.08);
  color: #6f00ff;
}
.site-nav__lang-menu a.is-current {
  background: rgba(111, 0, 255, 0.12);
  color: #6f00ff;
  font-weight: 700;
}
.site-nav__lang-menu a.is-current::after {
  content: "✓";
  font-size: 12px;
  opacity: 0.8;
}

/* When nav is in dark / scrolled / solid state, invert the switcher
 * surface to match (parallel to .site-nav--solid pattern). */
.site-nav--solid .site-nav__lang-trigger,
.site-nav--scrolled .site-nav__lang-trigger {
  color: #fff;
  background: rgba(255, 255, 255, 0.08);
  border-color: rgba(255, 255, 255, 0.14);
}
.site-nav--solid .site-nav__lang-trigger:hover,
.site-nav--scrolled .site-nav__lang-trigger:hover {
  background: rgba(255, 255, 255, 0.16);
  border-color: rgba(255, 255, 255, 0.28);
}

/* Hide the lang switcher on small screens · the mobile drawer carries
 * its own locale row, no need to crowd the top bar. */
@media (max-width: 900px) {
  .site-nav__lang { display: none; }
}

/* v10.397 · drawer lang row: chip-pills → inline dot-separated text.
 * Matches the new drawer text-link aesthetic. Active locale: white
 * + brand-violet accent dot, no pill bg. */
.site-nav__drawer-langs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0;
  list-style: none;
  margin: 0 0 24px;
  padding: 0;
  font-size: 14px;
  letter-spacing: 0.01em;
}
.site-nav__drawer-langs li {
  margin: 0;
  display: inline-flex;
  align-items: center;
}
.site-nav__drawer-langs li:not(:last-child)::after {
  content: "·";
  margin: 0 12px;
  color: rgba(255, 255, 255, 0.28);
}
.site-nav__drawer-langs a {
  display: inline-flex;
  align-items: center;
  /* v10.458 · WCAG 2.5.5 tap target · was 6px 0 (~26px tall, 0 horizontal).
     Now min 44px tall with real horizontal padding so the locale links
     are comfortably tappable on phones. */
  min-height: 44px;
  padding: 11px 6px;
  font-weight: 500;
  color: rgba(255, 255, 255, 0.6);
  background: transparent;
  border: none;
  border-radius: 0;
  transition: color 0.2s;
}
.site-nav__drawer-langs a:hover {
  color: #fff;
}
.site-nav__drawer-langs a.is-current {
  color: #fff;
  font-weight: 600;
}
.site-nav__drawer-langs a.is-current::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #6f00ff;
  margin-right: 8px;
  flex-shrink: 0;
}

/* Translation-beta banner · pinned to top of viewport on non-EN pages.
 * Build script injects the markup; English source omits it entirely.
 * Banner sits ABOVE the nav (nav z-index is 1000, banner z-index 1001
 * would cover the nav · instead we push the nav down via padding-top
 * on body when banner is present). */
.i18n-beta-banner {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1002;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 8px 16px;
  font: 500 13px/1.4 var(--font, system-ui);
  color: #fff;
  background: linear-gradient(90deg, #6f00ff 0%, #9d5cff 50%, #e040fb 100%);
  border-bottom: 1px solid rgba(255, 255, 255, 0.16);
}
.i18n-beta-banner a {
  color: #fff;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.i18n-beta-banner button {
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.5);
  color: #fff;
  padding: 4px 10px;
  border-radius: 999px;
  font: inherit;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}
.i18n-beta-banner button:hover {
  background: rgba(255, 255, 255, 0.16);
  border-color: rgba(255, 255, 255, 0.85);
}
body.has-i18n-banner .site-nav {
  /* Push the nav down so it doesn't overlap the banner. The banner is
   * ~38 px tall (one line of 13/1.4 text + 8 px padding × 2).
   * v10.526 · !important is REQUIRED: the mobile app-bar rule
   * (@media max-width:767px) sets `.site-nav{top:0 !important}`, which
   * otherwise wins over this non-important rule and pins the nav under
   * the banner on every zh/ja/ko mobile page (logo + burger covered). */
  top: calc(38px + max(16px, env(safe-area-inset-top))) !important;
}
@media (max-width: 600px) {
  .i18n-beta-banner { font-size: 12px; padding: 6px 12px; gap: 8px; }
  body.has-i18n-banner .site-nav { top: calc(46px + max(8px, env(safe-area-inset-top))) !important; }
}

/* v10.248 · Consent banner CSS moved to css/consent.css (also linked
 * from the standalone legal pages which do not load site.css). */

/* v10.259 · newsletter thank-you message (Beehiiv form wired)
 * v10.326 · recolored: was #fff on light-purple bg = unreadable; now
 * deep brand violet on a slightly stronger tinted bg for proper contrast
 * on the light Subscribe section. */
/* v10.453 · classic success state · title + body + quiet link.
 * Stripped of icons, telemetry language, progress bars, signal
 * indicators. Pure typographic hierarchy in the same width as the
 * form so the transition feels like the form replaced itself with
 * a confirmation message. Nothing to overcook. */
.subscribe__thanks {
  margin-top: 18px;
  width: 100%;
  max-width: 540px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  text-align: center;
  animation: subThanksIn 0.35s ease-out both;
  /* v10.454 · exit transition · opacity + tiny lift up. Triggered by JS
   * adding .subscribe__thanks--leaving before unmounting. Duration matches
   * the 280ms setTimeout in dismissThanks() so transitionend isn't needed. */
  transition: opacity 0.28s ease-out, transform 0.28s ease-out;
}
.subscribe__thanks--leaving {
  opacity: 0;
  transform: translateY(-4px);
  pointer-events: none;
}
@keyframes subThanksIn {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}
.subscribe__thanks[hidden] { display: none !important; }

.subscribe__thanks-title {
  margin: 0;
  font-size: 1.15rem;
  font-weight: 600;
  line-height: 1.3;
  color: #0f0523;
}
.subscribe__thanks-body {
  margin: 0;
  font-size: 0.95rem;
  line-height: 1.5;
  color: rgba(15, 5, 35, 0.6);
}
.subscribe__thanks-action {
  margin-top: 6px;
  padding: 4px 2px;
  background: transparent;
  border: none;
  color: #6f00ff;
  font-size: 0.88rem;
  font-weight: 500;
  font-family: inherit;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: rgba(111, 0, 255, 0.4);
  transition: color 0.15s ease, text-decoration-color 0.15s ease;
}
.subscribe__thanks-action:hover,
.subscribe__thanks-action:focus-visible {
  color: #4a00b3;
  text-decoration-color: #4a00b3;
}
.subscribe__thanks-action:focus-visible {
  outline: 2px solid rgba(111, 0, 255, 0.4);
  outline-offset: 3px;
  border-radius: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .subscribe__thanks { animation: none !important; }
}

/* ── Off-screen performance: drop will-change + transitions when not visible ──
 * initInViewSections() in app.js toggles .in-view on each major section.
 * When absent (off-screen), compositor layers and transition timers are freed. */
.gateway:not(.in-view) .gateway-card,
.gateway:not(.in-view) .gateway-card__media,
.gateway:not(.in-view) .gateway-core__logo-img,
.gateway:not(.in-view) .gateway-core__text,
.abox:not(.in-view) .abox__copy,
.roadmap:not(.in-view) .roadmap__line-fill,
.partner-marquee:not(.in-view) .partner-marquee__track {
  will-change: auto;
}

/* v10.444-pending · gateway-core ambient pseudo gets will-change:transform,
 * opacity while in view (needed because it animates). When the Gateway
 * section scrolls off-screen, release the GPU layer · the parent rule
 * above can't target ::before directly so we duplicate the off-screen
 * release here. */
.gateway:not(.in-view) .gateway-core::before {
  will-change: auto;
}

.gateway:not(.in-view) .gateway-card,
.gateway:not(.in-view) .gateway-card__media,
.gateway:not(.in-view) .gateway-card__icon svg,
.gateway:not(.in-view) .gateway-core,
.gateway:not(.in-view) .gateway-core__text,
.abox:not(.in-view) .abox__copy,
.story:not(.in-view) .story__word,
.roadmap:not(.in-view) .roadmap__line-fill,
.roadmap:not(.in-view) .roadmap__head {
  transition: none !important;
}

/* content-visibility: auto · desktop-only. On mobile fast-scroll it
 * caused lag (v10.262 regression). Scope to ≥900px viewports.
 * v10.417 · extended to .use-cases, .advisors, .roadmap · three
 * mid-page sections that are well-isolated (no cross-section shared
 * sticky-scroll context) and below the fold on first paint. Skips
 * rendering + style cost for these blocks until they enter the
 * viewport. .partner-marquee, .abox, .nodes, .gateway, .flywheel,
 * .story all DELIBERATELY excluded · they use sticky-scroll or
 * compositor animations that break under content-visibility:auto
 * (see anti-pattern lessons in v10.262 + the .nodes__visual comment
 * around line 5241). */
@media (min-width: 900px) {
  .use-cases   { content-visibility: auto; contain-intrinsic-size: 0 640px; }
  .advisors    { content-visibility: auto; contain-intrinsic-size: 0 720px; }
  .tokenomics  { content-visibility: auto; contain-intrinsic-size: 0 900px; }
  .roadmap     { content-visibility: auto; contain-intrinsic-size: 0 1100px; }
  .news        { content-visibility: auto; contain-intrinsic-size: 0 700px; }
  .subscribe   { content-visibility: auto; contain-intrinsic-size: 0 360px; }
  .site-footer { content-visibility: auto; contain-intrinsic-size: 0 480px; }
}

/* iPad / coarse-pointer tablet · kill backdrop-filter on heavy surfaces.
 * Tablets share mobile's GPU constraints but exceed the 767px mobile cap,
 * so the v10.x mobile kill rule missed them. Gated on pointer:coarse so
 * desktop touchscreen laptops (which CAN handle blur) keep the effect. */
@media (max-width: 1099px) and (pointer: coarse) {
  .site-nav,
  .site-nav__sub,
  .subscribe__form,
  .site-footer__lang {
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
  }
}

/* v10.380 · CJK line-break rules. Korean / Chinese / Japanese long
 * compound words wrap mid-word by default on narrow viewports. word-break:
 * keep-all preserves word boundaries (especially in Korean · eojeol).
 * overflow-wrap: anywhere allows wrap inside the rare URL/super-long word
 * so we don't get horizontal overflow on mobile. Applied per :lang() so
 * Latin scripts keep default break behavior. */
:root[lang="zh"] body,
:root[lang="zh-CN"] body,
:root[lang="ja"] body,
:root[lang="ja-JP"] body,
:root[lang="ko"] body,
:root[lang="ko-KR"] body {
  word-break: keep-all;
  overflow-wrap: anywhere;
  line-break: strict;
}

/* v10.447 · perf-lean tier (mid + low hardware).
 * Set by app.js getPerfTier() when navigator.deviceMemory <= 4GB,
 * hardwareConcurrency <= 4 cores, retina-DPR on touch, saveData,
 * slow effectiveType, or explicit ASTARTER_CONFIG.forcePerfTier.
 *
 * Strategy: keep the visual identity, drop GPU-expensive effects.
 *   - kill backdrop-filter blur sitewide (compositor re-blends every paint)
 *   - kill animated filter: blur() (re-rasterise per frame)
 *   - pause infinite background animations that aren't gated by .in-view
 *   - shorten transition durations (less time in animating state)
 *   - cap hero Ken Burns to a static frame
 *
 * DarkVeil fullscreen shader is bailed out in darkveil.js (same class).
 * Three.js DPR forced to 1.0 in app.js (same class). */
html.astarter-perf-lean .site-nav,
html.astarter-perf-lean .site-nav__sub,
html.astarter-perf-lean .subscribe__form,
html.astarter-perf-lean .site-footer__lang,
html.astarter-perf-lean .mobile-drawer,
html.astarter-perf-lean .mobile-drawer__panel {
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
}
html.astarter-perf-lean .gateway-core::before,
html.astarter-perf-lean .gateway-core::after {
  filter: none !important;
  animation: none !important;
}
html.astarter-perf-lean .hero {
  animation: none !important;
}
html.astarter-perf-lean .partner-marquee__track--left,
html.astarter-perf-lean .partner-marquee__track--right,
html.astarter-perf-lean .partner-marquee__track--left-slow {
  /* slower marquee · fewer transform ticks per second */
  animation-duration: 120s !important;
}
html.astarter-perf-lean body::before {
  display: none !important; /* full-viewport film grain · expensive on weak GPU */
}

/* v10.468 · The service-worker "New version available · Refresh" banner
 * (added v10.456) was REMOVED · the user found the pop-up pill annoying in
 * production. The silent update path stays in js/app.js (a new SW calls
 * skipWaiting + controls the next navigation), so fresh assets still land
 * without the prompt. All .sw-update-banner* rules were deleted here. */

/* ════════════════════════════════════════════════════════════════════
   v10.460 · LEGAL / PRESS PAGE POLISH
   ────────────────────────────────────────────────────────────────────
   The 4 legal/press pages (security · press · privacy · terms) now load
   the full chrome (.site-nav + .site-footer) from this file. They keep
   their own inline <style> block defining the base `.legal` layout; the
   rules below run AFTER that inline block in source order, so on
   equal-specificity ties these win. Goal: editorial / restraint
   (Pharos / textura tradition) · dark surfaces, hairline dividers,
   monospace eyebrows, NO gradient cards, NO glowing borders, brand-purple
   accents. Everything here is scoped under `.legal` (or the legal page's
   own classes) so the rest of the site is untouched.
   ════════════════════════════════════════════════════════════════════ */

/* The pages are DARK · keep them dark even though the rest of site.css
   carries the light product-page theme. Lock the surface explicitly so a
   stray global rule can't flip the legal pages to the light theme. */
body:has(> .site-nav) .legal,
.legal {
  --legal-fg: rgba(255, 255, 255, 0.78);
  --legal-accent: #9d5cff;
  --legal-hairline: rgba(255, 255, 255, 0.10);
}

/* Readability column · keep ~760 px (press uses 880 via its inline rule;
   we don't override width here so press keeps its wider asset grid). The
   fixed .site-nav sits on top, so the inline 140px top padding already
   clears it · we keep it and just guarantee a floor. */
.legal {
  padding-top: max(140px, calc(96px + env(safe-area-inset-top)));
}

/* ── Header block · eyebrow / title / "last updated", with a hairline
      rule beneath the whole block for editorial separation. ── */
.legal__eyebrow {
  font-family: ui-monospace, "SF Mono", Menlo, Monaco, monospace;
  font-size: 0.72rem;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  font-weight: 500;
  color: var(--legal-accent);
}
.legal__title {
  letter-spacing: -0.03em;
  line-height: 1.06;
}
.legal__updated {
  /* The header hairline lives on the "last updated" line's bottom edge so
     it sits directly under the whole title block, before the body copy.
     On the press page there is no `.legal__updated`-then-prose gap issue
     because it works there too. */
  padding-bottom: 30px;
  margin-bottom: 40px;
  border-bottom: 1px solid var(--legal-hairline);
}

/* ── Section rhythm · each major h2 gets clear top spacing + a thin top
      hairline divider so the numbered sections read as discrete movements.
      The leading "1. " / "2. " text stays untouched (we do NOT restructure
      or strip numbers); we just give the heading editorial breathing room.
      The first h2 after the header block keeps its own divider · that's
      fine, it reinforces the rhythm. ── */
.legal h2 {
  margin-top: 52px;
  padding-top: 30px;
  border-top: 1px solid var(--legal-hairline);
  line-height: 1.2;
}
/* Press page uses h2 as plain labels (Quick facts / Logos / Brand colors
   etc.) with no leading numbers · the same hairline rhythm still reads
   well there, so no special-case needed. */

/* ── Body copy · generous line-height, comfortable spacing. ── */
.legal p,
.legal li {
  line-height: 1.65;
}
.legal h3 {
  margin-top: 30px;
  letter-spacing: -0.005em;
}

/* ── Links · brand purple, quiet underline with offset, subtle hover.
      Scoped to prose links so it does NOT touch the brandkit download
      cards / swatches / nav / footer (those carry their own styling). ── */
.legal p a,
.legal li a,
.legal dd a {
  color: var(--legal-accent);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  text-decoration-color: rgba(157, 92, 255, 0.45);
  transition: color 0.2s ease, text-decoration-color 0.2s ease;
}
.legal p a:hover,
.legal li a:hover,
.legal dd a:hover {
  color: #fff;
  text-decoration-color: rgba(255, 255, 255, 0.65);
}

/* ── Callout boxes · subtle tinted background + a 2-3 px left accent
      border in brand purple. Overrides the inline gradient/full-border
      cards (anti-pattern: loud glowing boxes). These are quiet, editorial,
      and left-anchored. ──

      Covers:
        .legal__notice    (security · neutral violet note)
        .legal__protocol  (terms · protocol-level statement)
        .legal__risk      (terms · risk / no-advice · amber accent kept)
        .factbox          (press · quick-facts dl) */
.legal .legal__notice,
.legal .legal__protocol,
.legal .audit-card,
.legal .factbox {
  background: rgba(157, 92, 255, 0.05);
  border: 1px solid var(--legal-hairline);
  border-left: 3px solid var(--legal-accent);
  border-radius: 0 8px 8px 0;
  box-shadow: none;
}
/* Risk / no-advice boxes keep the amber accent (semantic warning colour
   the page already used) · just convert to the left-accent editorial form
   instead of a full glowing border. */
.legal .legal__risk {
  background: rgba(245, 158, 11, 0.05);
  border: 1px solid var(--legal-hairline);
  border-left: 3px solid rgba(245, 158, 11, 0.85);
  border-radius: 0 8px 8px 0;
  box-shadow: none;
}

/* v10.462 · CertiK audit card · recomposed. Was a plain left-accent box
   with the audit facts crammed into one run-on sentence. Now: a shield
   glyph + stacked eyebrow/name header, the facts broken into a clean
   label/value stat row with hairline dividers (Stripe/Linear style), and
   a quiet brand-purple CTA. No gradient surface · keeps the editorial
   left-accent form. */
.legal .audit-card {
  padding: 30px 32px;
}
.legal .audit-card__head {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 22px;
}
.legal .audit-card__shield {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  flex-shrink: 0;
  border-radius: 11px;
  background: rgba(157, 92, 255, 0.12);
  border: 1px solid rgba(157, 92, 255, 0.22);
  color: var(--legal-accent);
}
.legal .audit-card__heading {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}
.legal .audit-card__by {
  font-family: ui-monospace, "SF Mono", Menlo, Monaco, monospace;
  font-size: 0.66rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--legal-accent);
}
.legal .audit-card__name {
  margin: 0;
  font-size: 1.32rem;
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.01em;
  color: #fff;
  border: none;       /* override the .legal h2/h3 hairline rule */
  padding: 0;
}
.legal .audit-card__stats {
  display: grid;
  grid-template-columns: repeat(4, auto);
  justify-content: start;
  margin: 0 0 24px;
  padding: 0;
}
.legal .audit-card__stat {
  padding: 0 24px;
  border-left: 1px solid var(--legal-hairline);
}
.legal .audit-card__stat:first-child {
  padding-left: 0;
  border-left: none;
}
.legal .audit-card__stat dt {
  margin: 0 0 5px;
  font-size: 0.68rem;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.42);
}
.legal .audit-card__stat dd {
  margin: 0;
  font-size: 1.02rem;
  font-weight: 600;
  color: #fff;
  white-space: nowrap;
}
.legal .audit-card__scope {
  margin: 0 0 20px;
  font-size: 0.86rem;
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.5);
}
.legal .audit-card__cta {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 0;
  padding: 11px 22px;
  border-radius: 999px;
  background: var(--legal-accent);
  color: #fff !important;
  font-weight: 600;
  font-size: 0.92rem;
  letter-spacing: 0.01em;
  box-shadow: none;
  transition: background 0.18s ease, transform 0.18s ease;
}
.legal .audit-card__cta:hover {
  background: #8a44ff;
  transform: translateY(-1px);
}
@media (max-width: 620px) {
  .legal .audit-card { padding: 24px 22px; }
  .legal .audit-card__stats {
    grid-template-columns: 1fr 1fr;
    gap: 18px 0;
  }
  .legal .audit-card__stat { padding: 0 18px; }
  .legal .audit-card__stat:nth-child(odd) {
    padding-left: 0;
    border-left: none;
  }
}

/* ── Optional premium touch · in-page table of contents.
      Rendered only if a `.legal__toc` block is present (we add one to the
      two long pages · privacy + terms). Hairline-bounded, muted, monospace
      counters · editorial, not decorative. ── */
.legal__toc {
  margin: 4px 0 8px;
  padding: 0;
}
.legal__toc-title {
  font-family: ui-monospace, "SF Mono", Menlo, Monaco, monospace;
  font-size: 0.68rem;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  font-weight: 500;
  color: rgba(255, 255, 255, 0.45);
  margin: 0 0 14px;
}
.legal__toc ol {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px 28px;
}
@media (max-width: 600px) {
  .legal__toc ol {
    grid-template-columns: 1fr;
  }
}
.legal__toc li {
  margin: 0;
  font-size: 0.92rem;
  line-height: 1.4;
}
.legal__toc a {
  color: rgba(255, 255, 255, 0.62);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: color 0.2s ease, border-color 0.2s ease;
}
.legal__toc a:hover {
  color: #fff;
  border-bottom-color: rgba(157, 92, 255, 0.5);
}
/* Give the TOC its own bottom hairline so it reads as a contained nav
   panel before the first numbered section. */
.legal__toc {
  padding-bottom: 30px;
  border-bottom: 1px solid var(--legal-hairline);
}
/* When a TOC is present right after the header block, the first h2's top
   hairline would double the header rule visually · the TOC's own bottom
   hairline already separates it, so this still reads clean. No override
   needed; the rhythm is intentional. */


/* v10.461 · footer bottom strip · PC-ONLY layout.
 * Mobile (<768px) is intentionally LEFT UNTOUCHED · it keeps the existing
 * centered column stack (flex-direction:column + gap:6px from the
 * max-width:767px block above).
 *
 * v10.460 tried `justify-content: space-between`, but on the 1600px-wide
 * footer container that shoved the 3 short items (copyright · legal links ·
 * contact) to the extreme left/centre/right with two cavernous gaps · read
 * as disconnected on wide screens. v10.461 centres them as ONE cohesive
 * cluster (copyright · separator · legal links · separator · contact) so
 * the strip stays tight and intentional at any width. */
@media (min-width: 768px) {
  .site-footer { padding: 56px 0 28px; }
  .site-footer__top { margin-bottom: 36px; }
  .site-footer__divider { margin: 28px 0 20px; }
  .site-footer__bottom {
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: center;   /* cluster centred · not edge-spread */
    align-items: center;
    gap: 6px 22px;
  }
  .site-footer__bottom .site-footer__legal { justify-content: center; }
  /* Tame the oversized signature watermark on wide screens so it sits
     behind the content as texture rather than dominating an empty band. */
  /* v10.470 · opacity 0.55 → 0.8 · was double-dimming the already-faint
     watermark on wide screens. */
  .site-footer::after { font-size: clamp(6rem, 11vw, 11rem); opacity: 0.8; }
}

/* v10.463 · legal/press card-section polish · matches the recomposed
   CertiK audit card (v10.462). CSS-only · no HTML or copy changes.

   1. PRESS · Quick-facts <dl> → a proper 2-column spec grid (label |
      value) with hairline row separators · was a plain stacked dl that
      read as loose unstyled text. Reads like a spec sheet now. */
.legal .factbox {
  padding: 6px 28px;
}
.legal .factbox dl {
  display: grid;
  grid-template-columns: minmax(130px, max-content) 1fr;
  margin: 0;
}
.legal .factbox dt {
  /* v10.471 · was align-self:center · on the multi-line "Smart-contract
     audit" row that floated the label in the vertical middle, out of line
     with the single-line rows above (user: "alignment not correct"). Now
     top-aligned so every label sits level with the FIRST line of its value
     · the standard premium spec-table pattern. dt padding-top nudged +2px
     so the small-caps label optically baselines with the larger value. */
  align-self: start;
  margin: 0;
  padding: 16px 28px 14px 0;
  font-size: 0.7rem;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.45);
  border-top: 1px solid var(--legal-hairline);
  white-space: nowrap;
}
.legal .factbox dd {
  align-self: start;
  margin: 0;
  padding: 14px 0;
  font-size: 0.98rem;
  font-weight: 500;
  line-height: 1.5;
  color: #fff;
  border-top: 1px solid var(--legal-hairline);
}
.legal .factbox dt:first-of-type,
.legal .factbox dd:first-of-type {
  border-top: none;
}
@media (max-width: 560px) {
  .legal .factbox dl { grid-template-columns: 1fr; }
  .legal .factbox dt {
    padding: 12px 0 2px;
    border-top: 1px solid var(--legal-hairline);
  }
  .legal .factbox dd { padding: 0 0 12px; border-top: none; }
  .legal .factbox dt:first-of-type { border-top: none; }
}

/* 2. TERMS · the protocol / risk callout boxes · promote the leading
      <strong> into a small label so each box reads as a deliberate,
      composed notice (echoing the audit card's monospace eyebrow). The
      left-accent box form + colours come from the shared v10.460 rules. */
.legal .legal__protocol > strong:first-child {
  display: block;
  margin-bottom: 9px;
  font-size: 0.7rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--legal-accent);
}
.legal .legal__risk > strong:first-child {
  color: rgba(245, 158, 11, 0.95);
}
