/*
 * THE SITE STYLESHEET — one file, every hand-written page.
 *
 * This block used to live inline in every hand-written page, copied
 * whole, because none of them had a build step to share one through.
 * `docs/architecture.html` carried the note that named its own limit:
 * copy it until a fifth page wants it, then extract this file. The
 * per-demo guides under `docs/guides/` are that fifth page and the ones
 * after it, so here it is.
 *
 * WHY A FILE AND NOT A FIFTH COPY. A guide sits one directory down, and
 * the @font-face rules below load their faces with a RELATIVE url. In a
 * copied inline block that url is resolved against the DOCUMENT, so the
 * copy in `guides/` would have needed `../fonts/` — a divergence in the
 * one part every page must share, which is precisely what copying it
 * whole was meant to prevent. Resolved against this file instead, the
 * same `./fonts/` is correct from any depth. Nesting stops being a
 * question the stylesheet has an opinion about.
 *
 * Consumers: `docs/index.html`, `docs/architecture.html`,
 * `docs/roadmap.html`, `docs/manual.html`, `docs/guides/*.html`.
 * `docs/gallery.html` is the one page left out, and it is left out for a
 * reason that still holds: it is GENERATED, by `src/docs/gallery.ts`, and
 * carries its own older stylesheet inside the renderer — so folding it in
 * means editing the generator and re-running the docs chain, which is a
 * separate job from editing a page.
 *
 * `docs/manual.html` used to be named in that same sentence, described as
 * generated too. IT IS NOT, and the claim cost a round of work to
 * disprove: `scripts/gen-site.mjs` only rewrites the text between the
 * `<!--pcg:version-->` markers and passes everything else through byte for
 * byte. It is a stamper, not a generator, and the manual is hand-written
 * like every other page above.
 *
 * A page may still add a `<style>` of its own AFTER linking this, for
 * components this file does not have. `docs/guides/racetrack.html` does.
 * `docs/manual.html` does at length, and its block is also where the
 * handful of rules a manual genuinely has to override — its own page
 * width, its own heading scale, its own measure — are kept, each stating
 * the reason it differs. That is the shape to copy: an override with no
 * reason written beside it is how this file's rules and the manual's
 * diverged in twenty places while both claimed to be the same design.
 */

  /* Self-hosted rather than linked from a font CDN: no third-party
     request, nothing to block, and the page keeps its typography
     offline. Both files are the latin subset of a VARIABLE font, so one
     file per family covers every weight used here. OFL licensed. */
  @font-face {
    font-family: "Inter";
    src: url("./fonts/inter-latin.woff2") format("woff2");
    font-weight: 100 900;
    font-style: normal;
    font-display: swap;
  }
  @font-face {
    font-family: "JetBrains Mono";
    src: url("./fonts/jetbrains-mono-latin.woff2") format("woff2");
    font-weight: 100 800;
    font-style: normal;
    font-display: swap;
  }
  :root {
    /* ONE world, deliberately — there is no light theme and no
       [data-theme] hook, because the three primaries below cannot
       survive a light ground: #00ff00 on white measures 1.37:1 against
       the 4.5 AA floor for text. A light scheme would need different
       accents, i.e. a different design, so the page commits to black.
       `color-scheme: dark` still declares that intent to the browser so
       forced/auto dark mode has nothing left to invert. */
    color-scheme: dark;
    /* `color-scheme` gets the TRACK right and leaves the thumb the
       platform's mid-grey, which is a bright bar down a page whose
       ground is #000. These two are the standard, cross-engine pair and
       they inherit, so the whole page is covered here. `--line-hi`
       because a scrollbar is an emphasised border, which is the value's
       existing job. */
    scrollbar-width: thin;
    scrollbar-color: var(--line-hi) transparent;
    --bg: #000000;

    /* Pure greyscale — every value has R = G = B, no hue anywhere. The
       ratios are measured against #000000. */
    --ink-hi: #ffffff;   /* headings, max emphasis      21.00:1 */
    --ink: #ededed;      /* body text                   17.94:1 */
    --muted: #9a9a9a;    /* secondary text               7.46:1 */
    --faint: #7a7a7a;    /* tertiary text                4.89:1 */
    --line: #262626;     /* borders                             */
    --line-hi: #3a3a3a;  /* link rules, emphasised borders      */
    --card: #0d0d0d;
    --plate: #0a0a0a;
    --code-bg: #0a0a0a;
    --code-ink: #ededed;

    /* The three primaries, spent as MARKS and never on body copy. On
       #000 red measures 5.25:1 and green 15.30:1, so both clear AA as
       text — but blue is 2.44:1, under even the 3.0 large-text floor.
       So --b is only ever a fill or a stroke. Never set text to it. */
    --r: #ff0000;
    --g: #00ff00;
    --b: #0000ff;

    /* Kept under their old names so the ~40 var(--accent*) call sites
       downstream keep reading naturally. Note --accent-ink is now
       GREYSCALE: link and inline-code text carries no colour, and --g is
       spent only on interactive states and the one hero CTA. */
    --accent: #00ff00;
    --accent-ink: #ededed;
    --amber: #ff0000;

    --mono: "JetBrains Mono", "Cascadia Code", "SF Mono", Consolas, monospace;
    --sans: "Inter", "Segoe UI", system-ui, -apple-system, sans-serif;
  }

  * { box-sizing: border-box; }
  body {
    margin: 0;
    background: var(--bg);
    color: var(--ink);
    font-family: var(--sans);
    font-size: 16px;
    line-height: 1.65;
    -webkit-font-smoothing: antialiased;
  }
  /**
   * ONE COLUMN, TWELVE TRACKS, ONE MEASURE.
   *
   * Every width on this page used to be its own number. The column was
   * 960, prose was `70ch`, the lead `64ch` and the hero paragraph `46ch`
   * — and because `ch` scales with the element's OWN font-size, three
   * measures written to look alike landed at 707, 707 and 522px. The 64
   * was already a patch for that: it exists because `70ch` at the lead's
   * 17.5px overhung the 16px paragraphs under it. Patching each new
   * element against the last one does not converge.
   *
   * So the grid is stated instead, and read. `--track` is derived rather
   * than typed so the arithmetic cannot drift: thirteen gutters (two
   * outer, eleven between) subtracted from the page, split twelve ways.
   * A span of n tracks is `track*n + gutter*(n-1)`.
   *
   *   page 960 · gutter 24 · track 54 · 12 tracks = 912 of content
   *
   * Press `g` (or load with `?grid`) to see it — the overlay draws these
   * same values, so a width that does not land on a track shows up as
   * one that does not line up.
   */
  :root {
    --page: 960px;
    --gutter: 24px;
    --track: calc((var(--page) - var(--gutter) * 13) / 12);
    /* PROSE IS NINE OF TWELVE — 678px. Nearest track span to the 707 the
       page had settled on, so the body it was tuned for barely moves,
       and now it is a column count rather than a coincidence. */
    --measure: calc(var(--track) * 9 + var(--gutter) * 8);
  }
  /**
   * NO ROUNDED BOX ON THIS SITE, and the exception is drawn where it
   * belongs: the NODES in the diagrams keep their corners, because a node
   * is a thing in a graph rather than a panel of the page, and mermaid
   * draws those itself. Everything the stylesheet owns — buttons, cards,
   * plates, code blocks, the seed field, the tiles, the release chips —
   * is square. The page is ruled in straight lines: hairlines between
   * bands, a twelve-track grid, a field of square dots. A 6px corner on
   * a button was the one curve in it.
   */
  .wrap { max-width: var(--page); margin: 0 auto; padding: 0 var(--gutter); }

  h1, h2, h3 { font-family: var(--mono); letter-spacing: -0.01em; text-wrap: balance; color: var(--ink-hi); }
  h2 { font-size: 26px; font-weight: 600; margin: 0 0 8px; }
  /* The heading a page that is not the landing page leads with — the
     landing page's own <h1> is the lockup, which is why this is the
     first plain one on the site. */
  .page-title { font-size: 30px; font-weight: 600; margin: 0 0 10px; }
  /* A section that ends by pointing somewhere else. Set like a link and
     spaced like a paragraph, because it IS the paragraph after the last
     one — not a button pretending to be a call to action. */
  .more-link { color: var(--g); }
  .more-link:hover, .more-link:focus-visible { color: var(--ink-hi); }
  h3 { font-size: 16px; font-weight: 600; margin: 0 0 6px; }
  p { margin: 0 0 14px; max-width: var(--measure); }
  /* The rule is grey and only the HOVER is green: a link at rest is body
     copy, so colour marks the interaction rather than the text. */
  a { color: var(--accent-ink); text-decoration: none; border-bottom: 1px solid var(--line-hi); }
  a:hover, a:focus-visible { border-bottom-color: var(--accent); }
  :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 0; }

  .eyebrow {
    font-family: var(--mono);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--faint);
    margin: 0 0 10px;
  }
  section { padding: 44px 0 20px; }
  section + section { border-top: 1px solid var(--line); }
  .lead { font-size: 17.5px; color: var(--muted); max-width: var(--measure); }
  .lead strong { color: var(--ink); font-weight: 600; }

  /* ---------- hero ---------- */
  /* No rule of its own: the one under the buttons is drawn at the TEXT's
     width rather than the viewport's (see `.hero-actions`), and a
     full-width one here would sit a few pixels under it saying the same
     thing twice. */
  header { }
  /* The glyph is Phosphor's `graph`, bold, at the 256 viewBox it ships in
     — the same path the editor's own toolbar wears, copied rather than
     imported because this page has no build step and loads no module for
     its chrome. If `shared/graph/icons.ts` ever redraws it, this is the
     other copy. `currentColor` so it takes the hover with the word. */
  .hero-open {
    position: absolute;
    top: 16px;
    right: 16px;
    z-index: 1;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 6px 12px;
    border: 1px solid var(--line);
    border-radius: 0;
    color: var(--muted);
    font-family: var(--mono);
    font-size: 13px;
  }
  .hero-open .ic {
    width: 15px;
    height: 15px;
    flex: 0 0 auto;
    fill: currentColor;
  }
  .hero-open:hover, .hero-open:focus-visible {
    border-color: var(--ink);
    color: var(--ink-hi);
    background: var(--card);
  }
  /* TWO BANDS, and the field is only behind the second one. The lockup
     gets plain ground and room around it; the sentence under it gets the
     field, because that sentence is what the field is evidence for.

     Both are sized by their CONTENT rather than being fixed bands with
     something centred over them absolutely: the lockup keeps the
     artwork's aspect ratio, so its height is a function of the viewport
     width, and a fixed band would crop it or leave a gap at every width
     but one. The min-height on the field band is a floor for the FIELD,
     not for the text — a scatter needs some depth to read as one. */
  .hero-logo { background: var(--bg); padding: 64px 0 56px; }
  /* Ruled top and bottom, full width. The field is the one band on this
     page with a ground of its own, and without an edge it bled into the
     black above and below it — the dots simply thinned out and you could
     not tell where the cooked strip began. The same hairline every other
     seam on the page uses, so the header reads as banded rather than as
     having grown a frame. The canvas is `inset: 0` against the PADDING
     box, so it stops inside these rather than painting over them. */
  /* `position: relative` for two things now: the canvas stretched behind
     the band, and the editor link cornered inside it. */
  .hero {
    position: relative;
    min-height: 190px;
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
  }
  .hero canvas {
    position: absolute; inset: 0;
    display: block; width: 100%; height: 100%;
  }
  .hero-overlay { position: relative; padding: 48px 0; }
  .hero-overlay .wrap { width: 100%; }
  .hero-title {
    margin: 0;
    line-height: 0;
    color: var(--ink);
  }
  /* Out of sight, kept in the accessibility tree. The lockup is a canvas
     and a field of glyph outlines, so without this the page's only <h1>
     would contain no text at all. */
  .hero-title .vh {
    position: absolute; width: 1px; height: 1px;
    margin: -1px; padding: 0; border: 0;
    clip-path: inset(50%); overflow: hidden; white-space: nowrap;
  }
  /* ---------- the animated lockup ---------- */

  /* Two surfaces stacked in one box, so they cannot drift apart: a canvas
     for the wordmark, which is sliced and channel-split every frame — that
     is blitting rather than clipping — and an SVG for the split-flap board,
     where flipping a character is one href write against an outline that is
     already parsed, which is what lets the board run with no font anywhere.
     Driven by docs/logo/logo.js; the static wordmark beside it is what a
     browser that cannot run the module keeps. */
  .lockup {
    display: block;
    position: relative;
    width: min(100%, 1055px);
    aspect-ratio: 1055 / 245;
  }
  .lockup > canvas, .lockup > svg {
    position: absolute; inset: 0;
    display: block; width: 100%; height: 100%;
  }
  /* The board's three states. Greyscale like the rest of the page: the
     primaries are spent on the field's marks behind it, never here. */
  .lockup .cell { fill: var(--ink-hi); }
  .lockup .cell.roll { fill: var(--muted); opacity: 0.68; }
  .lockup .cell.corrupt { fill: var(--muted); }
  .lockup .cell.blank { visibility: hidden; }
  /* The wordmark ships white on transparent, which is invisible on the
     light scheme, so it is INLINED rather than linked: `fill:
     currentColor` lets it take --ink and stay legible in both. An <img>
     could not be recoloured. Sized by height with width:auto — the
     viewBox carries the 8.24:1 aspect ratio, and the clamp keeps it
     inside a narrow viewport. */
  .hero-title .wordmark {
    display: block;
    height: clamp(30px, 5vw, 46px);
    width: auto;
    max-width: 100%;
    fill: currentColor;
  }
  /* AFTER both of the rules above, deliberately. `hidden` is a UA rule at
     the lowest specificity there is, and each of them sets `display` from a
     class selector — which outranks it outright. This one only TIES with
     `.hero-title .wordmark`, so it has to come later to win, and a swap
     that reordered the block would put the fallback back on screen
     underneath the lockup. */
  .hero-title [hidden] { display: none; }
  .hero-sub {
    font-size: clamp(15px, 2.2vw, 18px);
    color: var(--muted);
    /* No top margin any more: it used to sit under the lockup inside one
       band, and it is now the only thing in its own. */
    margin: 0;
    /* The same nine tracks the prose below it gets. It was `46ch`, which
       at 18px came out 185px short of every paragraph on the page — the
       one width here that belonged to no system. */
    max-width: var(--measure);
  }
  /* A column: the seed reads with the field above it, and the row of
     places to go next reads on its own. Side by side they were one long
     line in which the only control on the page was just another item. */
  /* The horizontal padding is `.wrap`'s own 24px, restated because this
     shorthand would otherwise drop it — which is what had the buttons
     starting a gutter's width left of the sentence above them, and would
     now put the rule under them out of line with the text too. */
  .hero-bar {
    display: flex; flex-direction: column; align-items: flex-start;
    padding: 18px 24px 0;
    font-family: var(--mono); font-size: 13px;
  }
  /* `align-self: stretch` so the row fills the wrap's CONTENT box, which
     is what puts the rule under it at the width of the text rather than
     of the viewport — the buttons themselves are narrower than that and a
     rule ending with them would read as an underline on the last one. */
  .hero-actions {
    align-self: stretch;
    display: flex; flex-wrap: wrap; gap: 10px 12px; align-items: center;
    padding-bottom: 22px;
    border-bottom: 1px solid var(--line);
  }
  .hero-bar .install {
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 0;
    padding: 6px 12px;
    color: var(--ink);
  }
  .hero-bar .install::before { content: "$ "; color: var(--faint); }
  .hero-bar .manual-link {
    background: color-mix(in srgb, var(--accent) 13%, transparent);
    border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
    border-radius: 0;
    padding: 6px 12px;
    color: var(--g);
    font-weight: 600;
    pointer-events: auto;
  }
  .hero-bar .manual-link:hover, .hero-bar .manual-link:focus-visible {
    background: color-mix(in srgb, var(--accent) 22%, transparent);
    border-color: var(--accent);
  }
  /* Beside the CTA and quieter than it: --g is spent on ONE hero button,
     and two competing greens would leave neither of them the thing to
     click. The same shape, in greyscale — and now three of them, which is
     the reason the green one still reads as the one to press. */
  .hero-bar .editor-link,
  .hero-bar .out-link {
    border: 1px solid var(--line-hi);
    border-radius: 0;
    padding: 6px 12px;
    color: var(--ink);
    pointer-events: auto;
  }
  .hero-bar .editor-link:hover, .hero-bar .editor-link:focus-visible,
  .hero-bar .out-link:hover, .hero-bar .out-link:focus-visible {
    border-color: var(--ink);
    background: var(--card);
  }
  /* Off-site, and dimmer for it: these two leave the documentation, so
     they sit a step behind the two that do not. */
  .hero-bar .out-link { color: var(--muted); border-color: var(--line); }
  .seed-ctl { display: inline-flex; align-items: center; gap: 8px; pointer-events: auto; }
  /* Set off from the sentence above rather than run on from it: they are
     a claim and the control that backs it, not one paragraph. */
  .hero-overlay .seed-ctl { margin-top: 22px; font-family: var(--mono); font-size: 13px; }
  .seed-ctl label { color: var(--muted); }
  .seed-ctl input {
    font-family: var(--mono); font-size: 13px;
    /* 112, not 76: the stepper is two squares the height of the field,
       so at the old width a hovered box was almost entirely buttons and
       the value you were setting had nowhere to be read. */
    width: 112px; padding: 5px 8px;
    color: var(--ink); background: var(--bg);
    border: 1px solid var(--line); border-radius: 0;
  }
  /* The stepper: minus and plus, square, the full height of the field,
     at its right edge, shown on hover. The platform's spinner is two
     stacked arrows a few pixels wide — a target you aim at rather than
     hit — and it cannot be reshaped into this: WebKit gives one box for
     both arrows and Firefox gives nothing. So it is hidden and replaced.
     The app chrome carries the same control; see shared/NumberBox.svelte. */
  .seed-ctl .numbox { position: relative; display: inline-flex; }
  .seed-ctl input::-webkit-outer-spin-button,
  .seed-ctl input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
  .seed-ctl input[type="number"] { -moz-appearance: textfield; appearance: textfield; }
  .seed-ctl .steps {
    position: absolute; top: 1px; right: 1px; bottom: 1px;
    display: flex; opacity: 0; pointer-events: none;
    overflow: hidden;
    transition: opacity .08s;
  }
  .seed-ctl .numbox:hover .steps,
  .seed-ctl .numbox:focus-within .steps { opacity: 1; pointer-events: auto; }
  .seed-ctl .steps button {
    height: 100%; aspect-ratio: 1; padding: 0;
    display: grid; place-items: center;
    /* At rest only the glyph: the field already has a border, and a
       plate inside it would be a second frame a pixel from the first. */
    background: transparent; color: var(--muted);
    border: 0; border-radius: 0;
    font: 13px system-ui, sans-serif; line-height: 1;
    cursor: pointer; user-select: none;
  }
  .seed-ctl .steps button:hover { background: var(--line); color: var(--ink-hi); }
  .seed-ctl .steps button:active { background: var(--line-hi); }
  .seed-note { color: var(--faint); }

  /* ---------- stats strip ---------- */
  .stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
    gap: 1px;
    background: var(--line);
    border: 1px solid var(--line);
    border-radius: 0;
    overflow: hidden;
    margin: 26px 0 6px;
  }
  .stat { background: var(--card); padding: 14px 16px; }
  .stat b {
    display: block;
    font-family: var(--mono);
    font-size: 22px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--ink);
  }
  .stat span { font-size: 12.5px; color: var(--muted); }

  /* ---------- pillar cards ---------- */
  .cards {
    display: grid;
    /* Four cards, so two columns beats three: three leaves a lone card
       stranded on a second row. The min() keeps it responsive without a
       media query — below ~380px of track the grid drops to one column
       on its own. */
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 380px), 1fr));
    gap: 14px;
    margin: 18px 0 8px;
  }
  .card {
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: 0;
    padding: 18px 18px 14px;
  }
  .card h3 { color: var(--ink); }
  .card h3 code { color: var(--accent-ink); }
  .card p { font-size: 14.5px; color: var(--muted); margin: 0; }

  /* ---------- diagrams as plates ---------- */
  .plate {
    background: var(--plate);
    border: 1px solid var(--line);
    border-radius: 0;
    padding: 18px 14px 8px;
    margin: 18px 0 10px;
    overflow-x: auto;
  }
  .plate pre.mermaid { margin: 0; display: flex; justify-content: center; min-width: 560px; }
  /* Diagram ink follows the page theme tokens — transparent node fills,
     outlines matching the text — overriding whatever theme mermaid's
     runtime injects. Tokens (not pinned hex) keep the diagram coherent
     even under forced-dark/inverter extensions. */
  .plate svg .node rect, .plate svg .node circle, .plate svg .node ellipse,
  .plate svg .node polygon, .plate svg .node path {
    fill: transparent !important;
    stroke: var(--ink) !important;
    stroke-width: 1.1px !important;
  }
  .plate svg .nodeLabel, .plate svg .nodeLabel p, .plate svg .label text, .plate svg text {
    color: var(--ink) !important;
    fill: var(--ink) !important;
  }
  .plate svg .cluster rect {
    fill: transparent !important;
    stroke: var(--faint) !important;
    stroke-dasharray: 4 3 !important;
  }
  .plate svg .cluster-label text, .plate svg .cluster-label .nodeLabel,
  .plate svg .cluster-label p {
    color: var(--muted) !important;
    fill: var(--muted) !important;
  }
  .plate svg .edgePath path, .plate svg path.flowchart-link {
    stroke: var(--muted) !important;
  }
  .plate svg marker path { fill: var(--muted) !important; stroke: var(--muted) !important; }
  .plate svg .edgeLabel, .plate svg .edgeLabel p {
    background: transparent !important;
    background-color: transparent !important;
    color: var(--muted) !important;
    fill: var(--muted) !important;
  }
  .plate svg .edgeLabel rect { fill: transparent !important; opacity: 0 !important; }
  .caption {
    font-family: var(--mono);
    font-size: 12px;
    color: var(--faint);
    letter-spacing: 0.04em;
    margin: 8px 0 0;
  }
  .caption b { color: var(--muted); font-weight: 600; }

  /* ---------- module list ---------- */
  .modules { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 4px 28px; margin: 16px 0 8px; padding: 0; list-style: none; }
  /* Two columns, not a flex row: the module names differ in width
     (src/gpu 55px, src/primitives 110px), so a flex row started every
     description at a different x and wrapped lines under it. The fixed
     first column lands them all on one edge. At 13px the mono advance is
     7.8px, so 112px clears the longest name (src/primitives, 14 chars)
     — widen it if a longer one is ever added. */
  .modules li { display: grid; grid-template-columns: 112px 1fr; gap: 12px; align-items: baseline; padding: 6px 0; border-bottom: 1px dashed var(--line); font-size: 14px; }
  .modules code { font-family: var(--mono); font-size: 13px; color: var(--accent-ink); white-space: nowrap; }
  .modules span { color: var(--muted); }

  /* ---------- code ---------- */
  .codeblock {
    background: var(--code-bg);
    color: var(--code-ink);
    border-radius: 0;
    padding: 18px 20px;
    overflow-x: auto;
    margin: 16px 0 10px;
  }
  .codeblock pre { margin: 0; font-family: var(--mono); font-size: 13.5px; line-height: 1.65; }
  /* Syntax is carried by LIGHTNESS, not hue — four steps down the same
     greyscale ramp. Colouring tokens would spend the primaries on the
     most repeated element on the page, the opposite of sparingly. */
  .codeblock .k { color: #ffffff; font-weight: 600; }
  .codeblock .s { color: #9a9a9a; }
  .codeblock .c { color: #5a5a5a; font-style: italic; }
  .codeblock .n { color: #c8c8c8; }

  /* ---------- feature columns ---------- */
  .features { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 6px 32px; margin: 14px 0; padding: 0; list-style: none; }
  .features li { padding: 7px 0 7px 20px; position: relative; font-size: 14.5px; color: var(--muted); }
  .features li::before { content: "▸"; position: absolute; left: 0; color: var(--faint); }
  .features li b { color: var(--ink); font-weight: 600; }

  /* ---------- examples ---------- */
  /* The one full-width shot on the page: the editor doing the thing the grid
     below only links to. Same border and radius as a card, no hover state,
     because it is not a link. */
  /* TWO COLUMNS, not `auto-fit`: the row above them is one tile spanning
     both, so the count has to be knowable — `auto-fit` would re-flow the
     demos against the viewport and break the alignment with that tile.
     The count is FIVE, so the last row carries one card on its own. That
     is the honest cost of a fixed two-up and it is preferred to a width
     that changes under the reader; a sixth demo closes it, and until then
     do not "fix" it by reflowing, which only moves the stray card. */
  .examples { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px; margin: 16px 0 8px; }
  /* `full`, not `lead`: `.lead` is the section-opening paragraph and
     carries the prose measure, which a tile spanning the grid must
     not inherit — it capped this one at 678 of its 912. */
  .example.full { grid-column: 1 / -1; }
  a.example {
    display: block;
    border: 1px solid var(--line); border-radius: 0;
    background: var(--bg);
    overflow: hidden; /* the thumbnail stops at the card's edge */
    transition: border-color 0.15s ease;
  }
  a.example:hover, a.example:focus-visible { border-color: var(--accent); }
  /* The frame's aspect is reserved up front so lazily-loaded thumbnails
     cannot reflow the grid as they arrive. */
  /* `height: auto` is load-bearing, not tidiness: the `height` attribute
     on an <img> applies as a presentational CSS height, and with a width
     AND a height set the `aspect-ratio` below is ignored. Every thumbnail
     here is 640x345 so nobody could see it — the first picture with a
     different intrinsic size (the editor capture, 1454x783) rendered
     910 wide by its own 783 tall. */
  .example img {
    display: block; width: 100%; height: auto; aspect-ratio: 640 / 345; object-fit: cover;
    background: var(--card);
    border-bottom: 1px solid var(--line);
    opacity: 0.92; transition: opacity 0.15s ease;
  }
  a.example:hover img, a.example:focus-visible img { opacity: 1; }
  .example .meta { display: block; padding: 12px 14px 14px; }
  .example b { font-family: var(--mono); font-size: 13px; display: block; margin-bottom: 4px; color: var(--accent-ink); }
  .example span { font-size: 13px; color: var(--muted); }

  /* ---------- roadmap ---------- */
  .roadmap { list-style: none; margin: 20px 0 8px; padding: 0; }
  .roadmap li {
    display: grid; grid-template-columns: 120px 1fr; gap: 18px;
    padding: 16px 0; border-top: 1px dashed var(--line);
  }
  .roadmap li:first-child { border-top: none; }
  .rm-ver { font-family: var(--mono); font-size: 14px; font-weight: 700; }
  .rm-ver small { display: block; font-weight: 400; color: var(--faint); font-size: 11.5px; margin-top: 2px; }
  .rm-body p { font-size: 14.5px; color: var(--muted); margin: 6px 0 0; }
  .chip {
    display: inline-block; font-family: var(--mono); font-size: 11px; font-weight: 600;
    letter-spacing: 0.08em; text-transform: uppercase;
    padding: 2px 9px; border-radius: 0; vertical-align: 2px;
  }
  .chip.shipped { color: var(--accent-ink); background: color-mix(in srgb, var(--accent) 14%, transparent); }
  .chip.next { color: var(--amber); background: color-mix(in srgb, var(--amber) 14%, transparent); }

  footer {
    border-top: 1px solid var(--line);
    margin-top: 30px; padding: 24px 0 40px;
    font-family: var(--mono); font-size: 13px; color: var(--muted);
    display: flex; flex-wrap: wrap; gap: 8px 28px;
  }
  /**
   * THE GRID, MADE VISIBLE — a development tool, off in every visitor's
   * browser and one keystroke away in yours.
   *
   * Drawn from the same four custom properties the layout is, never from
   * a second copy of the numbers: an overlay that agrees with a hand-typed
   * grid rather than with the page is worse than none, because it tells
   * you a width lines up when it does not.
   *
   * `position: fixed` and `pointer-events: none`, so it covers the
   * viewport as you scroll and nothing on the page becomes unclickable
   * while it is up.
   */
  .grid-guide {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 99;
    pointer-events: none;
  }
  :root.show-grid .grid-guide { display: block; }
  .grid-guide .wrap {
    height: 100%;
    display: grid;
    grid-template-columns: repeat(12, 1fr);
    gap: 0 var(--gutter);
  }
  .grid-guide span { background: rgba(255, 0, 0, 0.11); }
  /* Where the prose measure ends: nine tracks in. A line rather than a
     tint, because the question it answers is "does this edge land on the
     grid", and an edge is what you compare against an edge. */
  .grid-guide span:nth-child(9) { box-shadow: 1px 0 0 rgba(0, 255, 0, 0.55); }
  .grid-note {
    position: fixed;
    right: 8px;
    bottom: 8px;
    z-index: 100;
    padding: 4px 8px;
    background: var(--bg);
    border: 1px solid var(--line-hi);
    color: var(--muted);
    font-family: var(--mono);
    font-size: 11px;
    pointer-events: none;
  }
  @media (max-width: 560px) {
    /* The band around the lockup is generous on a desktop and would be
       half a phone screen of nothing here; the lockup itself already
       scales with the viewport. */
    .hero-logo { padding: 36px 0 32px; }
    .hero { min-height: 200px; }
    .roadmap li { grid-template-columns: 1fr; gap: 6px; }
    /* One column: two 248px tiles do not fit, and the lead tile spans
       whatever it is given either way. */
    .examples { grid-template-columns: 1fr; }
  }
  @media (prefers-reduced-motion: reduce) { * { transition: none !important; } }
