/* ==========================================================================
   OA Labs design system — baseline
   --------------------------------------------------------------------------
   Standalone: this file does NOT depend on /styles.css. Pages move onto it one
   at a time as the redesign lands.

   Order of authority (cascade layers, later wins regardless of specificity):
     tokens → modes → themes → base → layout → components → utilities

   So a utility such as .span-6 or .bleed-r always beats a component default,
   which is what makes nudging things on and off the grid predictable.

   THE GRID
     · 12 columns at every width. 12 divides by 2, 3, 4 and 6, so halves,
       thirds, quarters and sixths all land on real column lines.
     · Content is capped at --maxw (1320px = 12 × 88 + 11 × 24; 1512px at xl
       = 12 × 104 + 11 × 24) and sits inside
       fluid outer margins (--margin).
     · Four breakpoints:  sm ≤ 640 · md 641–1000 · lg 1001–1599 · xl ≥ 1600.
       lg is the base (no media query); the others override it. Utilities
       follow the same names: .span-6 (lg, and xl unless overridden),
       .xl-span-5, .md-span-8, .sm-span-12.
     · Vertical rhythm: --bl (8px). Spacing tokens are whole baselines; type
       line-heights snap to half-baselines (4px).

   Press G on any page that loads grid-overlay.js to see it.
   ========================================================================== */

@layer tokens, modes, themes, base, layout, components, utilities;

/* ---- fonts -------------------------------------------------------------
   PP Neue Corp (Pangram Pangram), three widths of the Medium: Normal for
   body and UI, Narrow for labels, Extended for display (see the --font-*
   tokens). Latin subsets, made from the OTFs in /fonts-src with pyftsubset.
   One face per width, registered for every weight, so <strong> or a bold
   request never gets a faked bold. */
@font-face { font-family: "PP Neue Corp"; font-style: normal; font-weight: 100 900; font-display: swap; src: url("/_fonts/PPNeueCorp-NormalMedium.woff2") format("woff2"); }
@font-face { font-family: "PP Neue Corp Narrow"; font-style: normal; font-weight: 100 900; font-display: swap; src: url("/_fonts/PPNeueCorp-NarrowMedium.woff2") format("woff2"); }
@font-face { font-family: "PP Neue Corp Extended"; font-style: normal; font-weight: 100 900; font-display: swap; src: url("/_fonts/PPNeueCorp-ExtendedMedium.woff2") format("woff2"); }

/* ---- registered properties --------------------------------------------
   Grid placement props must NOT inherit: otherwise a nested grid inside a
   .span-6 item would hand "6" down to all of its own children. syntax "*"
   keeps them untyped so var() fallbacks still work when they are unset. */
@property --span     { syntax: "*"; inherits: false; }
@property --start    { syntax: "*"; inherits: false; }
@property --span-md  { syntax: "*"; inherits: false; }
@property --start-md { syntax: "*"; inherits: false; }
@property --span-sm  { syntax: "*"; inherits: false; }
@property --start-sm { syntax: "*"; inherits: false; }
@property --span-xl  { syntax: "*"; inherits: false; }
@property --start-xl { syntax: "*"; inherits: false; }
@property --nx       { syntax: "*"; inherits: false; }
@property --ny       { syntax: "*"; inherits: false; }
@property --out-l    { syntax: "*"; inherits: false; }
@property --out-r    { syntax: "*"; inherits: false; }
/* Nav collapse progress: 0 = full 160px bar, 1 = collapsed. Typed as a number
   so GSAP (or a CSS transition) can tween it; everything derives from it. */
@property --nav-p    { syntax: "<number>"; inherits: true; initial-value: 0; }


/* ==========================================================================
   TOKENS
   ========================================================================== */
@layer tokens {
  :root {
    /* colour — neutrals (ground, ink, lines) live in MODES below */
    --phos:        #03ff67;   /* brand green (logo). Components use --accent, not this. */
    --phos-dim:    #2f9f5f;

    /* ACCENT + LOOK — the brand default. Each product overrides these with
       data-theme="<name>" (see THEMES). Components never read these
       directly: they use --key, which picks --accent in dark mode and
       --accent-deep in light mode. */
    --accent:      var(--phos);
    --accent-deep: #007a33;       /* light-mode key colour: white on it 5.5:1, it on the light ground 5:1 */
    --accent-deep-ink: #fff;      /* text on a deep fill */
    --accent-2:    var(--phos);   /* secondary highlight (Phosphor: orange) */
    --accent-ink:  #000;          /* text on an accent fill */
    --theme-bg:    #0a0a0a;       /* the product "look": a colour or a gradient */

    /* type */
    /* PP Neue Corp, three widths, all Medium:
         --font-body     Normal    body copy, and all the UI: nav, buttons,
                                   tabs, track names, subheads (mixed case)
         --font-label    Narrow    eyebrows, labels, spec lines (uppercase),
                                   and buttons (caps, square corners)
         --font-display  Extended  product titles, h1/h2, module titles
       --font-head is the UI voice (= Normal); kept so components read clearly. */
    --font-body:    "PP Neue Corp", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
    --font-head:    var(--font-body);
    --font-label:   "PP Neue Corp Narrow", "PP Neue Corp", ui-sans-serif, system-ui, sans-serif;
    --font-display: "PP Neue Corp Extended", "PP Neue Corp", ui-sans-serif, system-ui, sans-serif;
    --font-mono:    ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    --font-sub:     var(--font-body);
    --font-sub-weight: 500;
    --font-sub-track: 0;

    /* The type scale: seven steps, fixed sizes that step at the breakpoints
       (never scaled by the window). Desktop (lg) here; xl, tablet (md) and
       phone (sm) below.                                       xl  lg  md  sm */
    --t-display: 120px;                                        /* 144 120 88 56 */
    --t-h1:      80px;                                         /*  96  80 64 44 */
    --t-h2:      48px;                                         /*  56  48 40 32 */
    --t-module:  36px;                                         /*  40  36 32 28 */
    --t-h3:      22px;                                         /*  24  22 20 18 */
    --t-body:    17px;                                         /*  17  17 17 17 */
    --t-small:   14px;                                         /*  14  14 14 14 */
    /* Names for the same steps, by role. Not new sizes: use a step above
       before adding a size, and never a px value in a module. */
    --t-feature: var(--t-h2);      /* the feature header's product name */
    --t-quote:   var(--t-module);  /* .t-statement */
    --t-lede:    var(--t-h3);      /* the reading text under a heading */
    --t-h4:      var(--t-body);    /* small titles: cards, FAQ, features */
    --t-label:   var(--t-small);   /* Narrow labels; 14 reads like the old 12 */
    --track:     0.04em;          /* uppercase Narrow labels and eyebrows only */

    /* rhythm + spacing (whole baselines) */
    --bl:    8px;
    --half:  4px;
    --s-1:   8px;
    --s-2:   16px;
    --s-3:   24px;
    --s-4:   32px;
    --s-5:   40px;
    --s-6:   48px;
    --s-8:   64px;
    --s-10:  80px;
    --s-12:  96px;
    --s-14:  112px;
    --s-16:  128px;
    --s-18:  144px;
    --s-20:  160px;

    /* grid */
    --cols:   12;
    --maxw:   1320px;                               /* content width */
    --margin: clamp(16px, 4vw, 64px);               /* outer page margin */
    --gap:    24px;                                 /* column gutter */
    --section-y: var(--s-14);
    --section-inset: var(--s-6);                    /* inner padding of a filled section */

    /* Derived. 100cqw is the width of <main class="page"> (a size container),
       which unlike 100vw excludes the scrollbar. Outside main it falls back to
       the viewport. Do not make a grid's ancestors size containers, or these
       measure the wrong box. */
    --content-w: min(var(--maxw), 100cqw - 2 * var(--margin));
    --col-w:     calc((var(--content-w) - (var(--cols) - 1) * var(--gap)) / var(--cols));
    --col-pitch: calc(var(--col-w) + var(--gap));    /* one column + one gutter */
    --bleed:     calc((100cqw - var(--content-w)) / 2);  /* content edge → page edge */

    /* RULES NEVER RUN EDGE TO EDGE. Every divider is the width of the grid
       (column 1 start → column 12 end), inset by the outer margin. --rule is a
       background you can drop on any full-width element to draw one on top:
         background: var(--rule);  */
    --rule-w: min(var(--maxw), 100% - 2 * var(--margin));
    --rule: linear-gradient(var(--line), var(--line)) top center / var(--rule-w) 1px no-repeat;

    /* shape + motion */
    --radius: 0;                  /* square: buttons, cards, panels, fields, modals, boxes */
    --radius-shot: 16px;          /* the one exception: interface screenshots */
    --ease:   cubic-bezier(.2, .7, .2, 1);

    /* nav */
    --nav-h-open:   160px;
    --nav-h-closed: 72px;
    --nav-logo-open:   48px;
    --nav-logo-closed: 32px;
  }

  @media (min-width: 1600px) {
    :root {
      --maxw:      1512px;           /* 12 × 104 + 11 × 24 */
      --section-y: var(--s-18);
      --t-display: 144px;
      --t-h1:      96px;
      --t-h2:      56px;
      --t-h3:      24px;
      --t-module:  40px;
    }
  }
  @media (max-width: 1000px) {
    :root {
      --section-y: var(--s-10);
      --t-display: 88px;
      --t-h1:      64px;
      --t-h2:      40px;
      --t-h3:      20px;
      --t-module:  32px;
    }
  }
  @media (max-width: 640px) {
    :root {
      --gap: 16px;
      --section-y: var(--s-8);
      --section-inset: var(--s-3);
      --t-display: 56px;
      --t-h1:      44px;
      --t-h2:      32px;
      --t-h3:      18px;
      --t-module:  28px;
      /* 160px would eat a third of a phone screen */
      --nav-h-open:   96px;
      --nav-h-closed: 64px;
      --nav-logo-open:   40px;
      --nav-logo-closed: 32px;
    }
  }
}


/* ==========================================================================
   MODES — dark (default) and light
   data-mode on <html> sets the whole site; on any element it sets just that
   part, so a light page can hold a dark section and vice versa:
     <html data-mode="light">          light site
     <html data-mode="auto">           follows the visitor's OS setting
     <section data-mode="dark">        stays dark inside a light page
   Product looks (--theme-bg) are usually dark, so a filled section or a
   .stage on one should carry data-mode="dark" to keep its text light.
   ========================================================================== */
@layer modes {
  :root, [data-mode="dark"] {
    color-scheme: dark;
    --mode-mix:    0%;         /* 0% = dark, 100% = light; drives the key colour */
    --ui-weight:   500;        /* everything is Medium; kept as a hook */
    --ground:      #000;
    --panel:       #0a0a0a;
    --panel-2:     #121212;
    --line:        #1f1f1f;
    --line-strong: #333;
    --ink:         #ededed;
    --ink-soft:    #adadad;    /* 9.3:1 on black */
    --ink-faint:   #8a8a8a;    /* 6.1:1 on black */
    --shadow:      rgb(0 0 0 / 0.6);
    --page-bg:     var(--ground);
  }
  [data-mode="light"] {
    color-scheme: light;
    --mode-mix:    100%;
    --ui-weight:   500;
    --ground:      #f5f5f2;
    --panel:       #ffffff;
    --panel-2:     #ebebe6;
    --line:        #dcdcd5;
    --line-strong: #b3b3ab;
    --ink:         #0d0d0d;
    --ink-soft:    #474744;    /* 8.9:1 on the light ground */
    --ink-faint:   #6b6b66;    /* 5.0:1 */
    --shadow:      rgb(0 0 0 / 0.12);
    --page-bg:     var(--ground);
  }
  @media (prefers-color-scheme: light) {
    [data-mode="auto"] {
      color-scheme: light;
      --mode-mix:    100%;
      --ui-weight:   500;
      --ground:      #f5f5f2;
      --panel:       #ffffff;
      --panel-2:     #ebebe6;
      --line:        #dcdcd5;
      --line-strong: #b3b3ab;
      --ink:         #0d0d0d;
      --ink-soft:    #474744;
      --ink-faint:   #6b6b66;
      --shadow:      rgb(0 0 0 / 0.12);
      --page-bg:     var(--ground);
    }
  }
  /* colour is inherited as a resolved value, so a nested mode must restate it */
  [data-mode] { color: var(--ink); }
}


/* ==========================================================================
   THEMES
   Put data-theme="<name>" on <body> to theme a whole page, or on any element
   (a section, a card) to theme just that part. A one-off colour works too:
     <section data-theme style="--accent:#ff5a1f; --theme-bg:#1a0c05">
   ========================================================================== */
