/* uncubed.app — the site's one stylesheet.
   No build step, no framework, no request off this origin, and no page that
   loads this file runs a script — except `try/index.html`, which loads its own
   `try.css` after this one and is the site's single exception (owner's ruling,
   2026-09-17: the cube goes "on its own page" so the home page stays
   script-free). Nothing in THIS file has ever depended on a script and nothing
   in it does now.

   **Promoted from `site/directions/style-c2.css` on 2026-09-17**, on the
   owner's verdict: *"C is best… I like the specimen sheet from B, and I like
   the gradient in hairlines from A."* The three directions and the candidate
   stay in `site/directions/` as the record of what he chose from; this file is
   what every page now loads, and the inner pages were brought onto it rather
   than left on the old one. `docs/2026-09-17-site-design-directions.md` §6–§7.

   THE CANDIDATE — C, with B's specimen plate and A's gradient hairlines.
   ----------------------------------------------------------------------------
   THE IDEA, IN ONE SENTENCE: the app's launch moment held open, with the
   gradient doing two jobs and never a third — the light in the room behind, and
   the page's few ruling hairlines in front — and the twenty puzzles laid out
   on one lit specimen plate whose coverage can be counted at a glance.
   ----------------------------------------------------------------------------
   THE OWNER'S VERDICT, 2026-09-17: *"C is best, and still needs some
   improvement, but a good basis. I like the specimen sheet from B, and I like
   the gradient in hairlines from A."* This file is that instruction carried
   out, and it is now `site/style.css`.

   WHAT CAME FROM WHERE
   - From C: the launch ground edge to edge, stages alternating with paper runs,
     the glow, the tilted hero tiles, panels crossing a stage's edge.
   - From B: the specimen plate — one hairline-ruled sheet of cells, each
     puzzle with its four modes marked — and B's monospace label register,
     adopted page-wide so the plate reads as native rather than pasted in.
   - From A: the gradient as a hairline rule, and A's discipline about how few
     of them there are. Five, listed under "the gradient's five jobs" below.

   THE GLOW IS AN AREA, AND THAT IS NOW ALLOWED. §10.9.1 rule 3 said the arc is
   "never a fill, a ground… if it occupies an area, it is wrong". **The owner
   granted the exception on 2026-09-17, for this site's launch ground and
   nothing else**, and the rule is amended in place in the design system with
   the reason it used to say otherwise. What keeps it honest is that **the
   gradient never carries ink** — every wash is under 0.22 alpha over the launch
   ground, every rule is two pixels with round ends (one pixel until
   2026-09-18, when the owner asked for the ends rounded and a round end needs
   a thickness to be seen), and it is checked by rendering each page
   twice and measuring every glyph against the ground under its own pixels, not
   by reading this file.

   No request leaves this origin. This file runs no script and needs none; the
   one page on the site that has a script of its own says so where it is. */

/* ------------------------------------------------------------------ tokens */

:root {
  color-scheme: light dark;

  --paper:         #F7F6F3;
  --panel:         #FCFBF8;
  --panel-active:  #FFFFFF;
  --panel-sunken:  #EFEDE8;
  --ink:           #16181C;
  --ink-secondary: #55595F;
  --rule:          #D8D5CE;
  --rule-emphasis: #8E8880;

  --mark-seg-1:    #0E39B0;
  --mark-seg-2:    #5310C5;
  --mark-seg-3:    #7F0A7E;
  --marker:        var(--mark-seg-2);
  --link:          var(--mark-seg-2);
  --caution:       #674C22;

  --rail:          #0D0D1F;
  --on-rail:       #F4F3EF;
  --on-rail-2:     #B4B8CF;
  --rail-line:     #34375A;
  --rail-raise:    #161733;
  --magic: linear-gradient(90deg, #98B1F7 0%, #C2A4F8 50%, #F68BF6 100%);
  --sweep: linear-gradient(90deg, #98B1F7 0%, #C2A4F8 25%, #F68BF6 50%,
                                  #C2A4F8 75%, #98B1F7 100%);

  /* The light. Alpha is the whole safety argument — see the header note. */
  --glow-blue:   rgba(152, 177, 247, 0.155);
  --glow-lilac:  rgba(194, 164, 248, 0.155);
  --glow-pink:   rgba(246, 139, 246, 0.115);
  --wash:        rgba(194, 164, 248, 0.14);
  --wash-2:      rgba(152, 177, 247, 0.12);
  --page:        var(--paper);

  /* The page's own ink under a second name, so a paper cell standing on a
     stage can ask for it back. A stage rewrites --ink for everything inside
     it; the plate and the tiles undo that, or their captions go white on
     paper — which is what happened in C's first draft, measured at 1.11:1. */
  --tile-ink:      #16181C;
  --tile-ink-2:    #55595F;
  --tile-rule:     #D8D5CE;
  --tile-rule-em:  #8E8880;

  --sp-1: 0.25rem; --sp-2: 0.5rem;  --sp-3: 0.75rem; --sp-4: 1rem;
  --sp-5: 1.25rem; --sp-6: 1.5rem;  --sp-8: 2rem;    --sp-10: 2.5rem;
  --sp-12: 3rem;   --sp-16: 4rem;   --sp-20: 5rem;   --sp-24: 6rem;

  /* One radius scale, tighter than C's. The plate is a cut sheet and the tiles
     are cut from the same sheet, so nothing is very round: C's 28px panel next
     to B's square cell was the most obvious seam between the two aesthetics. */
  --r-s: 8px; --r-m: 12px; --r-l: 16px;

  --fs-xs:   0.75rem;
  --fs-s:    0.9375rem;
  --fs-body: 1.0625rem;
  --fs-m:    1.25rem;
  --fs-l:    1.375rem;

  --font-ui:   -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
               "Helvetica Neue", Arial, sans-serif;
  --font-head: "UnCubed Heading", "UnCubed Heading Fallback",
               "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas,
               "Liberation Mono", monospace;

  --measure: 60ch;
}

@media (prefers-color-scheme: dark) {
  :root {
    --paper:         #0E1013;
    --panel:         #191C21;
    --panel-active:  #23272D;
    --panel-sunken:  #101317;
    --ink:           #F4F3EF;
    --ink-secondary: #A8ADB5;
    --rule:          #272B31;
    --rule-emphasis: #6B7178;
    --mark-seg-1:    #98B1F7;
    --mark-seg-2:    #C2A4F8;
    --mark-seg-3:    #F68BF6;
    --caution:       #F0A33C;
    --page:          #0A0A14;
    --wash:          rgba(194, 164, 248, 0.10);
    --wash-2:        rgba(152, 177, 247, 0.09);
    /* The light works harder against a dark ground to be seen at all. */
    --glow-blue:     rgba(152, 177, 247, 0.22);
    --glow-lilac:    rgba(194, 164, 248, 0.22);
    --glow-pink:     rgba(246, 139, 246, 0.17);
    --tile-ink:      #F4F3EF;
    --tile-ink-2:    #A8ADB5;
    --tile-rule:     #272B31;
    --tile-rule-em:  #6B7178;
  }
}

/* The one webfont, self-hosted beside this file. It said `../uncubed-heading.woff2` and "one
   directory up" until 29 October 2026, which worked on the server only because a URL cannot
   climb above the root; from a checkout or a sub-folder the font failed (W453). Quicksand by Andrew
   Paglinawan, SIL Open Font License 1.1, renamed because "Quicksand" is a
   Reserved Font Name and a subset is a Modified Version (OFL-FAQ 2.6) —
   https://github.com/andrew-paglinawan/QuicksandFamily. Headings only: this
   face's 1, l and I are near-identical, which is why no figure is set in it. */
@font-face {
  font-family: "UnCubed Heading";
  src: url("uncubed-heading.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap;
  unicode-range: U+0000-00FF, U+2010-2026, U+2030;
}
@font-face {
  font-family: "UnCubed Heading Fallback";
  src: local("Helvetica Neue Bold"), local("HelveticaNeue-Bold"),
       local("Helvetica Bold"), local("Arial Bold"), local("Arial-BoldMT");
  font-weight: 700;
  size-adjust: 101.2%; ascent-override: 98.8%;
  descent-override: 24.7%; line-gap-override: 0%;
}

/* -------------------------------------------------------------------- base */

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--page);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: var(--fs-body);
  line-height: 1.55;
  letter-spacing: -0.01em;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;   /* the stages bleed; nothing may scroll sideways */
}

img, svg { max-width: 100%; }

h1, h2, h3 {
  font-family: var(--font-head);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.015em;
  margin: 0 0 var(--sp-4);
  text-wrap: balance;
}
h1 { font-size: clamp(2.6rem, 1.3rem + 5.6vw, 5.25rem); line-height: 0.98; }
h2 { font-size: clamp(1.875rem, 1.3rem + 2.3vw, 3rem); }
h3 { font-size: var(--fs-m); margin-bottom: var(--sp-2); }

p { margin: 0 0 var(--sp-4); max-width: var(--measure); }
/* Running text does not end on a lone word (30 October 2026, the layout rules: no runts). `pretty`
   lets the browser pull a word down from the line above; headings and centred text keep `balance`
   where they say so. A one-letter word is bound to the next word in the HTML with a no-break space. */
p, li, dd, figcaption, blockquote { text-wrap: pretty; }
li, dd, blockquote { max-width: var(--measure); }
ul, ol { padding-left: 1.25em; margin: 0 0 var(--sp-4); }
li { margin-bottom: var(--sp-2); }

a { color: var(--link); text-underline-offset: 0.2em; text-decoration-thickness: from-font; }
a:hover { text-decoration-thickness: 2px; }
/* **Every link carries a glyph**, from 2026-10-16: the owner's rule of 2026-10-09 (930b), "never
   a bare text link". An arrow down for a place further on this page, an arrow across for anywhere
   else. Drawn here rather than typed into each link, so a link added later has one too; the
   no-break space keeps the arrow on its word's line. The second declaration gives the arrow empty
   alternative text, so a screen reader says the link's words only; an engine that cannot read it
   keeps the first. **The footer has them too since 29 October 2026** (W449, "a glyph beside every
   link and button, the masthead included"); the masthead draws its own, below, because its
   ::after is the current-page bar. */
main a::after, .hero .actions a::after, .foot a::after { content: "\00a0\2192"; content: "\00a0\2192" / ""; }
main a[href^="#"]:not([href="#"])::after,
.hero .actions a[href^="#"]::after { content: "\00a0\2193"; content: "\00a0\2193" / ""; }
/* **44 points of height on every link** (W449, and the app's own rule, ui-design-system.md §2.5).
   A link inside a sentence takes the height as padding, which an inline box adds to its target
   without moving a line of the text; the lines' targets overlap, which is the ordinary case for
   links in running text. */
main a:not(.btn):not(.brand), .hero a:not(.btn) { padding-block: calc(23px - 0.55em); }
:focus-visible { outline: 3px solid var(--link); outline-offset: 3px; border-radius: 3px; }

.lede { font-size: var(--fs-l); line-height: 1.4; color: var(--ink-secondary); }
.note { font-size: var(--fs-s); color: var(--ink-secondary); }

/* B's label register, adopted page-wide. Monospace, uppercase, tracked — the
   one change that stops the plate looking pasted into somebody else's page,
   because every section above and below it is labelled the same way. It is a
   label rather than a heading, so it never takes the heading face. */
.label {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-secondary);
  margin: 0 0 var(--sp-4);
  max-width: none;
}

