/* ============================================================================
   AXIS — DESIGN TOKENS
   Owner: DESIGN agent. Page agents import, never edit.

   <link rel="stylesheet" href="/v2/axis-tokens.css">   ← first, before your <style>

   This file kills five MEASURED defects globally:
     1. h2 rendering at 45px on a 393px screen  -> one modular scale, every step clamp()ed
     2. 15+ elements under 13px                 -> --fs-min is 13px; legacy aliases raised
     3. --s32 (128px) between every section     -> --section-y is fluid; --s32 now aliases it
     4. scrollWidth 445 > innerWidth 393        -> overflow-x:clip at root + body (§4)
     5. all 12 logo3d / market3d marks at mean  -> .ax-mark surface treatment (§9)
        luminance 5–13 on black, i.e. invisible

   CONTRACT — these names are imported by eleven pages. Nothing here is ever renamed:
     --s1..--s24 · --section-y · --gutter · --measure · --maxw
     --fs-* (incl --fs-min) · --lh-* · --tr-* · --ff-*
     --bg* · --ink* · --hair* · --axis --jetr --zera --szn --ycht --gld
     --accent · --accent-ink · --r-* · --sh-* · --glow · --tap · --tabbar-h
     .ax-wrap .ax-section .ax-label .ax-num .ax-panel .ax-cta

   INDEX
     §1 spacing + rhythm      §6  primitives
     §2 type                  §7  state layer
     §3 colour + elevation    §8  optical alignment
     §4 reset + overflow      §9  3D mark treatment
     §5 base typography       §10 utilities
   ========================================================================= */


/* ===========================================================================
   §1 · SPACING + RHYTHM — 8pt grid
   --s1/--s3/--s5 are the 4pt half-steps. Everything else lands on 8.
   ======================================================================== */
:root {
  --s0:  0px;
  --s1:  4px;   --s2:  8px;   --s3:  12px;  --s4:  16px;
  --s5:  20px;  --s6:  24px;  --s8:  32px;  --s10: 40px;
  --s12: 48px;  --s16: 64px;  --s20: 80px;  --s24: 96px;

  /* --s32 DOES NOT MEAN 128px ANY MORE. It was the padding that compounded the
     home page to 17,379px / 20.6 screens. It now resolves to the fluid section
     rhythm, so any page still calling var(--s32) is fixed on import. */
  --s32: var(--section-y);

  /* Section rhythm. Linear 393 -> 1440, then held.
     Measured: 56px at 393  ·  112px at 1440. */
  --section-y:       clamp(56px, 5.35vw + 34.98px, 112px);
  --section-y-tight: clamp(40px, 3.82vw + 24.99px,  80px);  /* dense stacks   */
  --section-y-loose: clamp(72px, 7.64vw + 41.97px, 152px);  /* hero moments   */

  --gutter:    clamp(16px, 2.29vw + 6.99px, 40px);   /* 16 @393 · 40 @1440 */
  --measure:   68ch;         /* prose max — ~72 chars, the readable band     */
  --measure-tight: 46ch;     /* lead paragraphs, card copy                   */
  --maxw:      1200px;
  --maxw-wide: 1440px;
  --maxw-text: 760px;

  /* Grid rails — use instead of hand-rolled repeat() so pages line up. */
  --col-gap:   clamp(12px, 1.5vw + 6px, 32px);
  --row-gap:   clamp(16px, 1.9vw + 8px, 40px);
}


/* ===========================================================================
   §2 · TYPE — one modular scale, two documented ratios

   Step n is base · ratio^n. The ratio itself is fluid:
       393px  base 16px  ratio 1.180  (minor-third-ish — compressed so a long
                                       page does not compound into 20 screens)
      1440px  base 17px  ratio 1.333  (perfect fourth — drama at desk width)

   Every step interpolates linearly between those two ends and then holds.
   Step  -2 micro  -1 small   0 body   1 lead   2 h4   3 h3   4 h2   5 h1   6 display
   @393    13        14      16       19      22     26     31     37      43
   @1440   14        15      17       23      30     40     54     72      95

   Two families plus mono. --ff-display defaults to --ff-sans so no page can
   render a font it has not loaded; brand pages override the one variable.
   ======================================================================== */