@layer themes {
  /* THE KEY COLOUR — what components actually use. Themes set --accent (for
     dark) and --accent-deep (for light); the key picks between them by mode,
     for fills AND text, so bright green never lands on white.
       --key      buttons, bars, eyebrows, hovers, focus rings
       --key-ink  text on a --key fill (black on bright, white on deep)
       --key-2    the secondary highlight, darkened in light mode
       --key-soft / --key-line   tints for backgrounds and hairlines
     Re-declared on every themed or moded element so they follow the LOCAL
     theme and mode rather than the page's. */
  :root, [data-theme], [data-mode] {
    --key:      color-mix(in srgb, var(--accent-deep) var(--mode-mix), var(--accent));
    --key-ink:  color-mix(in srgb, var(--accent-deep-ink) var(--mode-mix), var(--accent-ink));
    --key-2:    color-mix(in srgb, #000 calc(var(--mode-mix) * 0.45), var(--accent-2));
    --key-soft: color-mix(in srgb, var(--key) 8%, transparent);
    --key-line: color-mix(in srgb, var(--key) 32%, transparent);
    /* errors: warm red, deepened on light */
    --danger:   color-mix(in srgb, #c4281c var(--mode-mix), #ff6b5e);
    --danger-soft: color-mix(in srgb, var(--danger) 8%, transparent);
    --danger-line: color-mix(in srgb, var(--danger) 40%, transparent);
    /* the hero "glow": the product colour at the top, fading into the page */
    --theme-glow: linear-gradient(180deg,
      color-mix(in srgb, var(--accent) 40%, var(--ground)) 0%,
      color-mix(in srgb, var(--accent) 14%, var(--ground)) 34%,
      transparent 78%);
  }

  /* Phosphor — brand green for type and buttons (as in the hero mock), the
     plugin's grass-green for its gradient. Greens picked by eye from the mocks;
     swap in exact values when you have them. */
  /* One product line, one green: the synth makes it (Phosphor), Nitrate
     (and Nitrate Age) damages it, Rayl delivers it. */
  [data-theme="phosphor"], [data-theme="nitrate"], [data-theme="rayl"] {
    --accent:     #03ff67;
    --accent-deep:#007a33;
    --accent-2:   #f2a33a;
    --accent-ink: #000;
    --theme-bg:   linear-gradient(180deg, #37832a 0%, #1d4c17 42%, #081208 100%);
    --theme-feature: linear-gradient(180deg, #0f3a19 0%, #17521f 38%, #2a9f45 100%);
    --theme-glow: linear-gradient(180deg, #27721d 0%, #123d0e 34%, transparent 76%);
  }
  [data-mode="light"] :is([data-theme="phosphor"], [data-theme="nitrate"], [data-theme="rayl"]),
  :is([data-theme="phosphor"], [data-theme="nitrate"], [data-theme="rayl"])[data-mode="light"] {
    --theme-glow: linear-gradient(180deg, #cfe8c6 0%, #e6f1df 34%, transparent 76%);
  }

  /* Kudzu — its own line (growth, not decay): grey, after its white mark. */
  [data-theme="kudzu"] {
    --accent:     #ece8df;
    --accent-deep:#3a3833;
    --accent-2:   #b9b4a8;
    --accent-ink: #000;
    --theme-bg:   linear-gradient(180deg, #2b2a26 0%, #161512 42%, #0a0a09 100%);
    --theme-feature: linear-gradient(180deg, #161512 0%, #26241f 38%, #4a473f 100%);
    --theme-glow: linear-gradient(180deg, #2b2a26 0%, #141310 34%, transparent 76%);
  }
  [data-mode="light"] [data-theme="kudzu"], [data-theme="kudzu"][data-mode="light"] {
    --theme-glow: linear-gradient(180deg, #e6e3dc 0%, #f0eee9 34%, transparent 76%);
  }

  /* Silex — the next instrument line takes the orange: the spark off
     struck flint, a dark brown-to-orange for its panels. */
  [data-theme="silex"] {
    --accent:     #ff7a1a;
    --accent-deep:#b34700;
    --accent-2:   #ffc23d;
    --accent-ink: #000;
    --theme-bg:   linear-gradient(180deg, #6b2c0a 0%, #3a1806 42%, #120803 100%);
    --theme-feature: linear-gradient(180deg, #2a1205 0%, #4f2109 38%, #b8521a 100%);
    --theme-glow: linear-gradient(180deg, #6b2c0a 0%, #2e1305 34%, transparent 76%);
  }
  [data-mode="light"] [data-theme="silex"], [data-theme="silex"][data-mode="light"] {
    --theme-glow: linear-gradient(180deg, #f6d9c4 0%, #f7eadf 34%, transparent 76%);
  }

  /* PLACEHOLDER — shows a solid-colour look. Not a real product colour. */
  [data-theme="sample-solid"] {
    --accent:     #ff8a3d;
    --accent-deep:#b34700;
    --accent-2:   #ffd23d;
    --accent-ink: #000;
    --theme-bg:   #2a1407;
  }
}


/* ==========================================================================
   BASE
   ========================================================================== */
@layer base {
  *, *::before, *::after { box-sizing: border-box; }

  html {
    -webkit-text-size-adjust: 100%;
    scrollbar-gutter: stable;          /* no layout jump when the menu locks scroll */
    background: var(--ground);
  }

  body {
    margin: 0;
    background: var(--page-bg);
    color: var(--ink);
    font-family: var(--font-body);
    font-weight: 300;
    font-size: var(--t-body);
    line-height: 24px;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
  }

  /* <main class="page"> is the reference box for bleeds (see --bleed). */
  .page {
    position: relative;
    container-type: inline-size;
    padding-top: var(--nav-h-open);
    overflow-x: clip;                  /* a nudge can never cause sideways scroll */
  }

  img, svg, video { display: block; max-width: 100%; }
  a { color: inherit; }
  button { font: inherit; color: inherit; }
  :focus-visible { outline: 2px solid var(--key); outline-offset: 3px; }
  /* any #link lands its target just below the (collapsed) bar, not under it */
  [id] { scroll-margin-top: var(--nav-h-closed); }

  /* Headlines: Extended for h1/h2 and display, Normal for h3/h4. Line-heights
     round to the half-baseline so fluid sizes
     still sit on the rhythm. The first line-height is the fallback for
     browsers without round(). */
  h1, h2, h3, h4, .t-display, .t-h1, .t-h2, .t-h3, .t-h4 {
    font-family: var(--font-display);
    font-weight: 500;
    margin: 0;
    text-wrap: balance;
  }
  /* the mixed-case tier */
  h3, h4, .t-h3, .t-h4 {
    font-family: var(--font-sub);
    font-weight: var(--font-sub-weight);
    letter-spacing: var(--font-sub-track);
  }
  .t-display { font-size: var(--t-display); letter-spacing: -0.02em; line-height: 0.95; line-height: round(nearest, 0.95em, var(--half)); }
  h1, .t-h1  { font-size: var(--t-h1); letter-spacing: -0.015em; line-height: 1; line-height: round(nearest, 1em, var(--half)); }
  h2, .t-h2  { font-size: var(--t-h2); letter-spacing: -0.01em; line-height: 1.05; line-height: round(up, 1.05em, var(--half)); }
  h3, .t-h3  { font-size: var(--t-h3); letter-spacing: -0.01em; line-height: 1.15; line-height: round(up, 1.15em, var(--half)); }
  h4, .t-h4  { font-size: var(--t-h4); line-height: 24px; line-height: round(up, 1.35em, var(--half)); }
  /* module title: the uppercase Extended heading each page module opens with
     (AUDITION, the hero's product + USP) */
  .t-module {
    font-family: var(--font-display);
    font-weight: 500;
    font-size: var(--t-module);
    line-height: 1;
    letter-spacing: 0;
    text-transform: uppercase;
  }

  p { margin: 0; text-wrap: pretty; }
  .t-lede  { font-size: var(--t-lede); line-height: 32px; line-height: round(up, 1.45em, var(--half)); color: var(--ink); }
  .t-body  { font-size: var(--t-body); line-height: 24px; }
  .t-small { font-size: var(--t-small); line-height: 20px; }
  .t-label {
    font-family: var(--font-label);
    font-weight: var(--ui-weight);
    font-size: var(--t-label);
    line-height: 16px;
    letter-spacing: var(--track);
    text-transform: uppercase;
  }
  strong, b { font-weight: 700; }
  /* links inside running text: bold, like every other clickable thing */
  p a:not([class]), li a:not([class]) {
    font-weight: 700;
    color: var(--ink);
    text-decoration-color: var(--key);
    text-underline-offset: 3px;
  }
  p a:not([class]):hover, li a:not([class]):hover { color: var(--key); }

  /* Fine print: legal lines, footnotes, privacy links. Deliberately quiet so
     it never competes with the action above it: its links keep the text's
     colour and weight, with a hairline underline (the exception to bold links). */
  .t-fine { color: var(--ink-soft); font-size: var(--t-small); line-height: 20px; }
  .t-fine a:not([class]) {
    color: inherit;
    font-weight: inherit;
    text-decoration-color: var(--line-strong);
    text-decoration-thickness: 1px;
    text-underline-offset: 3px;
  }
  .t-fine a:not([class]):hover { color: var(--ink); text-decoration-color: currentColor; }
  code, kbd {
    font-family: var(--font-mono);
    font-size: 0.875em;
    padding: 1px 5px;
    border-radius: var(--radius);
    background: var(--panel-2);
    color: var(--key);
  }

  .upper { text-transform: uppercase; }
  .soft  { color: var(--ink-soft); }
  .faint { color: var(--ink-faint); }
  .accent { color: var(--key); }
  .accent-2 { color: var(--key-2); }
  .measure { max-width: 60ch; }

  .visually-hidden {
    position: absolute !important; width: 1px; height: 1px; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  }

  /* [hidden] must win over any component that sets its own display. */
  [hidden] { display: none !important; }
}


/* ==========================================================================
   LAYOUT — container, grid, section, stack
   ========================================================================== */
@layer layout {
  .container {
    width: 100%;
    max-width: calc(var(--maxw) + 2 * var(--margin));
    margin-inline: auto;
    padding-inline: var(--margin);
  }

  /* ---- section ----------------------------------------------------------
     Controls (set inline or in a page stylesheet; all optional):
       --section-pt / --section-pb   top / bottom padding     (--section-y)
       --section-ink                 text colour              (--ink for its mode)
     Mode: data-mode="dark" / "light" on the section (see MODES).
       --section-rule                divider above            (grid-width rule; none on the first)
     With .section--fill, also:
       --section-bg                  colour or gradient       (--theme-bg)
       --section-bg-w                width of the fill        (grid width; 100% = edge to edge)
       --section-radius              corners of the fill      (--radius)
       --section-inset               inner padding            (48px, 24px on phones)
     Accent: data-theme on the section (see THEMES). */
  .section {
    --section-rule: none;
    position: relative;
    isolation: isolate;
    padding-block: var(--section-pt, var(--section-y)) var(--section-pb, var(--section-y));
    color: var(--section-ink, var(--ink));
    background: var(--section-rule);
  }
  .section + .section, .hero + .section { --section-rule: var(--rule); }

  /* Filled: the background is a panel the width of the grid, never edge to
     edge. Content inside is inset so it does not touch the panel's edge. */
  .section.section--fill { background: none; }
  .section--fill::before {
    content: "";
    position: absolute;
    z-index: -1;
    top: 0;
    bottom: 0;
    left: 50%;
    width: var(--section-bg-w, var(--rule-w));
    translate: -50% 0;
    background: var(--section-bg, var(--theme-bg));
    border-radius: var(--section-radius, var(--radius));
  }
  .section--fill > .container { padding-inline: calc(var(--margin) + var(--section-inset)); }
  /* no divider directly under a panel; two panels in a row get a gutter */
  .section--fill + .section { --section-rule: none; }
  .section--fill + .section--fill { margin-top: var(--gap); }

  .section.section--no-rule { --section-rule: none; }
  .section--tight { --section-pt: var(--s-8); --section-pb: var(--s-8); }

  /* a standalone divider: <hr class="rule"> inside a .container */
  .rule { height: 1px; margin: 0; border: 0; background: var(--line); }

  /* a standalone divider: <hr class="rule"> inside a .container */
  .rule { height: 1px; margin: 0; border: 0; background: var(--line); }

  /* ---- the 12-column grid ----------------------------------------------
     Every direct child spans all 12 unless told otherwise:
       class:  .span-4  .start-3  .md-span-6  .sm-span-12   (see utilities)
       inline: style="--span:5; --start:2; --span-md:8; --span-sm:12"
     md falls back to the desktop value; sm falls back to full width. */
  .grid {
    display: grid;
    grid-template-columns: repeat(var(--cols), minmax(0, 1fr));
    column-gap: var(--gap);
    row-gap: var(--row-gap, var(--gap));
    align-items: start;
  }
  .grid > *, .subgrid > * {
    grid-column: var(--start, auto) / span var(--span, 12);
    min-width: 0;
  }
  @media (min-width: 1600px) {
    .grid > *, .subgrid > * { grid-column: var(--start-xl, var(--start, auto)) / span var(--span-xl, var(--span, 12)); }
  }
  @media (max-width: 1000px) {
    .grid > *, .subgrid > * { grid-column: var(--start-md, var(--start, auto)) / span var(--span-md, var(--span, 12)); }
  }
  @media (max-width: 640px) {
    .grid > *, .subgrid > * { grid-column: var(--start-sm, auto) / span var(--span-sm, 12); }
  }

  /* A child that re-uses the parent's column lines, so its own children
     align to the master grid. Give it a span like any other item; its
     children take spans the same way (and are clamped to its width). */
  .subgrid {
    display: grid;
    grid-template-columns: subgrid;
    column-gap: var(--gap);
    row-gap: var(--row-gap, var(--gap));
    align-items: start;
  }

  /* ---- presets: one class snaps equal items into 2, 3, 4 or 6 across ----
     Collapses: 4-up → 2-up at md → 1-up at sm; 3-up → 1-up at sm; 6-up →
     3-up at md → 2-up at sm. */
  .cols-2 > * { --span: 6; --span-sm: 12; }
  .cols-3 > * { --span: 4; --span-sm: 12; }
  .cols-4 > * { --span: 3; --span-md: 6; --span-sm: 12; }
  .cols-6 > * { --span: 2; --span-md: 4; --span-sm: 6; }

  .grid--flush  { --gap: 0px; }
  .grid--center { align-items: center; }
  .grid--stretch { align-items: stretch; }

  /* Vertical flow on the rhythm: spacing between children in baselines.
     style="--stack: 4" = 32px. */
  .stack > * + * { margin-top: calc(var(--stack, 3) * var(--bl)); }
}


/* ==========================================================================
   COMPONENTS
   ========================================================================== */
@layer components {

  /* ---- button ---------------------------------------------------------- */
  .btn {
    --btn-h: 48px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--s-1);
    height: var(--btn-h);
    padding-inline: var(--s-3);
    font-family: var(--font-label);   /* Narrow Medium, caps, square */
    font-size: var(--t-small);
    font-weight: 500;              /* the family is set in Medium throughout */
    /* the page's thin antialiasing (see body) starves small Medium caps;
       the default rendering gives them back their weight */
    -webkit-font-smoothing: auto;
    -moz-osx-font-smoothing: auto;
    letter-spacing: var(--track);
    text-transform: uppercase;
    line-height: 1;
    white-space: nowrap;
    text-decoration: none;
    border: 1px solid transparent;
    border-radius: 0;
    cursor: pointer;
    transition: background-color 160ms var(--ease), color 160ms var(--ease), border-color 160ms var(--ease);
  }
  .btn--primary   { background: var(--key); color: var(--key-ink); border-color: var(--key); }
  .btn--primary:hover { background: transparent; color: var(--key); }
  /* the outline is a control's edge, not a divider: --ink-faint (6:1 on the
     dark ground, 5:1 on the light), not a hairline token, so it never
     falls under the 3:1 a button's boundary needs */
  .btn--secondary { background: transparent; color: var(--ink); border-color: var(--ink-faint); }
  .btn--secondary:hover { border-color: var(--ink); }
  /* on a coloured panel the hairline disappears: a stronger outline and a
     faint dark backing, so the secondary button still reads as a button */
  :is(.section--fill, .fheader, .hero__panel ~ *, .callout, .promo--bar) .btn--secondary {
    border-color: color-mix(in srgb, var(--ink) 60%, transparent);
    background: color-mix(in srgb, var(--ground) 30%, transparent);
  }
  :is(.section--fill, .fheader, .hero__panel ~ *, .callout, .promo--bar) .btn--secondary:hover { border-color: var(--ink); }
  .btn--ghost     { background: transparent; color: var(--ink); padding-inline: 0; height: auto; }
  .btn--ghost::after { content: "→"; transition: translate 160ms var(--ease); }
  .btn--ghost:hover { color: var(--key); }
  .btn--ghost:hover::after { translate: 4px 0; }
  .btn--sm { --btn-h: 40px; padding-inline: var(--s-2); font-size: var(--t-small); }
  .btn--block { width: 100%; }

  /* ---- tabs: a row of filters; only the selected one gets a pill, in the
     key colour with key-ink text ----
     <div class="tabs" role="group" aria-label="Filter presets">
       <button class="tab" aria-pressed="true">All</button> …
     </div>
     Side padding (--tab-pad) is fitted to the space by audition.js, 5–22px;
     the row scrolls sideways only if even 5px will not fit. To switch
     panels with it (one shown at a time), add data-tabs and give each tab
     aria-controls: see tabs.js. */
  .tabs {
    display: flex;
    align-items: stretch;
    max-width: 100%;
    height: 32px;
    overflow-x: auto;
    scrollbar-width: none;
  }
  .tabs::-webkit-scrollbar { display: none; }
  .tab {
    flex: none;
    padding: 0 var(--tab-pad, 12px);
    background: none;
    border: 0;
    border-radius: var(--radius);
    color: color-mix(in srgb, var(--ink) 85%, var(--ground));
    font-family: var(--font-label);
    font-weight: var(--ui-weight);
    font-size: var(--t-small);
    line-height: 1;
    letter-spacing: var(--track);
    text-transform: uppercase;
    white-space: nowrap;
    cursor: pointer;
    transition: color 160ms var(--ease), background-color 160ms var(--ease);
  }
  .tab:hover { color: var(--ink); }
  .tab[aria-pressed="true"] {
    background: var(--key);
    color: var(--key-ink);
  }

  /* ---- eyebrow / field label ---------------------------------------------
     One spec for both, everywhere: same size, weight and tracking. Only the
     colour differs (eyebrows take the key colour; field labels stay soft).
     <p class="eyebrow">Free · v1.2.0</p>   <label class="field-label">Email</label> */
  .eyebrow, .field-label {
    display: block;
    font-family: var(--font-label);
    font-weight: var(--ui-weight);
    font-size: var(--t-label);
    line-height: 16px;
    letter-spacing: var(--track);
    text-transform: uppercase;
    color: var(--key);
  }
  .field-label { margin-bottom: var(--s-1); color: var(--ink-soft); }
  .eyebrow a { color: inherit; text-underline-offset: 3px; }
  .eyebrow a:hover { color: var(--ink); }
  /* One gap from an eyebrow to the headline under it, everywhere: the same
     24 a .section-head's grid gives it. (In a grid the row gap already
     does it, so no margin there.) */
  .eyebrow + :is(h1, h2, h3, .t-module, .t-statement, .t-h1, .t-h2, .t-h3) { margin-top: var(--s-3); }
  .grid > .eyebrow + * { margin-top: 0; }

  /* ---- section head: eyebrow, title, lede on the grid ----------------
     <div class="grid section-head">
       <p class="eyebrow">…</p> <h2 class="span-8">…</h2> <p class="t-lede span-6">…</p>
     </div> */
  .section-head { --row-gap: var(--s-3); margin-bottom: var(--s-8); }
  .section-head .t-lede { color: var(--ink-soft); }

  /* ---- card ------------------------------------------------------------ */
  .card {
    display: flex;
    flex-direction: column;
    gap: var(--s-2);
    padding: var(--s-3);
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    min-width: 0;
  }
  .card__title {
    font-family: var(--font-sub);
    font-weight: var(--font-sub-weight);
    font-size: var(--t-h4);
    line-height: 24px;
    letter-spacing: var(--font-sub-track);
  }
  .card__body  { color: var(--ink-soft); font-size: var(--t-small); line-height: 20px; }
  .card__foot  { margin-top: auto; padding-top: var(--s-1); }
  .card--flush { padding: 0; overflow: hidden; }
  /* a card that is also a subgrid keeps grid display (layout sets it, but
     .card's flex would otherwise win in this later layer) */
  .card.subgrid { display: grid; gap: 0 var(--gap); }
  .card--flush > :not(.ph) { padding-inline: var(--s-3); }
  .card--flush > :last-child { padding-bottom: var(--s-3); }

  /* ---- field: text input -------------------------------------------------
     <input class="field" type="email" …>   aria-invalid="true" marks an error */
  .field {
    width: 100%;
    height: 48px;
    padding: 0 var(--s-2);
    background: var(--ground);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius);
    color: var(--ink);
    font-family: var(--font-body);
    font-size: var(--t-body);
    line-height: 24px;
    transition: border-color 160ms var(--ease), box-shadow 160ms var(--ease);
  }
  .field::placeholder { color: var(--ink-faint); }
  .field:hover { border-color: var(--ink-faint); }
  .field:focus { outline: none; border-color: var(--key); box-shadow: 0 0 0 1px var(--key); }
  .field[aria-invalid="true"] { border-color: var(--danger); box-shadow: 0 0 0 1px var(--danger); }

  /* ---- dialog: the one modal frame -----------------------------------------
     <dialog class="dialog">
       <div class="dialog__head"><h2 class="t-module">…</h2>
         <button class="dialog__close" aria-label="Close"><svg>…</svg></button></div>
       …
     </dialog>
     --dialog-w sets the width; full-screen on phones. Opened with showModal(),
     so it sits above the nav and everything else. --backdrop (0–1) fades the
     backdrop in and out. */
  .dialog {
    --backdrop: 1;
    width: min(var(--dialog-w, 560px), 100% - 2 * var(--margin));
    max-width: none;
    max-height: calc(100dvh - 2 * var(--s-6));
    padding: 0;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--panel);
    color: var(--ink);
    overflow: hidden;
    translate: 0 var(--dialog-y, 0px);   /* dialog.js moves it through this, never transform */
  }
  .dialog[open] { display: flex; flex-direction: column; }
  .dialog::backdrop {
    background: rgb(0 0 0 / calc(0.8 * var(--backdrop, 1)));
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
  }
  .dialog__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-2);
    padding: var(--s-4) var(--s-4) var(--s-3);
  }
  .dialog__close {
    display: grid;
    place-items: center;
    flex: none;
    width: 40px;
    height: 40px;
    margin-right: -8px;
    padding: 0;
    background: none;
    border: 0;
    color: var(--ink);
    cursor: pointer;
  }
  .dialog__close svg { width: 24px; height: 24px; fill: none; stroke: currentColor; stroke-width: 2; }
  .dialog__close:hover { color: var(--key); }
  @media (max-width: 640px) {
    .dialog {
      width: 100%;
      height: 100dvh;
      max-height: none;
      border: 0;
      border-radius: 0;
    }
    .dialog__head { padding-inline: var(--margin); }
  }

  /* A sheet: a dialog with a title head and a body that scrolls (modal.js).
     <dialog class="dialog dialog--sheet" id="…">
       <div class="dialog__head">…</div> <div class="dialog__body">…</div>
     </dialog>
     --dialog-w 1120 (the full spec sheet); .dialog--narrow is 640. */
  .dialog--sheet { --dialog-w: 1120px; }
  .dialog--narrow { --dialog-w: 640px; }
  /* a fixed frame: switching tabs changes what scrolls inside, never the size */
  .dialog--fixed { height: min(720px, 100dvh - 2 * var(--s-6)); }
  /* a tab row between the head and the body; it stays put while the body scrolls */
  .dialog__tabs { padding: var(--s-2) var(--s-4); border-bottom: 1px solid var(--line); }
  /* one version in "What's new" */
  .release__date { margin-bottom: var(--s-2); color: var(--ink-soft); }
  .release__note { margin-top: var(--s-3); }
  .dialog--sheet > .dialog__head { align-items: flex-start; border-bottom: 1px solid var(--line); }
  .dialog--sheet .eyebrow { margin-bottom: var(--s-1); }
  .dialog__body {
    flex: 1;
    min-height: 0;
    padding: var(--s-4);
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  @media (max-width: 640px) {
    .dialog__body { padding: var(--s-3) var(--margin) var(--s-6); }
    .dialog__tabs { padding-inline: var(--margin); }
    .dialog--fixed { height: 100dvh; }
  }

  /* ---- placeholder (block-out media) ----------------------------------- */
  .ph {
    position: relative;
    display: grid;
    place-items: center;
    aspect-ratio: var(--ratio, 16 / 9);
    min-height: 0;
    background:
      repeating-linear-gradient(135deg, color-mix(in srgb, var(--ink) 4%, transparent) 0 1px, transparent 1px 12px),
      var(--panel);
    border: 1px dashed var(--line-strong);
    border-radius: var(--radius);
    color: var(--ink-faint);
    font-family: var(--font-label);
    font-weight: var(--ui-weight);
    font-size: var(--t-label);
    letter-spacing: var(--track);
    text-transform: uppercase;
    text-align: center;
    padding: var(--s-2);
  }
  .ph--tall   { --ratio: 4 / 5; }
  .ph--square { --ratio: 1; }
  .ph--wide   { --ratio: 21 / 9; }

  /* ---- stage: an inset panel in the product's look ---------------------
     For hero imagery (the plugin UI sitting on Phosphor's green gradient).
     Controls: --stage-bg (--theme-bg), --stage-pad (48px). */
  .stage {
    position: relative;
    padding: var(--stage-pad, var(--s-6));
    background: var(--stage-bg, var(--theme-bg));
    border-radius: var(--radius);
    overflow: hidden;
  }
  @media (max-width: 640px) { .stage { --stage-pad: var(--s-3); } }

  /* ================================================================
     NAV
     160px bar that collapses to 72px on scroll. Everything that changes
     (height, logo size, background) derives from one number, --nav-p,
     which nav.js tweens with GSAP. The menu panel and the grid overlay read
     the same variable, so it lives on :root.
     ================================================================ */
  /* nav.js sets --nav-p on these two only (never on <html>, which would
     restyle the whole page every scroll frame); each works out its height */
  .nav, .nav-menu {
    --nav-h: calc(var(--nav-h-open) - var(--nav-p) * (var(--nav-h-open) - var(--nav-h-closed)));
  }
  .nav {
    position: fixed;
    inset: 0 0 auto 0;
    z-index: 100;
    height: var(--nav-h);
    display: flex;
    align-items: center;
    isolation: isolate;
    /* a product bar (.pnav) pushes the nav off the top: --pnav-p 0 → 1 */
    translate: 0 calc(var(--pnav-p, 0) * -100%);
  }
  /* bottom rule: grid width, inset by the outer margin (never edge to edge) */
  .nav::after {
    content: "";
    position: absolute;
    bottom: 0;
    left: 50%;
    width: var(--rule-w);
    height: 1px;
    translate: -50% 0;
    background: var(--line);
  }
  /* The blur lives on a pseudo-element: backdrop-filter on .nav itself would
     trap position:fixed children. */
  .nav::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    background: color-mix(in srgb, var(--ground) calc((0.55 + var(--nav-p) * 0.3) * 100%), transparent);
    -webkit-backdrop-filter: blur(12px);
    backdrop-filter: blur(12px);
  }
  .nav.is-menu-open::before { background: var(--ground); }

  /* Edges sit on the grid (column 1 start, column 12 end); the links are
     centred on the grid's centre line. */
  .nav__inner {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: var(--s-3);
  }

  .nav__brand {
    justify-self: start;
    display: inline-flex;
    align-items: center;
    gap: var(--s-2);
    text-decoration: none;
    color: var(--ink);
  }
  .nav__logo {
    --size: calc(var(--nav-logo-open) - var(--nav-p) * (var(--nav-logo-open) - var(--nav-logo-closed)));
    width: var(--size);
    height: var(--size);
  }
  /* the wordmark stays Normal; the links take the button voice below */
  .nav__word, .nav__link {
    font-family: var(--font-head);
    font-weight: var(--ui-weight);
    font-size: var(--t-small);
    line-height: 16px;
    letter-spacing: 0;
    white-space: nowrap;
  }
  .nav__link {
    font-family: var(--font-label);
    font-size: var(--t-body);
    letter-spacing: var(--track);
    text-transform: uppercase;
  }

  .nav__links { display: flex; align-items: center; gap: var(--s-5); }
  .nav__link {
    display: inline-flex;
    align-items: center;
    gap: var(--s-1);
    padding: var(--s-1) 0;
    background: none;
    border: 0;
    color: var(--ink);
    text-decoration: none;
    cursor: pointer;
    transition: color 160ms var(--ease);
  }
  .nav__link:hover, .nav__link[aria-expanded="true"], .nav__link[aria-current="page"] { color: var(--key); }
  .nav__caret { width: 10px; height: 10px; fill: currentColor; transition: rotate 220ms var(--ease); }
  .nav__link[aria-expanded="true"] .nav__caret { rotate: 180deg; }

  .nav__actions {
    justify-self: end;
    display: flex;
    align-items: center;
    gap: var(--s-2);
  }

  /* Header buy button: waits off-stage until the page's own [data-cta] scrolls
     under the bar, then GSAP lifts it into this slot. Its space is always
     reserved so the hamburger never shifts. Without JS it simply shows. */
  .nav--enhanced .nav__cta { visibility: hidden; opacity: 0; }

  /* ---- products dropdown ---- */
  .nav__item { position: relative; }
  .nav__drop {
    position: absolute;
    z-index: 1;                       /* above the nav's bottom rule (.nav::after), which paints later */
    top: calc(100% + var(--s-2));
    left: 50%;
    translate: -50% 0;
    min-width: 264px;
    padding: var(--s-1);
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: 0 24px 48px var(--shadow);
  }
  /* invisible bridge over the gap so the pointer can travel down to the list */
  .nav__drop::before { content: ""; position: absolute; left: 0; right: 0; bottom: 100%; height: calc(var(--s-2) + 2px); }
  .nav__drop a {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--s-3);
    padding: var(--s-1) var(--s-2);
    border-radius: var(--radius);
    color: var(--ink);
    text-decoration: none;
    font-family: var(--font-head);
    font-weight: var(--ui-weight);
    font-size: var(--t-small);
    line-height: 24px;
    letter-spacing: 0;
  }
  .nav__drop a:hover, .nav__drop a:focus-visible { background: var(--panel-2); color: var(--key); }
  .nav__drop .nav__all { margin-top: var(--s-1); border-top: 1px solid var(--line); border-radius: 0; color: var(--ink-soft); font-size: var(--t-small); }
  /* Categories inside Products (Effects, Instruments) sit side by side, one
     column each, divided by a hairline; a long category (.nav__cat--split,
     set by nav.js) runs into two columns, so the menu widens rather than
     growing down the page. */
  .nav__cats { display: flex; align-items: flex-start; }
  .nav__cat { min-width: 240px; }
  .nav__cat + .nav__cat { margin-left: var(--s-1); padding-left: var(--s-1); border-left: 1px solid var(--line); }
  .nav__cat--split .nav__cat-items { columns: 2; column-gap: var(--s-1); width: calc(480px + var(--s-1)); }
  .nav__cat-items > a { break-inside: avoid; }
  .nav__cat-label {
    padding: var(--s-1) var(--s-2) var(--half);
    font-family: var(--font-label);
    font-size: var(--t-small);
    line-height: 16px;
    letter-spacing: var(--track);
    text-transform: uppercase;
    color: var(--ink-faint);
  }
  /* A product row: its icon tile (ICON TILE, below), the name, the tag. */
  .nav__drop a.nav__prod { justify-content: flex-start; align-items: center; gap: var(--s-2); }
  .nav__prod-name { flex: 1; white-space: nowrap; }    /* never breaks at a hyphen; the column widens instead */
  .nav__drop .icon-tile { --tile: 40px; --glyph: 26px; }
  .nav__drop a.nav__prod:hover .icon-tile,
  .nav__drop a.nav__prod:focus-visible .icon-tile { border-color: var(--key-line); }
  .nav__tag { margin-left: auto; }   /* a .flag, pushed to the row's end */

  /* ---- hamburger (SVG bars, morphed by GSAP) ---- */
  .nav__burger {
    display: none;                   /* JS-only control */
    width: 40px;
    height: 40px;
    padding: 4px;
    margin-right: -4px;              /* optical: bars end on the grid edge */
    background: none;
    border: 0;
    color: var(--ink);
    cursor: pointer;
  }
  .nav--enhanced .nav__burger { display: block; }
  .nav__burger svg { width: 32px; height: 32px; overflow: visible; }
  .nav__burger rect { fill: currentColor; }
  .nav__burger:hover { color: var(--key); }
  /* Fallback morph when GSAP failed to load (GSAP drives it otherwise). */
  .no-gsap .nav__burger rect { transform-box: fill-box; transform-origin: center; transition: transform 300ms var(--ease), opacity 200ms; }
  .no-gsap .nav__burger[aria-expanded="true"] .bar-1 { transform: translateY(7px) rotate(45deg); }
  .no-gsap .nav__burger[aria-expanded="true"] .bar-2 { opacity: 0; }
  .no-gsap .nav__burger[aria-expanded="true"] .bar-3 { transform: translateY(-7px) rotate(-45deg); }

  /* ---- full menu (hamburger) ---- */
  .nav-menu {
    position: fixed;
    inset: 0;
    z-index: 90;                     /* under the bar, so the X stays on top */
    padding-top: calc(var(--nav-h) + var(--s-8));
    padding-bottom: var(--s-8);
    overflow-y: auto;
    overscroll-behavior: contain;
    background: var(--ground);
  }
  .nav-menu__inner { --row-gap: var(--s-8); }
  .nav-menu__label { margin-bottom: var(--s-3); }
  .nav-menu__list { list-style: none; margin: 0; padding: 0; }
  .nav-menu__list li + li { border-top: 1px solid var(--line); }
  .nav-menu__link {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--s-2);
    padding-block: var(--s-2);
    font-family: var(--font-display);
    font-size: var(--t-h2);
    line-height: 1.1;
    line-height: round(up, 1.1em, var(--half));
    letter-spacing: -0.02em;
    color: var(--ink);
    text-decoration: none;
    transition: color 160ms var(--ease), translate 240ms var(--ease);
  }
  .nav-menu__link:hover, .nav-menu__link:focus-visible { color: var(--key); translate: var(--s-1) 0; }
  .nav-menu__link .nav__tag { margin-left: var(--s-2); vertical-align: middle; }
  /* a name never breaks at its hyphen (OA-Phosphor-1); short of room, the tag drops under it */
  .nav-menu__link { flex-wrap: wrap; }
  .nav-menu__link > span:first-child { white-space: nowrap; }
  /* categories in the full-screen menu */
  .nav-menu__sub {
    margin-top: var(--s-4);
    font-family: var(--font-label);
    font-size: var(--t-label);
    line-height: 16px;
    letter-spacing: var(--track);
    text-transform: uppercase;
    color: var(--ink-faint);
  }
  .nav-menu__label + .nav-menu__sub { margin-top: 0; }
  .nav-menu__list--all { margin-top: var(--s-4); }

  @media (max-width: 640px) {
    .nav__links { display: none; }
    .nav__inner { grid-template-columns: 1fr auto; }
    .nav__word { font-size: var(--t-small); }
  }
  @media (max-width: 1000px) {
    .nav__links { gap: var(--s-4); }
  }
  /* no-JS: the links must stay reachable, so they wrap instead of hiding */
  @media (max-width: 640px) {
    .nav:not(.nav--enhanced) .nav__links { display: flex; flex-wrap: wrap; gap: var(--s-2); justify-content: flex-end; }
    .nav:not(.nav--enhanced) .nav__inner { grid-template-columns: auto 1fr; }
    .nav:not(.nav--enhanced) .nav__actions { display: none; }
  }
}


/* ==========================================================================
   UTILITIES — placement, nudges, bleeds. Last layer, so they always win.
   ========================================================================== */
@layer utilities {
  /* span / start (desktop; md inherits unless overridden) */
  .span-1{--span:1}.span-2{--span:2}.span-3{--span:3}.span-4{--span:4}.span-5{--span:5}.span-6{--span:6}
  .span-7{--span:7}.span-8{--span:8}.span-9{--span:9}.span-10{--span:10}.span-11{--span:11}.span-12{--span:12}
  .start-1{--start:1}.start-2{--start:2}.start-3{--start:3}.start-4{--start:4}.start-5{--start:5}.start-6{--start:6}
  .start-7{--start:7}.start-8{--start:8}.start-9{--start:9}.start-10{--start:10}.start-11{--start:11}.start-12{--start:12}

  /* xl ≥ 1600px (default: same as lg) */
  .xl-span-1{--span-xl:1}.xl-span-2{--span-xl:2}.xl-span-3{--span-xl:3}.xl-span-4{--span-xl:4}.xl-span-5{--span-xl:5}.xl-span-6{--span-xl:6}
  .xl-span-7{--span-xl:7}.xl-span-8{--span-xl:8}.xl-span-9{--span-xl:9}.xl-span-10{--span-xl:10}.xl-span-11{--span-xl:11}.xl-span-12{--span-xl:12}
  .xl-start-1{--start-xl:1}.xl-start-2{--start-xl:2}.xl-start-3{--start-xl:3}.xl-start-4{--start-xl:4}.xl-start-5{--start-xl:5}.xl-start-6{--start-xl:6}
  .xl-start-7{--start-xl:7}.xl-start-8{--start-xl:8}.xl-start-9{--start-xl:9}.xl-start-10{--start-xl:10}.xl-start-11{--start-xl:11}.xl-start-12{--start-xl:12}

  /* md ≤ 1000px */
  .md-span-1{--span-md:1}.md-span-2{--span-md:2}.md-span-3{--span-md:3}.md-span-4{--span-md:4}.md-span-5{--span-md:5}.md-span-6{--span-md:6}
  .md-span-7{--span-md:7}.md-span-8{--span-md:8}.md-span-9{--span-md:9}.md-span-10{--span-md:10}.md-span-11{--span-md:11}.md-span-12{--span-md:12}
  .md-start-1{--start-md:1}.md-start-2{--start-md:2}.md-start-3{--start-md:3}.md-start-4{--start-md:4}.md-start-5{--start-md:5}.md-start-6{--start-md:6}
  .md-start-7{--start-md:7}.md-start-8{--start-md:8}.md-start-9{--start-md:9}.md-start-10{--start-md:10}.md-start-11{--start-md:11}.md-start-12{--start-md:12}
  .md-start-auto{--start-md:auto}

  /* sm ≤ 640px (default is full width) */
  .sm-span-1{--span-sm:1}.sm-span-2{--span-sm:2}.sm-span-3{--span-sm:3}.sm-span-4{--span-sm:4}.sm-span-5{--span-sm:5}.sm-span-6{--span-sm:6}
  .sm-span-7{--span-sm:7}.sm-span-8{--span-sm:8}.sm-span-9{--span-sm:9}.sm-span-10{--span-sm:10}.sm-span-11{--span-sm:11}.sm-span-12{--span-sm:12}
  .sm-start-1{--start-sm:1}.sm-start-2{--start-sm:2}.sm-start-3{--start-sm:3}.sm-start-4{--start-sm:4}.sm-start-5{--start-sm:5}.sm-start-6{--start-sm:6}
  .sm-start-7{--start-sm:7}.sm-start-8{--start-sm:8}.sm-start-9{--start-sm:9}.sm-start-10{--start-sm:10}.sm-start-11{--start-sm:11}.sm-start-12{--start-sm:12}

  /* ---- nudge: move without leaving the flow --------------------------
     --nx in COLUMNS (col + gutter pitch), --ny in BASELINES. Fractions ok.
       style="--nx: .5"   half a column right
       style="--ny: -2"   16px up
     Uses `translate`, so it composes with GSAP's `transform`. */
  .nudge { translate: calc(var(--nx, 0) * var(--col-pitch)) calc(var(--ny, 0) * var(--bl)); }

  /* ---- outset: grow past the item's own column lines -----------------
     --out-l / --out-r in COLUMNS. Into a neighbour's column or the margin. */
  .outset {
    margin-inline-start: calc(var(--out-l, 0) * var(--col-pitch) * -1);
    margin-inline-end:   calc(var(--out-r, 0) * var(--col-pitch) * -1);
  }

  /* ---- bleed: run to the page edge ----------------------------------- */
  .bleed-l { margin-inline-start: calc(var(--bleed) * -1); }
  .bleed-r { margin-inline-end:   calc(var(--bleed) * -1); }
  .bleed   { margin-inline: calc(var(--bleed) * -1); }
  /* hang: into the outer margin only as far as the gutter, for hanging
     punctuation, icons and rules */
  .hang-l { margin-inline-start: calc(var(--gap) * -1); }

  /* self-alignment inside a grid cell */
  .self-start  { align-self: start; }
  .self-center { align-self: center; }
  .self-end    { align-self: end; }
  .justify-end { justify-self: end; }

  /* vertical space in baselines */
  .btn-row { display: flex; flex-wrap: wrap; gap: var(--s-2); }   /* buttons side by side */
  .mt-0{margin-top:0}.mt-1{margin-top:var(--s-1)}.mt-2{margin-top:var(--s-2)}.mt-3{margin-top:var(--s-3)}
  .mt-4{margin-top:var(--s-4)}.mt-6{margin-top:var(--s-6)}.mt-8{margin-top:var(--s-8)}.mt-12{margin-top:var(--s-12)}
  .mt-16{margin-top:var(--s-16)}

  @media (max-width: 640px) {
    /* bleeds and outsets are layout flourishes; on a phone the item is
       already full width, so only full bleeds survive */
    .outset { margin-inline: 0; }
    .sm-no-nudge { translate: none; }
    /* a half bleed on a full-width phone item becomes a full bleed */
    .bleed-l, .bleed-r { margin-inline: calc(var(--bleed) * -1); }
  }
}

/* ==========================================================================
   HERO — product header
   Copy on the left (from column 2), the product image on the right starting
   at column 6 and running off the right edge of the page: a wider window
   shows more of it. A "glow" panel in the product colour sits behind:

     <section class="hero hero--panel-wide">   panel from the left grid edge
     <section class="hero hero--panel-tuck">   panel tucked behind the image
     <section class="hero">                    no panel

   Format line: <p class="hero__meta"><span>VST3</span> <span>AU</span> …</p>
   Title:       <h1 class="hero__title"><span class="hero__product">…</span>
                <span class="hero__usp">…</span></h1>
   First CTA gets data-cta so the nav's button takes over on scroll.

   Controls (defaults are the 1440px mock, snapped to the grid):
     --hero-pt / --hero-pb     space above the panel / below the image  32 / 80
     --hero-copy-top           panel top → first line of copy           160
     --hero-inset-x / -y       panel edge → image                       40 / 48
     --hero-media-w            rendered image width                     --maxw (1320; 1512 at xl)
     --hero-glow               panel background                         --theme-glow
     --hero-radius             panel + image corners                    --radius
   ========================================================================== */
@layer components {
  .hero {
    --hero-pt: var(--s-4);
    --hero-pb: var(--s-10);
    --hero-copy-top: var(--s-20);
    --hero-inset-x: var(--s-5);
    --hero-inset-y: var(--s-6);
    --hero-media-w: var(--maxw);
    --hero-glow: var(--theme-glow);
    --hero-radius: var(--radius);
    position: relative;
    isolation: isolate;
    padding-block: var(--hero-pt) var(--hero-pb);
  }
  .hero__grid { row-gap: 0; }

  /* everything shares row 1 and overlaps; z-order: panel, then copy/image.
     Explicit columns on phones too, so overlapping rows never spill into
     extra implicit columns. */
  .hero__panel, .hero__copy, .hero__media { grid-row: 1; --start-sm: 1; }

  .hero__panel {
    display: none;
    align-self: stretch;
    z-index: 0;
    background: var(--hero-glow);
    border-radius: var(--hero-radius) var(--hero-radius) 0 0;
  }
  .hero--panel-wide .hero__panel {
    display: block;
    --start: 1; --span: 9;
    /* ends half a column into column 10, as in the mock */
    margin-right: calc(var(--col-pitch) * -0.5);
    /* the copy sits on the grid edge, so the panel reaches out into the
       outer margin to give it room (never all the way to the page edge) */
    margin-left: calc(min(var(--hero-inset-x), var(--margin) - var(--s-2)) * -1);
  }
  .hero--panel-tuck .hero__panel {
    display: block;
    --start: 6; --span: 7;
    margin-right: calc(var(--bleed) * -1);       /* runs off the right edge */
    border-top-right-radius: 0;
  }

  .hero__copy {
    --start: 1; --span: 5;                          /* left grid edge, columns 1–5 */
    position: relative;
    z-index: 1;
    padding-top: var(--hero-copy-top);
  }
  .hero__meta {
    font-family: var(--font-label);
    font-weight: var(--ui-weight);
    font-size: var(--t-small);
    line-height: 16px;
    letter-spacing: var(--track);
    text-transform: uppercase;
    color: var(--key);
  }
  /* one <span> per item; the separator rides on the END of an item, so a
     wrapped line never starts with a dot */
  .hero__meta > span { white-space: nowrap; }
  .hero__meta > span:not(:last-child)::after { content: "\00a0·"; }
  .hero__product { white-space: nowrap; }
  .hero__title {
    margin-top: var(--s-6);
    font-family: var(--font-display);
    font-size: var(--t-module);
    line-height: 1;
    letter-spacing: 0;
    text-transform: uppercase;
    text-wrap: balance;
  }
  .hero__product { display: block; color: var(--key); }
  .hero__usp     { display: block; color: var(--ink); }
  .hero__lede {
    margin-top: var(--s-3);
    max-width: 20em;
    font-size: var(--t-body);
    line-height: 28px;
    color: var(--ink-soft);
  }
  .hero__ctas {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-2);
    margin-top: calc(var(--s-8) + var(--s-1));     /* 72 */
  }
  .hero__ctas .btn { --btn-h: 40px; font-size: var(--t-small); }

  /* hero--follow: the hero under a feature header (01b). The header already
     carries the name, formats, price and buttons, so this is the argument
     only: the headline as an h2 (one h1 per page), the lede and a second
     screenshot. No .hero__meta, .hero__product or .hero__ctas. */
  .hero.hero--follow { --hero-pt: var(--section-y); --hero-copy-top: var(--s-12); }
  @media (max-width: 1000px) { .hero.hero--follow { --hero-copy-top: 0px; } }
  .hero--follow .hero__title { margin-top: 0; }
  .hero--follow :is(.hero__meta, .hero__product, .hero__ctas) { display: none; }
  .hero--follow .hero__lede { max-width: 24em; }

  /* The image box runs to the page edge and clips; the image inside keeps
     its own width, so it is cropped by the window, never scaled by it. */
  .hero__media {
    --start: 6; --span: 7;
    position: relative;
    z-index: 1;
    margin-top: var(--hero-inset-y);
    margin-left: var(--hero-inset-x);
    margin-right: calc(var(--bleed) * -1);
    overflow: hidden;
    border-radius: var(--hero-radius) 0 0 var(--hero-radius);
  }
  .hero__media img, .hero__media .ph {
    display: block;
    width: var(--hero-media-w);
    max-width: none;
    height: auto;
    border-radius: var(--radius-shot);
  }

  /* Tablet and phone: copy first, image underneath, still running off the
     right edge at a fixed width. */
  @media (max-width: 1000px) {
    .hero {
      --hero-pt: var(--s-4);
      --hero-copy-top: 0px;
      --hero-inset-x: 0px;
      --hero-inset-y: var(--s-8);
      --hero-media-w: 960px;
      --hero-pad: var(--s-6);             /* copy inset inside a wide panel */
    }
    .hero__panel, .hero__copy, .hero__media { --start-md: 1; --span-md: 12; }
    .hero__copy  { grid-row: 1; }
    .hero__media { grid-row: 2; }

    /* wide: the panel wraps copy and image; copy sits inside it */
    .hero--panel-wide .hero__panel { grid-row: 1 / 3; margin-inline: 0; }
    .hero--panel-wide { --hero-inset-x: var(--hero-pad); }
    .hero--panel-wide .hero__copy { padding: var(--s-8) var(--hero-pad) 0; }

    /* tuck: the panel sits behind the image only, image inset inside it */
    .hero--panel-tuck .hero__panel { grid-row: 2; margin-top: var(--s-8); }
    .hero--panel-tuck { --hero-inset-x: var(--hero-pad); --hero-inset-y: calc(var(--s-8) + var(--hero-pad)); }
  }
  @media (max-width: 640px) {
    .hero {
      --hero-pt: var(--s-3);
      --hero-pb: var(--s-8);
      --hero-inset-y: var(--s-6);
      --hero-media-w: 600px;
      --hero-pad: var(--s-3);
    }
    .hero__title { margin-top: var(--s-4); }
    .hero__lede  { max-width: none; }
    .hero__ctas  { margin-top: var(--s-5); }
    .hero__ctas .btn { flex: 1 1 auto; }    /* if two wrap, both go full width */
    .hero--panel-wide .hero__copy { padding-top: var(--s-6); }
    .hero--panel-tuck .hero__panel { margin-top: var(--s-6); }
    .hero--panel-tuck { --hero-inset-y: calc(var(--s-6) + var(--hero-pad)); }
  }
  /* The format line stays on one line beside the image; it may run into the
     gutter before the image (up to 64px) on narrow desktops. */
  @media (min-width: 1001px) { .hero__meta { white-space: nowrap; } }
}

/* ==========================================================================
   AUDITION — audio previews
   Title in columns 1–3; the lists in columns 4–12 as two equal groups
   (Presets, Feature demos). Each row: a square thumbnail whose corner button
   plays/stops, then name, length and — while playing — a progress bar.
   Only the first --audition-show rows are visible; "Hear more" reveals the
   rest. Driven by /_system/audition.js.

   Controls:
     --audition-thumb     thumbnail size        144 (120 tablet, 96 phone)
     --audition-row-gap   space between rows    24
     --audition-bar-w     progress bar width    208
   ========================================================================== */
@layer components {
  .audition {
    --section-pt: var(--s-6);
    --section-pb: var(--s-10);
    --audition-thumb: 144px;
    --audition-row-gap: var(--s-3);
    --audition-bar-w: 208px;
  }
  .audition__grid { align-items: baseline; }        /* title and group labels share a baseline */
  .audition__title { --span: 3; }
  .audition__lists { --span: 9; --span-md: 12; }
  .audition__dots { display: none; }              /* phones only, below */
  .audition__groups { --gap: var(--s-4); row-gap: var(--s-10); }

  .audition__label { color: var(--ink-soft); }
  .audition__head {
    padding-bottom: var(--s-3);
    border-bottom: 1px solid var(--line);
  }
  /* the tabs get their own row between two lines (built by audition.js) */
  .audition__filters {
    padding-block: var(--s-2);
    border-bottom: 1px solid var(--line);
  }

  .audition__list {
    list-style: none;
    margin: 0;
    padding: var(--s-3) 0 28px;
    display: grid;
    gap: var(--audition-row-gap);
    border-bottom: 1px solid var(--line);
  }
  .audition__item {
    display: grid;
    grid-template-columns: var(--audition-thumb) minmax(0, 1fr);
    column-gap: var(--s-3);
    align-items: start;
  }

  /* the whole thumbnail is the button; the white square is its visible face */
  .audition__play {
    position: relative;
    display: block;
    width: var(--audition-thumb);
    aspect-ratio: 1;
    padding: 0;
    border: 0;
    border-radius: var(--radius);
    overflow: hidden;
    background: var(--panel-2);
    cursor: pointer;
  }
  .audition__play img { width: 100%; height: 100%; object-fit: cover; }
  /* optional moving thumbnail: a muted loop that fades in over the still
     while the track plays */
  .audition__video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0;
    transition: opacity 240ms var(--ease);
  }
  .audition__play[aria-pressed="true"] .audition__video { opacity: 1; }
  .audition__icon {
    position: absolute;
    right: 0;
    bottom: 0;
    display: grid;
    place-items: center;
    width: var(--s-5);
    height: var(--s-5);
    background: #fff;
    transition: background-color 160ms var(--ease);
  }
  .audition__icon::before {                          /* play triangle */
    content: "";
    width: 12px;
    height: 14px;
    margin-left: 2px;
    background: #000;
    clip-path: polygon(0 0, 100% 50%, 0 100%);
  }
  .audition__play[aria-pressed="true"] .audition__icon::before {   /* stop square */
    width: 12px;
    height: 12px;
    margin-left: 0;
    clip-path: none;
  }
  .audition__play:hover .audition__icon,
  .audition__play:focus-visible .audition__icon { background: var(--key); }
  .audition__play:hover .audition__icon::before,
  .audition__play:focus-visible .audition__icon::before { background: var(--key-ink); }

  .audition__time { font-variant-numeric: tabular-nums; }
  .audition__name, .audition__time {
    font-family: var(--font-head);
    font-size: var(--t-body);
    line-height: 28px;
    letter-spacing: 0;
  }
  .audition__meta {
    display: flex;
    flex-direction: column;
    min-height: var(--audition-thumb);
  }
  .audition__cat { margin-top: 2px; color: var(--ink-soft); }
  .audition__name { margin-top: var(--s-1); color: var(--ink); }
  /* the category label shows only under "All"; a filtered list drops it and
     the name moves up so its cap height is level with the thumbnail top */
  .audition__group.is-filtered .audition__cat { display: none; }
  .audition__group.is-filtered .audition__name { margin-top: -6px; }
  .audition__time { color: var(--ink); font-variant-numeric: tabular-nums; }
  /* optional one-line description under the name (demos): what to listen for */
  .audition__desc {
    max-width: var(--audition-bar-w);
    font-size: var(--t-small);
    line-height: 1.4;
    color: var(--ink-soft);
  }

  /* progress: played in the key colour, a 6px mid-grey playhead, the rest in
     light grey; bottom-aligned with the thumbnail. --p (0–1) is set by
     audition.js. Click to seek. */
  .audition__bar {
    --p: 0;
    --bar-gap: min(6px, var(--p) * 1000px);          /* no gap before playback starts */
    position: relative;
    width: 100%;
    max-width: var(--audition-bar-w);
    height: 6px;
    margin-top: auto;
    background: color-mix(in srgb, var(--ink) 35%, var(--ground));
    cursor: pointer;
  }
  .audition__bar[hidden] { display: none; }
  .audition__bar::before, .audition__bar::after { content: ""; position: absolute; top: 0; bottom: 0; }
  .audition__bar::before { left: 0; width: calc(var(--p) * (100% - var(--bar-gap))); background: var(--key); }
  .audition__bar::after {
    right: 0;
    width: calc((1 - var(--p)) * (100% - var(--bar-gap)));
    background: color-mix(in srgb, var(--ink) 85%, var(--ground));
  }
  /* a taller invisible hit area for seeking */
  .audition__bar > span { position: absolute; inset: -8px 0; }

  .audition__more {
    display: flex;
    align-items: center;
    gap: var(--s-1);
    width: fit-content;
    height: 32px;
    margin: var(--s-2) 0 0 auto;
    padding: 0 var(--s-2);
    background: none;
    border: 0;
    color: color-mix(in srgb, var(--ink) 85%, var(--ground));
    font-family: var(--font-label);
    font-weight: var(--ui-weight);
    font-size: var(--t-small);
    line-height: 1;
    letter-spacing: var(--track);
    text-transform: uppercase;
    cursor: pointer;
  }
  .audition__plus {
    position: relative;
    flex: none;
    width: 16px;
    height: 16px;
    border-radius: var(--radius);
    background: var(--key);
  }
  .audition__plus::before, .audition__plus::after {
    content: "";
    position: absolute;
    left: 50%; top: 50%;
    width: 8px; height: 2px;
    translate: -50% -50%;
    background: var(--key-ink);
  }
  .audition__plus::after { rotate: 90deg; }
  .audition__more:hover { color: var(--key); }

  /* ---- "See all" modal: the full list, condensed, with filters ----
     Built on .dialog; only the size and the list are its own. */
  .audition-modal {
    --dialog-w: 720px;
    --audition-thumb: 48px;
    /* fixed size: filtering changes what scrolls inside, never the frame */
    height: min(840px, 100dvh - 2 * var(--s-6));
  }
  .audition-modal__panel { display: flex; flex-direction: column; width: 100%; min-height: 0; }
  .audition-modal__filters {
    display: grid;
    gap: var(--s-1);
    padding: 0 var(--s-4) var(--s-3);
    border-bottom: 1px solid var(--line);
  }
  /* the category row keeps its height even when a group has no categories,
     so the list never jumps up and down */
  .audition-modal__filters > div { min-height: 32px; }
  .audition-modal__list {
    flex: 1;
    min-height: 0;
    margin: 0;
    padding: 0 var(--s-4) var(--s-2);
    list-style: none;
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  .audition-modal__row {
    display: grid;
    grid-template-columns: var(--audition-thumb) minmax(0, 1fr) auto;
    align-items: center;
    column-gap: var(--s-2);
    padding-block: var(--s-1);
    border-bottom: 1px solid var(--line);
  }
  .audition-modal__row .audition__icon { width: 20px; height: 20px; }
  .audition-modal__row .audition__icon::before { width: 7px; height: 8px; margin-left: 1px; }
  .audition-modal__row .audition__play[aria-pressed="true"] .audition__icon::before { width: 7px; height: 7px; margin-left: 0; }
  .audition-modal__main { position: relative; min-width: 0; }
  .audition-modal__name, .audition-modal__time {
    font-family: var(--font-head);
    font-size: var(--t-small);
    line-height: 20px;
    letter-spacing: 0;
  }
  .audition-modal__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .audition-modal__cat { color: var(--ink-faint); }
  .audition-modal__time { color: var(--ink-soft); font-variant-numeric: tabular-nums; }
  /* while playing, the progress bar takes the category line's place (no jump) */
  .audition-modal__main .audition__bar {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 5px;
    max-width: none;
    margin: 0;
  }
  .audition-modal__row:has(.audition__play[aria-pressed="true"]) .audition-modal__cat { visibility: hidden; }

  @media (max-width: 640px) {
    .audition-modal { height: 100dvh; }
    .audition-modal__filters, .audition-modal__list { padding-inline: var(--margin); }
  }

  @media (max-width: 1000px) {
    .audition { --audition-thumb: 120px; }
    .audition__grid { row-gap: var(--s-6); }
  }
  @media (max-width: 640px) {
    /* The groups become invisible cards in a sideways-swiping row that runs
       to both screen edges; the next card peeks in so the swipe is obvious. */
    .audition__groups {
      display: flex;
      gap: var(--s-3);
      margin-inline: calc(var(--bleed) * -1);
      padding-inline: var(--bleed);
      /* room after the last card so it can snap flush left like the first */
      padding-inline-end: calc(var(--s-6) + var(--bleed));
      scroll-padding-inline: var(--bleed);
      overflow-x: auto;
      overscroll-behavior-x: contain;
      scroll-snap-type: x mandatory;
      scrollbar-width: none;
    }
    .audition__groups::-webkit-scrollbar { display: none; }
    /* where you are in the row, and the way back: under the title (audition.js) */
    .audition__dots { display: flex; gap: var(--s-1); grid-column: 1 / -1; }   /* its dots: .dots__dot (DOTS) */
    .audition__groups > .audition__group {
      /* 100% of the padded track = screen − margins − the 48px peek */
      flex: 0 0 100%;
      scroll-snap-align: start;
    }
    .audition__grid { row-gap: var(--s-4); }
    .audition { --audition-thumb: 96px; --audition-row-gap: var(--s-2); }
    .audition__item { column-gap: var(--s-2); }
    .audition__name, .audition__time { font-size: var(--t-small); line-height: 24px; }
    .audition__bar { margin-bottom: 2px; }
    .audition__icon { width: var(--s-4); height: var(--s-4); }
    .audition__icon::before { width: 10px; height: 12px; }
    .audition__play[aria-pressed="true"] .audition__icon::before { width: 10px; height: 10px; }
  }
}


/* ==========================================================================
   GET (module 03): the two pathways to get a product, driven by get.js
     Free  [data-get] → this dialog: email + platform, starts the file
     Paid  <a href="/buy/<slug>"> → Dodo's checkout overlay (checkout.js)
   Built on .dialog. The visitor's platform is the one big button.
   ========================================================================== */
@layer components {
  .get-modal { --dialog-w: 520px; }
  .get-modal__panel { display: flex; flex-direction: column; min-height: 0; overflow-y: auto; outline: none; }
  .get-modal__head { align-items: flex-start; }
  .get-modal__head .eyebrow { margin-bottom: var(--s-1); }
  .get-modal__title { color: var(--key); }
  .get-modal__body {
    display: flex;
    flex-direction: column;
    gap: var(--s-3);
    padding: 0 var(--s-4) var(--s-4);
  }
  .get-modal__lede { font-size: var(--t-lede); line-height: 32px; }
  .get-modal__foot {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: var(--s-1) var(--s-3);
    padding-top: var(--s-2);
    border-top: 1px solid var(--line);
  }
  .get-modal__body strong { color: var(--ink); font-weight: 700; }

  .get-form { display: flex; flex-direction: column; gap: var(--s-1); }
  .get-form__hint {
    min-height: 20px;                      /* reserved: messages never shift the buttons */
    margin-bottom: var(--s-1);
    color: var(--ink-soft);
    font-size: var(--t-small);
    line-height: 20px;
  }
  .get-form__hint[data-kind="error"] { color: var(--danger); }
  .get-form__suggest {
    padding: 0;
    background: none;
    border: 0;
    color: var(--key);
    font: inherit;
    font-weight: 700;
    text-decoration: underline;
    text-underline-offset: 2px;
    cursor: pointer;
  }
  /* three equal platform boxes, on from the start like primary buttons (a
     click without an email explains what's missing); hover inverts, as on
     .btn--primary */
  .get-form__platforms {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
    gap: var(--s-1);
  }
  .get-form__platform {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    min-height: 88px;                    /* 11 baselines */
    padding: var(--s-1);
    background: var(--key);
    border: 1px solid var(--key);
    border-radius: var(--radius);
    color: var(--key-ink);
    cursor: pointer;
    transition: border-color 160ms var(--ease), color 160ms var(--ease), background-color 160ms var(--ease);
  }
  .get-form__platform:hover,
  .get-form__platform:focus-visible { background: transparent; color: var(--key); outline: none; }
  .get-form__platform:hover .get-form__note,
  .get-form__platform:focus-visible .get-form__note { color: var(--key); }
  .get-form__platform:disabled { cursor: default; opacity: 0.5; }
  .get-form__platform[aria-busy="true"] { opacity: 1; background: transparent; color: var(--key); }
  .get-form__os {
    font-family: var(--font-head);
    font-size: var(--t-body);
    font-weight: 700;
    line-height: 24px;
    letter-spacing: 0;
  }
  .get-form__note {
    font-family: var(--font-label);
    font-size: var(--t-label);
    font-weight: var(--ui-weight);
    line-height: 16px;
    letter-spacing: var(--track);
    text-transform: uppercase;
    color: inherit;
    opacity: 0.75;
  }

  @media (max-width: 640px) {
    .get-modal__body { padding-inline: var(--margin); }
  }
}


/* ==========================================================================
   FEATURE HEADER (module 01b) — driven by fheader.js
   The product colour edge to edge, with the spec line, the name, the price
   and two buttons above the interface. It scrolls at the normal speed; the
   scroll only draws the colour in from the sides until its edges sit on the
   grid, and its bottom up to match, while the interface eases down to sit one
   column in. The type sits on the grid's outer edges and never moves; it
   fades out as it scrolls up, before the colour's sides reach it.

     <section class="fheader" data-fheader data-theme="phosphor" data-mode="dark">
       <div class="fheader__stage">
         <div class="fheader__box"></div>
         <div class="container grid fheader__type"> eyebrow · name + sub · price + buttons </div>
         <div class="container fheader__media"><img class="fheader__ui"></div>
       </div>
     </section>

   Controls:
     --fheader-bg       the colour (default: the theme's --theme-feature)
     --fheader-pad      the frame of colour beside and above the interface
                        once drawn in                        (one column pitch,
                        so the interface spans columns 2–11; a gutter on phones)
     --fheader-overhang how much of the interface hangs below the colour, as a
                        share of its height                  (0.08)
   No JS: it simply stays edge to edge.
   ========================================================================== */
@layer components {
  .fheader {
    --fheader-pad: var(--col-pitch);
    --fheader-overhang: 0.08;
    position: relative;
    color: var(--ink);
  }
  .fheader__stage { position: relative; isolation: isolate; padding-bottom: var(--s-8); }

  /* the colour; fheader.js draws it in with a transform (square corners, so
     scaling it is exact), which the browser does without re-laying anything out */
  .fheader__box {
    position: absolute;
    z-index: -1;
    inset: 0;
    transform-origin: 0 0;
    will-change: transform;
    background: var(--fheader-bg, var(--theme-feature, var(--theme-bg)));
  }

  /* on the grid's outer edges, like the interface below it */
  .fheader__type {
    position: relative;
    z-index: 1;
    padding-top: var(--s-8);
    align-items: end;
    --row-gap: var(--s-4);
  }
  .fheader__type .eyebrow { --span: 12; margin-bottom: var(--s-6); }
  /* the spec line links to the full spec sheet */
  .fheader__specs { color: inherit; text-decoration: none; }
  .fheader__specs:hover { text-decoration: underline; text-underline-offset: 3px; }
  .fheader__name { --span: 7; }
  .fheader__title {
    font-family: var(--font-display);
    font-size: var(--t-feature);
    line-height: 1;
    letter-spacing: -0.01em;
    text-transform: uppercase;
    white-space: nowrap;
  }
  .fheader__sub { margin-top: var(--s-2); font-size: var(--t-body); line-height: 24px; }
  .fheader__buy { --start: 8; --span: 5; justify-self: end; text-align: right; }
  .fheader__price { margin-bottom: var(--s-3); font-size: var(--t-body); line-height: 24px; }
  .fheader__ctas { display: flex; gap: var(--s-3); }
  .fheader__ctas .btn { min-width: 160px; }

  .fheader__media { position: relative; z-index: 2; margin-top: var(--s-6); }
  .fheader__ui {
    width: 100%;
    height: auto;
    border-radius: var(--radius-shot);
    transform-origin: 50% 50%;
    will-change: scale;              /* its own layer: scaling never repaints it */
  }

  /* the section after it starts without its top padding: once drawn in, the
     interface has shrunk inside its own box, and that leftover space plus the
     stage's padding already make the gap (one section's worth or so) */
  .fheader + .section { --section-rule: none; --section-pt: 0px; }
  /* the colour starts right under the nav, so the nav's bottom rule goes */
  body:has(.fheader:not([hidden])) .nav::after { display: none; }

  @media (max-width: 1000px) {
    .fheader__name, .fheader__buy { --start-md: 1; --span-md: 12; }
    .fheader__buy { justify-self: start; text-align: left; }
  }
  @media (max-width: 640px) {
    .fheader { --fheader-pad: var(--gap); }
    .fheader__type .eyebrow { margin-bottom: var(--s-4); }
    .fheader__ctas .btn { flex: 1; min-width: 0; }
  }
}


/* ==========================================================================
   PRODUCT BAR (.pnav) — driven by pnav.js
   A product page's own bar: name, jump links, price and its buttons. Once the
   page's buttons ([data-pnav-from]) scroll under the site nav, the nav
   scrolls away with the page and this bar is left holding the top. It is
   uncovered from under the nav as it leaves, never seen through it, and the
   whole thing follows the scroll, so it reverses exactly on the way back.
   It runs edge to edge, like the site nav, with its contents on the grid's
   outer edges, so nothing behind it shows at its sides.

     <div class="pnav" data-pnav data-pnav-from=".fheader__ctas">
       <div class="pnav__inner">
         <a class="pnav__name" href="#intro">…</a>
         <nav class="pnav__links"><a href="#hear">Hear it</a> …</nav>
         <div class="pnav__buy"><p class="pnav__price">…</p> buttons </div>
       </div>
     </div>

   Controls: --pnav-bg (default: the page ground).
   When it gets tight it sheds the name, then the links, then the price
   (pnav.js); the buttons always stay.
   ========================================================================== */
@layer components {
  .pnav {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 95;                       /* under the site nav, which slides off it */
    height: var(--nav-h-closed);
    /* uncovered from the top down as the nav moves up and away */
    clip-path: inset(calc((1 - var(--pnav-p, 0)) * 100%) 0 0 0);
    visibility: hidden;
    background: var(--pnav-bg, var(--ground));
  }
  .pnav.is-on { visibility: visible; }
  /* three tracks like the site nav: name left, links centred on the bar
     (where the nav's links sit), price and buttons right */
  .pnav__inner {                       /* a .container: edges on the grid */
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: var(--s-5);
    height: 100%;
    max-width: calc(var(--maxw) + 2 * var(--margin));
    margin-inline: auto;
    padding-inline: var(--margin);
    color: var(--ink);
  }
  /* the product's lockup, built like the site's: its symbol beside the name
     in the wordmark's face, both in ink, as .nav__brand
       <a class="pnav__name" href="#intro"><span class="pnav__mark"
          style="--icon: url('/_images/brand/products/phosphor-1.svg')"
          aria-hidden="true"></span>OA-Phosphor-1</a> */
  .pnav__name {
    grid-column: 1;
    justify-self: start;
    display: inline-flex;
    align-items: center;
    gap: var(--s-2);
    font-family: var(--font-head);
    font-weight: var(--ui-weight);
    font-size: var(--t-small);
    line-height: 16px;
    letter-spacing: 0;
    white-space: nowrap;
    color: var(--ink);
    text-decoration: none;
  }
  .pnav__mark {
    flex: none;
    width: var(--nav-logo-closed);
    height: var(--nav-logo-closed);
    background: currentColor;
    -webkit-mask: var(--icon) center / contain no-repeat;
    mask: var(--icon) center / contain no-repeat;
  }
  .pnav__links { grid-column: 2; display: flex; gap: var(--s-5); }
  .pnav__links a {
    font-family: var(--font-label);
    font-size: var(--t-body);
    line-height: 1;
    letter-spacing: var(--track);
    text-transform: uppercase;
    white-space: nowrap;
    color: var(--ink);
    text-decoration: none;
    transition: color 160ms var(--ease);
  }
  .pnav__links a:hover { color: var(--key); }
  .pnav__links a[aria-current="true"] { color: var(--key); }
  .pnav__buy { grid-column: 3; justify-self: end; display: flex; align-items: center; gap: var(--s-3); }
  .pnav__price { font-size: var(--t-small); line-height: 18px; text-align: right; white-space: nowrap; }

  /* too tight: pnav.js sheds the name, then the links, then the price.
     The buttons always stay; alone, they share the bar. */
  .pnav--no-name .pnav__name,
  .pnav--no-links .pnav__links,
  .pnav--no-price .pnav__price { display: none; }
  .pnav--no-price .pnav__buy { grid-column: 1 / -1; justify-self: stretch; gap: var(--s-1); }
  .pnav--no-price .pnav__buy .btn { flex: 1; }
}


/* ==========================================================================
   PAGE MODULES 04–09 — first pass at the Phosphor page's remaining sections,
   moved onto the grid and the type scale. Blocked in to iterate on.
     04 .split        copy one side, a colour panel the other with a screenshot
                      running off the page edge; rows alternate
     05 .speclist     "everything else": spec lists, plus a highlight block
     06 .video-grid   devlog videos (16:9 embeds)
     07 .faq          accordion; markup matches /faq.js, which animates it
     08 .proof        pull quote and the press logos
     09 .get-section  the download, inline (same form as the modal)
     10 .pcard        other products;  .buybox  the paid panel
     11 .site-foot    footer
   ========================================================================== */
@layer components {

  /* ---- 04 split -----------------------------------------------------------
     Copy one side; on the other, a panel of the product colour holding a
     screenshot that runs out through the panel and off the page edge.

       <div class="split__row grid">
         <div class="split__copy">
           <h2 class="t-module split__title">…</h2>          (first row only)
           <article class="feat"><h3 class="feat__title">…</h3><p>…</p></article>
         </div>
         <div class="split__panel"><img class="split__shot" …></div>
       </div>

     Copy: columns 1–5, the heading at the top, the features at the bottom,
     each on a thin rule of the key colour. Panel: columns 6–12, at least
     --split-panel-ratio tall, as tall as the copy otherwise. The screenshot
     sits --split-inset in from the panel's left edge, centred, at
     --split-shot-h of the panel's height (40%), and is cropped by the panel:
     a wide shot runs out to the panel's right edge, never past it. Its left
     side always shows, in either direction. To scale one up, set
     --split-shot-h on its row (style="--split-shot-h: 80%"); the panel crops
     what no longer fits. .split__row--flip mirrors the columns;
     .split__row--short makes a one-feature row's panel shorter;
     .split__row--center fits the shot whole, centred, uncropped.
     Below 1000: heading, panel, then the features side by side under it
     (three across if there are three; two across on phones). */
  .split__row { --split-inset: var(--s-6); --split-shot-h: 40%; --row-gap: var(--s-6); align-items: stretch; }
  .split__row + .split__row { margin-top: var(--split-gap, var(--s-16)); }
  /* --short: a row with little copy (one feature) and a wide, flat shot.
     The panel drops to about half its width tall; the shot takes a larger
     share of it, so it stays the same size. */
  .split__row--short { --split-panel-ratio: 50%; --split-shot-h: 62%; }
  /* --center: a shot that fits whole, centred both ways in the panel, one
     inset in from each side (a compact module that needs no cropping) */
  .split__row--center .split__shot {
    left: 50%;
    width: calc(100% - 2 * var(--split-inset));
    height: auto;
    translate: -50% -50%;
  }

  .split__copy {
    --start: 1; --span: 5;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    gap: var(--s-8);
  }
  .split__title { margin-bottom: auto; padding-bottom: var(--s-8); text-wrap: balance; }

  /* A feature sits on the grid: its rule on the column line, the text one
     gutter in, the whole block exactly four columns wide. */
  .feat {
    width: calc(4 * var(--col-pitch) - var(--gap));
    max-width: 100%;
    padding-left: var(--gap);
    border-left: 2px solid var(--key);
  }
  .feat__title {
    font-family: var(--font-label);
    font-size: var(--t-body);
    line-height: 24px;
    letter-spacing: var(--track);
    text-transform: uppercase;
  }
  .feat__title + p { margin-top: var(--s-2); color: var(--ink); }

  /* Prose instead of features: .split__body paragraphs at the foot of the
     copy, and optionally a .split__pull, one line set large in its own block
     on the key rule (the same rule as a feature). */
  .split__body { display: grid; gap: var(--s-2); color: var(--ink-soft); }
  .split__pull, .statement__pull {
    padding-left: var(--gap);
    border-left: 2px solid var(--key);
    font-size: var(--t-h3);
    line-height: 1.25;
    color: var(--ink);
    text-wrap: balance;
  }
  .split__pull + .split__body { margin-top: calc(var(--s-8) * -0.5); }
  /* a placeholder or video in the panel instead of a screenshot: a loop is
     <video class="split__shot" data-loop …> (loop.js plays it on screen) */
  .split__shot.ph { aspect-ratio: var(--ratio, 16 / 9); }

  .split__panel {
    --start: 6; --span: 7;
    position: relative;
    overflow: hidden;                  /* the shot is cropped to the panel */
    background: var(--split-bg, var(--theme-feature, var(--theme-bg)));
  }
  /* the panel's minimum height, as a share of its width */
  .split__panel::before { content: ""; display: block; padding-top: var(--split-panel-ratio, 78%); }
  .split__shot {
    position: absolute;
    top: 50%;
    left: var(--split-inset);
    /* sized by the panel's height, so it reads the same at any window width;
       a wide shot runs to the panel's right edge, which crops it */
    height: var(--split-shot-h);
    width: auto;
    max-width: none;                   /* the base img rule caps at 100% */
    translate: 0 -50%;
    border-radius: var(--radius-shot);
  }
  /* one row, whatever the source order (a mirrored row puts its panel in
     the earlier columns, which would otherwise wrap it to a new row) */
  .split__copy, .split__panel { grid-row: 1; }
  .split__row--flip .split__copy  { --start: 8; --span: 5; }
  .split__row--flip .split__panel { --start: 1; --span: 7; }

  /* ---- 04b toggle -----------------------------------------------------------
     A list of features beside one screenshot; picking a feature opens its
     description and, if it names its own image, swaps the screenshot to it.
     Driven by toggle.js.

       <section class="section section--fill toggle" data-toggle>
         <div class="container grid toggle__grid">
           <div class="toggle__copy">
             <h2 class="t-module toggle__title">…</h2>
             <div class="toggle__list">
               <div class="toggle__item" data-shot="/img.jpg" data-shot-alt="…">   (data-shot optional)
                 <h3><button class="toggle__btn" type="button">Title</button></h3>
                 <div class="toggle__body"><div><p>…</p></div></div>
               </div> …
             </div>
           </div>
           <div class="toggle__media"><img class="toggle__shot" …></div>   (the default image)
         </div>
       </section>

     An item without data-shot shows the default image. The fill runs almost
     edge to edge (--toggle-inset from the page edge); the content stays on
     the grid: copy on columns 1–4, the screenshot from column 5 out to the
     fill's edge, which crops it. Below 1000: heading, screenshot, then the
     list. */
  .toggle {
    --toggle-inset: var(--s-2);
    /* everything is cropped to the fill, so the shot stops at its edge */
    clip-path: inset(0 var(--toggle-inset) round var(--radius));
    /* One big blurred pill of the product colour, standing upright off to
       the right, on a dark field. (Blurred that far, a pill and an ellipse
       look the same, so it is an elliptical gradient: no blur filter to
       compute.) Sized to the window so it scales with the page. Its core
       sits right of centre, behind the screenshot's far side, and it falls
       away slowly, with no hard bright centre, well before the copy.
       --toggle-glow-x / -y move it; --toggle-glow-w / -h size it. */
    --section-bg: var(--toggle-bg,
      radial-gradient(var(--toggle-glow-w, 48vw) var(--toggle-glow-h, 76vw) at var(--toggle-glow-x, 88%) var(--toggle-glow-y, 55%),
        var(--accent) 0%,
        color-mix(in srgb, var(--accent) 80%, #031006) 24%,
        color-mix(in srgb, var(--accent) 48%, #031006) 50%,
        color-mix(in srgb, var(--accent) 18%, transparent) 76%,
        transparent 100%),
      linear-gradient(180deg,
        color-mix(in srgb, var(--accent) 16%, #031006) 0%,
        color-mix(in srgb, var(--accent) 4%, #020a04) 100%));
    --section-bg-w: calc(100% - 2 * var(--toggle-inset));
    --section-radius: var(--radius);
    --section-pt: var(--s-16);
    --section-pb: var(--s-16);
  }
  /* the fill is wider than the grid, so the content keeps the normal grid */
  .toggle.section--fill > .container { padding-inline: var(--margin); }
  /* stretch: the screenshot's box is at least as tall as the copy beside it */
  .toggle__grid { align-items: stretch; --row-gap: var(--s-6); }
  /* the copy never sits closer than --s-5 to the fill's edge: on a narrow
     screen the page margin alone leaves it tight against it. Wider, the
     padding is 0 and the rules sit on the column line, like a .feat's. The
     screenshot keeps its columns and still runs off the page. */
  .toggle__copy {
    --start: 1; --span: 4; grid-row: 1;
    padding-left: max(0px, var(--toggle-inset) + var(--s-5) - var(--margin));
  }
  .toggle__title { color: var(--key); text-wrap: balance; }
  .toggle__list { margin-top: var(--s-6); }
  /* optional: a paragraph under the title (stacked, it stays under the title) */
  .toggle__lede { margin-top: var(--s-3); color: var(--ink-soft); }

  /* each item on a rule like a .feat's: 2px on the column line, the text one
     gutter in. The closed ones' rules are a light grey that holds on the
     colour, as the phone tabs' outlines are */
  .toggle__item {
    padding: var(--s-3) 0 var(--s-3) var(--gap);
    border-left: 2px solid var(--toggle-rule, color-mix(in srgb, var(--ink) 60%, transparent));
    transition: border-color 240ms var(--ease);
  }
  .toggle__item + .toggle__item { margin-top: var(--s-1); }
  .toggle__item.is-open { border-left-color: var(--key); }
  .toggle__item h3 { margin: 0; }
  .toggle__btn {
    display: block;
    width: 100%;
    padding: 0;
    background: none;
    border: 0;
    text-align: left;
    font-family: var(--font-label);
    font-size: var(--t-body);
    line-height: 24px;
    letter-spacing: var(--track);
    text-transform: uppercase;
    color: var(--ink);
    cursor: pointer;
    transition: color 160ms var(--ease);
  }
  .toggle__btn:hover { color: var(--key); }
  .toggle__item.is-open .toggle__btn { cursor: default; color: var(--ink); }
  /* opens to its content's height (0fr → 1fr), no measuring */
  .toggle__body {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows 360ms var(--ease);
  }
  .toggle__body > div { overflow: hidden; }
  .toggle__body p { padding-top: var(--s-2); color: var(--ink); }
  .toggle__item.is-open .toggle__body { grid-template-rows: 1fr; }

  .toggle__media { --start: 5; --span: 8; grid-row: 1; position: relative; margin-left: var(--gap); }
  .toggle__shot {
    display: block;
    /* from the column, out to the page edge; the fill crops it */
    width: calc(100% - var(--gap) + var(--bleed) + var(--col-pitch));
    max-width: none;                   /* the base img rule caps at 100% */
    height: auto;
    /* never shorter than the copy beside it: on a narrower screen the copy
       is the taller of the two, so the shot grows to match and crops on the
       right, keeping its top-left corner */
    min-height: 100%;
    object-fit: cover;
    object-position: left top;
    border-radius: var(--radius-shot);
    transition: opacity 450ms var(--ease);
  }
  /* the default image is in the flow and sets the height (at least the
     copy's); swapped-in ones lie over it, cropped to the same box */
  .toggle__shot--alt {
    position: absolute;
    top: 0;
    left: 0;
    height: 100%;
    object-fit: cover;
    object-position: left top;
    opacity: 0;
  }
  .toggle__media.is-swapped .toggle__shot:not(.is-on) { opacity: 0; }
  .toggle__shot--alt.is-on { opacity: 1; }
  /* toggle--contain: media that stays inside its columns rather than
     running off the page (videos, squarer shots), up to --toggle-media-w */
  .toggle--contain .toggle__shot { width: min(100% - var(--gap), var(--toggle-media-w, 600px)); }

  @media (prefers-reduced-motion: reduce) {
    .toggle__body, .toggle__shot, .toggle__item { transition: none; }
  }
  .toggle__caption { display: none; }            /* smaller screens only, below */
  /* Smaller screens: heading, then the titles as a row of tabs, the image,
     and the open description under it, so the choices and the picture are
     on screen together. The content keeps --section-inset from the fill. */
  @media (max-width: 1000px) {
    .toggle { --section-pt: var(--s-10); --section-pb: var(--s-10); }
    .toggle.section--fill > .container { padding-inline: calc(var(--toggle-inset) + var(--section-inset)); }
    .toggle__grid { --row-gap: var(--s-3); }
    .toggle__copy { display: contents; padding-left: 0; }
    .toggle__copy, .toggle__media { grid-row: auto; }
    .toggle__title { order: 1; grid-column: 1 / -1; }
    .toggle__lede { order: 1; grid-column: 1 / -1; margin-top: 0; }
    .toggle__list { order: 2; grid-column: 1 / -1; margin-top: 0; display: flex; flex-wrap: wrap; gap: var(--s-1); }
    .toggle__media { order: 3; --start-md: 1; --span-md: 12; margin-left: 0; }
    .toggle__caption { order: 4; grid-column: 1 / -1; display: block; color: var(--ink); }
    .toggle__item, .toggle__item + .toggle__item { margin: 0; padding: 0; border: 0; }
    .toggle__body { display: none; }
    .toggle__btn {
      padding: var(--s-1) var(--s-2);
      border: 1px solid color-mix(in srgb, var(--ink) 60%, transparent);   /* as a secondary button on colour */
      border-radius: var(--radius);
      font-size: var(--t-small);
      line-height: 16px;
    }
    .toggle__item.is-open .toggle__btn { background: var(--key); border-color: var(--key); color: var(--key-ink); }
    .toggle__shot { width: calc(100% + var(--bleed) + var(--col-pitch)); }
    .toggle--contain .toggle__shot { width: min(100%, var(--toggle-media-w, 600px)); }
  }

  /* ---- 05 index -----------------------------------------------------------
     A spec list under a label. .speclist rules sit inside one column, so they
     never run the width of the page. */
  .speclist { margin: 0; padding: 0; list-style: none; }
  .speclist li {
    padding-block: var(--s-1);
    border-top: 1px solid var(--line);
    color: var(--ink-soft);
    font-size: var(--t-body);
    line-height: 24px;
  }
  .index__group > .t-label { margin-bottom: var(--s-1); }

  /* ---- 05b spec sheet --------------------------------------------------------
     The full specification, grouped. Groups flow down three columns, each
     exactly four grid columns wide (column gap = the grid gutter), so long
     and short groups balance without gaps. Two columns below 1000, one on
     phones. Linkable: give the section an id (#specs).

       <div class="specs">
         <section class="specs__group">
           <h3 class="specs__title">Filter</h3>
           <dl class="specs__list"><div><dt>Slope</dt><dd>12 or 24 dB/oct</dd></div> …</dl>
         </section> …
       </div> */
  .specs { columns: 3; column-gap: var(--gap); }
  .specs__group { break-inside: avoid; padding-bottom: var(--s-6); }
  .specs__title {
    margin: 0 0 var(--s-1);
    font-family: var(--font-label);
    font-size: var(--t-body);
    line-height: 24px;
    letter-spacing: var(--track);
    text-transform: uppercase;
    color: var(--key);
  }
  .specs__list { margin: 0; }
  .specs__list > div {
    display: grid;
    grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
    gap: var(--s-2);
    padding-block: var(--s-1);
    border-top: 1px solid var(--line);
  }
  .specs__list dt { color: var(--ink-soft); font-size: var(--t-small); line-height: 20px; padding-top: 2px; }
  .specs__list dd { margin: 0; color: var(--ink); font-size: var(--t-body); line-height: 24px; overflow-wrap: anywhere; }   /* a long id breaks rather than overflows */
  .specs__note { margin-top: var(--s-2); }
  @media (max-width: 1000px) { .specs { columns: 2; } }
  @media (max-width: 640px)  { .specs { columns: 1; } }

  /* ---- 05c specs at a glance ---------------------------------------------
     The scannable version: four headline numbers, then the essentials as a
     short two-column list (.specs__list rows, flowed into columns), then
     buttons that open the full sheet and what's new as modals.

       <div class="grid cols-4 stats"><div class="stat">
         <p class="stat__value">16</p><p class="stat__label">Voices</p></div> …</div>
       <dl class="specs__list specs__list--brief"> rows … </dl>
       <div class="specs-more"><button class="btn btn--secondary" data-modal="full-specs">Full specs</button> …</div>
     and the full sheet in <dialog class="dialog dialog--sheet" id="full-specs">
     (modal.js). #full-specs in the URL opens it. */
  .stats { --row-gap: var(--s-4); }
  /* .stats--large: the numbers as a page moment of their own (a text-led
     page with no screenshot to carry it), set at headline size */
  .stats--large { --row-gap: var(--s-8); }
  .stats--large .stat { padding-top: var(--s-3); }
  .stats--large .stat__value { font-size: var(--t-h1); }
  .stats--large .stat__label { margin-top: var(--s-2); }
  .stat { padding-top: var(--s-2); border-top: 1px solid var(--line); }
  .stat__value {
    font-family: var(--font-display);
    font-size: var(--t-h2);
    line-height: 1;
    text-transform: uppercase;
    color: var(--key);
  }
  .stat__label {
    margin-top: var(--s-1);
    font-family: var(--font-label);
    font-size: var(--t-label);
    line-height: 16px;
    letter-spacing: var(--track);
    text-transform: uppercase;
    color: var(--ink-soft);
  }
  .specs__list--brief { margin-top: var(--s-8); columns: 2; column-gap: var(--gap); }
  .specs__list--brief > div { break-inside: avoid; grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); }
  /* the list straight under the head (no numbers above it) */
  .section-head + .container > .specs__list--brief,
  .section-head + .stats[hidden] + .container > .specs__list--brief { margin-top: 0; }

  /* the way into the modals: Full specs, What's new (modal.js) */
  .specs-more { display: flex; flex-wrap: wrap; gap: var(--s-2); margin-top: var(--s-8); }

  @media (max-width: 1000px) { .stats > .stat { --span-md: 6; } }
  @media (max-width: 640px)  { .specs__list--brief { columns: 1; } }

  /* ---- 06 video grid ------------------------------------------------------
     A 16:9 box so the page never jumps while the embed loads. */
  .video-embed {
    aspect-ratio: 16 / 9;
    border-radius: var(--radius);
    background: var(--panel-2);
    overflow: hidden;
  }
  .video-embed iframe { display: block; width: 100%; height: 100%; border: 0; }
  .video__label { margin-top: var(--s-2); color: var(--ink-soft); }
  .video__title { margin-top: 4px; }

  /* ---- 06b devlog ---------------------------------------------------------
     The latest devlogs, drawn by /_system/devlog.js from /devlogs.js: the
     video's still with a play mark in the 06 frame, the number and title
     under it. Pressing the still swaps in the player. */
  .devlog__play {
    position: relative;
    display: grid;
    place-items: center;
    width: 100%;
    height: 100%;
    padding: 0;
    border: 0;
    background: var(--panel);
    cursor: pointer;
  }
  .devlog__still { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0.85; transition: opacity 160ms var(--ease); }
  .devlog__mark {
    position: relative;
    display: grid;
    place-items: center;
    width: var(--s-8);
    height: var(--s-8);
    background: var(--ground);
    border: 1px solid var(--key-line);
    border-radius: var(--radius);
    color: var(--key);
    transition: border-color 160ms var(--ease);
  }
  .devlog__icon { --icon-size: var(--s-3); --icon: url('/_images/icons/play.svg'); }
  .devlog__play:hover .devlog__still, .devlog__play:focus-visible .devlog__still { opacity: 1; }
  .devlog__play:hover .devlog__mark, .devlog__play:focus-visible .devlog__mark { border-color: var(--key); }
  .devlog__label { margin-top: var(--s-2); color: var(--ink-soft); }
  .devlog__title { margin-top: var(--s-1); font-family: var(--font-body); font-size: var(--t-body); line-height: 24px; letter-spacing: 0; }

  /* ---- 07b faq, grouped (the support page) ----------------------------------
     A sticky list of the groups on the left (columns 1–3), the groups on the
     right (5–12): one group for everything that applies to all plugins,
     then one per product. Stacked below 1000, the list sits on top. */
  .faq-nav, .doc-toc { --span: 3; --span-md: 12; position: sticky; top: calc(var(--nav-h-closed) + var(--s-4)); }
  .faq-nav__label, .doc-toc__label {
    margin-bottom: var(--s-1);
    font-family: var(--font-label);
    font-size: var(--t-label);
    line-height: 16px;
    letter-spacing: var(--track);
    text-transform: uppercase;
    color: var(--ink-faint);
  }
  .faq-nav ul, .doc-toc ul { margin: 0 0 var(--s-4); padding: 0; list-style: none; }
  .faq-nav a, .doc-toc a { display: block; padding-block: var(--half); color: var(--ink); text-decoration: none; }
  .faq-nav a:hover, .doc-toc a:hover { color: var(--key); }
  .faq-groups { --start: 5; --span: 8; --start-md: 1; --span-md: 12; }
  .faq-group + .faq-group { margin-top: var(--s-10); }
  .faq-group__title {
    margin-bottom: var(--s-2);
    font-family: var(--font-label);
    font-size: var(--t-body);
    line-height: 24px;
    letter-spacing: var(--track);
    text-transform: uppercase;
    color: var(--key);
  }
  .faq-group { scroll-margin-top: calc(var(--nav-h-closed) + var(--s-4)); }
  @media (max-width: 1000px) {
    .faq-nav, .doc-toc { position: static; }
    .faq-nav ul, .doc-toc ul { margin-bottom: var(--s-3); }
  }

  /* ---- 10b link cards ------------------------------------------------------
     A grid of links, each whole card the link: a title with an arrow, one
     line under it. For help and resources (the support page). With a
     .icon.linkcard__icon first (see ICON) the card becomes a tile: taller,
     the icon at the top, the words at the foot, no arrow, and a slower
     hover (key outline and a rising wash).
       <nav class="container grid cols-4 grid--stretch linkcards" aria-label="…">
         <a class="linkcard" href="…">
           <span class="icon linkcard__icon" style="--icon: url('/_images/icons/bug.svg')" aria-hidden="true"></span>
           <span class="linkcard__title">…</span>
           <span class="linkcard__line">…</span></a> …
       </nav> */
  .linkcards { --row-gap: var(--gap); }
  .linkcard {
    display: flex;
    flex-direction: column;
    gap: var(--s-1);
    padding: var(--s-3);
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    color: var(--ink);
    text-decoration: none;
    transition: border-color 160ms var(--ease);
  }
  .linkcard__title {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--s-2);
    font-family: var(--font-label);
    font-size: var(--t-body);
    line-height: 24px;
    letter-spacing: var(--track);
    text-transform: uppercase;
    transition: color 160ms var(--ease);
  }
  .linkcard__title::after { content: "\2192"; transition: translate 160ms var(--ease); }
  .linkcard__line { color: var(--ink-soft); font-size: var(--t-small); line-height: 20px; }
  /* with an icon the card becomes a tile: taller, the icon at the top and
     the words at the foot, no arrow. On hover the outline goes to the key
     colour and a wash of it rises from the foot, both slowly. */
  .linkcard:has(.linkcard__icon) {
    position: relative;
    min-height: var(--s-20);
    padding: var(--s-4);
    background: linear-gradient(to top, var(--key-soft), transparent) bottom / 100% 0% no-repeat, var(--panel);
    transition: border-color 400ms var(--ease), background-size 400ms var(--ease);
  }
  .linkcard:has(.linkcard__icon) .linkcard__title::after { content: none; }
  .linkcard__icon { --icon-size: var(--s-6); margin-bottom: auto; color: var(--key); }
  .linkcard:has(.linkcard__icon):is(:hover, :focus-visible) { border-color: var(--key); background-size: 100% 100%, auto; }
  .linkcard__icon + .linkcard__title { padding-top: var(--s-4); }
  .linkcard:has(.linkcard__icon) .linkcard__line { min-height: 2lh; }   /* two lines held, so titles in a row line up */
  @media (max-width: 640px) {
    /* one per row on phones: no held height, the icon just above the words */
    .linkcard:has(.linkcard__icon) { min-height: 0; padding: var(--s-3); }
    .linkcard__icon + .linkcard__title { padding-top: var(--s-2); }
    .linkcard:has(.linkcard__icon) .linkcard__line { min-height: 0; }
  }
  .linkcard:hover, .linkcard:focus-visible { border-color: var(--key-line); }
  .linkcard:hover .linkcard__title, .linkcard:focus-visible .linkcard__title { color: var(--key); }
  .linkcard:hover .linkcard__title::after { translate: 4px 0; }
  @media (prefers-reduced-motion: reduce) { .linkcard__title::after { transition: none; } }

  /* ---- 07 faq -------------------------------------------------------------
     <details class="faq-item"><summary class="faq-item__q">…<span class="faq-item__marker">
     Works with no JS; /faq.js animates the open and close. */
  .faq-item { border-top: 1px solid var(--line); }
  .faq-item:last-of-type { border-bottom: 1px solid var(--line); }
  .faq-item__q {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-3);
    padding-block: var(--s-3);
    font-family: var(--font-sub);
    font-weight: var(--font-sub-weight);
    letter-spacing: var(--font-sub-track);
    font-size: var(--t-h4);
    line-height: 24px;
    cursor: pointer;
    list-style: none;
  }
  .faq-item__q::-webkit-details-marker { display: none; }
  .faq-item__q:hover { color: var(--key); }
  /* a plus that becomes a minus */
  .faq-item__marker {
    position: relative;
    flex: none;
    width: 16px;
    height: 16px;
  }
  .faq-item__marker::before, .faq-item__marker::after {
    content: "";
    position: absolute;
    inset: 50% 0 auto;
    height: 1px;
    background: currentColor;
    transition: rotate 200ms var(--ease), opacity 200ms var(--ease);
  }
  .faq-item__marker::after { rotate: 90deg; }
  .faq-item[open] .faq-item__marker::after { rotate: 0deg; opacity: 0; }
  .faq-item__a { padding-bottom: var(--s-3); color: var(--ink-soft); max-width: 68ch; }
  .faq-item__a > * + * { margin-top: var(--s-2); }

  /* ---- 08 proof -----------------------------------------------------------
     Quote left (6 columns), the press tiles right (5), both centred on
     each other — the layout from the old page, on the grid. */
  .proof__grid { align-items: center; --row-gap: var(--s-6); }
  .proof__quote { --start: 1; --span: 6; text-wrap: balance; }
  /* .t-statement: large body type for an opening statement (a page's first
     line, under its title) */
  .t-statement {
    margin: 0;
    font-family: var(--font-body);
    font-size: var(--t-quote);
    line-height: 1.2;
    line-height: round(up, 1.2em, var(--half));
    letter-spacing: -0.01em;
  }
  /* the pull quote: still the body face (reads as someone talking), but a
     step up at h2 and set tight. The opening mark hangs into the margin so
     the words line up on the column edge; an .accent picks out the line
     that matters:  <blockquote class="proof__text">&ldquo;… <span
     class="accent">fall apart.</span>&rdquo;</blockquote> */
  .proof__text {
    margin: 0;
    font-family: var(--font-body);
    font-size: var(--t-h2);
    line-height: 1.05;
    line-height: round(up, 1.05em, var(--half));
    letter-spacing: -0.02em;
    text-indent: -0.24em;              /* the width of an opening quote mark in Neue Corp */
  }
  .proof__credit { margin-top: var(--s-2); color: var(--key); }
  .t-statement { text-wrap: pretty; text-transform: none; }   /* also works as a page's h1 */
  .proof__featured { --start: 8; --span: 5; }
  .proof__featured .eyebrow { margin-bottom: var(--s-2); }
  .proof__logos {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--s-1);
  }
  .proof__logo {
    display: block;
    aspect-ratio: 1 / 1;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    overflow: hidden;
  }
  .proof__logo img { width: 100%; height: 100%; object-fit: cover; }

  /* ---- 07 get section -----------------------------------------------------
     The same form as the modal, inline where someone has scrolled to it, so
     it is never an extra click. get.js fills [data-get-form]. */
  .get-section__form { --start: 7; --span: 6; }
  .get-section__copy { --start: 1; --span: 5; }

  /* ---- 10 related + buy panel ---------------------------------------------- */
  .pcard { gap: var(--s-2); }
  .pcard__head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-2); }
  .pcard__price { color: var(--key); }

  /* The paid counterpart of the download: price, one button, the terms. */
  .buybox { gap: var(--s-3); }
  .buybox__price { font-family: var(--font-display); font-size: var(--t-h2); line-height: 1; }
  .buybox__terms { color: var(--ink-soft); }
  /* the full buy box (Nitrate): was-price, a second store, what's included */
  .buybox__was { margin-right: var(--s-1); color: var(--ink-faint); text-decoration-thickness: 2px; }
  .buybox__alt { color: var(--ink-soft); }
  .buybox__includes { margin: 0; padding: var(--s-2) 0 0; list-style: none; border-top: 1px solid var(--line); }
  .buybox__includes li { padding-block: var(--s-1); color: var(--ink); }
  .buybox__includes li + li { border-top: 1px solid var(--line); }
  /* the copy beside it: the pricing note and an offer (early buyers) */
  .buynote { color: var(--ink-soft); }
  .buyoffer {
    padding: var(--s-3);
    border: 1px solid var(--key-line);
    background: var(--key-soft);
  }
  .buyoffer > .eyebrow { margin-bottom: var(--s-1); }
  .buybox > .eyebrow + .buybox__price { margin-top: calc(var(--s-3) * -1 + var(--s-1)); }

  /* ---- 09 footer ----------------------------------------------------------- */
  .site-foot { padding-block: var(--s-12) var(--s-8); }
  .site-foot__grid { --row-gap: var(--s-6); }
  .site-foot__about { --span: 4; color: var(--ink-soft); }
  .site-foot__about strong { color: var(--ink); }
  .site-foot__nav { --span: 2; display: flex; flex-direction: column; gap: var(--s-1); }
  .site-foot__nav a { color: var(--ink-soft); text-decoration: none; }
  .site-foot__nav a:hover { color: var(--key); }
  .site-foot__legal {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: var(--s-1) var(--s-4);
    margin-top: var(--s-8);
    padding-top: var(--s-2);
    border-top: 1px solid var(--line);
  }

  @media (max-width: 1000px) {
    /* stacked: the image leads, then the copy */
    /* Stacked: heading, then the panel, then the features side by side
       under it. The copy column dissolves (display: contents) so the
       heading and each feature become items on the row's own grid. */
    .split__row { --split-gap: var(--s-12); }
    .split__copy { display: contents; }
    .split__copy, .split__panel { grid-row: auto; }
    /* (the heading and features are the copy's children, not the grid's, so
       the grid's span variables don't reach them: place them directly) */
    .split__title { order: 1; grid-column: 1 / -1; padding-bottom: 0; margin-bottom: 0; }
    .split__panel,
    .split__row--flip .split__panel { order: 2; --start-md: 1; --span-md: 12; }
    .feat { order: 3; width: auto; grid-column: span 6; }
    .split__body, .split__pull { order: 3; grid-column: 1 / -1; }
    /* three features: three across */
    .split__copy:has(> .feat:nth-of-type(3)) > .feat { grid-column: span 4; }
    /* one feature: it takes the whole row (both places), not half of it */
    .split__copy:has(> .feat:only-of-type) > .feat { grid-column: 1 / -1; }
    .get-section__form, .get-section__copy { --start-md: 1; --span-md: 12; }
    .site-foot__about { --span-md: 12; }
    .site-foot__nav { --span-md: 4; }
    .proof__quote, .proof__featured { --start-md: 1; --span-md: 12; }
    .proof__featured { max-width: 520px; }
  }
  @media (max-width: 640px) {
    .split__row + .split__row { margin-top: var(--s-10, var(--s-8)); }
    /* phones: two across at most; a third wraps under the first */
    .feat,
    .split__copy:has(> .feat:nth-of-type(3)) > .feat { grid-column: span 6; padding-left: var(--s-2); }
    .site-foot__nav { --span-sm: 6; }
  }
}


/* ==========================================================================
   09b NOTIFY — "tell me when it's out", driven by /_system/notify.js
   One email field and a button on one row (stacked on phones), the same
   human fine print as the download form, and a status line. On success the
   row is replaced by a thank-you line. .hp is the honeypot: off-screen.
   ========================================================================== */
@layer components {
  .notify { max-width: 560px; }
  .notify__row { display: flex; gap: var(--s-1); }
  .notify__row > .field { flex: 1; min-width: 0; }
  .notify__row > .btn { flex: none; }
  .notify__fine { margin-top: var(--s-2); }
  .notify__status:empty { display: none; }
  .notify__status { margin-top: var(--s-1); font-size: var(--t-small); line-height: 20px; color: var(--ink-soft); }
  .notify__status.is-error { color: var(--danger); }
  .notify__done { font-size: var(--t-lede); line-height: 1.4; color: var(--key); outline: none; }
  .hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
  @media (max-width: 640px) {
    .notify__row { flex-direction: column; }
  }
}


/* ==========================================================================
   PROMO PANEL — a filled panel (.section--fill) that points somewhere else:
   the copy and buttons on the left, screenshots on the right, cropped by
   the panel's right and bottom edges. Two shots cascade (back, front), or
   one (.promo__shot--solo) for a panel that points at a single product.
     <section class="section section--fill promo">
       <div class="container grid promo__grid">
         <div class="promo__copy">eyebrow, h2, lede, .btn-row</div>
         <div class="promo__media" aria-hidden="true">
           <picture class="promo__shot promo__shot--back">…</picture>
           <picture class="promo__shot promo__shot--front">…</picture>
         </div>
       </div>
     </section>
   Stacked below 1000: the copy, then the shots under it.
   .promo--bar: the same fill as a slim banner inside a section, no shots:
   the copy on the left, the buttons on the right; they wrap to stacked.
     <div class="container">
       <aside class="promo promo--bar"><div class="promo__copy">eyebrow, h2.t-h3, p</div> .btn-row</aside>
     </div>
   ========================================================================== */
@layer components {
  .promo { --section-pb: 0px; }
  .promo__grid { align-items: stretch; --row-gap: var(--s-6); }
  .promo__copy { --span: 6; --span-md: 12; align-self: center; padding-bottom: var(--section-y); }
  .promo__copy .btn-row { margin-top: var(--s-6); }
  .promo--bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-3) var(--s-4);
    padding: var(--s-4);
    background: var(--theme-bg);
    border-radius: var(--radius);
  }
  .promo--bar .promo__copy { padding-bottom: 0; max-width: 40em; }
  .promo--bar .promo__copy > * + * { margin-top: var(--s-1); }
  .promo__media {
    --start: 7; --span: 6; --start-md: 1; --span-md: 12;
    position: relative;
    min-height: calc(var(--s-20) * 3);
    margin-right: calc(var(--section-inset) * -1);    /* out to the panel's edge */
    /* crop only where the panel ends (right, bottom); left and top stay open
       so the shots' shadows fall softly instead of being cut off in mid-panel */
    clip-path: inset(calc(var(--s-20) * -1) 0 0 calc(var(--s-20) * -1));
  }
  .promo__shot { position: absolute; display: block; width: 130%; }
  .promo__shot img { display: block; width: 100%; height: auto; max-width: none; border-radius: var(--radius-shot); box-shadow: 0 24px 64px var(--shadow); }
  .promo__shot--back { top: var(--s-4); left: 22%; }
  .promo__shot--back img { opacity: 0.85; }
  .promo__shot--front { top: 32%; left: 0; }
  .promo__shot--solo { top: var(--s-8); left: var(--s-4); }
  @media (max-width: 1000px) {
    .promo__copy { padding-bottom: 0; }
    /* stacked, the media runs to the panel's left edge too: crop that side as well */
    .promo__media { min-height: calc(var(--s-20) * 1.5); margin-left: calc(var(--section-inset) * -1); clip-path: inset(calc(var(--s-20) * -1) 0 0 0); }
    .promo__shot--back { left: 30%; }
    .promo__shot--front { left: var(--section-inset); }
    .promo__shot--solo { top: var(--s-2); left: var(--section-inset); }
  }
}


/* ==========================================================================
   ICON TILE — a product's symbol in a square with a hairline
   Every tile is the same square, so the products read as one family; the
   symbol takes its own product's colour from data-theme on the tile. The
   symbol is a mask, so any single-colour SVG works as is.
     <span class="icon-tile" data-theme="nitrate" aria-hidden="true">
       <span class="icon-tile__glyph" style="--icon: url('/_images/brand/products/nitrate.svg')"></span>
     </span>
   Size: --tile and --glyph (48 / 32 by default; 40 / 26 in the nav).
   ========================================================================== */
@layer components {
  .icon-tile {
    --tile: 48px;
    --glyph: 32px;
    flex: none;
    display: grid;
    place-items: center;
    width: var(--tile);
    height: var(--tile);
    background: var(--ground);
    border: 1px solid color-mix(in srgb, var(--ink) 14%, transparent);
    border-radius: var(--radius);
    transition: border-color 160ms var(--ease);
  }
  .icon-tile__glyph {
    width: var(--glyph);
    height: var(--glyph);
    background: var(--key);
    -webkit-mask: var(--icon) center / contain no-repeat;
    mask: var(--icon) center / contain no-repeat;
  }

  /* product cards (module 10 with an icon): the tile and the price on top */
  .pcard__top { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--s-2); }
  .card:has(.pcard__cta:hover) .icon-tile { border-color: var(--key-line); }
}


/* ==========================================================================
   10c CALLOUT — a product, featured: a pair of them side by side
   Each is the feature header in miniature: the product's colour field
   (--theme-feature), its symbol and name large, its status flag, the
   buttons, and its interface hanging off the bottom edge. The theme on the
   callout colours everything in it, so a pair can show two products.
     <div class="container grid callouts">
       <article class="callout" data-theme="phosphor" aria-labelledby="…">
         <div class="callout__head">
           <span class="callout__mark" style="--icon: url('/_images/brand/products/phosphor-1.svg')" aria-hidden="true"></span>
           <span class="flag flag--free">Free</span>
         </div>
         <h2 class="callout__name" id="…">OA-Phosphor-1</h2>
         <p class="callout__line">…</p>
         <div class="btn-row"> buttons </div>
         <picture class="callout__shot" aria-hidden="true">…</picture>
       </article> …
     </div>
   Two across, one each on tablets and phones. Shots are cropped to one
   ratio from the top, so a pair lines up whatever the screenshots are.
   ========================================================================== */
@layer components {
  .callouts { --row-gap: var(--gap); align-items: stretch; }
  .callouts > .callout { --span: 6; --span-md: 12; }
  .callout {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    padding: var(--s-6) var(--s-6) 0;
    background: var(--theme-feature, var(--theme-bg));
    border-radius: var(--radius);
    color: var(--ink);
  }
  .callout__head { display: flex; align-items: center; justify-content: space-between; gap: var(--s-2); }
  .callout__mark {
    width: var(--s-8);
    height: var(--s-8);
    background: var(--ink);
    -webkit-mask: var(--icon) left center / contain no-repeat;
    mask: var(--icon) left center / contain no-repeat;
  }
  .callout__name {
    margin-top: var(--s-6);
    font-family: var(--font-display);
    font-size: var(--t-module);
    line-height: 1;
    text-transform: uppercase;
    white-space: nowrap;   /* a product name never breaks at its hyphen */
  }
  .callout__line { margin-top: var(--s-2); max-width: 32em; font-size: var(--t-lede); line-height: 1.4; color: var(--ink); }
  .callout .btn-row { margin-top: var(--s-4); }
  .callout__shot { display: block; margin-top: auto; padding-top: var(--s-8); }
  .callout__shot img {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 16 / 10;
    object-fit: cover;
    object-position: top left;
    border-radius: var(--radius-shot) var(--radius-shot) 0 0;
    box-shadow: 0 24px 64px var(--shadow);
  }
  @media (max-width: 640px) {
    .callout { padding: var(--s-4) var(--s-2) 0; }   /* room for a long name on one line */
    .callout__mark { width: var(--s-6); height: var(--s-6); }
    /* one line down to ~380 px wide; on the narrowest phones a long name may
       break (after "OA-") rather than be clipped */
    .callout__name { margin-top: var(--s-4); white-space: normal; text-wrap: balance; }
  }
}


/* ==========================================================================
   FLAG — a product's status: Free or Soon (the nav, product cards)
   Two kinds that never read alike: Free is filled in the product's own
   colour (data-theme on the flag); Soon is a quiet grey fill, the same on
   every product. Square, like everything else.
     <span class="flag flag--free" data-theme="phosphor">Free</span>
     <span class="flag flag--soon">Soon</span>
   nav.js makes them from a product's `tag` in products.js.
   ========================================================================== */
@layer components {
  .flag {
    display: inline-flex;
    align-items: center;
    flex: none;
    height: var(--s-3);
    padding: 0 var(--s-1);
    border-radius: var(--radius);
    font-family: var(--font-label);
    font-weight: var(--ui-weight);
    font-size: var(--t-small);
    line-height: 1;
    letter-spacing: var(--track);
    text-transform: uppercase;
    white-space: nowrap;
  }
  .flag--free { background: var(--key); color: var(--key-ink); }
  .flag--soon { background: color-mix(in srgb, var(--ink) 12%, transparent); color: var(--ink-soft); }
  /* in development: further off than Soon, so no fill, just an outline */
  .flag--dev { background: none; box-shadow: inset 0 0 0 1px var(--line-strong); color: var(--ink-faint); }
  .flag + .flag { margin-left: var(--s-1); }
}


/* ==========================================================================
   ICON — Iconoir (MIT), vendored by scripts/icons.sh into
   /_images/icons/<name>.svg (the regular weight: a 1.5 line on a 24 grid).
   Add a name to scripts/icons.txt and re-run the script. Parked for a
   revert, each with its own script: Carbon (_images/icons-carbon),
   Pixelarticons (_images/icons-pixel), Phosphor (_images/icons-phosphor).
     <span class="icon" style="--icon: url('/_images/icons/cpu.svg')" aria-hidden="true"></span>
   Drawn as a mask in currentColor, so it takes the text colour (or .accent)
   in any theme. Sized to the text (1em) unless --icon-size says otherwise.
   Decorative: the words beside it carry the meaning.
   ========================================================================== */
@layer components {
  .icon {
    display: inline-block;
    flex: none;
    width: var(--icon-size, 1em);
    height: var(--icon-size, 1em);
    vertical-align: -0.125em;
    background: currentColor;
    -webkit-mask: var(--icon) center / contain no-repeat;
    mask: var(--icon) center / contain no-repeat;
  }
}


/* ==========================================================================
   12 DOC — long-form pages (terms, privacy, refunds, about, changelog …)
   The head is a .section-head (eyebrow: "Last updated …"; h1). Below it,
   .doc-layout: the contents on the left (columns 1–3, sticky; doc.js fills
   it from the h2s) and the text on the right (5–11), set for reading.
   Stacked below 1000, the contents sit on top.
     <div class="container grid doc-layout">
       <nav class="doc-toc" aria-label="On this page" data-doc-toc></nav>
       <div class="prose"> h2, h3, p, ul, ol, code, .note, .colophon </div>
     </div>
   ========================================================================== */
@layer components {
  .prose { --start: 5; --span: 7; --start-md: 1; --span-md: 12; font-size: var(--t-body); line-height: 28px; }
  .prose > * + * { margin-top: var(--s-2); }
  .prose h2 {
    margin-top: var(--s-8);
    font-family: var(--font-label);
    font-size: var(--t-body);
    line-height: 24px;
    letter-spacing: var(--track);
    text-transform: uppercase;
    color: var(--key);
    scroll-margin-top: calc(var(--nav-h-closed) + var(--s-4));
  }
  .prose > h2:first-child { margin-top: 0; }
  .prose h3 { margin-top: var(--s-4); font-family: var(--font-sub); font-size: var(--t-h4); line-height: 24px; }
  .prose ul, .prose ol { padding-left: 1.25em; }
  .prose li + li { margin-top: var(--s-1); }
  .prose li::marker { color: var(--key); }
  .prose a:not(.btn) { color: var(--ink); text-decoration: underline; text-decoration-color: var(--key); text-underline-offset: 3px; }
  .prose a:not(.btn):hover { color: var(--key); }   /* a button in prose keeps its own look */
  .prose code { font-family: var(--font-mono); padding: 1px var(--half); background: var(--panel-2); }
  /* a boxed aside: a note for existing users, a warning */
  .prose .note { padding: var(--s-3); border: 1px solid var(--key-line); background: var(--key-soft); }
  .prose .note > * + * { margin-top: var(--s-1); }
  .prose .note > h2:first-child { margin-top: 0; }
  /* .note--danger: the same box in the error colour (a failed payment) */
  .prose .note--danger { border-color: var(--danger-line); background: var(--danger-soft); }
  .prose .note--danger > h2 { color: var(--danger); }
  /* a receipt in a note: the specs rows (.specs__list), ruled off the text above */
  .prose .note > .specs__list { margin-top: var(--s-3); }
  .prose .note > .btn-row { margin-top: var(--s-3); }
  /* .doc-layout--pair: no contents column; the text in half-width cells, two
     to a row, stacked below 1000 (thank-you pages: receipt beside key). A
     box alone in its cell fills the row's height, so paired boxes match. */
  .doc-layout--pair { --row-gap: var(--s-4); align-items: stretch; }
  .doc-layout--pair .prose { --start: auto; --span: 6; }
  .doc-layout--pair .prose > .note:only-child { height: 100%; padding: var(--s-4); }   /* paired boxes share the key box's padding, so their heads line up */
  /* .note--key: one value the reader must keep (a licence key), boxed in the
     key colour and set large enough to read back */
  .prose .note--key { position: relative; border-color: var(--key); padding: var(--s-4); }
  .prose .note--key h2 { margin-top: 0; padding-right: var(--s-6); }
  .prose .note--key code { display: block; padding: 0; background: none; font-size: var(--t-h3); line-height: 1.3; color: var(--ink); overflow-wrap: anywhere; }
  /* its copy button: an icon in the top corner (copy, then a tick once done) */
  .note__copy {
    position: absolute;
    top: var(--s-2);
    right: var(--s-2);
    display: grid;
    place-items: center;
    width: var(--s-5);
    height: var(--s-5);
    padding: 0;
    background: transparent;
    border: 1px solid var(--key-line);
    border-radius: var(--radius);
    color: var(--key);
    cursor: pointer;
    transition: border-color 160ms var(--ease), background-color 160ms var(--ease);
  }
  .prose .note > .note__copy { margin: 0; }      /* not the note's stacking gap: same inset top and right */
  .note__copy:hover, .note__copy:focus-visible { border-color: var(--key); background: var(--key-soft); outline: none; }
  .note__copy .icon { --icon-size: var(--s-3); --icon: url('/_images/icons/copy.svg'); }
  .note__copy.is-done .icon { --icon: url('/_images/icons/check.svg'); }
  .prose .colophon { margin-top: var(--s-6); font-size: var(--t-small); line-height: 20px; color: var(--ink-faint); }
  /* the changelog: one release per section, its date under the version */
  .prose .release + .release { margin-top: var(--s-8); }
  .prose .release > * + * { margin-top: var(--s-2); }
  .prose .release__date { margin-top: var(--s-1); }
}


/* ==========================================================================
   12b GUIDE — a product's guide, one page per product (/guide/<product>);
   the same text as the help built into the plugin
   Written by scripts/guide.py from the plugin's HELP.md; guide.js adds the
   filter and marks where you are in the contents.
     <div class="container grid guide" data-guide>
       <aside class="guide-side">
         <label class="visually-hidden" for="guide-q">Filter</label>
         <input class="field guide-search" id="guide-q" type="search" data-guide-search>
         <nav class="guide-nav"> <ol> <li class="guide-nav__chapter">
           <a href="#chapter">…</a> <ol class="guide-nav__modules"><li><a href="#module">…</a></li> …</ol>
         </li> … </ol> </nav>
       </aside>
       <div class="guide-body">
         <section class="guide-chapter" id="…"> <div class="guide-chapter__head"> h2, p </div>
           <article class="guide-module" id="…"> h3.guide-module__title, p.guide-module__intro,
             <dl class="guide-controls"> <div id="…"><dt>…</dt><dd>…</dd></div> … </dl>
           </article> …
         </section> …
         <p class="guide-empty" data-guide-empty hidden>…</p>
       </div>
     </div>
   Every chapter, module and control has an id, so each can be linked to.
   The contents sit on the left (columns 1–3, sticky, scrolling on their
   own); the text on 5–12. Below 1000 the contents sit on top, chapters
   only. Tokens only.
   ========================================================================== */
@layer components {
  .guide { align-items: start; }
  .guide-side {
    --span: 3; --span-md: 12;
    position: sticky;
    top: calc(var(--nav-h-closed) + var(--s-4));
  }
  .guide-side__bar { display: none; }            /* the bottom bar: smaller screens only */
  /* the product, small, at the head of the contents: its symbol, its name
     (the page's h1) and the guide's version. The page has no other header. */
  .guide-brand { display: flex; align-items: center; gap: var(--s-2); margin-bottom: var(--s-4); }
  .guide-brand__mark {
    flex: none;
    width: var(--s-4);
    height: var(--s-4);
    background: var(--key);
    -webkit-mask: var(--icon) center / contain no-repeat;
    mask: var(--icon) center / contain no-repeat;
  }
  .guide-brand__name { font-family: var(--font-head); font-weight: var(--ui-weight); font-size: var(--t-body); line-height: 20px; color: var(--ink); }
  .guide-brand__meta { font-size: var(--t-small); line-height: 20px; color: var(--ink-soft); }
  .guide-brand__name a { color: inherit; text-decoration: none; }
  .guide-brand__name a:hover { color: var(--key); }
  .guide-brand__meta a { color: inherit; text-underline-offset: 3px; }
  .guide-brand__meta a:hover { color: var(--key); }
  /* on smaller screens the contents leave for the bottom bar, so the same
     line heads the text instead */
  .guide-brand--body { display: none; }
  .guide-search { margin-bottom: var(--s-4); }
  .guide-nav ol { margin: 0; padding: 0; list-style: none; }
  .guide-nav__chapter + .guide-nav__chapter { margin-top: var(--s-1); }
  /* a chapter: the section label, in caps; it opens its modules */
  .guide-nav__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-2);
    width: 100%;
    padding: var(--s-1) 0;
    background: none;
    border: 0;
    text-align: left;
    font-family: var(--font-label);
    font-size: var(--t-small);
    line-height: 16px;
    letter-spacing: var(--track);
    text-transform: uppercase;
    color: var(--ink-soft);
    text-decoration: none;
    cursor: pointer;
  }
  button.guide-nav__head::after { content: "+"; color: var(--ink-faint); }
  button.guide-nav__head[aria-expanded="true"]::after { content: "\2212"; }
  .guide-nav__modules { margin-bottom: var(--s-2); }
  /* a module: normal case, on a hairline */
  .guide-nav__modules a {
    display: block;
    padding: var(--half) 0 var(--half) var(--s-2);
    border-left: 1px solid var(--line);
    font-size: var(--t-small);
    line-height: 20px;
    color: var(--ink);
    text-decoration: none;
  }
  .guide-nav :is(a, button):hover { color: var(--key); }
  .guide-nav [aria-current="true"] { color: var(--key); border-left-color: var(--key); }

  .guide-body { --start: 5; --span: 8; --start-md: 1; --span-md: 12; }
  .guide-body a { color: var(--ink); text-decoration: underline; text-decoration-color: var(--key); text-underline-offset: 3px; }
  .guide-body a:hover { color: var(--key); }
  /* the page's opening: how to use the help, and where to start */
  .guide-intro { display: grid; gap: var(--s-2); max-width: 68ch; color: var(--ink-soft); }
  .guide-note { margin-top: var(--s-4); padding: var(--s-3); border: 1px solid var(--key-line); background: var(--key-soft); color: var(--ink); max-width: 68ch; }

  .guide-chapter { margin-top: var(--s-16); scroll-margin-top: calc(var(--nav-h-closed) + var(--s-4)); }
  .guide-chapter--start { margin-top: 0; }        /* the opening section */
  .guide-chapter__head p { margin-top: var(--s-2); max-width: 60ch; color: var(--ink-soft); }
  .guide-chapter__head + .guide-intro { margin-top: var(--s-4); }
  .guide-module {
    margin-top: var(--s-8);
    padding-top: var(--s-4);
    border-top: 1px solid var(--line-strong);
    scroll-margin-top: calc(var(--nav-h-closed) + var(--s-4));
  }
  .guide-module__title {
    display: flex;
    align-items: baseline;
    gap: var(--s-1);
    font-family: var(--font-label);
    font-size: var(--t-h3);
    line-height: 1.2;
    letter-spacing: var(--track);
    text-transform: uppercase;
    color: var(--key);
  }
  .guide-module__icon { --icon-size: var(--s-4); align-self: center; margin-right: var(--s-1); color: var(--key); }
  /* the link to this module or control, shown on hover or focus */
  .guide-body .guide-anchor { color: var(--ink-faint); text-decoration: none; opacity: 0; transition: opacity 160ms var(--ease); }
  :is(.guide-module__title, .guide-controls dt):hover .guide-anchor,
  .guide-body .guide-anchor:focus-visible { opacity: 1; }
  .guide-module__intro { margin-top: var(--s-2); max-width: 68ch; color: var(--ink); }
  /* the module itself, cropped from the interface (scripts/guide-shots.py) */
  .guide-module__shot { display: block; max-width: 100%; height: auto; margin-top: var(--s-4); border-radius: var(--radius-shot); }
  .guide-controls { margin-top: var(--s-4); }
  .guide-controls > div {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 3fr);
    gap: var(--gap);
    padding-block: var(--s-2);
    border-top: 1px solid var(--line);
    scroll-margin-top: calc(var(--nav-h-closed) + var(--s-4));
  }
  .guide-controls dt {
    display: flex;
    align-items: baseline;
    gap: var(--s-1);
    font-family: var(--font-label);
    font-size: var(--t-small);
    line-height: 24px;
    letter-spacing: var(--track);
    text-transform: uppercase;
    color: var(--ink);
  }
  .guide-controls dd { margin: 0; color: var(--ink-soft); }
  /* the one a link pointed at */
  .guide-controls > div:target { background: var(--key-soft); box-shadow: calc(var(--s-1) * -1) 0 0 var(--key-soft), var(--s-1) 0 0 var(--key-soft); }
  .guide-controls > div:target dt { color: var(--key); }
  .guide-empty { margin-top: var(--s-8); color: var(--ink-soft); }
  .guide-foot { margin-top: var(--s-16); padding-top: var(--s-3); border-top: 1px solid var(--line); font-size: var(--t-small); line-height: 20px; color: var(--ink-faint); }

  /* smaller screens: the contents float at the foot of the screen as a bar
     naming the section you are in; it opens upward into the full contents */
  @media (max-width: 1000px) {
    .guide-side {
      position: fixed;
      z-index: 90;                     /* under the site nav and its menu */
      top: auto;
      left: var(--margin);
      right: var(--margin);
      bottom: var(--s-2);
      display: flex;
      flex-direction: column-reverse;
      background: var(--panel);
      border: 1px solid var(--line-strong);
      border-radius: var(--radius);
      box-shadow: 0 16px 48px var(--shadow);
    }
    .guide-side__bar {
      display: flex;
      align-items: center;
      gap: var(--s-2);
      width: 100%;
      height: var(--s-6);
      padding: 0 var(--s-2);
      background: none;
      border: 0;
      color: var(--ink);
      text-align: left;
      cursor: pointer;
    }
    .guide-side__label {
      flex: none;
      font-family: var(--font-label);
      font-size: var(--t-small);
      letter-spacing: var(--track);
      text-transform: uppercase;
      color: var(--key);
    }
    .guide-side__current { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--t-small); }
    .guide-side__bar::after { content: "+"; color: var(--ink-faint); }
    .guide-side__bar[aria-expanded="true"]::after { content: "\2212"; }
    .guide-side__panel {
      display: none;
      max-height: 60vh;
      overflow-y: auto;
      overscroll-behavior: contain;
      padding: var(--s-2);
      border-bottom: 1px solid var(--line);
    }
    .guide-side.is-open .guide-side__panel { display: block; }
    .guide-side .guide-brand { display: none; }
    .guide-brand--body { display: flex; margin-bottom: var(--s-6); }
    .guide-body { padding-bottom: var(--s-12); }   /* room for the bar */
    .guide-chapter { margin-top: var(--s-12); }
    .guide-chapter--start { margin-top: 0; }
  }
  @media (max-width: 640px) {
    .guide-controls > div { grid-template-columns: minmax(0, 1fr); gap: var(--half); }
  }
}