/* Read by a screen reader, drawn for nobody. Used by the plate's mode marks,
   whose letters alone would say "L F P R" out loud. */
.vh {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 20;
  background: var(--ink); color: var(--paper);
  padding: var(--sp-3) var(--sp-4); border-radius: 0 0 var(--r-s) 0;
}
.skip:focus { left: 0; }

.wrap { max-width: 74rem; margin: 0 auto; padding: 0 var(--sp-5); }
.narrow { max-width: 46rem; }

/* ===================================== the gradient's five jobs, and no more

   A's rule, carried over: colour marks structure, it is always one or two
   pixels, and the list is short enough to read. Every use below is an edge.

   1. `.masthead::after`      — the hairline under the masthead, every page
   2. `.hero::after`          — the hairline that closes a page's opening block
   3. `.plate`                — the 1px frame around the specimen plate
   4. `.cta-rule::before`     — the hairline above a closing panel
   5. `.counting`             — the underline under the owner's own words
   6. `.magic-bar`            — a 4rem rule above a heading, where a page wants
                                one and has no stage to close (§10.9.1's
                                original second use, kept)

   Plus the glow, which is the light rather than a rule, and is the one thing
   here that occupies an area — see the header note. C's four gradient card
   edges are deliberately gone: the mode chip and the plate's frame carry the
   brand in that section now, and eight edges was more than A's discipline
   allows. ================================================================= */

.magic-rule {
  position: absolute; left: 0; right: 0; height: 2px;
  border-radius: 999px;
  background: var(--sweep);
  background-size: 200% 100%;
  animation: sweep-drift 9s linear infinite;
}
@keyframes sweep-drift { from { background-position: 0 0; } to { background-position: -200% 0; } }

/* ------------------------------------------------------------- the stages */

.stage {
  position: relative;
  background: var(--rail);
  color: var(--on-rail);
  isolation: isolate;
  overflow: hidden;

  --ink:           var(--on-rail);
  --ink-secondary: var(--on-rail-2);
  --rule:          var(--rail-line);
  --rule-emphasis: var(--rail-line);
  --panel:         var(--rail-raise);
  --panel-active:  var(--rail-raise);
}

/* The light. Two drifting radials and one still.
   **The sizes are `min()`ed against the viewport**, which is an improvement on
   C: there the radials were 38–46rem wide, so on a 375px phone (23.4rem) the
   falloff was entirely off-screen and the glow read as a flat tint rather than
   as light. Looked at in the 375 frames on 2026-09-17. */
.stage::before,
.stage::after {
  content: "";
  position: absolute;
  inset: -40% -20%;
  z-index: -1;
  pointer-events: none;
}
.stage::before {
  background:
    radial-gradient(min(42rem, 96vw) min(30rem, 62vh) at 10% 16%, var(--glow-blue), transparent 68%),
    radial-gradient(min(38rem, 88vw) min(28rem, 58vh) at 74% 44%, var(--glow-lilac), transparent 68%);
  animation: lift 34s ease-in-out infinite alternate;
}
.stage::after {
  background: radial-gradient(min(46rem, 104vw) min(32rem, 66vh) at 86% 92%,
                              var(--glow-pink), transparent 70%);
  animation: drift 46s ease-in-out infinite alternate;
}
@keyframes lift  { from { transform: translate3d(0, 0, 0); } to { transform: translate3d(2.5%, -3%, 0); } }
@keyframes drift { from { transform: translate3d(0, 0, 0); } to { transform: translate3d(-3%, 2%, 0); } }