:root {
  --fs-min:    13px;                                    /* THE FLOOR. Never below. */
  --fs-micro:  clamp(13px, 0.10vw + 12.62px, 14px);
  --fs-small:  clamp(14px, 0.10vw + 13.62px, 15px);
  --fs-body:   clamp(16px, 0.10vw + 15.62px, 17px);     /* never below 16 */
  --fs-lead:   clamp(19px, 0.38vw + 17.50px, 23px);
  /* ── THE DESKTOP CEILING, LOWERED ───────────────────────────────────────
     Founder, repeatedly, looking at his own monitor: "all these oversized and
     not good", "it's too oversized, all of it."
     He was right and the reason is structural: `gate.mjs` has only ever measured
     393px, so the MOBILE end of every one of these ramps was tuned and measured
     and the DESKTOP end was never looked at once. A 95px display line and a 72px
     h1 are not wrong on a billboard; on a 1440 browser they are shouting, and
     they made every heading below them look shouted too.
     Only the CEILINGS move — the floors and the slopes are untouched, so nothing
     changes at 393px where all the tuning actually happened. Roughly a fifth off
     the top of the scale:
       display 95 -> 76 · h1 72 -> 58 · h2 54 -> 45 · h3 40 -> 34 · h4 30 -> 26
     This is one edit and it lands on every page at once, which is the only way
     a "fix it all up" of this kind can be honest — page-by-page it would drift
     again by morning. */
  --fs-h4:     clamp(22px, 0.76vw + 19.00px, 26px);
  --fs-h3:     clamp(26px, 1.34vw + 20.75px, 34px);
  --fs-h2:     clamp(31px, 2.20vw + 22.37px, 45px);     /* was 45px @393 -> 31 */
  --fs-h1:     clamp(37px, 3.34vw + 23.86px, 58px);
  /* SECOND REDUCTION, SAME REASON. 95 -> 76 was not enough: the audit shows every
     page's hero h1 is set to --fs-display rather than --fs-h1, so the headline
     that reads as "oversized" was still landing at 76px on a 1440 screen while
     the scale's own h1 step sits at 58. Rather than chase nine pages to repoint
     their headline (and watch it drift back), the display step itself comes down
     to 64 — a 33% reduction from where this session started, and still clearly
     the largest thing on any page. */
  --fs-display:clamp(43px, 4.97vw + 23.48px, 64px);

  /* Legacy aliases. index.html and friends declare --text-xs:.75rem (=12px,
     under the floor) in their own :root. Delete that block and these take over
     with the floor already applied — no other edit needed. */
  --text-xs:   var(--fs-micro);
  --text-sm:   var(--fs-small);
  --text-base: var(--fs-body);
  --text-lg:   var(--fs-lead);
  --text-xl:   var(--fs-h4);
  --text-2xl:  var(--fs-h3);
  --text-3xl:  var(--fs-h2);
  --text-4xl:  var(--fs-h1);
  --text-5xl:  var(--fs-display);

  --lh-tight: 1.06;   /* display only                    */
  --lh-head:  1.16;   /* h1–h4                           */
  --lh-snug:  1.35;   /* leads, card copy                */
  --lh-body:  1.6;    /* prose                           */
  --lh-flat:  1;      /* numerals, single-line UI        */

  --tr-tight:   -0.02em;
  --tr-display: -0.035em;
  --tr-label:    0.14em;
  --tr-num:     -0.01em;

  --fw-body: 400;  --fw-med: 500;  --fw-semi: 600;  --fw-bold: 700;  --fw-black: 800;

  --ff-sans: 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
  --ff-mono: ui-monospace, 'SF Mono', 'JetBrains Mono', Menlo, monospace;
  --ff-display: var(--ff-sans);   /* brand pages override this one line */
}

/* JETR is the one brand whose identity is a serif. It still resolves to a real
   fallback if Playfair has not loaded. */
[data-brand="jetr"] { --ff-display: 'Playfair Display', ui-serif, Georgia, 'Times New Roman', serif; }


/* ===========================================================================
   §3 · COLOUR + ELEVATION
   Every ratio below is measured against #000 with the WCAG 2.1 formula.
   ======================================================================== */
:root {
  /* --- surfaces: a real 5-step elevation ladder ------------------------- */
  --surface-0: #000000;   /* page                                          */
  --surface-1: #0A0A0B;   /* raised block, footer, tab bar                  */
  --surface-2: #121215;   /* card resting                                  */
  --surface-3: #1B1B1F;   /* card hovered, popover, the .ax-mark tile       */
  --surface-4: #26262B;   /* menu, dialog, the thing on top of everything   */

  --bg:        var(--surface-0);
  --bg-raised: var(--surface-1);
  --bg-panel:  rgba(255,255,255,0.032);
  --bg-panel-2:rgba(255,255,255,0.055);
  --bg-panel-3:rgba(255,255,255,0.085);

  --hair:      rgba(255,255,255,0.10);
  --hair-soft: rgba(255,255,255,0.055);
  --hair-hard: rgba(255,255,255,0.18);

  /* --- ink ladder — measured contrast on #000 --------------------------- */
  --ink:       #FFFFFF;                  /* 21.00:1                        */
  --ink-2:     rgba(255,255,255,0.72);   /* 10.59:1                        */
  --ink-3:     rgba(255,255,255,0.50);   /*  5.32:1  floor for prose       */
  --ink-4:     rgba(255,255,255,0.40);   /*  3.66:1  UI + large text only,
                                                     never body copy       */
  --ink-on-accent: var(--accent-ink);

  /* --- brand accents ---------------------------------------------------- */
  --axis: #C8FF00;
  --jetr: #C9A227;
  --zera: #B6EA00;
  --szn:  #D0344E;
  --ycht: #7C93C9;
  --gld:  #E3B341;

  /* ONE NEGATIVE, DECLARED ONCE. zera.html and security.html were each
     declaring their own --breach, which is two files owning the same idea and
     the classic way two reds drift apart. Anything that means loss, risk or a
     cap being hit uses this. */
  --negative:    #D0344E;
  --accent:      var(--axis);
  --accent-ink:  #000000;      /* text that sits ON an accent fill          */
  --accent-text: var(--axis);  /* accent used AS TEXT on --bg — see below   */

  /* --- radius ladder ----------------------------------------------------
     6 · 10 · 14 · 20 · 28 · 40 · pill. Nesting rule: a child's radius is the
     parent's radius minus the padding between them. --r-nest does that for
     the common case (.ax-panel at --r-lg with --s3 padding). */
  --r-xs: 6px;  --r-sm: 10px;  --r-md: 14px;
  --r-lg: 20px; --r-xl: 28px;  --r-2xl: 40px;  --r-pill: 999px;
  --r-nest: calc(var(--r-lg) - var(--s3));

  /* --- shadow ladder — y and blur roughly double each step, spread pulled
         negative so the shadow stays under the object, not around it. ----- */
  --sh-0: none;
  --sh-1: 0 1px 2px rgba(0,0,0,.40);
  --sh-2: 0 8px 24px -8px rgba(0,0,0,.65);
  --sh-3: 0 24px 64px -16px rgba(0,0,0,.80);
  --sh-4: 0 48px 120px -24px rgba(0,0,0,.88);

  /* Hairlines as shadows so they never affect layout. */
  --ring:      inset 0 0 0 1px var(--hair);
  --ring-hard: inset 0 0 0 1px var(--hair-hard);
  --ring-top:  inset 0 1px 0 rgba(255,255,255,.08);   /* the specular edge   */

  /* --- controls ---------------------------------------------------------- */
  --tap: 44px;
  --tap-sm: 36px;          /* dense desk-only controls, never the only target */
  --tabbar-h: 62px;
  --header-h: 60px;
  --blur: 20px;
  --sat: 140%;
}