/* ==========================================================================
   13 FORM — a stacked form on the grid (submit an idea, a waitlist)
   Label over field, the button left-aligned, a status line for the result.
   .hint is the status line the page scripts write to.
   ========================================================================== */
@layer components {
  .form { display: grid; gap: var(--s-2); max-width: 560px; }
  .form .field-label { margin-bottom: calc(var(--s-1) - var(--s-2)); }
  .form .btn { justify-self: start; }
  textarea.field { height: auto; min-height: 176px; padding-block: var(--s-2); resize: vertical; }
  .hint:empty { display: none; }
  .hint { font-size: var(--t-small); line-height: 20px; color: var(--ink-soft); }

  /* link cards that are not live yet (no href): dimmed, inert */
  .linkcard--soon { opacity: 0.55; pointer-events: none; }
  .linkcard--soon .linkcard__title::after { content: none; }   /* not a link yet: no arrow */
}


/* ==========================================================================
   04c FEATURE GRID — a map of the product's parts, read not operated
   Driven by /_system/fgrid.js, which builds it from JSON (see there). Items
   sit on the 12-column grid: 3 across on desktop, 2 on tablets, 1 on phones.
   No row is ever left holding one item: where the count would strand one,
   the last row(s) share the space instead (fgrid.js marks the remainder):
     3 across, a remainder of 1 (4, 7, 10):  the last four go 2 + 2
     2 across, odd counts (3, 5, 7, 9, 11):  the last three go 3 across
   Items in a row share top and bottom edges. Numbered in source order (the
   signal order), across groups.
   Tokens only: no colour, size, radius or gap values of its own.
   ========================================================================== */