.stage a { color: var(--on-rail); }
.stage :focus-visible { outline-color: #C2A4F8; }
.stage .mode { border: 1px solid var(--rail-line); }

/* The hairline that closes the hero stage — job 2. **It is the hero's own
   pseudo-element and not the stage's, and that is a fix rather than a
   preference.** It was `.stage-close::after`, which collided with the glow
   already using `.stage::after`: the later rule won the background and the
   height, `inset` left `top: -40%` in place, and with top, bottom and height
   all set CSS drops `bottom` — so the rule was drawn three hundred pixels
   above the stage, clipped away by `overflow: hidden`, and the hero quietly
   lost its third glow to pay for it. Two jobs on one pseudo-element, where the
   thing that broke was invisible in both directions. Diagnosed from computed
   styles on 2026-09-17, not from the render. */
.hero::after {
  content: "";
  position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
  border-radius: 999px;
  background: var(--sweep);
  background-size: 200% 100%;
  animation: sweep-drift 9s linear infinite;
}

/* A paper run between the stages, the same light turned down to a wash. */
.run {
  position: relative;
  background:
    radial-gradient(min(40rem, 96vw) min(28rem, 60vh) at 88% 8%, var(--wash), transparent 70%),
    radial-gradient(min(36rem, 88vw) min(26rem, 56vh) at 6% 82%, var(--wash-2), transparent 70%),
    var(--page);
  padding: var(--sp-20) 0;
}

/* ---------------------------------------------------------------- masthead */

/* THE MENU STAYS IN VIEW — the owner's request of 2026-09-18, on every page.
   CSS only, like everything else in this file.

   **It is the masthead's stage that sticks, not the masthead**, because a
   sticky element sticks only within its parent: on the home page and `/try`
   the masthead used to share one stage with the hero, so it would have let go
   at the hero's foot. Every page now opens `<div class="stage stage-head">`
   holding the masthead alone, and on those two pages the hero follows in a
   stage of its own on the same ground; the seam between them is the
   masthead's own gradient hairline, which was always there.

   The stage is opaque (`--rail`), so whatever scrolls under it is covered
   rather than seen through, and its ink is measured on its own ground in
   both positions — stuck or not, nothing behind it changes. `z-index` 10
   sits above the floating panels (3) and below the skip link (20).

   **Anchor targets clear it** through `scroll-margin-top` on every element
   with an `id`, set from `--head-h`: the menu's own height at each width plus
   a breath. The heights are measured, not guessed — `Tools/site/scrolled.mjs`
   checks every in-page link lands below the menu at 1280, 375 and 320. */
.stage-head { position: sticky; top: 0; z-index: 10; }
/* The light, turned down in the bar. On its own the masthead is a strip about
   80px tall, so the stage's radials land their brightest part behind the nav
   rather than spreading over a hero; measured on 2026-09-18 that took the dark
   nav's worst glyph from 6.65:1 to 5.23:1. Still AA, and still a regression,
   so the bar keeps the light at a little over half strength. */
.stage-head::before, .stage-head::after { opacity: 0.55; }
:root { --head-h: 5rem; }
[id] { scroll-margin-top: calc(var(--head-h) + var(--sp-4)); }

.masthead { position: relative; z-index: 2; padding: var(--sp-5) 0 var(--sp-6); }
.masthead .wrap {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--sp-4) var(--sp-6); justify-content: space-between;
}

.masthead::after { content: ""; bottom: 0; }
/* Rounded caps at both ends of every gradient rule — the owner, 2026-09-18.
   A cap needs a thickness to be seen, so the rules are 2px, not the 1px of A's
   hairlines: at 1px a round end is half a pixel and nobody can see it. */
.masthead::after,
.cta-rule::before {
  position: absolute; left: 0; right: 0; height: 2px;
  border-radius: 999px;
  background: var(--sweep);
  background-size: 200% 100%;
  animation: sweep-drift 9s linear infinite;
  content: "";
}

.brand { text-decoration: none; display: inline-flex; align-items: center; min-height: 44px; }
/* **The wordmark carries the same arrow as the links beside it** (W458 b, 30 October 2026; W449,
   "a glyph beside every link and button, the masthead included"). It is the site's name and it
   links home, so it is a link like the others. Drawn before the word, as the nav's are, in the nav's
   own face, size and colour, outside `.wordmark` so the wordmark's gradient does not reach it; its
   alternative text is empty, so a screen reader still says the name only. */
.brand::before {
  content: "\2192"; content: "\2192" / "";
  margin-right: 0.55em;
  font-family: var(--font-mono); font-size: var(--fs-xs); letter-spacing: 0;
  color: var(--on-rail-2);
}
.wordmark {
  font-family: var(--font-head); font-weight: 700;
  font-size: 1.85rem; letter-spacing: -0.01em; line-height: 1;
  color: var(--on-rail);
}
/* The registered sign after the wordmark (2026-10-17, the owner's S2-22a): small, raised, and
   out of the line box so the masthead does not grow. It takes the wordmark's colour or gradient. */