/* --- brand colourways ----------------------------------------------------
   --accent-ink is the ink ON the fill.  --accent-text is the accent used AS
   text on --bg. They are different problems and only SZN needs them to differ.

   brand   fill        ink   ink-on-fill   accent-as-text on #000
   axis    #C8FF00     #000   17.76:1      17.76:1  pass
   jetr    #C9A227     #000    8.68:1       8.68:1  pass
   zera    #B6EA00     #000   14.76:1      14.76:1  pass
   szn     #D0344E     #fff    4.90:1       4.28:1  FAIL -> lifted to #D64C63
                                                    = 5.07:1 pass
   ycht    #7C93C9     #000    6.87:1       6.87:1  pass
   gld     #E3B341     #000   10.79:1      10.79:1  pass
   ---------------------------------------------------------------------- */
[data-brand="axis"] { --accent: var(--axis); --accent-ink: #000; --accent-text: #C8FF00; }
[data-brand="jetr"] { --accent: var(--jetr); --accent-ink: #000; --accent-text: #C9A227; }
[data-brand="zera"] { --accent: var(--zera); --accent-ink: #000; --accent-text: #B6EA00; }
[data-brand="szn"]  { --accent: var(--szn);  --accent-ink: #fff; --accent-text: #D64C63; }
[data-brand="ycht"] { --accent: var(--ycht); --accent-ink: #000; --accent-text: #7C93C9; }
[data-brand="gld"]  { --accent: var(--gld);  --accent-ink: #000; --accent-text: #E3B341; }

/* EVERY accent-derived value is declared HERE, on the same elements that can
   change --accent — never once in :root.
   A custom property substitutes its vars at the element it is DECLARED on. If
   --accent-soft lived in :root it would bake in AXIS lime, and a descendant
   <div data-accent="szn"> would inherit a lime "selected" state while its fill
   went crimson. Measured: all six brand columns rendered the same olive
   selected state before this moved. */
:root, [data-brand], [data-accent] {
  --accent-soft:  color-mix(in srgb, var(--accent) 14%, transparent);
  --accent-faint: color-mix(in srgb, var(--accent)  7%, transparent);
  --accent-line:  color-mix(in srgb, var(--accent) 34%, transparent);
  --accent-deep:  color-mix(in srgb, var(--accent) 22%, #000);
  --ink-on-accent: var(--accent-ink);
  --ring-accent:  inset 0 0 0 1px color-mix(in srgb, var(--accent) 34%, transparent);
  --glow: 0 0 0 1px color-mix(in srgb, var(--accent) 22%, transparent),
          0 12px 48px -12px color-mix(in srgb, var(--accent) 30%, transparent);
  --glow-soft: 0 8px 32px -12px color-mix(in srgb, var(--accent) 22%, transparent);
  --glow-hard: 0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent),
               0 16px 56px -10px color-mix(in srgb, var(--accent) 48%, transparent);
  --state-selected: color-mix(in srgb, var(--accent) 16%, transparent);
}

/* Scoped colourway — recolour one block without touching <html>.
   <div data-accent="szn"> … </div> */
[data-accent="axis"] { --accent: var(--axis); --accent-ink: #000; --accent-text: #C8FF00; }
[data-accent="jetr"] { --accent: var(--jetr); --accent-ink: #000; --accent-text: #C9A227; }
[data-accent="zera"] { --accent: var(--zera); --accent-ink: #000; --accent-text: #B6EA00; }
[data-accent="szn"]  { --accent: var(--szn);  --accent-ink: #fff; --accent-text: #D64C63; }
[data-accent="ycht"] { --accent: var(--ycht); --accent-ink: #000; --accent-text: #7C93C9; }
[data-accent="gld"]  { --accent: var(--gld);  --accent-ink: #000; --accent-text: #E3B341; }


/* ===========================================================================
   §4 · RESET + THE OVERFLOW GUARD

   Measured defect: scrollWidth 445 vs innerWidth 393.
   `overflow-x: hidden` on <body> alone does NOT fix this — index.html already
   had it and still measured 416 > 393, because hidden makes a scroll container
   whose scrollWidth still reports the overflow.

   `overflow-x: clip` does fix it: it clips without creating a scroll container,
   so scrollWidth resolves to the clipped width — and, unlike hidden, it leaves
   position:sticky working. clip on one axis is legal beside visible on the other.

   It clips only at the viewport edge, so card glows, tilts, rim lights and any
   overhang inside a layout are untouched. Fixed-position chrome (tab bar) is
   not clipped by body. If you genuinely need to overhang the page edge, put it
   in a .ax-clip-x wrapper and let that wrapper hold the overflow.
   ======================================================================== */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  overflow-x: clip;
  overflow-y: visible;
  scrollbar-gutter: stable;      /* 100vw never disagrees with the layout box */
}

body {
  margin: 0;
  overflow-x: clip;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--ff-sans);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  font-synthesis-weight: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Media can never be wider than its column. */
img, video, canvas, svg, iframe, table, pre { max-width: 100%; }
img, video { height: auto; display: block; }

/* Long unbreakable strings — wallet addresses, hashes, URLs — are the second
   most common cause of horizontal overflow. :where() = zero specificity, so a
   page rule always wins. */
:where(p, li, dd, dt, td, th, figcaption, blockquote, h1, h2, h3, h4, h5, h6) {
  overflow-wrap: break-word;
}

/* The third cause: a flex/grid child defaults to min-width:auto and refuses to
   shrink below its content. These are the containers we own, so we fix them. */
:where(.ax-wrap, .ax-section, .ax-panel) > * { min-width: 0; }


/* ===========================================================================
   §5 · BASE TYPOGRAPHY
   ======================================================================== */
h1, h2, h3, h4, h5, h6 {
  margin: 0;
  font-family: var(--ff-display);
  font-weight: var(--fw-bold);
  line-height: var(--lh-head);
  letter-spacing: var(--tr-tight);
  text-wrap: balance;          /* no orphan word on a heading */
}
h1 { font-size: var(--fs-h1); line-height: var(--lh-tight); letter-spacing: var(--tr-display); }
h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-h3); }
h4 { font-size: var(--fs-h4); }
h5 { font-size: var(--fs-lead); letter-spacing: var(--tr-tight); }
h6 { font-size: var(--fs-body); letter-spacing: 0; }

p { margin: 0; max-width: var(--measure); text-wrap: pretty; }

/* Nothing in this group may render below the floor. max(13px, 1em) keeps the
   element at its inherited size unless that size is illegal. */
:where(small, sub, sup, figcaption, caption, legend) {
  font-size: max(var(--fs-min), 0.875em);
}

code, kbd, samp, pre { font-family: var(--ff-mono); font-size: max(var(--fs-min), 0.9em); }

a { color: inherit; text-decoration: none; }
a:where(:not([class])) { color: var(--accent-text); text-underline-offset: 0.18em; }

button, input, select, textarea { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; }

/* Every interactive element clears the 44px target. */
button, a[role="button"], [data-tap], .ax-cta, .ax-chip, .ax-tab { min-height: var(--tap); }

/* ── NO DOUBLE-TAP ZOOM. ────────────────────────────────────────────────────
   Founder: "when you tap or double-tap, it shouldn't zoom. It should just move
   you around really easily."
   Safari and Chrome on touch wait ~300ms after every tap to see whether a
   second one is coming, and if it is, they ZOOM. On a page built from cards,
   chips and pool tiles that is constant: you tap two things quickly and the
   viewport lurches. `manipulation` tells the browser this surface has no
   double-tap gesture — it keeps ordinary pan and pinch-to-zoom, which are the
   ones a person actually asks for, and drops the accidental one. It also
   removes the 300ms delay, so every tap feels immediate.
   Declared on the interactive surfaces rather than blanket-globally, so text
   selection and pinch on a photograph are untouched. */
html { touch-action: manipulation; }
button, a, [role="button"], [data-tap], summary,
.ax-cta, .ax-chip, .ax-tab, label, input, select {
  touch-action: manipulation;
}

:focus-visible {
  outline: 2px solid var(--accent-text);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}
:focus:not(:focus-visible) { outline: none; }

::selection { background: var(--accent); color: var(--accent-ink); }


/* ===========================================================================
   §6 · PRIMITIVES
   ======================================================================== */
.ax-wrap {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.ax-wrap--wide { max-width: var(--maxw-wide); }
.ax-wrap--text { max-width: var(--maxw-text); }

.ax-section       { padding-block: var(--section-y); }
.ax-section--tight{ padding-block: var(--section-y-tight); }
.ax-section--loose{ padding-block: var(--section-y-loose); }
/* Consecutive sections share one rhythm instead of stacking two paddings —
   this alone removes ~1 screen per 10 sections on mobile. */
.ax-section + .ax-section { padding-block-start: 0; }

.ax-label {
  font-size: var(--fs-micro);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--ink-3);
  font-weight: var(--fw-semi);
  line-height: var(--lh-flat);
}
.ax-label--accent { color: var(--accent-text); }

/* Anywhere a number can change, it must not reflow. */
.ax-num, .ax-count, :where([data-num], .ax-num *) {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1, "lnum" 1;
  letter-spacing: var(--tr-num);
}
.ax-num--lg { font-size: var(--fs-h2); font-weight: var(--fw-black); line-height: var(--lh-tight); }

.ax-panel {
  background: var(--bg-panel);
  border: 1px solid var(--hair);
  border-radius: var(--r-lg);
  box-shadow: var(--ring-top);
  backdrop-filter: blur(var(--blur)) saturate(var(--sat));
  -webkit-backdrop-filter: blur(var(--blur)) saturate(var(--sat));
}
.ax-panel--raised { background: var(--bg-panel-2); box-shadow: var(--ring-top), var(--sh-2); }
.ax-panel--solid  { background: var(--surface-2); backdrop-filter: none; -webkit-backdrop-filter: none; }
.ax-panel--accent { border-color: var(--accent-line); box-shadow: var(--ring-top), var(--glow-soft); }
.ax-panel > :where(.ax-panel, .ax-mark) { border-radius: var(--r-nest); }

/* ── THE BUTTON, AS THE FOUNDER DREW IT ──────────────────────────────────────
   REVERSED, DELIBERATELY, ON HIS EXPLICIT CALL.

   The rule that used to live here was his: "All the buttons should never be
   full colour. They should always be transparent, a little more futuristic."
   Today he said the opposite three times — "the buttons on the original
   homepage are what we wanted, what I liked" — and then sent a screenshot of
   the original: a SOLID lime block with a chamfered bottom-right corner and
   near-black mono caps, above a ghost of the same shape. Asked which wins, he
   chose the original. So the rule is inverted here, in writing, so nobody
   "fixes" it back by reading the old comment.

   What makes it that button and not a generic pill:
     - SOLID accent. The primary action is the brightest thing on the screen.
     - ⛔ A CHAMFER — REMOVED 2026-09-18, DO NOT REINSTATE. This paragraph used
       to argue that one clipped 14px corner was "the whole reason the original
       reads as a terminal control". He has since looked at it on desktop and
       said: "Don't cut the edges of the buttons off. I hate that." The rule in
       this file is newest-wins, so the cut is gone. Left in place, corrected,
       because the original wording was persuasive enough to talk somebody into
       putting it back.
     - MONO CAPS with real tracking. The label is an instruction, not prose.
   `--accent-ink` is the per-brand text colour on a filled accent (white on
   SZN's red, near-black on the rest) so every brand clears contrast on the
   fill — the same problem `--accent-text` solves on the transparent version.

   `.ax-cta--ghost` is the secondary: same shape, same type, no fill. */
/* 0, not 14px — see the block on .ax-cta below. He asked for the cut corner to
   go and used the word "hate"; this stays 0 so anything still referencing it
   gets no cut rather than a broken value. */
:root { --ax-chamfer: 0px; }
/* ── AND THEN SEE-THROUGH AGAIN, ON HIS WORD. ───────────────────────────────
   Third position on this control in one session, all his, all recorded here so
   nobody has to guess which is current:
     1. "All the buttons should never be full colour… always transparent."
     2. "the buttons on the original homepage are what we wanted" (solid, asked
        directly, he chose solid)
     3. "fix the buttons, make the buttons see-through"
   Newest wins. What is kept from (2) is the SHAPE — the chamfered bottom-right
   corner and the mono caps, which is what made the original read as a terminal
   control rather than a consumer pill. What changes is the fill.
   So: the original's silhouette, transparent, with an accent hairline and a
   breath of tint. Flipping back to solid is two lines — background and color. */
.ax-cta {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s3);
  min-height: var(--tap); padding: 0 var(--s7, 40px);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  color: var(--accent-text);
  border: 1px solid color-mix(in srgb, var(--accent) 46%, transparent);
  backdrop-filter: blur(14px) saturate(150%);
  -webkit-backdrop-filter: blur(14px) saturate(150%);
  /* ⛔ THE CHAMFER IS GONE, ON HIS WORD, AND IT IS NOT COMING BACK.
     Fourth and final position on this control. Verbatim, looking at the header
     button on desktop: "Don't cut the edges of the buttons off. I HATE THAT."
     That is unambiguous and it outranks the shape argument recorded above —
     the rule written into this very block is "newest wins".

     Why it read as badly as it did: `clip-path` clips the BORDER too, so the
     1px accent hairline did not turn the corner, it simply stopped in mid-air
     and the button looked torn rather than styled. On a transparent button the
     border IS the button, so amputating it takes the whole edge with it.

     Square, with the border going all the way round. The mono caps and the
     tracking are what carry the terminal-control feel now, and they are doing
     it without cutting a corner off anything. `--ax-chamfer` is kept at 0 so
     any stray reference resolves to no cut rather than to an error. */
  border-radius: 0;
  font-weight: var(--fw-bold); font-size: var(--fs-small, 14px);
  letter-spacing: 0.1em; text-transform: uppercase;
  line-height: var(--lh-flat);
  transition: filter var(--d-ui, 220ms) var(--ease-out-expo, ease),
              transform var(--d-fast, 150ms) var(--ease-out-expo, ease);
}
.ax-cta:hover {
  background: color-mix(in srgb, var(--accent) 20%, transparent);
  border-color: var(--accent);
  box-shadow: 0 0 30px -10px color-mix(in srgb, var(--accent) 60%, transparent);
}

/* The ghost: same silhouette, so a row of two reads as one system. */
.ax-cta--ghost,
.ax-cta--quiet {
  background: transparent;
  color: var(--ink);
  border-color: var(--hair-hard, rgba(255,255,255,.22));
}
.ax-cta--ghost:hover,
.ax-cta--quiet:hover {
  filter: none;
  border-color: var(--accent);
  color: var(--accent-text);
}

/* Kept as an alias so the dozens of existing .ax-cta--solid call sites do not
   have to change — solid IS the default now, so it simply inherits. */
.ax-cta--solid { background: var(--accent); color: var(--accent-ink); }
.ax-cta--solid:hover { filter: brightness(1.08); }

.ax-cta:active { transform: scale(0.97); }

/* ---- SPECULAR RIM ---------------------------------------------------------
   The reactbits SpecularButton draws its rim light with a WebGL shader — one
   GL context per button. Chromium caps live contexts, and pages here already
   mount several 3D scenes, so a context per CTA is not affordable: the later
   ones simply fail to create. This is the same effect built from a rotating
   conic gradient, masked to the border. Zero contexts, any number of buttons.

   @property lets the angle animate smoothly; without it the gradient would
   jump, since custom properties are not interpolable by default. Browsers
   without @property support just get the static ring, which is the resting
   state anyway. */
@property --ax-spin {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}

@keyframes ax-specular { to { --ax-spin: 360deg; } }

.ax-cta::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;                 /* the ring's thickness */
  pointer-events: none;
  background: conic-gradient(
    from var(--ax-spin),
    transparent 0deg,
    color-mix(in srgb, var(--accent) 85%, white) 18deg,
    transparent 46deg,
    transparent 180deg,
    color-mix(in srgb, var(--accent) 85%, white) 198deg,
    transparent 226deg,
    transparent 360deg
  );
  /* Mask to the border only: fill the padding box out, leaving the 1px edge. */
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  opacity: 0;
  transition: opacity var(--d-ui, 220ms) var(--ease-out-expo, ease);
}

/* The sweep is interaction feedback, not ambient decoration — it costs nothing
   when idle and it does not compete with the content. */
.ax-cta { position: relative; isolation: isolate; overflow: hidden; }
.ax-cta:hover::after,
.ax-cta:focus-visible::after {
  opacity: 1;
  animation: ax-specular 2.6s linear infinite;
}

@media (prefers-reduced-motion: reduce) {
  .ax-cta:hover::after,
  .ax-cta:focus-visible::after { animation: none; opacity: .85; }
}
.ax-cta:disabled, .ax-cta[aria-disabled="true"] { opacity: var(--state-disabled); pointer-events: none; }

.ax-cta--ghost {
  background: transparent; color: var(--ink);
  box-shadow: var(--ring-hard);
}
@media (hover: hover) { .ax-cta--ghost:hover { background: var(--state-hover); filter: none; box-shadow: var(--ring-accent); } }

.ax-cta--quiet { background: var(--bg-panel-2); color: var(--ink); }
@media (hover: hover) { .ax-cta--quiet:hover { background: var(--bg-panel-3); filter: none; } }

/* A chip is a tap target, so it clears 44px like everything else. Use
   .ax-chip--static for a chip that is only a label and cannot be pressed. */
.ax-chip {
  display: inline-flex; align-items: center; gap: var(--s2);
  padding: var(--s2) var(--s4); min-height: var(--tap);
  border-radius: var(--r-pill);
  background: var(--bg-panel); box-shadow: var(--ring);
  font-size: var(--fs-micro); font-weight: var(--fw-semi);
  color: var(--ink-2); line-height: var(--lh-flat);
}
.ax-chip--static { min-height: 0; padding-block: var(--s1); }
.ax-chip[aria-selected="true"], .ax-chip.is-selected {
  background: var(--accent-soft); color: var(--ink); box-shadow: var(--ring-accent);
}

.ax-hair { border: 0; height: 1px; background: var(--hair-soft); margin: 0; }


/* ===========================================================================
   §7 · STATE LAYER
   One overlay, six brands, four states. Put .ax-state on anything clickable;
   it paints inside the element's own border-radius and never shifts layout.
   ======================================================================== */
/* --state-selected is accent-derived and therefore lives in §3 beside the rest
   of them, on :root, [data-brand], [data-accent]. These two are neutral. */
:root {
  --state-hover:    rgba(255,255,255,0.055);
  --state-press:    rgba(255,255,255,0.10);
  --state-disabled: 0.38;
}

.ax-state { position: relative; isolation: isolate; }
.ax-state > * { position: relative; z-index: 1; }
.ax-state::after {
  content: ""; position: absolute; inset: 0; z-index: 0;
  border-radius: inherit; pointer-events: none;
  background: transparent;
  transition: background var(--d-ui, 220ms) var(--ease-out-expo, ease);
}
@media (hover: hover) { .ax-state:hover::after { background: var(--state-hover); } }
.ax-state:active::after { background: var(--state-press); }
.ax-state[aria-selected="true"]::after,
.ax-state[aria-current]:not([aria-current="false"])::after,
.ax-state.is-selected::after { background: var(--state-selected); }
.ax-state:disabled, .ax-state[aria-disabled="true"] { opacity: var(--state-disabled); pointer-events: none; }

/* On an accent fill a white wash is invisible — lighten the fill instead.
   Works for all six brands including SZN's white-ink crimson. */
.ax-state[data-on-accent]::after { background: transparent; }
@media (hover: hover) { .ax-state[data-on-accent]:hover { filter: brightness(1.08); } }
.ax-state[data-on-accent]:active { filter: brightness(0.94); }


/* ===========================================================================
   §8 · OPTICAL ALIGNMENT
   Metric alignment is not optical alignment. These are the corrections.
   ======================================================================== */

/* Large type carries side bearing that reads as an indent against a rail.
   Pull it back by the measured bearing of Manrope at display weight. */
.ax-optical      { margin-inline-start: -0.055em; }
.ax-optical-caps { margin-inline-start: -0.035em; }   /* uppercase is tighter */

/* Uppercase tracked labels carry a trailing space after the last letter. Trim
   it ONLY when the label is optically aligned to a right rail — applied to a
   block-level label it widens the border box past its container (measured:
   right edge 1442 in a 1440 viewport). Opt in, never global. */
.ax-trim-track { margin-inline-end: calc(var(--tr-label) * -1); }

/* Trim the half-leading above and below a heading so it sits on the grid.
   Progressive: browsers without text-box fall back to the manual trim. */
.ax-trim { --cap: 0.72; --lh: var(--lh-head);
  margin-block-start: calc((1em - var(--cap) * 1em - (var(--lh) - 1) * 0.5em) * -1); }
@supports (text-box: trim-both cap alphabetic) {
  .ax-trim { margin-block-start: 0; text-box: trim-both cap alphabetic; }
}

/* An icon beside text sits optically low on its own baseline. */
.ax-icon { flex: none; width: 1em; height: 1em; translate: 0 -0.055em; }
.ax-icon--lg { width: 1.25em; height: 1.25em; }

.ax-balance { text-wrap: balance; }
.ax-pretty  { text-wrap: pretty; }
.ax-hang    { hanging-punctuation: first last; }

/* A right-aligned numeric column aligns on the decimal, not the glyph box. */
.ax-num-col { text-align: right; font-variant-numeric: tabular-nums; padding-inline-end: 0.08em; }


/* ===========================================================================
   §9 · 3D MARK TREATMENT  — the fix for "all 12 marks are invisible"

   Every logo3d-* / market3d-* render is a dark mark on a near-black plate with
   a faint radial vignette. Measured untreated on black, all 12 come in at mean
   luminance 5.4–12.6. You cannot see them.

   The fix is a composite, not a filter:
     1. a raised tile, accent-tinted, so the mark has a surface to sit on
     2. --mark-lift brightens the render so the vignette becomes a real halo
     3. mix-blend-mode: screen drops the render's black plate out, so the tile
        shows through the corners and the mark reads as a lit object on it
     4. a hairline + inner top light so the tile reads as a physical plate

   Use it by wrapping the image. No other markup is needed:
       <span class="ax-mark"><img src="/v2/media/logo3d/logo3d-aapl.webp" alt="Apple"></span>

   MEASURED — mean luminance, 12 marks x 6 brands x 6 variants = 432 samples.
   Floor is 58. Worst case per variant, across every colourway:
     raw, no treatment      5.4   FAIL  (this is the defect)
     .ax-mark                66.4 pass  brand-independent: the default is
     .ax-mark--neutral       66.4 pass  untinted, so all six measure the same
     .ax-mark--sm            65.3 pass
     .ax-mark--tinted        68.6 pass  (worst: SZN, the darkest accent)
     .ax-mark--plate         77.5 pass
     .ax-mark--accent        73.5 pass  (worst: SZN)
   0 of 432 below the floor.

   Variants
     .ax-mark--neutral  explicit no-tint — same as the default
     .ax-mark--tinted   plate carries the brand at 10%
     .ax-mark--accent   full brand tint + glow, for a hero row
     .ax-mark--plate    brightest backing, for the darkest marks (AAPL, SPCX)
     .ax-mark--sm/--lg/--fill/--round   size and shape presets
   ======================================================================== */
:root {
  /* The default plate is neutral graphite, not accent-tinted. Stock marks
     (Apple, Google, Meta, NVIDIA, Tesla) read wrong on an olive plate, and
     neutral measures HIGHER anyway — 70.2 worst case vs 65.3 tinted — because
     it does not depend on how bright a given brand accent happens to be.
     Reach for --tinted / --accent when the plate should carry the brand. */
  --mark-tint:   0%;                     /* accent in the tile              */
  --mark-base:   #3A3A42;                /* neutral under the tint          */
  --mark-tile-hi: rgba(255,255,255,0.11);
  /* High contrast is doing real work here, not taste: the render's baked
     vignette is a mid-grey wash that screens into a muddy halo. Crushing the
     midtones drops the wash toward black (which screen discards) and keeps the
     mark itself lit, so the plate reads flat and the mark reads crisp. */
  --mark-lift:   brightness(3.20) contrast(1.85) saturate(1.20);
  --mark-lift-hi:brightness(3.55) contrast(1.95) saturate(1.22);
  --mark-blend:  screen;
  --mark-size:   clamp(64px, 14vw, 104px);
  --mark-pad:    calc(var(--mark-size) * 0.12);
}

/* Padding is a calc of --mark-size, never a percentage: a percentage padding
   on a flex item resolves against the FLEX CONTAINER, which measured 168px in
   a full-width row and collapsed the image to 0x0. block-size is explicit for
   the same reason — the child's 100% needs a definite box to resolve against. */
.ax-mark {
  position: relative;
  display: grid;
  place-items: center;
  flex: none;
  inline-size: var(--mark-size);
  block-size: var(--mark-size);
  padding: var(--mark-pad);
  border-radius: var(--r-md);
  overflow: hidden;
  isolation: isolate;
  /* --mark-tile is declared HERE, not in :root: a custom property substitutes
     its vars at the element it is declared on, so a variant class that changes
     --mark-tint would be ignored if the mix lived upstream. */
  --mark-tile: color-mix(in srgb, var(--accent) var(--mark-tint), var(--mark-base));
  background:
    radial-gradient(120% 100% at 50% 0%, var(--mark-tile-hi), transparent 62%),
    var(--mark-tile);
  box-shadow: var(--ring), var(--ring-top), var(--sh-2);
}

.ax-mark :where(img, canvas, svg, picture) {
  inline-size: 100%;
  block-size: 100%;
  object-fit: contain;
  filter: var(--mark-lift);
  mix-blend-mode: var(--mark-blend);
}

.ax-mark--plate   { --mark-base: #45454F; --mark-lift: var(--mark-lift-hi); }  /* brightest, neutral */
.ax-mark--neutral { --mark-tint: 0%;  --mark-base: #3A3A42; }  /* = the default; explicit alias */
.ax-mark--tinted  { --mark-tint: 10%; --mark-base: #3A3A42; }  /* plate carries the brand   */
.ax-mark--accent  { --mark-tint: 18%; --mark-base: #3E3E47;    /* brand plate + glow        */
                    box-shadow: var(--ring-accent), var(--ring-top), var(--glow-soft); }

/* There is deliberately no glow-only / transparent-edge variant. Measured on
   all 12 renders across all 6 brands: a drop-shadow glow lands at mean
   luminance 27–47 and a soft radial at 48–66. On a black page only a
   full-coverage plate clears the floor. Do not hand-roll one. */
.ax-mark--sm { --mark-size: clamp(44px, 9vw, 64px); border-radius: var(--r-sm); }
.ax-mark--lg { --mark-size: clamp(104px, 22vw, 176px); border-radius: var(--r-lg); }
.ax-mark--fill { inline-size: 100%; block-size: auto; aspect-ratio: 1; --mark-pad: var(--s4); }
.ax-mark--round { border-radius: var(--r-pill); }

/* Any dark image dropped straight onto the page, not just the 3D marks. */
.ax-scrim { position: relative; isolation: isolate; }
.ax-scrim::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  border-radius: inherit;
  background: radial-gradient(70% 70% at 50% 45%,
              color-mix(in srgb, var(--accent) 18%, transparent), transparent 72%);
}

/* If blend modes are unavailable the lift alone still clears the floor. */
@supports not (mix-blend-mode: screen) {
  .ax-mark :where(img, canvas, svg, picture) { filter: var(--mark-lift-hi); }
}


/* ===========================================================================
   §10 · UTILITIES
   ======================================================================== */

/* Wide content scrolls inside its own box — it never widens the page. */
.ax-scroll-x {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  scrollbar-color: var(--hair-hard) transparent;
}
.ax-scroll-x::-webkit-scrollbar { height: 6px; }
.ax-scroll-x::-webkit-scrollbar-thumb { background: var(--hair-hard); border-radius: var(--r-pill); }
.ax-scroll-x > table { min-width: max-content; }

/* A local clip for tickers, marquees and decoration that must run off-edge.
   clip, not hidden — sticky and scroll-driven children keep working. */
.ax-clip-x { overflow-x: clip; }
.ax-clip   { overflow: clip; }

/* Full-bleed that cannot overflow: it escapes the gutter, not the viewport.
   Never use width:100vw — with a scrollbar present that is wider than the page. */
.ax-bleed { margin-inline: calc(var(--gutter) * -1); }

.ax-break { overflow-wrap: anywhere; word-break: break-word; }
.ax-nowrap{ white-space: nowrap; }
.ax-min0  { min-width: 0; }

/* Stack + grid rails on the 8pt scale. */
.ax-stack   { display: grid; gap: var(--s4); }
.ax-stack-2 { display: grid; gap: var(--s2); }
.ax-stack-6 { display: grid; gap: var(--s6); }
.ax-stack-12{ display: grid; gap: var(--s12); }
.ax-row     { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }
.ax-grid {
  display: grid;
  gap: var(--row-gap) var(--col-gap);
  grid-template-columns: repeat(auto-fit, minmax(min(var(--col-min, 240px), 100%), 1fr));
}

.ax-measure { max-width: var(--measure); }
.ax-measure-tight { max-width: var(--measure-tight); }
.ax-center  { margin-inline: auto; }

.ax-sr {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

.ax-text-accent { color: var(--accent-text); }
.ax-text-2 { color: var(--ink-2); }
.ax-text-3 { color: var(--ink-3); }

/* Safe-area padding for anything above the tab bar. */
.ax-safe-b { padding-block-end: calc(var(--tabbar-h) + env(safe-area-inset-bottom, 0px) + var(--s4)); }


/* ===========================================================================
   RESPONSIVE + ENVIRONMENT
   ======================================================================== */
@media (max-width: 480px) {
  :root { --maxw: 100%; --maxw-wide: 100%; --blur: 14px; }
  .ax-panel { border-radius: var(--r-md); }
  .ax-panel > :where(.ax-panel, .ax-mark) { border-radius: var(--r-sm); }
  .ax-grid  { --col-min: 100%; }
}

@media (prefers-reduced-transparency: reduce) {
  .ax-panel { background: var(--surface-2); backdrop-filter: none; -webkit-backdrop-filter: none; }
}

@media (forced-colors: active) {
  .ax-panel, .ax-mark, .ax-chip { border: 1px solid CanvasText; }
  @media (forced-colors: active) { .ax-cta { border: 1px solid ButtonText; } }
  .ax-mark :where(img, svg) { mix-blend-mode: normal; filter: none; }
}

@media print {
  html, body { background: #fff; color: #000; }
  .ax-section { padding-block: var(--s8); }
}

/* ══ EVERY HORIZONTAL ROW MOVES UNDER A FINGER ═══════════════════════════════
   Founder: "Should be able to scroll these with your fingers and fix it all up."
   Rails were built as overflow:auto and that is enough on a desktop trackpad, so
   it looked done. On a phone it is not: without `-webkit-overflow-scrolling` the
   row scrolls in a dead, un-momentumed way, and without `touch-action:pan-x` the
   browser spends the first ~10px deciding whether the gesture belongs to the row
   or to the page — which reads, to a thumb, as a row that will not move.

   So this is a LAW, applied to the shapes rather than to a class, because pages
   were authored by nine different agents and they do not share a class name.
   Anything that scrolls sideways gets momentum, gets the axis claimed up front,
   and gets proximity snapping (proximity, not mandatory — mandatory fights a
   fast flick and yanks the row back).                                        */
[class*="rail"], [class*="row"], [class*="strip"], [class*="scroller"],
[class*="track"], [class*="marquee"], [data-scroll-x], .ax-scroll-x {
  scrollbar-width: none;
}
[class*="rail"]::-webkit-scrollbar, [class*="row"]::-webkit-scrollbar,
[class*="strip"]::-webkit-scrollbar, [class*="scroller"]::-webkit-scrollbar,
[class*="track"]::-webkit-scrollbar, [data-scroll-x]::-webkit-scrollbar,
.ax-scroll-x::-webkit-scrollbar { height: 0; width: 0; }

.ax-scroll-x,
[data-scroll-x] {
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  touch-action: pan-x;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scroll-padding-inline: var(--gutter, 20px);
}
.ax-scroll-x > *,
[data-scroll-x] > * { scroll-snap-align: start; flex: 0 0 auto; }

/* The retrofit: any element the page already made scroll sideways inherits the
   same physics without needing to be re-authored. `overflow-x` cannot be matched
   by a selector, so this catches the two idioms the pages actually use. */
[style*="overflow-x:auto"], [style*="overflow-x: auto"],
[style*="overflow-x:scroll"], [style*="overflow-x: scroll"] {
  -webkit-overflow-scrolling: touch;
  touch-action: pan-x;
  overscroll-behavior-x: contain;
}

/* A MARQUEE MUST YIELD TO A THUMB. An auto-playing track that keeps animating
   under a finger is the single most frustrating thing on a phone — you push it
   and it pushes back. Pointer down pauses the animation and hands over control;
   `:hover` does the same on a desktop. The pause is on the ANIMATION, so the
   row keeps its place instead of snapping back to frame zero. */
.ax-scroll-x:active, [data-scroll-x]:active,
[class*="marquee"]:hover, [class*="marquee"]:active,
[class*="marquee"][data-held] {
  animation-play-state: paused;
}
[class*="marquee"] > *, [class*="marquee"] > * > * {
  animation-play-state: inherit;
}