@layer components {
  .fgrid { counter-reset: fgrid; }
  .fgrid__group + .fgrid__group { margin-top: var(--s-8); }
  .fgrid__label {
    margin-bottom: var(--s-2);
    font-family: var(--font-label);
    font-size: var(--t-label);
    letter-spacing: var(--track);
    text-transform: uppercase;
    color: var(--ink-soft);
  }
  .fgrid__list {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: var(--gap);
    align-items: stretch;
    margin: 0;
    padding: 0;
    list-style: none;
  }
  .fgrid__item {
    counter-increment: fgrid;
    grid-column: span 4;
    display: flex;
    flex-direction: column;
    gap: var(--s-1);
    padding: var(--s-3);
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--radius);
  }
  .fgrid__name {
    display: flex;
    align-items: baseline;
    gap: var(--s-1);
    font-family: var(--font-label);
    font-size: var(--t-h4);
    letter-spacing: var(--track);
    text-transform: uppercase;
    color: var(--key);
  }
  .fgrid__name::before {
    content: counter(fgrid, decimal-leading-zero);
    font-size: var(--t-label);
    color: var(--ink-faint);
  }
  .fgrid__line { color: var(--ink); }

  /* With an image: the picture is texture, not a figure. It fills the card
     behind the text, faint, and fades out toward the top, where the text
     sits on clear ground. The card is at least 4:5, so the texture shows. */
  .fgrid__item--media {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    padding: 0;
    aspect-ratio: var(--fgrid-media-ratio, 4 / 5);
  }
  .fgrid__media { position: absolute; inset: 0; z-index: -1; background: var(--panel); }
  .fgrid__media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center bottom;
    opacity: 0.75;
    -webkit-mask-image: linear-gradient(to bottom, transparent 20%, var(--ink) 75%);
    mask-image: linear-gradient(to bottom, transparent 20%, var(--ink) 75%);
  }
  .fgrid__text { display: flex; flex-direction: column; gap: var(--s-1); padding: var(--s-3); }

  /* figures (data-fgrid-images="figure"): the image full strength across
     the top, its own height, the text under it */
  .fgrid--figures .fgrid__item--media { aspect-ratio: auto; }
  .fgrid--figures .fgrid__media { position: static; z-index: auto; aspect-ratio: var(--fgrid-figure-ratio, 826 / 858); border-bottom: 1px solid var(--line); }
  .fgrid--figures .fgrid__media img { opacity: 1; -webkit-mask-image: none; mask-image: none; object-position: center; }

  /* flow (data-fgrid-flow): a sequence. A large step number over each card,
     and on wide screens an arrow in the gap to the next one. */
  .fgrid--flow .fgrid__item { position: relative; padding-top: calc(var(--s-3) + var(--t-h3) + var(--s-2)); }
  .fgrid--flow .fgrid__item--media { padding-top: 0; }
  .fgrid--flow .fgrid__item--media .fgrid__text { padding-top: calc(var(--s-3) + var(--t-h3) + var(--s-2)); }
  .fgrid--flow .fgrid__name::before { content: none; }
  .fgrid--flow .fgrid__item::before {
    content: counter(fgrid, decimal-leading-zero);
    position: absolute;
    z-index: 1;
    top: var(--s-2);
    left: var(--s-3);
    font-family: var(--font-display);
    font-size: var(--t-h3);
    line-height: 1;
    color: var(--key);
  }
  @media (min-width: 1001px) {
    .fgrid--flow .fgrid__item:not(.is-row-end)::after {      /* fgrid.js marks the last item of each row */
      content: "\2192";
      position: absolute;
      z-index: 1;
      top: var(--fgrid-arrow-top, 50%);
      right: calc(var(--gap) / -2);
      translate: 50% -50%;
      display: grid;
      place-items: center;
      width: var(--s-5);
      height: var(--s-5);
      background: var(--ground);
      border: 1px solid var(--key-line);
      border-radius: var(--radius);
      color: var(--key);
    }
  }

  /* type (data-fgrid-type): no cards, set as type. Each item opens on a
     hairline, its number small in the key colour over the name. Image
     items keep their card (the texture needs the ground). */
  .fgrid--type .fgrid__list { row-gap: var(--s-8); }
  .fgrid--type .fgrid__item:not(.fgrid__item--media) {
    gap: var(--s-2);
    padding: var(--s-3) 0 0;
    background: none;
    border: 0;
    border-top: 1px solid var(--line);
    border-radius: 0;
  }
  .fgrid--type .fgrid__name {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--s-3);
    font-family: var(--font-display);
    font-size: var(--t-h3);
    line-height: 1.15;
    color: var(--ink);
  }
  .fgrid--type .fgrid__name::before { font-family: var(--font-label); font-size: var(--t-label); letter-spacing: var(--track); color: var(--key); }
  .fgrid--type .fgrid__line { color: var(--ink-soft); }
  /* with icons ("icon" in an item's data): the icon leads and takes the
     number's place, so there is no number */
  .fgrid__icon { --icon-size: var(--s-6); margin-bottom: var(--s-1); color: var(--key); }
  .fgrid__item:has(.fgrid__icon) .fgrid__name::before { content: none; }   /* the icon stands in for the number */
  .fgrid--type .fgrid__item:not(.fgrid__item--media) { position: relative; }
  .fgrid--type .fgrid__icon { margin-bottom: var(--s-2); }
  /* a flow set as type (data-fgrid-flow + data-fgrid-type) is a chain:
     no numbers. Each item's icon sits in a tile on one line in the key
     colour, an arrow in the gap carries it to the next, and the text hangs
     under it. The line stops at the end of each row. */
  .fgrid--type.fgrid--flow {
    --fgrid-node: var(--s-10);  /* the chain's own scale: a 48 icon with room round it */
    --fgrid-arrow-top: calc(var(--fgrid-node) / 2);
  }
  .fgrid--type.fgrid--flow .fgrid__item:not(.fgrid__item--media) {
    padding-top: calc(var(--fgrid-node) + var(--s-3));
    border-top: 0;
    background: linear-gradient(var(--key-line), var(--key-line)) 0 calc(var(--fgrid-node) / 2) / 100% 1px no-repeat;
  }
  .fgrid--type.fgrid--flow .fgrid__item.is-row-end { background: none; }
  .fgrid--type.fgrid--flow .fgrid__item:not(.fgrid__item--media)::before {
    content: "";
    top: 0;
    left: 0;
    width: var(--fgrid-node);
    height: var(--fgrid-node);
    background: var(--ground);
    border: 1px solid var(--key-line);
    border-radius: var(--radius);
  }
  /* the arrow is a plain arrowhead in the gap, between the line's end and
     the next tile (the circle is for the card flow) */
  .fgrid--type.fgrid--flow .fgrid__item:not(.is-row-end)::after {
    width: auto;
    height: auto;
    background: none;
    border: 0;
    line-height: 1;
  }
  .fgrid--type.fgrid--flow .fgrid__icon {
    --icon-size: var(--s-6);
    position: absolute;
    z-index: 2;
    top: calc((var(--fgrid-node) - var(--icon-size)) / 2);
    left: calc((var(--fgrid-node) - var(--icon-size)) / 2);
    margin: 0;
  }

  /* phones: the chain runs down. Tiles on the left, joined by the line;
     the text beside them */
  @media (max-width: 640px) {
    .fgrid--type.fgrid--flow .fgrid__item:not(.fgrid__item--media) {
      padding: 0 0 0 calc(var(--fgrid-node) + var(--s-2));
      min-height: var(--fgrid-node);
      background: none;
    }
    .fgrid--type.fgrid--flow .fgrid__item:not(.fgrid__item--media):not(:last-child)::after {
      content: "";
      position: absolute;
      left: calc(var(--fgrid-node) / 2);
      top: var(--fgrid-node);
      bottom: calc(var(--s-8) * -1);
      width: 1px;
      background: var(--key-line);
    }
    .fgrid--type.fgrid--flow .fgrid__name { min-height: var(--fgrid-node); justify-content: center; }
  }

  /* 3 across: a remainder of one → the last four share two rows of two */
  .fgrid__list[data-r3="1"] > .fgrid__item:nth-last-child(-n + 4) { grid-column: span 6; }

  /* data-fgrid-row: four items in one row on wide screens, three columns
     each; below 1000 they fall back to two across, then one */
  @media (min-width: 1001px) {
    .fgrid--row .fgrid__list > .fgrid__item,
    .fgrid--row .fgrid__list[data-r3="1"] > .fgrid__item:nth-last-child(-n + 4) { grid-column: span 3; }
  }

  @media (max-width: 1000px) {
    .fgrid__item,
    .fgrid__list[data-r3="1"] > .fgrid__item:nth-last-child(-n + 4) { grid-column: span 6; }
    /* 2 across: an odd count → the last three share one row of three */
    .fgrid__list[data-r2="1"] > .fgrid__item:nth-last-child(-n + 3) { grid-column: span 4; }
  }
  @media (max-width: 640px) {
    /* phones: one column, a plain list */
    .fgrid__list > .fgrid__item,
    .fgrid__list[data-r2="1"] > .fgrid__item:nth-last-child(-n + 3),
    .fgrid__list[data-r3="1"] > .fgrid__item:nth-last-child(-n + 4) { grid-column: 1 / -1; }
  }
}