.wordmark .reg {
  font-size: 0.4em; font-weight: 700; letter-spacing: 0; line-height: 0;
  vertical-align: 1.35em; margin-left: 0.08em;
}
@supports (background-clip: text) or (-webkit-background-clip: text) {
  .wordmark {
    background-image: linear-gradient(100deg, #98B1F7 0%, #C2A4F8 52%, #F68BF6 100%);
    -webkit-background-clip: text; background-clip: text; color: transparent;
  }
}
.nav { display: flex; gap: var(--sp-5); flex-wrap: wrap; }
.nav a {
  position: relative;
  color: var(--on-rail-2); text-decoration: none;
  font-family: var(--font-mono); font-size: var(--fs-xs);
  letter-spacing: 0.12em; text-transform: uppercase;
  display: inline-flex; align-items: center; min-height: 44px;
  padding: 0;
}
/* The masthead's glyph (W449), drawn before the word because the bar after it is ::after. A
   small arrow across, as every other link has, with empty alternative text. */
.nav a::before { content: "\2192\00a0"; content: "\2192\00a0" / ""; letter-spacing: 0; }
/* The current-page rule, and the hover rule, with rounded caps — 2026-09-18.
   It was a 2px `border-bottom`, which cannot have round ends, so it is now a
   2px bar of its own in the same place and the same colour. The letter
   spacing leaves a tracking gap after the last letter; the bar stops short of
   it, so it sits under the word rather than under the word and a space. */
.nav a::after {
  content: "";
  position: absolute; left: 1.3em; right: 0.12em; bottom: 0; height: 2px;
  border-radius: 999px;
  background: transparent;
}
.nav a:hover { color: var(--on-rail); }
.nav a:hover::after { background: var(--on-rail-2); }
.nav a[aria-current="page"] { color: var(--on-rail); }
.nav a[aria-current="page"]::after { background: #C2A4F8; }

/* Below 44rem the nav cannot share the wordmark's line, so it takes a line of
   its own on purpose rather than by overflow, and the bar is drawn tighter:
   a menu that stays on screen is paid for in reading height on the smallest
   phone, and at 320px it was a sixth of the screen. */
@media (max-width: 43.99rem) {
  /* 5.5rem until 30 October 2026. The menu is 108px tall here since W449 gave the wordmark and each
     link a 44px target (a line of each, with a gap and the padding), and an anchor then landed
     under its foot; Tools/site/scrolled.mjs reported 234 failures at 375 and 320. */
  :root { --head-h: 6.75rem; }
  .masthead { padding: var(--sp-3) 0 var(--sp-1); }
  .masthead .wrap { gap: var(--sp-1) var(--sp-4); }
  .wordmark { font-size: 1.6rem; }
  .nav { flex-basis: 100%; flex-wrap: nowrap; justify-content: space-between; gap: var(--sp-2); }
  .nav a { letter-spacing: 0.08em; }
  .nav a::after { right: 0.08em; }
}
@media (max-width: 25rem) {
  .nav a { font-size: 0.6875rem; letter-spacing: 0.04em; }
  .nav a::after { left: 1.2em; }
  .nav a::after { right: 0.04em; }
}

/* -------------------------------------------------------------------- hero */

.hero { position: relative; padding: var(--sp-10) 0 var(--sp-12); }
.stage .hero { padding: var(--sp-16) 0 var(--sp-24); }
.hero h1 { max-width: none; margin-bottom: var(--sp-6); }
.hero .lede { max-width: 38ch; }

/* THE HEADLINE IS THE OWNER'S OWN SENTENCE, 2026-09-17, and the words do not
   change except by him: "15 puzzles – and counting. Learn how to solve them, or
   just fix them, play with virtual versions, or race a friend!"

   **It read "14 puzzles" until 2026-09-18**, which was true when he wrote it and
   stopped being true the same day the Twist Cube arrived — the title, the meta
   description and every other count on the page already said fifteen, so the
   headline was contradicting the grid beneath it. He ruled the number to fifteen
   and the rest of the sentence unchanged. **And it read "15" until the same
   evening**, when the 7×7 joined and he ruled a full pass for sixteen: the
   headline, the counts and the plate together. Fix's count, which was a
   different number (fourteen) and stayed while the headline moved, is sixteen
   too from that pass: every puzzle now has a solver.

   **And it read "16" until 2026-09-24**, when the launch roadmap's site item —
   "sixteen puzzles becomes twenty", his — moved it with the catalogue's twenty
   entries (the launch set, frozen at twenty). The sentence is otherwise his and
   untouched. The per-mode counts did not all move with it this time: Fix is
   nineteen and Learn sixteen, and the plate says which puzzles, so the headline
   counts puzzles and the legend counts modes, as he ruled on 2026-09-17.

   The first beat takes
   the stage; the second arrives under the rule at reading size. The break is
   after "and counting.", which is the one he sanctioned; the en dash is bound
   to "puzzles" with a non-breaking space so it can never start a line.

   **The second beat is heavier than C's** — same face, 1.375→1.875rem at the
   top of the ramp, and ink rather than secondary. In C it read as a caption
   under the headline; it is the half of his sentence that says what the app
   does, and it was the weakest thing in the hero. */
.hero .beat { display: block; max-width: 14ch; }
.hero .beat-2 {
  display: block;
  margin-top: var(--sp-6);
  font-size: clamp(1.3125rem, 1rem + 1.5vw, 1.875rem);
  line-height: 1.24;
  letter-spacing: -0.008em;
  max-width: 26ch;
}

.counting {
  display: inline-block;
  position: relative;
  /* **The bar sits BELOW the descenders, and that is a measurement.** At
     `0 97%` with 0.05em of padding it crossed the `g` of "counting", and on
     this ground the ink is --on-rail while the gradient's far end is #F68BF6:
     1.90:1 where they met, measured from the rendered pixels on 2026-09-17.
     `site/style.css` carried the same rule and escaped it only because the
     words it underlines — "can't read" — have no descender. */
  padding-bottom: 0.34em;
}
/* The underline itself, as a bar with round ends (2026-09-18). It was a
   background image, whose ends are square and cannot be otherwise. */
.counting::after {
  content: "";
  position: absolute; left: 0; right: 0; bottom: 0; height: 0.09em;
  border-radius: 999px;
  background: var(--magic);
}

@media (min-width: 60rem) {
  .hero-grid { display: grid; grid-template-columns: minmax(0, 1.08fr) minmax(0, 1fr); gap: var(--sp-16); align-items: center; }
}

/* The cluster: three tiles standing in the light, staggered. Paper, because
   each drawing picks its own ink from `prefers-color-scheme` inside the SVG
   (`.c0s` is #16181c, #f4f3ef under dark) and has to sit on the ground it was
   drawn for. The radius is the plate's, so they read as cut from one sheet. */
.cluster {
  list-style: none; margin: var(--sp-10) 0 0; padding: 0;
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-3); max-width: none;
}
.cluster > li, .cell, .plate {
  --ink:           var(--tile-ink);
  --ink-secondary: var(--tile-ink-2);
  --rule:          var(--tile-rule);
  --rule-emphasis: var(--tile-rule-em);
  color: var(--tile-ink);
}
.cluster > li {
  margin: 0; max-width: none;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--r-m);
  padding: var(--sp-3) var(--sp-2);
  text-align: center;
}
.cluster img { display: block; width: 100%; max-width: 10rem; height: auto; aspect-ratio: 1 / 1; margin: 0 auto var(--sp-2); }
.cluster .name { display: block; font-size: var(--fs-s); font-weight: 600; line-height: 1.25; }

@media (min-width: 34rem) {
  .cluster { gap: var(--sp-4); padding-bottom: var(--sp-8); }
  .cluster > li:nth-child(1) { transform: translateY(var(--sp-8)) rotate(-2.5deg); }
  .cluster > li:nth-child(2) { transform: rotate(1.5deg); }
  .cluster > li:nth-child(3) { transform: translateY(var(--sp-10)) rotate(3deg); }
}

/* **A light card behind the coloured solids in dark mode** (30 October 2026). The solids are
   transparent PNGs drawn with a black outline, black gaps and a black centre ball, for the light
   paper they were rendered on; on the dark page those parts vanished into the ground and the
   puzzles read as shapes cut out of the page. Dark mode puts a light card behind each, the
   hero's whole tile and the picture itself in Fix, Learn and Play. In light mode nothing
   changes: the ground is already paper. */
@media (prefers-color-scheme: dark) {
  .cluster-solid > li {
    background: #F7F6F3; border-color: #D8D5CE;
    --tile-ink: #16181C; --tile-ink-2: #55595F; --tile-rule: #D8D5CE; --tile-rule-em: #8E8880;
  }
  img.solid {
    background: #F7F6F3; border-radius: var(--r-m);
    padding: var(--sp-2); box-sizing: border-box;
  }
}

/* ----------------------------------------------------------------- buttons */

.actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); margin: var(--sp-8) 0 0; }
.btn {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  min-height: 3.1rem; padding: 0.7rem 1.5rem;
  border-radius: 999px;
  font-weight: 600; text-decoration: none; line-height: 1.2;
  border: 2px solid var(--rule-emphasis);
  color: var(--ink); background: var(--panel-active);
}
.btn:hover { text-decoration: underline; text-decoration-thickness: 2px; }
.btn-magic {
  color: var(--on-rail);
  border: 2px solid transparent;
  background: linear-gradient(var(--rail), var(--rail)) padding-box,
              var(--magic) border-box;
}
.stage .btn { color: var(--on-rail); background: var(--rail-raise); border-color: var(--rail-line); }
.stage .btn-magic {
  border-color: transparent;
  background: linear-gradient(var(--rail), var(--rail)) padding-box,
              var(--magic) border-box;
}

/* ------------------------------------------------------------------ floats */

.float {
  background: var(--panel-active);
  border: 1px solid var(--rule);
  border-radius: var(--r-l);
  padding: var(--sp-8) var(--sp-6);
}
.float-up { margin-top: calc(-1 * var(--sp-16)); position: relative; z-index: 3; }
.float > :last-child { margin-bottom: 0; }
@media (min-width: 52rem) { .float { padding: var(--sp-10); } }

@media (min-width: 56rem) {
  .two-up { display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); gap: var(--sp-10); align-items: start; }
}

/* ------------------------------------------------- the second lead's plate */

/* **The "turn it first" panel has real room now**, which is the second thing
   C needed: it was a two-column panel of prose and it is the owner's second
   lead. It is three parts at desk width — the claim, the argument, and one
   drawing captioned the way A captions a plate — so the thing being argued
   for is on the page rather than described. */
@media (min-width: 62rem) {
  .three-up {
    display: grid;
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr) 15rem;
    gap: var(--sp-10);
    align-items: start;
  }
}
.figure { margin: var(--sp-8) 0 0; }
@media (min-width: 62rem) { .figure { margin: 0; } }
.figure img { display: block; width: 100%; max-width: 14rem; height: auto; aspect-ratio: 1 / 1; }
.figure figcaption {
  font-size: var(--fs-xs); color: var(--ink-secondary);
  border-top: 1px solid var(--rule);
  padding-top: var(--sp-2); margin-top: var(--sp-3);
  max-width: 26ch;
}
.figure figcaption b { color: var(--ink); font-weight: 600; }

/* ======================================================= the specimen plate

   B's sheet, rendered for this ground. One plate, not fifteen tiles: the cells
   are paper because each drawing picks its own ink inside the SVG and has to
   sit on the ground it was drawn for, and the plate as a whole is an object
   standing in the light with a gradient hairline for an edge — which is the
   one place on the page where A's rule and C's light do the same job at once.
   ======================================================================== */

.plate {
  margin: var(--sp-10) 0 0;
  border: 1px solid transparent;
  border-radius: var(--r-m);
  /* Job 3: the 1px frame is the gradient, the fill is paper. */
  background: linear-gradient(var(--paper), var(--paper)) padding-box,
              var(--magic) border-box;
  overflow: hidden;
}

/* The legend teaches the notation and carries the counts, which is where the
   owner ruled the per-mode figures live — beside the cells they describe and
   not beside his headline. Learn 16, Fix 19, Play 20, Race 20 since 2026-09-24
   (Learn 16, Fix 16, Play 16, Race 16 from 2026-09-18, and Learn 11, Fix 14,
   Play 15, Race 15 the day before), each read from source at the tip of the
   branch that changed it; docs/2026-09-24-site-desk-photo.md §2 says how. The
   dashed-mark key is back with the dashed marks, as it was on 2026-09-17. */
.legend {
  list-style: none; margin: 0; padding: var(--sp-4) var(--sp-4);
  display: flex; flex-wrap: wrap; gap: var(--sp-3) var(--sp-6);
  max-width: none;
  border-bottom: 1px solid var(--rule);
  background: var(--panel-sunken);
}
.legend > li {
  margin: 0; max-width: none;
  display: flex; align-items: center; gap: var(--sp-2);
}
.legend .word {
  font-family: var(--font-mono); font-size: var(--fs-xs);
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-secondary);
}
/* The one item in the legend that is a sentence rather than a label, so it is
   not set in the label's uppercase — at 375 it wrapped to two shouting lines. */
.legend .hint {
  font-family: var(--font-ui); font-size: var(--fs-xs);
  letter-spacing: 0; text-transform: none; color: var(--ink-secondary);
}
.legend .count {
  font-family: var(--font-mono); font-size: var(--fs-m);
  font-variant-numeric: tabular-nums; letter-spacing: -0.02em;
  color: var(--ink);
}

/* **The hairlines are each cell's own 1px shadow, not a 1px grid gap**, and
   that is a fix rather than a flourish. With `gap: 1px` over a `--rule`
   background the grid's leftover area — fifteen cells do not divide into two
   or four columns — showed as a rule-coloured block at the end of the plate,
   looking like a sixteenth cell somebody had forgotten to fill. Looked at in
   the 375 frame on 2026-09-17. A shadow overlaps its neighbour instead, so
   there are no gaps to leave over and the remainder is simply paper. */
.specimens {
  list-style: none; margin: 0; padding: 0; max-width: none;
  display: grid; gap: 0;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  background: var(--paper);
}
/* **2 columns on a phone, then 3, then 7**, from 2026-10-16, when the Molecube made the plate 21.
   It was 2, 3, 4 and 5 columns at 0, 30, 46 and 64rem, which 20 filled at every width but 3; 21
   fills 3 and 7 and leaves one cell on the last row at 2, 4 and 5. 3 columns on a 375 phone was
   tried and cut the marks and the longer names, so the phone keeps 2 and its last row holds the
   Molecube alone, on the grid. Change these with the catalogue: the count to divide is the
   number of cells. */
/* **23 cells from 29 October 2026** (W440, the 2 Bricks joined): 2, 3, 4 and 6 columns at 0, 30, 46
   and 64rem. 23 divides by none of them, and at each the last row is one cell short; at 7 columns
   it would hold 2 cells and leave 5 empty. */