/* ==========================================================================
   04d STATEMENT — a text-only pitch, for a section with no visual
   One large line across the grid, then the prose and, beside it, either a
   pull line on the key rule or a short aside (one question, answered once).
   The prose and its companion share a top edge; below 1000 they stack.
     <section class="section" aria-labelledby="idea-title">
       <div class="container grid statement">
         <div class="statement__head">
           <p class="eyebrow">The idea</p>
           <h2 class="t-statement" id="idea-title">…</h2>
         </div>
         <div class="statement__body"> <p class="t-lede">…</p> … </div>
         <p class="statement__pull">…</p>
           or
         <div class="statement__aside"> <h3 class="statement__q">…</h3> <p>…</p> </div>
       </div>
     </section>
   For a louder line, the head takes .t-h1 instead of .t-statement (the
   Extended face at headline size), with an .accent on its last words.
   Tokens only.
   ========================================================================== */
@layer components {
  .statement { --row-gap: var(--s-8); align-items: start; }
  .statement__head { --span: 10; --span-md: 12; }
  .statement__head .t-statement { text-wrap: balance; }
  .statement__body { --start: 1; --span: 6; --span-md: 12; display: grid; gap: var(--s-3); color: var(--ink-soft); }
  .statement__pull,
  .statement__aside { --start: 8; --span: 5; --start-md: 1; --span-md: 12; }
  .statement__aside {
    display: grid;
    gap: var(--s-2);
    padding-left: var(--gap);
    border-left: 1px solid var(--line);
    color: var(--ink-soft);
  }
  .statement__q {
    font-family: var(--font-label);
    font-size: var(--t-h4);
    line-height: 1.3;
    letter-spacing: var(--track);
    text-transform: uppercase;
    color: var(--ink);
  }
}
/* ==========================================================================
   A/B PLAYER — one clip, dry or wet, switched without losing your place
   Driven by /_system/abplayer.js (+ audio.js). Two variants of one component:

   .ab--prominent   the hero. Sits on the grid: on a .grid it takes the
                    parent's column lines (subgrid), so its rows line up with
                    the page. Clip tabs + length, a wide waveform, then the
                    play button (one column) and the big dry/wet switch.
   .ab--compact     one row: play, name, waveform, length, switch. Four of
                    them go in a .grid.cols-2 in the proof section.

   Waveform: dry is the filled shape, wet its outline over it, both drawn
   from the decoded audio. The canvas's `color` is the wet ink and its
   border colour the dry ink; the script reads both from here.

   Controls:
     --ab-wave-h    waveform height        prominent 192 / 160 / 112 · compact 40
     --ab-ctl-h     play + switch height   prominent 64 / 64 / 56 · compact 40
   ========================================================================== */