@media (min-width: 30rem) { .specimens { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 46rem) { .specimens { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (min-width: 64rem) { .specimens { grid-template-columns: repeat(6, minmax(0, 1fr)); } }


.cell {
  margin: 0; max-width: none;
  background: var(--paper);
  box-shadow: 0 0 0 1px var(--rule);
  padding: var(--sp-3) var(--sp-3) var(--sp-4);
  display: grid;
  grid-template-rows: 1fr auto auto;
  gap: var(--sp-2);
  justify-items: center;
  text-align: center;
}
/* `aspect-ratio` holds the square before the file arrives: a lazy tile measured 0px tall until it
   loaded, in Chrome on 2026-10-16, so the plate grew by a row's worth as it scrolled into view,
   width and height attributes notwithstanding, because the <picture> around it is a grid item
   that shrinks to its content, and an image that has not arrived has none. So the picture takes
   the tile's width itself. Every puzzle drawing is 160 by 160. */
.cell > picture { display: block; width: 100%; max-width: 8rem; }
.cell img { display: block; width: 100%; max-width: 8rem; height: auto; aspect-ratio: 1 / 1; }
.cell .name { font-weight: 600; font-size: var(--fs-s); line-height: 1.25; }
.cell .aka { display: block; font-size: var(--fs-xs); color: var(--ink-secondary); line-height: 1.3; }
/* **One row of cells shares its tracks**, so every name in a row sits on one line and every row
   of marks on another, whichever cell carries a second line under its name. Until 2026-10-16
   each cell centred its own drawing, and a cell with an "also known as" line set its name higher
   than its neighbours'. */
@supports (grid-template-rows: subgrid) {
  .specimens { grid-auto-rows: auto; }
  .cell { grid-row: span 3; grid-template-rows: subgrid; align-items: start; }
  .cell > picture, .cell > .slot { align-self: center; }
}
.cell .slot {
  display: grid; place-items: center;
  aspect-ratio: 1 / 1; width: 100%; max-width: 8rem;
  border: 1px dashed var(--rule-emphasis);
  border-radius: var(--r-s);
  color: var(--ink-secondary);
  font-family: var(--font-mono); font-size: var(--fs-xs);
  letter-spacing: 0.06em; text-transform: uppercase; line-height: 1.3;
}

/* The four mode marks. **Colour is never the signal**: a mode this puzzle has
   is a filled chip, one it does not have yet is a dashed outline, and either
   way a screen reader is given the word rather than the letter (§8.1 rule 1,
   and the app's own colour grid dims-rather-than-removes for the same reason).
   In greyscale the fill and the dashed edge still separate. */
.marks {
  list-style: none; margin: 0; padding: 0; max-width: none;
  display: flex; gap: 0.3rem; justify-content: center;
}
.marks > li { margin: 0; max-width: none; }
.mark {
  display: grid; place-items: center;
  width: 1.5rem; height: 1.5rem;
  border-radius: 4px;
  font-family: var(--font-mono);
  font-size: 0.6875rem; font-weight: 700; line-height: 1;
  letter-spacing: 0;
}
.mark-on  { background: var(--ink); color: var(--paper); border: 1px solid var(--ink); }
.mark-off { background: transparent; color: var(--ink-secondary); border: 1px dashed var(--rule-emphasis); }

/* ================================================================ the desk

   The owner's photograph of the puzzles the app is built with, and the app's
   drawing of the same stacks — added 2026-09-24,
   docs/2026-09-24-site-desk-photo.md. Two figures in the accessibility page's
   `.shot` style, side by side at desk width and one above the other on a phone,
   standing on the plate's stage because they are the same twenty seen twice.

   **The names are a layer on the photograph, not a second photograph.** Each
   puzzle named is a real `<button>` with an `aria-label`, placed in percentages
   of the picture (`--x`, `--y`, `--w`, `--h`, written by
   Tools/site/desk-photo.py) so it tracks the image at every width. A name shows
   while its puzzle is pointed at, pressed or focused, and every name shows
   while the checkbox is ticked.

   **No script does any of it**, because this page runs none — the owner's
   ruling of 2026-09-17, which `Tools/site/no-script-elsewhere.mjs` guards and
   the privacy paragraph at the foot of the page states. A tap reaches a name
   through `:hover`, which a touch screen holds after a tap, and through
   `:focus`; the checkbox reaches the names through `~`, which every engine has,
   rather than `:has()`.

   **Ink, never the arc.** The checkbox is ink on the stage, the focus ring is
   the stage's own focus colour, and a name tag is the launch ground under
   `--on-rail`, opaque enough (0.88) to read on any part of the photograph:
   12.7:1 at its worst, over a pure white pixel. */
.desk { margin-top: var(--sp-16); }
.desk-pair {
  display: grid; gap: var(--sp-6);
  grid-template-columns: minmax(0, 1fr);
  margin-top: var(--sp-8);
}
@media (min-width: 56rem) {
  .desk-pair { grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: start; }
}
.desk-pair .shot { margin: 0; }

/* The checkbox and its words sit above the photograph, in the label register. */
.desk-photo { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2) var(--sp-3); }
.desk-photo > .desk-frame,
.desk-photo > figcaption { flex: 1 0 100%; }
.desk-toggle {
  appearance: none; -webkit-appearance: none;
  flex: none; display: grid; place-items: center;
  width: 1.375rem; height: 1.375rem; margin: 0 0 0 var(--sp-1);
  border: 2px solid var(--ink-secondary); border-radius: 4px;
  background: transparent; cursor: pointer;
}
.desk-toggle:checked { background: var(--ink); border-color: var(--ink); }
/* The tick is the launch ground on an ink fill: this control only ever stands
   on the stage, where ink is --on-rail. */
.desk-toggle:checked::after {
  content: "";
  width: 0.35rem; height: 0.7rem; margin-top: -0.15rem;
  border: solid var(--rail); border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
.desk-switch {
  font-family: var(--font-mono); font-size: var(--fs-xs);
  letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--ink); cursor: pointer;
  display: inline-flex; align-items: center; min-height: 44px;
}

.desk-frame { position: relative; container-type: inline-size; }
.desk-frame img { display: block; width: 100%; height: auto; border-radius: var(--r-s); }
.spots { list-style: none; margin: 0; padding: 0; max-width: none; }
.spots > li { margin: 0; max-width: none; }
.spot {
  position: absolute;
  left: var(--x); top: var(--y); width: var(--w); height: var(--h);
  z-index: var(--z);
  margin: 0; padding: 0; border: 0; border-radius: var(--r-s);
  background: transparent; color: inherit; font: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
/* The puzzle being named rises above its neighbours, so its tag is never under
   one of theirs. */
/* **A touch area at least 44px tall** (W449, 29 October 2026). A name's box is the puzzle's own
   size in the photograph, and 4 of them were 22 to 34px tall on a 375 phone, so each carries an
   invisible band 44px tall across its middle that takes the tap too. */
.spot::before {
  content: ""; position: absolute; left: 0; right: 0; top: 50%;
  height: max(100%, 44px); transform: translateY(-50%);
}
.spot:hover, .spot:focus { z-index: 6; }
.spot:focus-visible { outline: 3px solid #C2A4F8; outline-offset: -3px; }
/* A tag sits in the middle of its puzzle unless `--tx`/`--ty` move it: four do,
   so that neighbours' tags do not meet at phone width and none leaves the
   picture. */
.spot-name {
  position: absolute; left: var(--tx, 50%); top: var(--ty, 50%);
  transform: translate(-50%, -50%);
  width: max-content; max-width: 7em;
  padding: 0.2rem 0.45rem;
  border-radius: 6px;
  background: rgba(13, 13, 31, 0.88);
  color: #F4F3EF;
  font-family: var(--font-ui); font-size: 0.6875rem; font-weight: 600;
  line-height: 1.2; letter-spacing: 0; text-align: center;
  opacity: 0; transition: opacity 0.15s ease-out;
  pointer-events: none;
}
.spot:hover .spot-name,
.spot:focus .spot-name,
.spot:active .spot-name,
.desk-toggle:checked ~ .desk-frame .spot-name { opacity: 1; }
/* A photograph as wide as a tablet's has room for the page's small size. */
@container (min-width: 30rem) { .spot-name { font-size: var(--fs-xs); max-width: 9em; } }

/* The drawing stands on paper, as the plate's drawings do: each one picks its own
   ink from `prefers-color-scheme` inside the file. */
.desk-paper { background: var(--paper); border-radius: var(--r-s); padding: var(--sp-4); }
.desk-paper img { display: block; width: 100%; height: auto; border-radius: 0; }

/* The names as text, for a screen reader and for anybody who would rather read
   them than point. A native disclosure, so it needs no script either. */
.desk-list { margin-top: var(--sp-3); }
.desk-list summary {
  cursor: pointer;
  min-height: 44px; padding-block: 0.8rem;
  font-family: var(--font-mono); font-size: var(--fs-xs);
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--ink);
}
.desk-list ul { margin: var(--sp-2) 0 0; padding-left: 1.1em; }
.desk-list li { margin-bottom: var(--sp-1); }

/* ------------------------------------------------------------------- cards */

.cards {
  list-style: none; margin: var(--sp-10) 0 0; padding: 0;
  display: grid; gap: var(--sp-4);
  grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr));
  max-width: none;
}
.cards > li {
  margin: 0; max-width: none;
  background: var(--panel-active);
  border: 1px solid var(--rule);
  border-radius: var(--r-m);
  padding: var(--sp-5);
}
/* S2-84a, 2026-10-12. The mode name is the card's h3, drawn as the label was (.mode below), and the
   card's line is a paragraph set exactly as the h3 line was: the heading face at --fs-m, the same
   margins and leading, the page ink. The line is a paragraph so that Play's may carry commas,
   which a heading may not (HeadingCommasTests). */
.cards h3 { margin: 0; text-wrap: wrap; }
.cards .line {
  margin: var(--sp-3) 0 var(--sp-2); max-width: none;
  color: inherit;
  font-family: var(--font-head); font-weight: 700; font-size: var(--fs-m);
  line-height: 1.1; letter-spacing: -0.015em;
  text-wrap: balance;
}

.mode {
  display: inline-block;
  background: var(--rail); color: var(--on-rail);
  border-radius: 4px;
  font-family: var(--font-mono);
  font-size: var(--fs-xs); font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase; line-height: 1;
  padding: 0.5rem 0.55rem 0.6rem;
}

/* ------------------------------------------------------------- ruled lists */

.facts { list-style: none; padding: 0; margin: var(--sp-8) 0 0; max-width: none; }
.facts > li {
  margin: 0; max-width: none;
  border-top: 1px solid var(--rule);
  padding: var(--sp-4) 0;
}
.facts > li:last-child { border-bottom: 1px solid var(--rule); }
.facts strong { display: block; margin-bottom: var(--sp-1); }
.facts span { color: var(--ink-secondary); display: block; max-width: 54ch; }

.flow { list-style: none; counter-reset: flow; padding: 0; margin: var(--sp-6) 0 0; max-width: none; }
.flow > li {
  counter-increment: flow; position: relative;
  padding: 0 0 var(--sp-5) 3.5rem; margin: 0; max-width: 52ch;
}
.flow > li::before {
  content: counter(flow);
  position: absolute; left: 0; top: -0.15rem;
  width: 2.5rem; height: 2.5rem;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--rail); color: var(--on-rail);
  font-family: var(--font-mono); font-weight: 700; font-size: var(--fs-s);
}
.flow strong { display: block; }
.flow span { color: var(--ink-secondary); }

/* --------------------------------------------------------------- the quote */

.said { margin: 0 0 var(--sp-3); padding: 0; }
.said p {
  font-size: clamp(1.25rem, 1rem + 1vw, 1.6rem);
  line-height: 1.3; margin: 0; max-width: 32ch; color: var(--ink);
}
.said p::before { content: "\201C"; }
.said p::after  { content: "\201D"; }

.notation {
  display: inline-block; font-family: var(--font-mono);
  font-size: var(--fs-s); font-weight: 600; letter-spacing: 0.03em;
  border: 1px solid var(--rule-emphasis); border-radius: var(--r-s);
  padding: 0.15rem 0.5rem; color: var(--ink-secondary);
}

/* ------------------------------------------------------------- shifters */

.shifters {
  list-style: none; margin: var(--sp-10) 0 0; padding: 0;
  display: grid; gap: var(--sp-4);
  grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
  max-width: none;
}
.shifters > li {
  margin: 0; max-width: none;
  background: var(--panel); border: 1px solid var(--rule);
  border-radius: var(--r-m); padding: var(--sp-5);
}
.shifters img { display: block; width: 100%; max-width: 8rem; height: auto; aspect-ratio: 1 / 1; margin: 0 0 var(--sp-4); }
.shifters h3 { margin: 0 0 var(--sp-2); }
.shifters p { margin: 0; font-size: var(--fs-s); color: var(--ink-secondary); }
.shifters p + p { margin-top: var(--sp-3); }

/* ------------------------------------------------------------------ glyphs */

.glyphs {
  display: flex; flex-wrap: wrap; gap: var(--sp-3);
  margin: var(--sp-8) 0 0; padding: 0; list-style: none; max-width: none;
}
.glyphs li {
  margin: 0; width: 3.5rem; height: 3.5rem;
  display: grid; place-items: center;
  background: var(--panel-active);
  border: 1px solid var(--rule);
  border-radius: var(--r-s);
  color: var(--ink);
}
.glyphs svg { width: 1.75rem; height: 1.75rem; }

/* The eight accessibility features, on the plate's own grid — the device
   recurs rather than appearing once, which is the other half of stopping the
   page read as three aesthetics stitched together. These cells carry no
   drawing, so they stay on the stage's own ink and need no paper. */