@layer components {
  .ab {
    --ab-wet-ink: var(--key);
    --ab-dry-ink: color-mix(in srgb, var(--ink) 24%, var(--ground));
    position: relative;
    display: grid;
    column-gap: var(--gap);
    align-items: center;
  }

  /* ---- play: the white square from the audition thumbnails ---- */
  .ab__play {
    display: grid;
    place-items: center;
    height: var(--ab-ctl-h);
    padding: 0;
    border: 0;
    border-radius: var(--radius);
    background: #fff;
    color: #000;
    cursor: pointer;
    transition: background-color 160ms var(--ease);
  }
  .ab__icon {
    width: 14px;
    height: 16px;
    margin-left: 3px;
    background: currentColor;
    clip-path: polygon(0 0, 100% 50%, 0 100%);
  }
  .ab__play[aria-pressed="true"] .ab__icon { width: 14px; height: 14px; margin-left: 0; clip-path: none; }
  .ab.is-loading .ab__icon { opacity: 0.4; }
  .ab__play:hover, .ab__play:focus-visible { background: var(--key); color: var(--key-ink); }

  .ab__name {
    font-family: var(--font-head);
    font-size: var(--t-body);
    line-height: 24px;
    color: var(--ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .ab__time {
    font-family: var(--font-label);
    font-size: var(--t-label);
    letter-spacing: var(--track);
    line-height: 16px;
    font-variant-numeric: tabular-nums;
    color: var(--ink-soft);
    text-align: right;
  }

  /* ---- waveform + playhead ---- */
  .ab__wave {
    position: relative;
    height: var(--ab-wave-h);
    min-width: 0;
    overflow: hidden;
  }
  .ab__canvas {
    display: block;
    width: 100%;
    height: 100%;
    color: var(--ab-wet-ink);
    border: 0 solid var(--ab-dry-ink);
  }
  .ab__head {                    /* moved by transform from the script; functional, so it moves under reduced motion too */
    position: absolute;
    top: 0;
    bottom: 0;
    left: -1px;
    width: 2px;
    background: var(--ink);
    will-change: transform;
    pointer-events: none;
  }

  /* ---- the dry/wet switch: one toggle button, pressed = wet ---- */
  .ab__switch {
    display: grid;
    grid-template-columns: 1fr 1fr;
    height: var(--ab-ctl-h);
    padding: 0;
    border: 1px solid var(--line-strong);
    border-radius: var(--radius);
    background: none;
    font-family: var(--font-label);
    font-weight: var(--ui-weight);
    font-size: var(--ab-switch-size, var(--t-body));
    letter-spacing: var(--track);
    text-transform: uppercase;
    line-height: 1;
    cursor: pointer;
    transition: border-color 160ms var(--ease);
  }
  .ab__switch:hover { border-color: var(--ink); }
  .ab__side {
    display: grid;
    place-items: center;
    padding-inline: var(--s-2);
    color: var(--ink-faint);
    transition: background-color 160ms var(--ease), color 160ms var(--ease);
  }
  .ab:not(.is-wet) .ab__side--dry { background: color-mix(in srgb, var(--ink) 14%, var(--ground)); color: var(--ink); }
  .ab.is-wet .ab__side--wet { background: var(--key); color: var(--key-ink); }

  /* a dry/wet pair that failed the development check (see the console) */
  .ab[data-ab-invalid] { outline: 2px dashed var(--danger); outline-offset: var(--s-1); }
  .ab[data-ab-invalid]::after {
    content: "A/B pair invalid: see console";
    position: absolute;
    right: 0;
    bottom: 100%;
    margin-bottom: var(--s-2);
    font: 500 12px/16px var(--font-mono);
    color: var(--danger);
  }

  /* ---- prominent ----
     Rows: clip tabs + length / waveform / play + switch. Standalone it is its
     own 12 columns; on a .grid it borrows the parent's lines. */
  .ab--prominent {
    --ab-wave-h: 192px;
    --ab-ctl-h: 64px;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    row-gap: var(--s-3);
  }
  .grid > .ab--prominent { grid-template-columns: subgrid; }
  .ab--prominent > .ab__clips,
  .ab--prominent > .ab__name { grid-column: 1 / -4; grid-row: 1; }
  .ab--prominent > .ab__time { grid-column: -4 / -1; grid-row: 1; }
  .ab--prominent > .ab__wave {
    grid-column: 1 / -1;
    grid-row: 2;
    border-block: 1px solid var(--line);
  }
  .ab--prominent > .ab__play { grid-column: 1 / span 1; grid-row: 3; }
  .ab--prominent > .ab__switch { grid-column: 2 / span 3; grid-row: 3; }

  /* ---- compact: one row ---- */
  .ab--compact {
    --ab-wave-h: 40px;
    --ab-ctl-h: 40px;
    --ab-switch-size: var(--t-small);
    grid-template-columns: var(--ab-ctl-h) minmax(4em, max-content) minmax(0, 1fr) auto auto;
    column-gap: var(--s-2);
    padding-block: var(--s-2);
    border-top: 1px solid var(--line);
  }
  .ab--compact > .ab__play { width: var(--ab-ctl-h); }
  .ab--compact > .ab__clips { height: var(--ab-ctl-h); }
  .ab--compact > .ab__switch { min-width: 112px; }

  /* the proof section's set: a label, then compact players 2-up on the grid */
  .ab-proof { margin-top: var(--s-10); --row-gap: var(--s-2); }
  .ab-proof__label { color: var(--ink-soft); }
  .ab-proof__list { row-gap: 0; }
  .proof-video { margin-top: var(--s-10); }           /* a walkthrough under the compact players */
  .ab-proof__list > .ab--compact:nth-last-child(-n + 2) { border-bottom: 1px solid var(--line); }

  @media (max-width: 1000px) {
    .ab--prominent { --ab-wave-h: 160px; }
    .ab--prominent > .ab__play { grid-column: 1 / span 2; }
    .ab--prominent > .ab__switch { grid-column: 3 / span 5; }
  }
  @media (max-width: 640px) {
    .ab--prominent { --ab-wave-h: 112px; --ab-ctl-h: 56px; --ab-switch-size: var(--t-body); row-gap: var(--s-2); }
    .ab--prominent > .ab__clips,
    .ab--prominent > .ab__name { grid-column: 1 / -1; }
    .ab--prominent > .ab__time { grid-column: 1 / -1; grid-row: 4; text-align: left; }
    .ab--prominent > .ab__play { grid-column: 1 / span 3; }
    .ab--prominent > .ab__switch { grid-column: 4 / -1; }
    /* name on its own line; play, waveform and switch underneath */
    .ab--compact { grid-template-columns: var(--ab-ctl-h) minmax(0, 1fr) auto; row-gap: var(--s-1); }
    .ab--compact > .ab__name, .ab--compact > .ab__clips { grid-column: 1 / -1; grid-row: 1; }
    .ab--compact > .ab__time { display: none; }
    .ab-proof__list > .ab--compact:nth-last-child(2) { border-bottom: 0; }   /* 1-up: one rule between rows */
  }
}


/* ==========================================================================
   DOTS — where you are among a few things, and the way to the others. One
   button per thing; the current one is marked aria-current and stretches.
     <div class="dots" role="group" aria-label="…">
       <button class="dots__dot" type="button" aria-label="1 of 2" aria-current="true"></button> …
     </div>
   Used by the PAGER (pager.js) and the audition's phone row (audition.js).

   PAGER — one of several items at a time, dots to switch; never on its own
   (pager.js). The items share one cell, so the box keeps the height of the
   tallest and nothing below it moves. Without JS they stack.
     <div class="pager" data-pager data-pager-label="Press quotes">
       <div class="pager__item">…</div> <div class="pager__item">…</div>
     </div>
   ========================================================================== */
@layer components {
  .dots { display: flex; gap: var(--s-1); }
  .dots__dot {
    width: var(--s-1);
    height: var(--s-1);
    padding: 0;
    border: 0;
    border-radius: var(--radius);
    background: var(--line-strong);
    cursor: pointer;
    transition: background-color 160ms var(--ease), width 160ms var(--ease);
  }
  .dots__dot:hover { background: var(--ink-faint); }
  .dots__dot[aria-current="true"] { width: var(--s-3); background: var(--key); }
  .dots__dot:focus-visible { outline: 1px solid var(--key); outline-offset: var(--half); }

  .pager > .pager__item + .pager__item { margin-top: var(--s-6); }    /* no JS: stacked */
  .pager.is-paged { display: grid; }
  .pager.is-paged > .pager__item {
    grid-area: 1 / 1;
    margin: 0;
    transition: opacity 240ms var(--ease), visibility 240ms;
  }
  .pager.is-paged > .pager__item[aria-hidden="true"] { opacity: 0; visibility: hidden; }
  .pager > .dots { margin-top: var(--s-4); }
}


/* ==========================================================================
   GRID OVERLAY (design aid, driven by grid-overlay.js; press G)
   Sits in <main>, above content and below the nav. Magenta columns, green
   baseline every 8px, brighter every 64px.
   ========================================================================== */
@layer components {
  .grid-overlay {
    display: none;
    position: absolute;
    inset: 0;
    z-index: 80;
    pointer-events: none;
    background-image:
      repeating-linear-gradient(to bottom, transparent 0 calc(var(--s-8) - 1px), rgb(3 255 103 / 0.22) calc(var(--s-8) - 1px) var(--s-8)),
      repeating-linear-gradient(to bottom, transparent 0 calc(var(--bl) - 1px), rgb(3 255 103 / 0.08) calc(var(--bl) - 1px) var(--bl));
  }
  .show-grid .grid-overlay { display: block; }
  .grid-overlay__cols { height: 100%; }
  .grid-overlay__cols > i {
    --span: 1; --span-md: 1; --span-sm: 1;
    height: 100%;
    background: rgb(255 0 128 / 0.07);
    box-shadow: inset 1px 0 rgb(255 0 128 / 0.25), inset -1px 0 rgb(255 0 128 / 0.25);
  }
}