.a11y {
  list-style: none; margin: var(--sp-8) 0 0; padding: 0; max-width: none;
  display: grid; gap: 1px;
  grid-template-columns: 1fr;
  background: var(--rule);
  border: 1px solid var(--rule);
  border-radius: var(--r-m);
  overflow: hidden;
}
@media (min-width: 40rem) { .a11y { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 64rem) { .a11y { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.a11y > li {
  margin: 0; max-width: none;
  background: var(--paper);
  padding: var(--sp-5) var(--sp-4);
}
/* On a stage the tokens above already resolve to the launch ground; on a paper
   page they resolve to paper. One rule, both grounds. */
.stage .a11y > li { background: var(--rail); }
.a11y h3 { margin: 0 0 var(--sp-2); font-size: var(--fs-body); }
.a11y p { margin: 0; font-size: var(--fs-s); color: var(--ink-secondary); }

/* ----------------------------------------------------------------- notice */

.notice {
  background: var(--panel);
  border: 1px solid var(--caution);
  border-left: 4px solid var(--caution);
  border-radius: var(--r-m);
  padding: var(--sp-5);
  display: flex; gap: var(--sp-4); align-items: flex-start;
  max-width: var(--measure);
}
.notice svg { flex: none; width: 1.75rem; height: 1.75rem; color: var(--caution); }
.notice h3 { margin: 0 0 var(--sp-1); }
.notice p { margin: 0; font-size: var(--fs-s); color: var(--ink-secondary); }

/* -------------------------------------------------------------- the close */

.cta-rule { position: relative; }
.cta-rule::before { top: 0; }

/* ------------------------------------------------------------------ footer */

.foot {
  background: var(--rail); color: var(--on-rail-2);
  border-top: 1px solid var(--rail-line);
  padding: var(--sp-10) 0 var(--sp-12);
  font-size: var(--fs-s);
}
.foot a { color: var(--on-rail); display: inline-flex; align-items: center; min-height: 44px; }
.foot ul { list-style: none; padding: 0; display: flex; gap: 0 var(--sp-5); flex-wrap: wrap; }
.foot li { margin: 0; }
.foot p { color: var(--on-rail-2); }
.foot :focus-visible { outline-color: #C2A4F8; }

/* ---------------------------------------------------------- reduced motion */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  /* The light and the hairlines stand still where they are rather than
     snapping to the end of a drift. The tiles keep their tilt: a tilt is a
     position, not a movement. */
  .stage::before, .stage::after,
  .masthead::after, .hero::after, .cta-rule::before { animation: none !important; }
}

/* ------------------------------------------------------------------- print */

@media print {
  body { background: #FFF; color: #000; }
  .stage, .run { background: #FFF; color: #000; }
  .stage::before, .stage::after { display: none; }
  .stage h1, .stage h2, .stage h3, .stage p, .stage .lede { color: #000; }
  .a11y > li { background: #FFF; }
  .masthead, .foot, .skip { display: none; }
  a { color: #000; }
}

/* ====================================================== the inner pages

   Accessibility, Suggest, Support, Privacy, Company details and the race
   landing page, brought onto the same aesthetic on 2026-09-17 rather than left
   on the old stylesheet: the same label register, the same hairline ruling, the
   same radius scale. Each one opens on the launch ground — masthead and title —
   and then runs on paper, which is the home page's own rhythm at one section's
   scale. Nothing here is a second design; it is the components those six pages
   already used, restated in this file's terms.
   ==================================================================== */

/* `.wrap-wide` was the wide measure on the old stylesheet. One measure now. */
.wrap-wide { max-width: 74rem; margin: 0 auto; padding: 0 var(--sp-5); }

/* A page's title block. On the home page it is inside a stage and takes the
   stage's padding; on an inner page it is the paper block under the masthead.
   Either way it closes with a gradient hairline — job 2. */
.hero h1 { margin-bottom: var(--sp-4); }
.stage .hero h1 { margin-bottom: var(--sp-6); }
.hero .lede { max-width: 46ch; }
.stage .hero .lede { max-width: 38ch; }

/* §10.9.1's original second use, kept: a short rule above a heading, carrying
   nothing. `aria-hidden` in the markup. */
.magic-bar {
  display: block;
  width: 4rem; height: 2px;
  border-radius: 2px;
  background: var(--magic);
  margin: 0 0 var(--sp-5);
}

/* Level-2 panel — §3.5. The thing being read: fill lifts, border firms, and the
   leading edge takes the arc's segment 2, which is §10.9.1's own structural
   role and is untouched by the amendment. */
.panel-focus {
  background: var(--panel-active);
  border: 1px solid var(--rule);
  border-left: 3px solid var(--marker);
  border-radius: var(--r-m);
  padding: var(--sp-6);
  margin: var(--sp-6) 0;
}
.panel-focus > :last-child { margin-bottom: 0; }
.panel-focus + .panel-focus { margin-top: var(--sp-4); }

/* A run of questions and answers. */
.qa { margin-top: var(--sp-10); }
.qa h3 { margin-top: var(--sp-10); }
.qa h3:first-of-type { margin-top: 0; }

/* Prose that opens a page, wider than a lede and not secondary. */
.lede-block { max-width: var(--measure); }
.lede-block > :last-child { margin-bottom: 0; }

/* A rendered app screen with its caption. The image carries width/height so
   nothing shifts as it arrives. */
.shots {
  list-style: none; margin: var(--sp-8) 0 0; padding: 0;
  display: grid; gap: var(--sp-4);
  grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr));
  max-width: none;
}
.shots > li, .shot {
  margin: 0; max-width: none;
  background: var(--panel);
  border: 1px solid var(--rule);
  border-radius: var(--r-m);
  padding: var(--sp-3);
}
.shot { margin: var(--sp-8) 0 0; }
.shots img, .shot img {
  display: block; width: 100%; height: auto;
  border-radius: var(--r-s);
}
.shots figcaption, .shot figcaption {
  font-size: var(--fs-s); color: var(--ink-secondary);
  padding: var(--sp-3) var(--sp-2) var(--sp-1);
}
.shots figcaption strong, .shot figcaption strong { color: var(--ink); display: block; }
.shot-tall img { max-width: 20rem; margin: 0 auto; }

/* The worked palette examples: a rendered cube, then the verdict in words.
   The `min(19rem, 100%)` floor is load-bearing: a bare 19rem track is wider
   than a 320px viewport's content box and pushed the page 4px sideways. */
.palettes {
  list-style: none; margin: var(--sp-3) 0 0; padding: 0;
  display: grid; gap: var(--sp-4);
  grid-template-columns: minmax(0, 1fr);
  max-width: none;
}
/* Six accepted on two rows of three, then the three refused on one — the
   owner's layout of 2026-09-18. Explicit columns rather than `auto-fit`, so a
   row is always three at desk width and the refused row never borrows a place
   from the accepted ones. Two across on a tablet, one on a phone. */
@media (min-width: 40rem) { .palettes { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 62rem) { .palettes { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.palettes-head { margin-top: var(--sp-8); }
.palettes > li {
  margin: 0; max-width: none;
  background: var(--panel-active);
  border: 1px solid var(--rule);
  border-radius: var(--r-m);
  padding: var(--sp-4);
  display: grid; grid-template-columns: 7rem 1fr; gap: var(--sp-4);
  align-items: start;
}
.palettes img { display: block; width: 100%; height: auto; }
.palettes h3 { margin: 0 0 var(--sp-1); font-size: var(--fs-body); }
.palettes p { margin: var(--sp-2) 0 0; font-size: var(--fs-s); color: var(--ink-secondary); }

/* The verdict: a word, then a rule under it — never colour alone. */
.verdict {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: var(--fs-xs); font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase;
  padding-bottom: 2px;
  border-bottom: 2px solid var(--rule-emphasis);
  color: var(--ink-secondary);
}
.verdict-refused { border-bottom-color: var(--caution); color: var(--caution); }

/* Where a feature lives, in the app's own words. The label register again. */
.where {
  display: block; margin-top: var(--sp-3);
  font-family: var(--font-mono); font-size: var(--fs-xs);
  letter-spacing: 0.06em; color: var(--ink-secondary);
}

/* A place in the app, written as a path — the owner, 2026-09-18: *"every time
   the site gives directions to a place in the app, set the whole path in a
   monospace code style."* `site/README.md`'s copy rules say when to use it.

       <code class="app-path"><span>Settings</span> › <span>Palette</span></code>

   One segment per `<span>`, and a segment never breaks: a long path wraps only
   at its separators, so "How a step advances" is never split across lines. The
   chip is the notation chip's ground and rule, on either ground, and its ink is
   the body's `--ink`, measured by the glyph pass like any other text.
   `box-decoration-break` gives a wrapped path a chip on each line rather than
   one chip torn in two. */
.app-path {
  font-family: var(--font-mono);
  font-size: 0.88em;
  letter-spacing: 0;
  color: var(--ink);
  background: var(--panel-sunken);
  border: 1px solid var(--rule);
  border-radius: 6px;
  padding: 0.08em 0.4em;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}
.app-path > span { white-space: nowrap; }
.stage .app-path { background: var(--rail-raise); }
/* Inside a `.where` line the label register already says "this is a place",
   so the path keeps the line's size rather than shrinking again. */
.where .app-path { font-size: 1em; }

/* ------------------------------------------------ the accessibility icons

   One small drawing per panel and per explanation section on
   `accessibility.html` — the owner, 2026-09-18. Drawn inline, 24 units square,
   in the mark's own line: one stroke weight, round caps and joins, no fill but
   the odd dot. `currentColor` in ink, never the arc (README: colour never
   becomes an icon), `aria-hidden` because the heading beside each one already
   says what it says. Each sits in a tile cut from the same sheet as the
   index's glyph chips. */
.icon {
  display: grid; place-items: center;
  flex: none;
  width: 2.5rem; height: 2.5rem;
  margin: 0 0 var(--sp-3);
  background: var(--panel-active);
  border: 1px solid var(--rule);
  border-radius: var(--r-s);
  color: var(--ink);
}
.icon svg {
  width: 1.5rem; height: 1.5rem;
  fill: none; stroke: currentColor; stroke-width: 1.75;
  stroke-linecap: round; stroke-linejoin: round;
}
.icon svg .dot { fill: currentColor; stroke: none; }
.icon-lg { width: 3rem; height: 3rem; margin-bottom: var(--sp-4); }
.icon-lg svg { width: 1.75rem; height: 1.75rem; }
.a11y .icon { background: var(--panel); }

/* Prose one side, a panel the other. */
.split { display: grid; gap: var(--sp-8); margin-top: var(--sp-6); }
@media (min-width: 56rem) {
  .split { grid-template-columns: 1.1fr 1fr; align-items: start; }
}

/* An inner page's sections want air between them without a band each. */
main > .run > .wrap > section,
main > .run > .wrap-wide > section { margin-top: var(--sp-12); }
main > .run > .wrap > section:first-of-type,
main > .run > .wrap-wide > section:first-of-type { margin-top: 0; }

/* The eyebrow is retired in favour of `.label`; the rule stays so a page that
   still carries one does not lose its type. Both resolve to the same thing. */
.eyebrow, .kicker { }
.eyebrow, .kicker {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--ink-secondary);
  margin: 0 0 var(--sp-4);
  max-width: none;
}

/* ------------------------------------------------------------------ price */
/* support.html's price line, added 2026-10-02. Hidden until prices.js finds the App Store's
   prices in prices.json; the three currency buttons are the visitor's choice. */

.price { margin: 0 0 var(--sp-4); }
.price p { margin-bottom: var(--sp-2); }
.price-choices { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.price-choice {
  font: inherit; font-weight: 600; line-height: 1.2;
  min-height: 2.75rem; min-width: 3.5rem; padding: 0.5rem 1rem;
  border-radius: 999px; border: 2px solid var(--rule-emphasis);
  color: var(--ink); background: transparent; cursor: pointer;
}
.price-choice[aria-pressed="true"] { background: var(--panel-active); }
.price-choice:hover { text-decoration: underline; text-decoration-thickness: 2px; }
/* A glyph beside every button (W449): a tick on the chosen currency, a ring on the others. */
.price-choice::before { content: "\25CB\00a0"; content: "\25CB\00a0" / ""; }
.price-choice[aria-pressed="true"]::before { content: "\2713\00a0"; content: "\2713\00a0" / ""; }

/* ----------------------------------------------------------- the home page, 29 October 2026 */
/* Learn and Play as equals (W443): 2 halves of one width, the same parts in the same order. */
.pair { display: grid; gap: var(--sp-16); }
@media (min-width: 56rem) { .pair { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-12); } }
.pair-half .figure { margin: var(--sp-6) 0 var(--sp-6); }
.pair-half .figure img { max-width: 12rem; }
/* Arranging the puzzles (W454): the words beside the app's puzzles page at desk width. */
@media (min-width: 56rem) {
  /* The words sit at the middle of the picture, not pinned to its top over an empty column (the layout
     rule for a picture beside text, 30 October 2026). */
  .arrange { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 22rem); gap: var(--sp-12); align-items: center; }
  .arrange .arrange-shot { margin-top: 0; }
}
.arrange-shot img { max-width: 22rem; margin: 0 auto; }
