/* grai.ls — site stylesheet
   Implements the "Black Label" colour system and the "Provenance" type system
   (docs/12-design-system.md, docs/13-typography-mark.md).

   The content site is light-first for reading and SEO; the product app is
   dark-first. Both run the same tokens, so this file is the system's first
   implementation. Token names here are canonical — do not rename locally.

   Four rules that fail silently if broken:
   - --rule is DECORATIVE ONLY (1.28:1 light / 1.31:1 dark). Functional
     boundaries use --rule-strong.
   - A gold CTA on light REQUIRES --cta-edge. The fill alone is 2.28:1 on paper
     and is conformant only because the keyline supplies the boundary at 5.49:1.
   - Azeret Mono's variable default weight is 100, NOT 400. Every rule that uses
     it must set font-weight explicitly or it renders Thin.
   - Literata's slashed zero lives only on the tabular set (zero.tf -> its slashed
     alternate), so "zero" does nothing unless "tnum" is on too. Always both.

   Fonts are self-hosted because the Google Fonts CDN serves `tnum` but strips
   `zero`, `onum`, `lnum`, `case`, `smcp` and `c2sc` from the upstream binaries.
   Rebuild with tools/fonts/build-fonts.py; regenerate the mark with
   tools/fonts/build-mark.py. Those scripts live in tools/ because fontTools is
   a build dependency and must not ship inside the binary; this directory is
   embedded into it and does.
*/

/* ---------- Faces ---------- */
/* Literata: opsz is PINNED at build and never animated. Its tabular advance is
   weight-stable (0.582 em at every weight) but NOT optical-size stable — it
   drifts to 0.620 em at opsz 7 — so a live numeric column must never ride the
   axis. font-optical-sizing: none enforces that. */
@font-face {
  font-family: "Literata Read";
  src: url("/fonts/literata-read.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Literata Read";
  src: url("/fonts/literata-read-italic.woff2") format("woff2");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}
/* The display face. It is not a third voice added for variety -- it is the one
   the reference design is drawn in, and the reason a heading set in Literata at
   the same size reads sturdy where the design reads quiet: Cormorant has high
   stroke contrast, a small x-height and long extenders, and Literata is a screen
   reading face that deliberately has none of those. It sets DISPLAY only --
   headings, the rail's rows, the field, card and row titles -- and never a
   paragraph, which is still Literata's job and still the argument
   docs/13-typography-mark.md makes about reading. */
@font-face {
  font-family: "Cormorant Display";
  src: url("/fonts/cormorant-display.woff2") format("woff2");
  font-weight: 300 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Cormorant Display";
  src: url("/fonts/cormorant-display-italic.woff2") format("woff2");
  font-weight: 300 400;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: "Azeret Mono";
  src: url("/fonts/azeret-mono.woff2") format("woff2");
  font-weight: 400 800;
  font-style: normal;
  font-display: swap;
}
/* The Label step's face, and the only grotesque in the tree.
   IT SHIPPED FOR THE FOOTER AND NOW CARRIES EVERY TRACKED MICRO-LABEL on every
   surface -- eyebrows, table headers, chips, small uppercase controls -- which
   is a wider scope than the sentence that first justified it. That sentence
   still holds and is still the clearest case: a mono advance at 11px tracked
   .14em reads as terminal output, and a copyright line and a disclosure
   sentence are the two things on the site that must not.
   THE LINE IT DOES NOT CROSS is machine data. Identifiers, grades, cert
   numbers, set codes, figures in a column and code stay in --mono, and so does
   any figure sitting inside one of these labels -- see "figures inside a label"
   at .tally b. Two reasons, and the second is not a preference: this face is
   subset to 400-500 and Azeret to 400-800, so a bolded figure would be
   synthesised; and tabular-nums and the slashed zero exist in Azeret's feature
   set and not in this one. docs/13-typography-mark.md carries the amendment. */
@font-face {
  font-family: "Instrument Sans";
  src: url("/fonts/instrument-sans.woff2") format("woff2");
  font-weight: 400 500;
  font-style: normal;
  font-display: swap;
}

:root {
  /* core colour */
  --ground:           #FAF8F4;
  --surface:          #FFFFFF;
  --raised:           #FFFFFF;
  --sunken:           #F1EFEB;
  --rule:             #E3DDD0;  /* decorative hairline only — 1.28:1 */
  --rule-strong:      #8A8272;  /* any functional boundary — 3.59:1 */
  --text:             #1A1610;  /* 16.98:1 on ground */
  --text-muted:       #665F52;  /* 5.96:1 on ground */
  --text-disclosure:  #4B463C;  /* 8.84:1 — FTC disclosure has its own floor */
  --accent:           #7E6114;  /* 5.49:1 — links, brand mark, brass rules */
  --plate:            #080706;  /* the rail. Theme-invariant by design */

  /* affiliate CTA */
  --cta-fill:         #C9A227;
  --cta-label:        #14110C;  /* 7.78:1 on fill */
  --cta-edge:         #7E6114;  /* required on light */

  --focus-inner:      var(--text);
  --focus-outer:      var(--ground);

  /* type */
  --serif: "Literata Read", Charter, Georgia, "Times New Roman", serif;
  /* Display only. The fallback is Literata rather than a system serif, because
     the one thing worse than the wrong display face is a heading that reflows
     to Georgia while the prose under it stays Literata. */
  --display: "Cormorant Display", "Literata Read", Charter, Georgia, serif;
  --mono:  "Azeret Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  /* Every tracked micro-label, and the foot. Its fallbacks are the design's own
     -- Helvetica, then Arial -- rather than a system-ui stack, because a
     grotesque is what has to arrive if the webfont does not, and system-ui on a
     Mac is a humanist. */
  --sans:  "Instrument Sans", Helvetica, Arial, sans-serif;
  --measure: 68ch;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ground:           #0F0D0A;
    --surface:          #17140F;
    --raised:           #201C16;
    --sunken:           #0B0A08;
    --rule:             #2C2720;  /* 1.31:1 */
    --rule-strong:      #6F675A;  /* 3.48:1 */
    --text:             #F3EFE6;  /* 16.91:1 */
    --text-muted:       #A69C8B;  /* 7.16:1 */
    --text-disclosure:  #C4BAA8;  /* 10.10:1 */
    --accent:           #D9B65E;  /* 9.98:1 */
    --cta-fill:         #C9A227;
    --cta-label:        #14110C;
    --cta-edge:         transparent;  /* fill is 8.02:1 here; no keyline needed */
  }
}

:root[data-theme="dark"] {
  --ground:           #0F0D0A;
  --surface:          #17140F;
  --raised:           #201C16;
  --sunken:           #0B0A08;
  --rule:             #2C2720;
  --rule-strong:      #6F675A;
  --text:             #F3EFE6;
  --text-muted:       #A69C8B;
  --text-disclosure:  #C4BAA8;
  --accent:           #D9B65E;
  --cta-fill:         #C9A227;
  --cta-label:        #14110C;
  --cta-edge:         transparent;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--text);
  font-family: var(--serif);
  font-optical-sizing: none;      /* opsz is pinned at build — never animate it */
  font-size: 18px;
  line-height: 1.7;
  /* Prose gets PROPORTIONAL figures. Tabular is for columns that must align and
     for values that change in place — applying it to running text is a
     typographic error, and the slashed zero reads as technical notation in a
     sentence. Both are switched on per-context by .tabular below. */
  font-variant-numeric: proportional-nums;
  -webkit-text-size-adjust: 100%;
  /* The reference sets this on its own body and this stylesheet never did,
     which is the single largest reason everything here rendered heavier than
     the design at the same nominal weights: on macOS the default is subpixel
     antialiasing, which fattens every stem. It is a rendering hint, not a
     weight -- nothing about the type changes, only how the rasteriser inks it,
     and on the plate especially (light on dark) the difference is a whole
     apparent step. */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Anything a machine recorded, or any figure that must hold a column: prices,
   grades, cert and serial numbers, countdowns, table data. Literata's slashed
   zero lives only on the tabular set, so the two features are inseparable. */
.tabular,
.price,
code,
th,
td {
  font-variant-numeric: tabular-nums slashed-zero;
  font-feature-settings: "tnum" 1, "zero" 1;
}

.price {
  font-weight: 600;
  font-size: 1.05rem;
}

/* ---------- Split Rail ----------
   A fixed rail carries identity, navigation and the standing disclosures; the
   content column scrolls beside it. The rail is the page's one dark surface,
   so it redefines the tokens it needs rather than hard-coding literals — which
   means the wordmark mask, links and rules inside it all resolve correctly
   without a single rail-specific colour rule. */
.page {
  display: grid;
  /* 14.5rem is the reference design's 232px at a 16px root. Stated in rem
     rather than px so it still tracks a reader's own text size, which a fixed
     pixel rail does not. */
  grid-template-columns: 14.5rem 1fr;
  min-height: 100vh;
}

/* SUBPIXEL INSIDE THE RAIL, grayscale everywhere else. body (:191) sets
   -webkit-font-smoothing: antialiased to match the reference design, which
   lightens every stem; that is right for the paper column and it costs the
   rail's rows their crispness, because Cormorant's hairlines are the thinnest
   strokes on the site and they sit on the darkest ground. `auto` hands these
   rows back to the platform rasteriser. It is scoped to this element and
   inherits only to its own contents -- founder call, 2026-09-07.

   IT IS A HINT, NOT A GUARANTEE. Subpixel antialiasing is what the browser
   does when it can, and it cannot when the text sits on a composited layer or
   the display is HiDPI -- Chrome falls back to grayscale in both, and this
   rail is `position: sticky`, which is itself a compositing trigger. So the
   effect is real on a 1x display and absent on a 2x one, and nothing here
   breaks either way. */
.rail {
  -webkit-font-smoothing: auto;
  -moz-osx-font-smoothing: auto;
  --text:            #F3EFE6;   /* 17.5:1 on the plate */
  --text-muted:      #A69C8B;   /*  7.4:1 */
  --text-disclosure: #C4BAA8;   /* 10.5:1 — the disclosure keeps its own floor */
  --accent:          #D9B65E;   /* 10.4:1 */
  --rule:            #2C2720;
  background: var(--plate);
  color: var(--text);
  padding: 2rem 1.5rem 1.75rem;
  display: flex;
  flex-direction: column;
  gap: 1.15rem;
  position: sticky;
  top: 0;
  height: 100vh;
}

/* There is no global `a` rule in this stylesheet — links are coloured per
   region (main a, .site-footer a). The rail needs its own or its links fall
   through to the browser default, which on the plate is an unreadable blue and
   would take the FTC disclosure link with it. */
.rail a { color: var(--accent); }

/* ---------- A field with no catalogue behind it ----------
   templates/pages/field.html, drawn as asset/coming_soon.html draws it: the
   field name as a small accent eyebrow, a ring, the status at display size, one
   sentence, one act. Eleven pages use it.

   THE COLUMN FLEXES, NOT main. The asset centres its body with
   `min-height:100vh` on the section itself; here main is one row of .column,
   which is one cell of the .page grid, and a main that measured 100vh would
   push the footer a screen below the fold on every one of the eleven. The grid
   already stretches .column to the row's full height, so making THAT the flex
   parent and giving main `flex:1` centres the body in whatever height is left
   after the footer -- which is the thing the asset was expressing. */
.column:has(> .coming) { display: flex; flex-direction: column; }
/* max-width: none because `main` is capped at one measure for the nine
   prose pages, and this one centres a 35rem block in the full column instead.
   Same exemption main.home takes, and for the same reason. */
main.coming { max-width: none; }
main.coming {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding: 3.5rem 0 0;            /* the asset's 56px top; .column owns the 40 */
}
/* The h1 wearing the asset's eyebrow. It is the heading of record -- see the
   template's note on why the status is not -- and it is set as furniture,
   which is the whole trick: .lab's own dress, in accent rather than muted,
   because it is the page's subject rather than a label on something else. */
.coming-field {
  font-family: var(--sans);
  font-weight: 500;               /* stated: this face is subset to 400-500 */
  font-size: .6875rem;            /* the asset's 11px */
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0;
}
.coming-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  width: 100%;
  max-width: 35rem;               /* the asset's 560px */
  margin: 0 auto;
  padding: 3.75rem 0 7.5rem;      /* its 60px, and the 120px foot of the section */
}
/* Its 44px ring with a 10px dot, which is between .ring-lg and .ring-md. Set as
   the two custom properties the .ring rule already reads rather than as a fifth
   size class, because nothing outside this page asks for it. */
.coming .ring { --ring-size: 2.75rem; --ring-dot: .625rem; }
/* The status. A paragraph rather than a heading, at the size the asset sets its
   h2 -- 56px of Cormorant at 300, which is the display face this stylesheet
   already carries. */
.coming-title {
  font-family: var(--display);
  font-weight: 300;
  font-size: 3.5rem;
  line-height: 1;
  margin: 1.75rem 0 1.125rem;     /* its 28/18 */
  text-wrap: balance;
}
.coming-note {
  font-size: 1.25rem;             /* its 20px */
  line-height: 1.4;
  color: var(--text-muted);
  margin: 0;
  text-wrap: pretty;
}
/* The one word the asset emphasises inside that sentence: the field name, at
   full strength against the muted sentence around it. The identification line
   shares the sentence and NOT the emphasis -- it runs to a dozen words, and a
   dozen bold words is a second heading rather than a stress. */
.coming-note em { color: var(--text); }
.coming-act { margin: 2.25rem 0 0; }
/* The solid button, in the slot the asset gives its want-list form.
   INK-FILLED RATHER THAN GOLD-FILLED, which is the asset's own choice and also
   the one docs/12-design-system.md:85 allows: prohibition 7 rations gold by
   area, and this spends none of it until the hover. Both fill and label come
   from the token pair, so it inverts correctly on the dark theme instead of
   painting near-black on near-black. */
.coming .btn-solid {
  display: inline-block;
  font-family: var(--sans);
  font-weight: 500;
  font-size: .75rem;              /* its 12px */
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ground);
  background: var(--text);
  border-radius: 0;               /* cut square, like every button here */
  line-height: 1.55;
  padding: .75rem 1.125rem;       /* its 12px/18px */
  text-decoration: none;
  white-space: nowrap;
}
.coming .btn-solid:hover {
  background: var(--accent);
  color: var(--ground);
  text-decoration: none;
}
.coming-fine {
  font-size: .8rem;
  color: var(--text-muted);
  margin: 3rem 0 0;
}

@media (max-width: 43.75rem) {    /* the asset's 700px */
  main.coming { padding-top: 2rem; }
  .coming-body { padding: 2.5rem 0 5rem; }
  .coming-title { font-size: 2.75rem; }
}

/* ---------- The ring ----------
   A circle with a disc centred in it, at four sizes.

   THIS IS AN AMENDMENT, not an oversight. docs/13-typography-mark.md's Pair is
   "two solid convex shapes; no container, no ring, no stroke", and this is a
   container with a stroke around one of them. It is here by founder call of
   2026-09-05 and the doc records it. What has NOT changed is the identity: the
   symbol at /img/symbol.svg is still the Pair at its 2:1 ratio, still what the
   favicon and the schema.org Organization carry, and the wordmark is still the
   drawn one with its true-circle period. The ring is furniture that travels
   beside them.

   Drawn in CSS rather than as inline SVG -- the opposite of what partials.html
   does for the confidence discs, and for the opposite reason. Those carry
   meaning, need currentColor and a half-fill no border-radius can express, and
   are read by a script out of a <template>. This is a circle with a dot in it
   and nothing more, so a border-radius and a pseudo-element cost no markup per
   instance and no bytes in the DOM.

   Decorative in every instance: aria-hidden on the span, the dot in ::after, no
   text node anywhere. The lockup's accessible name stays the wordmark's own. */
.ring {
  --ring-size: 1rem;
  --ring-dot: .25rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: var(--ring-size);
  height: var(--ring-size);
  border: 1.5px solid currentColor;
  border-radius: 50%;
}
.ring::after {
  content: "";
  display: block;
  width: var(--ring-dot);
  height: var(--ring-dot);
  border-radius: 50%;
  /* The dot is the accent in every instance. It is the one place the ring
     carries the identity's gold, and it is why the ring reads as ours rather
     than as a generic target icon. */
  background: var(--accent);
}
.ring-lg { --ring-size: 3rem; --ring-dot: .75rem; }   /* the design's 48/12 */
.ring-md { --ring-size: 1.375rem; --ring-dot: .375rem; }
.ring-sm { --ring-size: 1rem; --ring-dot: .25rem; }

/* The lockup: ring, then wordmark, on one baseline. */
/* The design's lockup: a 48px ring, 14px of air, and the wordmark at a 54px
   cap. The wordmark is drawn rather than typeset (build-mark.py), so it is
   sized by its box rather than by font-size -- 9.6rem wide at the mark's own
   3.096 aspect is the same optical size as the design's 54px setting. */
.lockup { display: flex; align-items: center; gap: .875rem; }
/* Sized by its box, because the mark is drawn rather than typeset -- and the
   box is ascender-to-descender, so it always sets a smaller letter than the
   same number of rem in type would.

   MEASURED against the design rather than judged by eye, because judging it by
   eye is what got it wrong: the design's logotype is 115x54 at a 1440 window
   and this was 154x70, a fifth too large in both directions. That was the whole
   of the band being 15px too deep and everything in it sitting too low.
   3.375rem is its 54, and 7.36rem is that height at the cut's 2.181 aspect --
   both numbers move when build-mark.py reports a new one, which it prints on
   every run. */
/* THE BOX IS THE INK AND THE INK IS NOT SYMMETRIC. build-mark.py fits the
   viewBox to the drawn outline rather than to nominal metrics, so the mark's
   box runs from the top of the l's ascender to the bottom of the g's
   descender -- 745 units above the baseline and 300 below, on a 1045 box. The
   band of ink a reader reads as "the word" is the x-height, baseline up to 396,
   and its centre sits at 547/1045 = 52.34% of the box rather than at 50%.
   Centring the BOX therefore hangs the word 2.34% of its own height high, which
   is 1.26px at this size. translateY takes its percentage from the element's
   own height, so this stays correct if the mark is ever resized. */
.lockup .wordmark { width: 7.36rem; height: 3.375rem; transform: translateY(-2.34%); }
@supports not ((-webkit-mask-image: url("")) or (mask-image: url(""))) {
  .lockup .wordmark { font-family: var(--display); font-size: 3rem; font-weight: 400; width: auto; height: auto; }
}

/* The nav's head, and the rule under the standing disclosures: the two pieces
   of furniture that turn a column of links into an index. The head sits in the
   rail's gutter while the rows it heads are full-bleed, which is what makes the
   rows read as rows. */
/* The index's head: name at one end, the count of rows at the other. The
   figure counts THIS LIST and nothing else -- twelve rows, twelve -- which is
   the only count on the rail and the only one that cannot be read as a claim
   about coverage. */
.rail-head {
  /* 32px above and 12px below, in the rail's own 24px gutter -- the design's
     fields-head, which is what sets the index's whole vertical rhythm.

     THE 12 BELOW IS PADDING, and the negative margin beside it EXACTLY cancels
     the rail's flex gap so that padding is the whole of the separation. The
     note that stood here said never to do this: a negative bottom margin was
     once used to claw back that gap, it clawed back more than the gap was
     worth, and the first nav row rode up over the head and struck a line
     through it. What makes this safe is that the two numbers are the same
     number -- -1.15rem against `gap: 1.15rem` nets to zero, and every pixel of
     separation then comes from the positive padding below. The invariant to
     keep if either is ever edited: this margin must equal the rail's gap
     negated, and the padding must be what you actually want to see. Measured
     after the change at 12.0px, head bottom to first row top. */
  margin: 1.25rem 0 -1.15rem;
  padding: 0 0 .75rem;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
}
/* Under a band the lockup and tagline are hidden (:740), so this is the rail's
   first visible child and its top margin would sit on top of the rail's own
   32px padding -- 52px of air where the design has 32. The margin exists to
   separate the head from the tagline, and with no tagline there is nothing to
   separate it from. */
.topbar ~ .rail .rail-head { margin-top: 0; }
/* .rail-scoped, so (0,2,0) beats `.lab` at :1590. A bare `.rail-head` here is
   (0,1,0), ties .lab and LOSES on source order -- .lab is 1100 lines further
   down and would hand this back its .6rem and .15em. The same one-class-short
   mistake the rail's sub-list and the field status both made. */
.rail .rail-head {
  font-size: .625rem;           /* the design's 10px */
  letter-spacing: .2em;
  /* Its own, not body's 1.7. At this size that inherited 17px line box put
     three extra pixels under the word before the 12 below it even started. */
  line-height: 1.4;
}
.rail-head span:first-child { color: var(--accent); }

/* The row the reader is on, or Trading cards when they are not on one.
   IT IS A CURRENT STATE NOW and the note here used to say it could never be
   one -- "the rail renders on seventeen pages and knows which of them it is on
   for none of them". rail.go changed that: a page that is one of the twelve
   declares it and the highlight moves, and aria-current rides along wherever
   the row's href really is the document being read. A page outside the index
   declares nothing and the mark falls where it always did, on the field that
   has a catalogue, which is why the name still fits. */
.site-nav a.field-indexed {
  color: var(--text);
  border-left-color: var(--accent);
  background: rgba(201, 167, 92, .1);
  /* The border eats a pixel of the gutter, so the row's text would jog left by
     one against every row above and below it. The design compensates in the
     same place and by the same amount. */
  padding-left: calc(1.5rem - 1px);
}

/* The games, under the field that holds them. A size step down and an indent,
   because a game is a division of a field rather than one more of them, and
   the rail has to show that difference without a second head. */
/* A wrapped strip, not five more rows. As full-height rows the games added
   about 184px to a twelve-row index and read as five more fields; as a strip
   they read as what they are -- divisions of the row above. Every href
   survives, so browse.go:19's "the nav never loses a door" is untouched. */
.field-sub {
  display: flex;
  flex-wrap: wrap;
  gap: 0 1rem;
  padding: .35rem 1.5rem .8rem;
}
/* .site-nav is in the selector deliberately. `.field-sub a` and `.site-nav a`
   are both (0,1,1), they are both ancestors of these links, and `.site-nav a`
   is declared LATER in this file -- so it won every property they share and the
   indent that makes a game read as a division of a field silently did nothing.
   Weight, not source order: this file is 2,000 lines and its blocks move. */
.site-nav .field-sub a {
  font-family: var(--display);
  font-size: 1.02rem;
  color: rgba(243, 239, 230, .55);
  text-decoration: none;
  padding: .1rem 0;
  border: 0;
}
.site-nav .field-sub a:hover { color: var(--accent); background: none; }

/* The account control, which is not a field and no longer sits in the list of
   them. The design's own rail keeps its non-field links in a foot block set in
   tracked caps, and this is that block. */
/* The rail's foot, in the design's treatment: a rule at a twelfth of the cream,
   24px above, 14px between rows, and each row a name at one end with its value
   at the other. */
.rail-acct, .rail-foot {
  display: flex;
  flex-direction: column;
  gap: .875rem;
  margin: .9rem 0 0;
  padding-top: 1.5rem;
  border-top: 1px solid rgba(243, 239, 230, .12);
}
/* Sign out is a <button> in a <form> in both navs, because the act is a POST
   (signin.go: a sign-out on GET can be fired by an image tag). Every rule that
   dresses a row here has to name the button too, or the one row that ends the
   session is the one row that looks like nothing. The form is display:contents
   so the button stays a flex item of the nav and keeps the column's gap --
   otherwise the form becomes the item and the gap lands on the wrong box. */
.rail-acct form, .acct-menu form { display: contents; }
.rail-acct button, .acct-menu button {
  background: none;
  border: 0;
  /* The UA gives a button padding-block:1px as well as a border, and zeroing
     only the border left the row 2px taller than the link beside it. */
  padding: 0;
  width: 100%;
  cursor: pointer;
  text-align: left;
  /* line-height is the one font property a button does NOT inherit -- the UA
     sheet pins it to `normal` -- so without this the row measured 17px against
     the link's 20.4px, with every other property already identical. Both
     numbers measured; the menu's own padding rule below still overrides the
     zero above, which is why only the rail's row needs it. */
  line-height: inherit;
}
.rail-acct a, .rail-acct button, .rail-foot a {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  font-family: var(--sans);
  font-weight: 500;
  font-size: .75rem;              /* the design's 12px */
  letter-spacing: .14em;
  text-transform: uppercase;
  color: rgba(243, 239, 230, .7);
  text-decoration: none;
}
.rail-acct a:hover, .rail-acct button:hover, .rail-foot a:hover { color: var(--text); }
/* The value at a row's end takes the accent -- the one place the design spends
   gold in the rail below the index. */
.rail-foot a span:last-child { color: var(--accent); }
/* Content column. WCAG 1.4.8 caps the measure at 80 characters; Atilgan &
   Legge put the floor at 13. --measure sits at 68. */
.column {
  min-width: 0;                 /* let long words wrap instead of widening the grid */
  padding: 0 2.5rem;
}
main { max-width: var(--measure); }
/* THE FOOTER IS NOT CAPPED, and it was until asset/footer.html's 2026-09-06
   revision made the cap untenable. It used to share main's one measure so its
   hairline did not overrun the prose column. The asset's foot is a three-track
   grid whose two ends are equal `1fr`, so each end gets exactly half the
   leftover -- and at 68ch the links (four of them, tracked caps, ~415px in
   Azeret Mono) needed more than their half and broke onto a second line, which
   also pulled the centred mark off the row's own middle. The asset's footer
   spans its page and has no cap at all; ours does now, on all eighteen. The two
   `+ .site-footer` exemptions below went with it -- they were exempting it from
   a rule that no longer exists. */
.site-footer { max-width: none; }

/* ---------- The front door's width ----------
   main is one measure wide (68ch) because eight of the nine content pages are
   prose. The homepage is not: a masthead, a strip of chips, a worked example
   and three lists of rows. It widens itself to the product's own 60rem (.wrap
   below) and pins every paragraph it carries back to the measure, so a
   sentence never runs 960px wide while a row may. The footer widens with it,
   or its rule stops short of the rows above it. Any paragraph added inside
   <main class="home"> without one of these classes will run to 60rem. */
/* No cap. main is one measure wide on the nine prose pages and the front door
   is not prose -- it was capped at 60rem AND flushed left, so every viewport
   wider than about 1200px painted a column of bare ground down the right edge
   of everything below the band: 208px of it at 1440, 536 at 1728. The design
   has no such column, its paper runs to the window. Every SENTENCE on the page
   is still pinned to the measure by the rules below, which is the thing the cap
   was actually protecting. */
main.home { max-width: none; }
.home h1, .home h2,
.home .lede, .home .illus, .home .curated-fine, .home .layer,
.home .fold p, .home .absent { max-width: var(--measure); }
/* The question, in the display register the section heads now use: larger and
   lighter than the product screens' h1, which is 2.15rem at 700 (:320) and was
   the heaviest thing on a page whose whole idea is quiet. */
/* The reference design's scale, in its own face. The numbers are larger than
   the ones they replace and the type is not: Cormorant sets roughly a size step
   smaller than Literata at the same px, so 2.7rem of Literata and 3.1rem of
   Cormorant are about the same mark on the page. Weight 300 through the display
   steps -- it is a display face and 400 is its text weight. */
.home h1 {
  margin-top: 2.25rem;
  font-family: var(--display);
  font-size: 3.1rem;
  font-weight: 300;
  letter-spacing: -.005em;
  line-height: 1.06;
}
/* Section heads on the front door are set larger and lighter than the product
   screens' h2, because on this page a heading is the only thing separating one
   list of rows from the next -- there are no cards, no rules and no colour
   between them. The weight drops as the size rises, which is the scale's own
   rule (docs/13-typography-mark.md:41) and what keeps 1.85rem from reading as
   a shout. Bare `h2` so the pinned tags -- <h2>Live matches</h2> and the rest
   -- keep exactly the attributes the tests quote, which is none.

   :not(.lab) IS LOAD-BEARING. The worked example's eyebrow is an <h2> that
   wears .lab -- it is section 3's heading and has to be one for heading
   navigation -- and `.home h2` (0,1,1) beats `.lab` (0,1,0), so without the
   guard "Worked example" renders at 1.85rem in the display serif instead of
   at .6rem in tracked mono. Any future micro-label that is also a heading
   wants the same exemption. */
.home section { padding-bottom: 1.5rem; }
.home h2:not(.lab) {
  font-family: var(--display);
  font-size: 2.5rem;          /* the design's 40px section head */
  font-weight: 300;
  letter-spacing: 0;
  line-height: 1.1;
  margin: 3.25rem 0 .5rem;
}
/* The line beside a section head is the design's note: display italic, muted,
   on the heading's baseline. Recorded as an amendment in
   docs/13-typography-mark.md rather than left contradicting the italic rule. */
.home .curated .tally,
.home .curated .tally b {
  font-family: var(--display);
  font-style: italic;
  font-weight: 400;
  font-size: 1.15rem;
  letter-spacing: 0;
  text-transform: none;
}
/* The tally under "Grails we are watching" is that section's own subtitle, so
   it sits on the heading's baseline rather than under it -- the band's
   section-head, built from the two elements the section already had. */
.home .curated { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: baseline; }
/* Both exclusions are needed, and the reason is specificity rather than taste:
   `.home .curated > *:not(.tally)` is (0,3,0) and `.home .curated > h2` is
   (0,2,1), so the wildcard WINS and the heading spans both tracks unless it is
   excluded here too. With the heading spanning, the tally overlaps it and
   parks at the top of the row -- a caption floating a heading's height above
   the heading it captions, which is what this looked like. */
.home .curated > *:not(.tally):not(h2) { grid-column: 1 / -1; }
.home .curated > h2 { grid-column: 1; }
/* The top margin is the h2's own (:233), not zero: baseline alignment shifts
   the two boxes into agreement only once they start together, and a tally with
   no margin sits a whole heading-margin above the line it is captioning. */
.home .curated .tally { grid-column: 2; grid-row: 1; margin: 3.25rem 0 0; }
@media (max-width: 46rem) {
  .home .curated { display: block; }
  .home .curated .tally { margin: .35rem 0 1.25rem; }
}
/* Sized against the 18px body, not the 16px root: a lede is the one line
   read before the prose, and it must not be smaller than the prose. */
.home .lede { font-size: 1.2rem; line-height: 1.5; max-width: 58ch; margin: 0 0 1rem; }
/* Section rhythm. Each section states its own top margin and its heading
   drops the Display step's 2.75rem, or the two stack into 5rem of air. */
.home section > h2:first-child,
.home .fold > h2:first-child { margin-top: 0; }
.home .matches { margin-top: 2.5rem; }
/* The scope line's try chips, as one unit that wraps together. */
.scope .tries { display: inline-flex; gap: .5rem; white-space: nowrap; }

/* ---------- The masthead band ----------
   The front door is the one page that emits THREE grid children: a topbar, the
   rail, the column. It spans both tracks, so grid auto-placement puts it in an
   implicit first row and drops the rail and the column into the second — which
   is why nothing here needs grid-template-rows, and why the other sixteen
   pages, which emit two children, are untouched by every rule in this block.
   Each one is anchored on `.topbar` or on `.topbar ~ .rail`, and nothing else
   in the tree matches either.

   Together with the rail it is a single L of plate: the band runs the full
   width and turns down the left edge. That is the whole of the layout idea,
   and it is why the topbar's left track is the rail's width exactly.

   THE TOPBAR IS THEME-INVARIANT, like the rail and for the same reason
   (docs/12-design-system.md:134 — the product is dark-first and the content
   site light-first off one token set, and the plate belongs to neither). It
   therefore redeclares the same local tokens the rail does at :164, plus the
   three the rail never needed: a light field sits inside this one. */
.topbar {
  grid-column: 1 / -1;
  --text:            #F3EFE6;   /* 17.5:1 on the plate */
  --text-muted:      #A69C8B;   /*  7.4:1 */
  --accent:          #D9B65E;   /* 10.4:1 */
  --rule:            #2C2720;
  --rule-strong:     #6F675A;   /*  3.5:1 — functional edges on the plate */
  background: var(--plate);
  color: var(--text);
  display: grid;
  /* auto, not the rail's width. A fixed first track held the identity column
     open to 232px whatever the lockup measured, so the field began 55px right
     of where the design puts it and lost that width off its own end. The design
     has no track here at all -- its band is a flex row and the field takes what
     the logo leaves -- and `auto` is that behaviour in a grid. The lockup still
     LANDS on the rail's edge, because the mark and the wordmark measure to it;
     it is no longer held there by a number. */
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  column-gap: 2rem;
  padding: 1.5rem 2.5rem 1.5rem 1.5rem;
}
/* The pair sits in the left track, over the rail beneath it, at the position
   the rail's own pair occupies on every other page — so the identity does not
   move when a visitor walks from /privacy.html to the front door. */
/* ONE ROW, and the second one was a ghost. The finder and the account act used
   to declare `grid-row: 1 / span 2`, which pushed the panel to row 3 and left
   row 2 holding nothing -- except that a spanning item distributes its excess
   height across every row it covers, so row 2 measured 1.19px whenever the
   panel was hidden, which is always until someone types. The lockup was placed
   in row 1 ALONE, so `align-items: center` centred it on a row 1.19px shorter
   than the row the field was centred on, and the pair sat half of that high.
   Measured, not reasoned: `grid-template-rows` computed to `54px 1.1875px`.

   With one row the panel auto-places into row 2 on its own -- it already
   declares `grid-column: 2 / -1` and needs no row of its own to find -- and the
   three things in the band share one centre line by construction.

   The two rules this replaces named .wordmark and .tagline. Both were dead:
   every band in the tree wraps the mark in a .lockup, so the grid item is the
   lockup and neither selector had matched anything since it was written. */
.topbar .lockup { grid-column: 1; }
.topbar .finder { grid-column: 2; }
/* The auth slot, and it is a FIXED WIDTH on purpose.
   The band's third track is `auto`, so it measured whatever the auth control
   measured, and the middle track is minmax(0, 1fr) -- which means the finder
   absorbed the difference. "Sign in" and the Vault chip are not the same width,
   so the search field changed size the moment a visitor signed in: the one
   element on the band that must not move, moving on login. Pinning the track
   costs the finder a fixed 136px it was never entitled to keep, and neither
   state can shift it now. Both children take the whole slot, so the button
   edges line up between the two states as well. */
.topbar-act {
  grid-column: 3;
  width: 8.5rem;                /* the design's 136px */
  display: flex;
}
.topbar-act > * { width: 100%; }
/* .cta-outline is inline-block and centres its text on its own line box; as a
   full-width slot child it needs telling. */
.topbar-act > .cta-outline { text-align: center; }

/* One wordmark per document. The rail's is hidden rather than branched away in
   content-chrome.html, because that template serves seventeen pages and this
   is one of them; display:none takes it out of the accessibility tree too, so
   there is one "grai.ls" link and one descriptor, not two of each. */
/* The LOCKUP, not its children. Hiding .wordmark and .tagline individually left
   the wrapper standing, and once the wrapper grew a ring of its own that ring
   was all that was left of it -- a gold dot floating above the field index with
   nothing beside it. Hide the thing, not the parts of it. */
/* The design page draws its own rail and its own band, so the lockup is not
   duplicated there and must not be hidden. Scoped to the front door's markup,
   where the rail's pair really is a second copy of the band's. */
.home:not(.mockup) ~ .rail .lockup,
.topbar ~ .rail .lockup,
.topbar ~ .rail .tagline,
/* The whole block, not just its links: hiding the anchors alone would leave an
   empty <nav> holding the rail's flex gap open above the disclosures. */
.topbar ~ .rail .rail-acct { display: none; }
.topbar ~ .rail .site-nav { margin-top: 0; }

/* The rail is sticky at height:100vh (:176). With a band above it, a viewport
   -tall box in the second grid row hangs its own height past the fold before
   the page has scrolled at all. align-self:start plus a min-height is the same
   picture without the overhang. */
.topbar ~ .rail {
  align-self: start;
  height: auto;
  min-height: 100vh;
}

/* ---------- The field ----------
   Paper on plate, and paper in BOTH themes: the band does not follow the
   theme, so neither may the thing sitting on it. --surface resolves to #17140F
   under the dark block (:85), which would put dark type on a dark field, so
   the two tokens the field needs are stated here as literals with their
   ratios, exactly as .rail (:164) states its four. */
.topbar .finder {
  --text:    #1A1610;           /* 17.0:1 on the field */
  --text-muted: #665F52;        /*  6.0:1 */
  /* The light theme's accent. Without it the ring's dot inherited the PLATE's
     #D9B65E and sat on paper at 1.83:1 -- a non-text contrast failure against
     docs/12-design-system.md, introduced when the ring replaced a magnifier
     that drew in currentColor and so never had the problem. */
  --accent:  #7E6114;           /*  5.5:1 */
  /* Restating the token is not enough: .topbar already resolved `color` to the
     plate's cream and every child inherits THAT, not the redeclared variable.
     The magnifier strokes in currentColor, so without this line it is drawn
     cream-on-paper and the field appears to have no icon at all. */
  color: var(--text);
  max-width: none;
  margin-top: 0;
}
.topbar .finder input {
  background: #FAF8F4;
  border: 0;
  border-radius: 6px;
  font-family: var(--display);
  font-weight: 400;
  font-size: 1.625rem;        /* the design's 26px field */
  line-height: 1.2;           /* 31px of content, so the field measures its 55 */
  /* THE PADDING IS ASYMMETRIC BY .12em, and that number is the face's, not a
     nudge. A browser centres an input's text on its CONTENT box, which puts the
     BASELINE where the metrics say -- and Cormorant's ascent and descent are
     924 and 287 against an x-height of 386, so the band of ink a reader
     actually sees sits (924 - 287 - 396) / 2 = 120.5 units, or .12em, BELOW the
     box it is centred in. At this size that is 3.1px, measured in a 3x render:
     the field's box centred on 51.5 and its placeholder's x-height band on
     54.4. Lifting the content box by .12em puts the ink where the box is. The
     total is unchanged, so the field still measures its 55. */
  padding: calc(.75rem - .12em) 1.25rem calc(.75rem + .12em) 3.1rem;
  /* The one elevation recipe on the site, and it exists to lift the field and
     its panel off the plate. Nothing else takes a shadow. */
  box-shadow: 0 10px 30px rgba(0, 0, 0, .35);
}
/* NOT `outline: 0`. The design the band comes from removes the focus ring on
   this input and on its close button; docs/12-design-system.md:53 requires the
   two-tone ring, and the field is the page's one control. The ring's outer
   tone is the ground it sits on, which here is the plate, not --ground. */
.topbar .finder input:focus-visible { box-shadow: 0 0 0 4px var(--plate); }
/* Italic and light, deliberately: the design's own placeholder. The reserved
   meaning of italic still holds for Literata's italic in prose (docs/13); this
   is a display face setting a prompt, which is not prose and cannot be read as
   emphasis inside a sentence. */
.topbar .finder input::placeholder { font-style: italic; font-weight: 300; }
/* The field's ring sits where the magnifier sat, on the same absolute geometry
   .finder-gl uses (:632) -- the input's left padding is cut for it and nothing
   else changes. pointer-events:none so a click on the mark still lands in the
   field, which is what the magnifier did and what a visitor expects. */
.topbar .finder-ring {
  position: absolute;
  left: 1.15rem;
  top: 50%;
  transform: translateY(-50%);
  pointer-events: none;
  --ring-size: 1rem;          /* the design's 16/4 */
  --ring-dot: .25rem;
  border-color: var(--text);
}

/* ---------- The finder's panel ----------
   The popup the combobox controls, on the same paper as the field and lifted
   the same way. It is hidden by ATTRIBUTE and never by a style — style-src is
   'self' with no 'unsafe-inline' (csp.go) and csp_readiness_test.go:151 fails
   on a style attribute — so static/firstrun.js toggles `hidden` and the one
   rule below is all the CSS that participates. */
/* THE PANEL DECLARES A WHOLE LIGHT TOKEN SET, not just a colour.
   Setting `color` alone is what the field taught: .topbar has already resolved
   every token to its plate value, and the rows inside here are built from
   .results, .miss and .lab rules that read --text, --text-muted, --accent,
   --rule, --rule-strong and --sunken by name. With only `color` set, the miss
   sentence renders in the plate's #A69C8B on white -- 1.9:1, unreadable -- and
   the bold card name inside it in the plate's near-white, which is invisible.
   The ratios below are the light theme's own (:56-66), stated rather than
   inherited because this surface does not follow the theme. */
.topbar .finder-panel {
  grid-column: 2 / -1;
  --text:         #1A1610;   /* 17.0:1 on the panel */
  --text-muted:   #665F52;   /*  6.0:1 */
  --accent:       #7E6114;   /*  5.5:1 — the match highlight */
  --rule:         #E3DDD0;   /* decorative hairline between rows */
  --rule-strong:  #8A8272;   /*  3.6:1 — functional edges */
  --surface:      #FFFFFF;
  --sunken:       #F1EFEB;   /* the row hover and focus well */
  color: var(--text);
  margin-top: 1rem;
  border-radius: 6px;
  overflow: hidden;
  background: var(--surface);
  box-shadow: 0 10px 30px rgba(0, 0, 0, .35);
}
/* Inside the panel the list is not the bordered card it is on /search: the
   panel carries the edge, the radius and the shadow for it. .results keeps its
   class regardless — `.results .meta` (:727) beats bare `.meta` (:1104) by
   descendant specificity alone, and dropping the class three screens away
   restyles a feed row. */
.topbar .results {
  margin: 0;
  max-width: none;
  border: 0;
  border-radius: 0;
  background: transparent;
}
.topbar .results li + li { border-top: 1px solid var(--rule); }
/* The row is a flex line with the trail pushed right by margin-left:auto
   (:733). In a 34rem column that works; in the panel's third column it does
   not, and the card's NAME is what gets squeezed -- "Umbreon ex" broke across
   two lines while the set and number sat comfortably beside it. Letting the
   line wrap moves the trail under the name instead, still right-aligned,
   which is the one of the two that can afford to move. */
.topbar .results button { flex-wrap: wrap; row-gap: .15rem; }
/* The miss is a different KIND of thing from a row -- a sentence about the
   search rather than an answer to it -- so the line above it is the functional
   one, per docs/12-design-system.md:86. It draws only when there is a sentence
   to separate; #miss is present and empty on every other keystroke. */
.topbar #miss:not(:empty) { border-top: 1px solid var(--rule-strong); }
/* ---------- The outline button ----------
   Gold as an EDGE rather than as a fill, which is how the same page can carry
   three of them without becoming docs/12-design-system.md:85's gold-striped
   page: prohibition 7 rations gold by AREA, and a keyline is a fraction of the
   area a fill is. The one fill on the front door stays with the pitch at the
   fold, which is the action the page is actually asking for. On hover it
   inverts to the fill, so the affordance is unambiguous at the moment of use.
   .cta and not .cta-outline is what home_test.go:64 forbids on a signed-in
   render, and this is deliberately neither that class nor inside that
   section. */
.cta-outline {
  display: inline-block;
  font-family: var(--sans);
  font-weight: 500;               /* stated: this face is subset to 400-500 */
  font-size: .75rem;            /* the design's 12px */
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 0;             /* its button is cut square like its cards */
  line-height: 1.55;            /* 19px of content, so the button measures its 37 */
  padding: .5625rem 1rem;       /* its 9px/16px */
  text-decoration: none;
  white-space: nowrap;
}
/* ---------- The account menu ----------
   The design's signed-in control, at the band's right-hand end. A <details>
   rather than a button and a script -- content-chrome.html carries why.

   .acct AND NOT .account. That name is already the BODY class of the auth and
   admin screens (:2439 -- display:grid, place-items:center, min-height:100vh),
   same specificity and 1400 lines further down, so it won every property this
   control set: the disclosure became a viewport-tall grid and its absolutely
   positioned menu resolved against the initial containing block, landing 847px
   down the page. Measured, not guessed. The whole family is prefixed so the
   generic halves -- an avatar, a chevron -- cannot collect a second meaning
   either.

   Its colours are the band's own tokens rather than the design's literals: the
   topbar redeclares --accent as #D9B65E (10.4:1 on the plate) where the mock
   writes #C9A75C, and using the literal here alone would make this the one gold
   on the band that does not match the Sign in button beside it. */
.acct { position: relative; }
/* The marker goes: the chevron below is the disclosure's affordance, and two of
   them is one too many. ::-webkit-details-marker for the browsers that ignore
   `list-style` on a summary. */
.acct > summary { list-style: none; }
.acct > summary::-webkit-details-marker { display: none; }

/* THE CHIP CARRIES .cta-outline TOO, and shares its box rather than restating
   it. The brief asks for "identical visual language to the Sign in button", and
   two copies of a border, a padding, a size and a tracking are identical only
   until someone edits one of them. Everything that makes the box is inherited;
   this rule adds only what a label-plus-chevron needs that a word does not. */
.acct-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .625rem;                 /* the design's 10px */
  cursor: pointer;
}
/* The hover DOES diverge, and this is the one place it should.
   .cta-outline:hover inverts -- solid gold, dark label -- which is right for a
   button you press and leave. This one stays lit for as long as the menu is
   open, and a solid gold block under an open menu reads as an error rather than
   a state. A 10% wash carries "open" without shouting it, and open and hover
   look the same because they are the same condition to a reader.
   The colour reset is not optional: .cta-outline:hover sets `color: var(--plate)`
   at equal specificity, so without this line the label goes dark on a dark
   wash and disappears. */
.acct-btn:hover,
.acct[open] .acct-btn {
  background: rgba(217, 182, 94, .1);
  color: var(--accent);
}
.acct-chev {
  width: .375rem; height: .375rem;   /* the design's 6px */
  border-right: 1px solid currentColor;
  border-bottom: 1px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
  transition: transform .15s;
  flex: none;
}
.acct[open] .acct-chev { transform: translateY(1px) rotate(-135deg); }
@media (prefers-reduced-motion: reduce) { .acct-chev { transition: none; } }
.acct-menu {
  position: absolute; right: 0; top: calc(100% + .625rem);
  min-width: 15rem; z-index: 20;
  background: var(--plate);
  border: 1px solid var(--accent);
  padding: .375rem 0;
  box-shadow: 0 14px 40px rgba(0, 0, 0, .45);
}
/* No justify-content: space-between here. It would be the right rule the day an
   item carries a figure on its right -- a want-list count is the obvious one --
   and that is exactly why it is absent. The rail carried an empty <span
   class="count"> on all twelve rows for the same reason and the founder took it
   out on 2026-09-07: an empty slot is invisible, so nothing distinguishes "held
   open for a figure" from "forgotten". The figure comes first, then the rule. */
.acct-menu a, .acct-menu button {
  display: flex; align-items: center;
  padding: .625rem 1.125rem;
  font-family: var(--sans); font-weight: 500; font-size: .75rem;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--text-muted); text-decoration: none;
}
.acct-menu a:hover, .acct-menu button:hover,
.acct-menu a:focus-visible,
.acct-menu button:focus-visible { color: var(--text); background: rgba(243, 239, 230, .05); }
/* Sign out is set apart, because it is the one item that ends the session
   rather than going somewhere. */
.acct-menu .acct-signout {
  margin-top: .375rem; padding-top: .875rem;
  border-top: 1px solid var(--rule);
  color: var(--accent);
}
.acct-menu .acct-signout:hover { color: var(--accent); }

.cta-outline:hover {
  background: var(--accent);
  /* The plate, not --cta-label: this button inverts against whatever dark
     ground it sits on, and both of its homes -- the band and the want-list
     card -- are the plate. */
  color: var(--plate);
  text-decoration: none;
}

/* ---------- The finder panel's three columns ----------
   The shape the design uses: a list of places on the left, the terms in the
   middle, what the search answered on the right with the want-list under it.
   The two dividers are --rule-strong because a column boundary is functional
   (docs/12-design-system.md:86, prohibition 8) -- --rule is 1.28:1 and would
   put three columns behind an invisible line. */
.topbar .finder-panel {
  display: grid;
  grid-template-columns: 13rem 15rem minmax(0, 1fr);
  align-items: stretch;
}
/* WITHOUT THIS THE PANEL IS ALWAYS OPEN.
   `hidden` is an attribute, and the rule enforcing it lives in the UA sheet as
   the bare selector `[hidden]{display:none}`. Any author rule that sets
   `display` on the same element outranks it -- (0,2,0) here against (0,1,0)
   there -- so the moment this panel became a grid it stopped being hideable,
   on every render, with no error anywhere. firstrun.js is still setting the
   attribute correctly; the stylesheet was ignoring it. Every component in this
   file that is toggled by attribute and also given a display needs this pair. */
.topbar .finder-panel[hidden] { display: none; }
.panel-col, .panel-attrs { border-right: 1px solid var(--rule-strong); min-width: 0; }
.panel-col { padding: 1.25rem 0 1.5rem; display: flex; flex-direction: column; }
.panel-col > .lab { padding: 0 1.25rem .7rem; }
/* Serif, because every one of these is a game -- a thing a person collects,
   which is the side of docs/13-typography-mark.md:9's line Literata is for. */
.panel-item {
  display: block;
  font-family: var(--display);
  font-size: 1.3rem;
  color: var(--text);
  text-decoration: none;
  padding: .55rem 1.25rem;
  border-top: 1px solid var(--rule);
  border-left: 2px solid transparent;
}
.panel-item:hover, .panel-item:focus-visible {
  background: var(--sunken);
  border-left-color: var(--accent);
  color: var(--accent);
}

.panel-attrs { padding: 1.25rem 1.25rem 1.5rem; display: flex; flex-direction: column; gap: 1.05rem; }
.panel-attrs > .lab { margin-bottom: -.35rem; }
.panel-group { display: flex; flex-direction: column; gap: .2rem; min-width: 0; }
/* A reading, not a control: no border, no pointer, nothing that offers a
   click. The design's own version of this column is a row of <span>s wired to
   a click handler, which is a filter a keyboard cannot reach and a tab stop
   that goes nowhere. These filter nothing yet and say so by looking like type. */
.panel-val {
  font-family: var(--display);
  font-size: 1.2rem;
  line-height: 1.3;
  color: var(--text);
  text-wrap: pretty;
}

.panel-results { padding: 1.25rem 1.25rem 1.25rem 1.4rem; display: flex; flex-direction: column; gap: 1.1rem; min-width: 0; }
/* The rows sit at the top of their column and the card under them, so the
   panel does not change height as answers arrive and leave. */
.panel-results .results { flex: 0 0 auto; }

/* The want-list card: the panel's floor, and the plate's fourth and last
   appearance on the page. Theme-invariant like every other plate surface, so
   it restates the same four local tokens .rail does at :164. */
.wantlist {
  --text:       #F3EFE6;   /* 17.5:1 on the plate */
  --text-muted: #A69C8B;   /*  7.4:1 */
  --accent:     #D9B65E;   /* 10.4:1 */
  background: var(--plate);
  color: var(--text);
  border-radius: 6px;
  padding: 1.1rem 1.2rem 1.25rem;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: .5rem;
}
.wantlist .lab { color: var(--accent); }
.wantlist-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; width: 100%; }
/* Cream ring, gold dot -- the ring takes the card's ink and the dot keeps the
   accent, which is the contrast the design draws on a dark card. */
.wantlist-head .ring { color: var(--text); }
.wantlist-line { margin: 0; font-family: var(--display); font-size: 1.6rem; line-height: 1.12; }
.wantlist-note { margin: 0; font-size: .82rem; line-height: 1.5; color: var(--text-muted); }

@media (max-width: 900px) {
  /* One column. The dividers become the separators between stacked blocks,
     which is the same functional boundary turned ninety degrees. */
  .topbar .finder-panel { grid-template-columns: minmax(0, 1fr); }
  .panel-col, .panel-attrs {
    border-right: 0;
    border-bottom: 1px solid var(--rule-strong);
  }
  .panel-col { padding-bottom: 1rem; }
}

/* Inside the panel the sentence is a last row, not a card: the dashed edge and
   the radius .miss carries on /search would be a box drawn inside a box. */
.topbar .miss {
  max-width: none; margin: 0; border: 0; border-radius: 0;
  padding: .8rem 1.1rem;
}

/* The three children are PLACED, not auto-flowed. Auto-placement gets the
   desktop right on its own, but the 900px block below re-declares .page's rows
   for a one-column grid, and a `1fr` row meant for the content column lands on
   the rail the moment a third child exists. Stating all three here means the
   two grids differ by their track lists alone. */
.topbar          { grid-row: 1; }
.topbar ~ .rail  { grid-row: 2; grid-column: 1; }
.topbar ~ .column { grid-row: 2; grid-column: 2; }
.page:has(> .topbar) { grid-template-rows: auto 1fr; }

@media (max-width: 900px) {
  /* One column, so the band stacks: pair, then field, then panel. The rail
     follows it as a strip of links and the content column takes the rest --
     which is the row the 1fr has to land on, and the reason for the explicit
     placement above. */
  .page:has(> .topbar) { grid-template-rows: auto auto 1fr; }
  .topbar ~ .column { grid-row: 3; grid-column: 1; }
  .topbar ~ .rail { min-height: 0; }
  /* The pair and the account control share the first line; the field takes the
     whole of the second, which is the arrangement the design reaches at its own
     narrow breakpoint (`.field{order:3;flex-basis:100%}`).

     EVERY child is placed explicitly. .topbar-act keeps `grid-column: 3` from
     the desktop rule otherwise, and against a one-track template that opens an
     implicit third column -- which is what halved the field's width and clipped
     its placeholder mid-word. */
  .topbar {
    grid-template-columns: minmax(0, 1fr) auto;
    padding: 1.4rem 1.25rem 1.25rem;
  }
  .topbar .lockup { grid-column: 1; grid-row: 1; }
  .topbar-act     { grid-column: 2; grid-row: 1; align-self: center; }
  .topbar .finder       { grid-column: 1 / -1; grid-row: 2; }
  .topbar .finder-panel { grid-column: 1 / -1; grid-row: 3; }
  .topbar .finder { margin-top: 1.1rem; }
  /* The same .12em correction, restated because this rule restates the padding.
     It is in em, so it scales with the smaller face by itself. */
  .topbar .finder input { font-size: 1.1rem; padding: calc(.85rem - .12em) 1rem calc(.85rem + .12em) 2.6rem; }
  .topbar .finder-gl { left: 1rem; width: .95rem; height: .95rem; }
}

/* Visually hidden, read aloud: the bucket word beside a glyph that is
   aria-hidden, where the visible word is the row head above rather than on
   the row itself. */
.sr {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---------- Header and wordmark ---------- */
/* The header block now lives inside the rail; see Split Rail above. */

/* The wordmark is drawn, not typeset — the period is a true circle at 0.26 em
   and the i's tittle a circle at exactly half that diameter. It is masked so it
   inherits the theme's accent; the text stays in the DOM for assistive tech. */
.wordmark {
  display: inline-block;
  width: 4.15rem;
  height: 1.9rem;    /* aspect 2.181, per tools/fonts/build-mark.py */
  /* Cream, not gold. The whole word was painted in the accent, which made the
     lockup a solid gold logotype where the design has fine cream lettering
     with ONE gold mark in it. The period is that mark, added below. */
  position: relative;
  background-color: var(--text);
  -webkit-mask: url("img/wordmark.svg") no-repeat left center / contain;
  mask: url("img/wordmark.svg") no-repeat left center / contain;
  font-size: 0;
  color: transparent;
  text-decoration: none;
  vertical-align: middle;
}
/* The period, in gold, masked from its own asset on the SAME viewBox as the
   word, so the two register exactly. Two-tone without a second element in the
   markup and without an <img>, which could not follow a theme.

   THE COMPLIANCE READING, because this looks like it breaks a rule and does
   not. docs/12-design-system.md quotes the EPN agreement verbatim: a partner
   may not display branding "in an uneven, staggered, multi-color format that
   invokes the distinctive multi-color eBay logo". That is four saturated hues
   across four letters. This is a monochrome word with a single accent on its
   punctuation, and the same paragraph says in terms that "a single gold accent
   is expressly not what is prohibited". The house rule derived from it was
   stricter than its own source; docs/12 records the amendment. */
.wordmark::after {
  content: "";
  position: absolute;
  inset: 0;
  background-color: var(--accent);
  -webkit-mask: url("img/wordmark-dot.svg") no-repeat left center / contain;
  mask: url("img/wordmark-dot.svg") no-repeat left center / contain;
}

@supports not ((-webkit-mask-image: url("")) or (mask-image: url(""))) {
  .wordmark::after { display: none; }
  .wordmark {
    -webkit-mask: none;
    mask: none;
    background: none;
    color: var(--accent);
    font-size: 1.6rem;
    font-weight: 600;
    width: auto;
    height: auto;
    letter-spacing: .005em;
  }
}

/* The descriptor. The wordmark is never used alone in any context that
   produces metadata — it carries a locked descriptor, because typography
   cannot touch the Apache Grails collision on its own. */
.tagline {
  font-family: var(--sans);
  font-weight: 500;               /* stated: this face is subset to 400-500 */
  font-size: .66rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: .35rem 0 0;
}

/* Each row is a name and a figure on one baseline, which is the shape the
   design's index has. The figure is EMPTY on every row and that is deliberate,
   not unfinished: eleven of the twelve fields have no catalogue behind them, so
   any number in that slot would be invented, and CLAUDE.md's rule is to cut a
   claim rather than invent one. The column is here so the row is already the
   right shape the day a real figure exists for it. */
.site-nav a .name { min-width: 0; }
.site-nav a .count {
  font-family: var(--sans);
  font-weight: 400;
  font-size: .69rem;
  letter-spacing: .04em;
  color: var(--text-muted);
  flex: none;
}
.site-nav a.field-indexed .count { color: var(--accent); }

.site-nav {
  display: flex;
  flex-direction: column;
  margin-top: .35rem;
  /* Full-bleed rows: the negative margin is the rail's own side padding (:172)
     so a row's hover wash and its active edge run to the plate's edges rather
     than stopping in a gutter. The two numbers move together. */
  margin-left: -1.5rem;
  margin-right: -1.5rem;
}
/* A destination in this product is a THING A PERSON COLLECTS -- a game, a set
   -- so the nav is set in the serif that means exactly that
   (docs/13-typography-mark.md:9). It was mono uppercase, which is the face
   this stylesheet reserves for what a machine recorded, and it made a list of
   four games read as a list of four status codes. .lab still dresses the
   labels; nothing else in the rail changed face. */
.site-nav a {
  font-family: var(--display);
  font-weight: 400;
  font-size: 1.3rem;          /* the design's 21px rail row */
  line-height: 1.25;
  /* .75 alpha, not the full token. The design rests its rows at
     rgba(244,241,234,.75) and brings them to full cream on hover, and that
     three-quarter step is what gives the active row somewhere to be brighter
     THAN -- at full opacity every row competes with the one that is selected
     and the index reads flat. */
  color: rgba(243, 239, 230, .75);
  text-decoration: none;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  /* .5rem, not 1rem. The rail is 232px with a 24px gutter either side, so a row
     has 184px for a name and its figure; a 16px gap took enough of that to wrap
     "Sports memorabilia" onto a second line where the design keeps it on one,
     and one wrapped row makes the whole index a row taller than the design's.
     space-between already holds the two apart -- the gap is only a floor. */
  gap: .5rem;
  /* The design's rows are airier than the list they replaced, and at a display
     face's small x-height they have to be: 21px of Cormorant occupies about as
     much ink as 16px of Literata and needs the same room around it to read as a
     row rather than as a line of a paragraph. */
  padding: .69rem 1.5rem;     /* the design's 11px/24px row */
  /* No hairline. Seventeen of them turned an index into a ruled table; the
     design separates its rows by the space around them and marks exactly one
     with an edge. */
  border-left: 1px solid transparent;
  transition: background-color .15s;
}
/* The wash is the accent at a tenth, not a lighter grey: the rail declares its
   own accent (:167) and a hover that reached for --sunken would resolve
   against the page's theme rather than against the plate it is drawn on. */
.site-nav a:hover {
  color: var(--accent);
  background: rgba(217, 182, 94, .10);
  border-left-color: var(--accent);
}
@media (prefers-reduced-motion: reduce) { .site-nav a { transition: none; } }

/* Signing in is an account action, not another page of the site — and now that
   the nav is an index of fields it is not one of those either, so it left the
   list rather than sitting at the bottom of it looking like a thirteenth
   field. The rule that dressed it here is gone with it; .rail-acct below is
   where it lives and what styles it. */

/* ---------- Headings ---------- */
h1, h2, h3 { text-wrap: balance; }

h1 {
  font-size: 2.15rem;
  font-weight: 700;
  line-height: 1.18;
  letter-spacing: -.012em;
  margin: 2.75rem 0 1rem;
}

h2 {
  font-size: 1.4rem;
  font-weight: 600;
  line-height: 1.3;
  margin: 2.75rem 0 .75rem;
}

h3 {
  font-size: 1.12rem;
  font-weight: 600;
  margin: 2rem 0 .5rem;
}

main a { color: var(--accent); }

/* Focus — two-tone, because a single gold ring on a gold button is invisible */
a:focus-visible,
button:focus-visible {
  outline: 2px solid var(--focus-inner);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--focus-outer);
  border-radius: 2px;
}
.cta:focus-visible {
  outline: 2px solid var(--focus-inner);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--focus-outer);
  border-radius: 6px;
}

/* ---------- Article bodies ---------- */
/* WCAG 1.4.12: generous letter and word spacing is the intervention that
   actually replicates for dyslexia-adjacent legibility. Specialised dyslexia
   typefaces do not. */
.prose {
  max-width: var(--measure);
}

.prose p,
.prose ul,
.prose ol { margin: 0 0 1.2rem; }

.prose li { margin-bottom: .4rem; }

.prose strong { font-weight: 600; }

blockquote {
  margin: 1.75rem 0;
  padding-left: 1.25rem;
  border-left: 3px solid var(--accent);
  color: var(--text-muted);
}

hr {
  border: none;
  border-top: 1px solid var(--rule);
  margin: 2.75rem 0;
}

/* ---------- Guide listing ---------- */
.guide-list {
  list-style: none;
  padding: 0;
  margin: 1.75rem 0;
}

.guide-list li { margin: 0 0 1.4rem; }

.guide-list a {
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--accent);
  text-decoration: none;
}
.guide-list a:hover { text-decoration: underline; }

.guide-list p {
  margin: .3rem 0 0;
  color: var(--text-muted);
  font-size: .95rem;
}

/* ---------- Cards and callouts ---------- */
.card {
  background: var(--surface);
  border: 1px solid var(--rule-strong);
  border-radius: 6px;
  padding: 1.25rem 1.5rem;
  margin: 1.75rem 0;
}

/* ---------- Call to action ---------- */
.cta {
  display: inline-block;
  background: var(--cta-fill);
  color: var(--cta-label);
  border: 1px solid var(--cta-edge);
  padding: .72rem 1.5rem;
  border-radius: 6px;
  text-decoration: none;
  font-weight: 600;
  font-size: .95rem;
}
.cta:hover { text-decoration: underline; }

/* ---------- Landing sections ---------- */
.hero { margin: 2.5rem 0 1.75rem; }
.hero h1 { margin-top: 0; }
.what { margin: 2.25rem 0; }

/* ---------- Footer ---------- */
/* ---------- The foot ----------
   asset/footer.html: 26/40/28 of padding on a hairline, a row of colophon, mark
   and links in 11px tracked caps, and one paragraph of fine print under it. */
.site-footer {
  border-top: 1px solid var(--rule);
  padding: 1.625rem 0 1.75rem;
  color: var(--text-muted);
}
/* THREE TRACKS, NOT space-between, and this is the whole of what the asset
   changed in its 2026-09-06 revision. Under space-between the mark sits wherever
   the two end blocks leave it -- the colophon measures 20 characters and the
   four links measure 48, so the dot landed left of centre by half that
   difference and the row read as lopsided. `1fr auto 1fr` gives the two ends
   equal tracks and centres the mark on the FOOTER rather than between its
   neighbours, which is what makes the row balance. The ends then have to be
   pushed outward themselves: a grid item fills its track, so the links need
   justify-content to reach the right edge where flex put them for free. */
.foot-row {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 1rem;
  /* --sans, not --mono. The row was set in the machine-data face because that
     is what this tree substitutes wherever the mock says --sans; on eyebrows
     and table headers that holds, and here it did not. Azeret's uniform advance
     makes GRAI.LS · MMXXVI a fixed-pitch string, and the four links a column of
     them, so the row read as a status line rather than as a colophon. */
  font-family: var(--sans);
  font-weight: 400;               /* the asset's regular; Azeret needed 500 */
  font-size: .6875rem;            /* the design's 11px */
  letter-spacing: .14em;
  text-transform: uppercase;
}
.site-footer .colophon { margin: 0; }
.foot-links { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 1.375rem; }
/* No middots. The homepage mock ran these together in one span with separators;
   asset/footer.html sets them apart with a 22px gap and nothing between, and it
   is the later of the two. The gap is what separates them, so there is no
   character for a hover underline or a focus ring to wrap. */
.foot-links a {
  color: var(--text-muted);
  text-decoration: none;
}
.foot-links a:hover,
.foot-links a:focus-visible { color: var(--accent); text-decoration: underline; }
.site-footer .foot-mark { flex: 0 0 auto; color: var(--text-muted); }

/* The fine print: the copyright, the sourcing, the affiliate relationship and
   the non-affiliation, in one paragraph.

   THE CONTRAST CAME DOWN ONE STEP AND STOPPED. This used to read
   --text-disclosure (8.84:1) and argue that the size came down with the design
   and the contrast did not. It is --text-muted (5.96:1) now, so the paragraph
   reads lighter than the nav above it -- which is the order asset/footer.html
   sets and the last visible difference between the two feet once the face
   changed. It stops there and does not reach the asset's own --ink-45, which is
   2.93:1 and fails WCAG AA outright: 4.5:1 is the floor for a legal disclosure
   whatever a mock says, so the ORDER is the design's and the FLOOR is ours.
   docs/13-typography-mark.md records the step. */
.foot-fine {
  /* ONE FAMILY FOR THE WHOLE FOOT, and it is the asset's own. This paragraph
     used to inherit --serif off body while the row beside it was --mono, so the
     foot read as two blocks from two documents; the first fix made both --mono,
     which made them agree and made both wrong. Instrument Sans ships now (:68)
     and both halves are set in it.

     WHY IT MATTERS MOST HERE. This is the one running SENTENCE in the foot.
     Mono at 11px gives it a fixed advance, no kerning worth the name and a
     colour far heavier than the rule above it -- the paragraph came out darker
     and denser than the nav, which is the opposite of the order the design puts
     them in. A proportional grotesque at 400 sets the same words in fewer
     pixels and less ink. */
  font-family: var(--sans);
  font-weight: 400;
  /* Its 22px, up from 18: the row above it grew a track and needed the air. */
  margin: 1.375rem 0 0;
  /* The 820px cap is gone with the asset's revision -- the sentence runs the
     width of the foot now, on every page. The footer's own measure cap went
     with it in the same change (see .site-footer, "THE FOOTER IS NOT CAPPED"),
     so there is no longer a set of pages where this is narrower. */
  max-width: none;
  font-size: .6875rem;
  line-height: 1.6;
  /* --text-muted (5.96:1), down from --text-disclosure (8.84:1). The asset sets
     this paragraph LIGHTER than the row above it and this tree had it darker,
     which is the last thing between the two footers after the face changed.
     This is as far as it goes: the asset's own --ink-45 is 2.93:1 and fails
     WCAG AA outright, and 4.5:1 is the floor for a legal disclosure whatever a
     mock says. So the ORDER is the design's -- fine print lighter than nav --
     and the floor is ours. docs/13-typography-mark.md records the step down. */
  color: var(--text-muted);
}
/* The one link in the fine print keeps a real underline -- it is inside a
   sentence, where position marks nothing, and it is the route to the disclosure
   page that EPN asks the property to carry. */
.foot-fine a {
  color: var(--text-muted);
  text-decoration: underline;
  text-decoration-color: var(--accent);
  text-underline-offset: 3px;
}
.foot-fine a:hover, .foot-fine a:focus-visible { color: var(--accent); }

@media (max-width: 46rem) {
  /* The mark goes: a lone dot between two stacked blocks is furniture with
     nothing left to sit between.
     ONE TRACK, not the three above. The asset leaves `flex-wrap:wrap` in this
     block, which did something when the row was a flex container and does
     nothing now -- and hiding the mark under `1fr auto 1fr` would drop the
     links into the auto track and leave a whole 1fr of paper to their right,
     pinning them against the colophon. Stacked and flush left is the shape the
     rest of the site's narrow layouts take. */
  .foot-row { grid-template-columns: 1fr; gap: .875rem; }
  .site-footer .foot-mark { display: none; }
  .foot-links { justify-content: flex-start; gap: 1rem; }
}

/* Both rules are gone with the markup that used them: asset/footer.html merges
   the disclosure line and the colophon into one .foot-fine paragraph, styled
   above. The contrast half of what .disclosure-line protected survives there --
   --text-disclosure, unchanged; the size half did not, and docs/13 records it.

   .disclosure on match detail is a different rule and is untouched. That is the
   per-link disclosure, adjacent to the CTA, and it is the one 16 CFR 255.0(f)
   actually reaches. */

/* ---------- Tables ---------- */
/* Cell borders are functional structure, so --rule-strong. Figures are tabular
   and slashed-zero by inheritance from body. */
table {
  width: 100%;
  border-collapse: collapse;
  margin: 1.75rem 0;
  font-size: .95rem;
}

th, td {
  border: 1px solid var(--rule-strong);
  text-align: left;
  padding: .55rem .75rem;
  vertical-align: top;
}

th {
  background: var(--sunken);
  font-family: var(--sans);
  font-weight: 500;
  font-size: .76rem;
  letter-spacing: .06em;
  text-transform: uppercase;
}

/* Identifiers, grades, cert and serial numbers — anything a machine recorded */
code, .data {
  font-family: var(--mono);
  font-weight: 500;
  font-size: .88em;
}

/* ---------- Media ---------- */
img { max-width: 100%; height: auto; }

/* ---------- Narrow viewports ----------
   The rail stops being a rail and becomes a compact masthead. Everything it
   carries stays on the page — identity, navigation, and both disclosures. */
@media (max-width: 900px) {
  /* Two implicit auto rows share the grid's free space under min-height:100vh,
     so on a page shorter than the viewport -- or a tall screenshot window --
     the masthead grows to half the height. The rail is its content's height;
     the column takes the rest. */
  .page { grid-template-columns: 1fr; grid-template-rows: auto 1fr; }
  .home h1 { margin-top: 1.25rem; }
  .rail {
    position: static;
    height: auto;
    padding: 1.4rem 1.25rem;
    gap: .8rem;
  }
  /* A strip of links rather than a list of rows: the full-bleed edges, the
     hairlines and the active border are all row furniture and go with it. */
  .site-nav { flex-direction: row; flex-wrap: wrap; gap: 0 1.2rem; margin-left: 0; margin-right: 0; }
  .site-nav a { border-left: 0; padding: .2rem 0; font-size: .98rem; }
  .site-nav a:hover { background: none; }
  .site-nav a.field-indexed { background: none; border-left: 0; padding-left: 0; }
  /* A strip item, not a row: space-between belongs to a full-width row and
     here it stretched each link to the wrap width, pushing the name away from
     the empty figure slot and leaving two ragged columns. */
  .site-nav a { justify-content: flex-start; gap: .4rem; }
  .site-nav a .count { display: none; }
  .field-sub { flex-direction: row; flex-wrap: wrap; gap: 0 1.2rem; }
  /* .site-nav in the selector for the third time in this file, and the third
     time for the same reason: `.site-nav .field-sub a` (0,3,1) is declared
     above and a bare `.field-sub a` (0,2,1) here loses to it, so the strip kept
     the row's hairlines and indent. Weight, not media order. */
  .site-nav .field-sub a { border: 0; padding: .2rem 0; }
  .site-nav .field-sub a:hover { background: none; }
  .rail-acct { margin-top: .6rem; padding-top: .6rem; }
  .column { padding: 0 1.25rem; }
}

/* ---------- The first-run instrument ----------
   The homepage's opening screen is the product's own loop rather than a
   description of it: name the thing you want, see what would and would not
   reach you. Structure and behaviour live in firstrun.js; everything here is
   token-driven, so it holds in both themes.

   It is a plate: one bordered object on --surface, so on the first screen the
   worked example reads as the thing the page is about rather than as more
   text. Inside it, top to bottom: the card (art at identification scale, or
   its printed number in the same well), its identity, the terms as chips,
   and three OUTCOME ROWS -- glyph, count, word, then the cases that landed
   there -- in the showcase's own vocabulary. The reason each case landed
   where it did is one click away in a <details> dressed by the same
   .specimens rules the showcase and the grail page use. The three prose boxes
   this replaced explained the gate before showing it; the rows show it, and
   the gold fill their "alerts you" head carried is gone, so the signup CTA is
   the only gold fill on the page (docs/12-design-system.md, prohibition 7). */
.lab {
  font-family: var(--sans);
  font-weight: 500;               /* stated: this face is subset to 400-500 */
  font-size: .6rem;
  letter-spacing: .15em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* ---------- Micro-label compositions ----------
   .lab above is this stylesheet's most reused non-prose device. What follows
   are the three compositions of it that replaced running prose: a tally of the
   figures a section contains, a head for the row grids, and a stated absence.
   Each exists because a paragraph was describing something the markup beneath
   it already rendered. */

/* A tally. The register is set.html's header -- "N cards indexed · M in the
   printed set" -- figures in the machine face, the words naming them small
   enough to read as labels.

   Deliberately NOT merged with .scope, which looks similar: .scope carries
   <button> children that would inherit this uppercase and render "MOONBREON". */
.tally {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: .5rem;
  margin: .35rem 0 1.25rem;
  font-family: var(--sans); font-weight: 500; font-size: .66rem;
  letter-spacing: .11em; text-transform: uppercase; color: var(--text-muted);
}
/* Mono, though .tally around it is sans -- see "figures inside a label" below.
   700 is a real master in Azeret's 400-800 and would be synthesised bold in
   Instrument Sans, and the two feature settings on this rule exist in neither
   the sans's GSUB nor its GPOS. */
.tally b {
  font-family: var(--mono);
  color: var(--text); font-weight: 700; font-size: .95rem; letter-spacing: 0;
  font-variant-numeric: tabular-nums slashed-zero;
  font-feature-settings: "tnum" 1, "zero" 1;
}
.tally .sep { color: var(--rule-strong); }

/* A column head for the row primitives. No rule beneath it: --rule is
   decorative only at 1.28:1 and would be invisible, while --rule-strong reads
   as a control edge, which a caption is not. The mono uppercase separates it
   from the rows on its own. */
.colhead { display: grid; gap: 0 1rem; margin: 1.25rem 0 .35rem; padding: 0 0 .35rem; }
.colhead .lab { white-space: nowrap; }
/* Three tracks, matching .trend-row (:1439). There was a fourth, headed
   Collectors, and it went with the demand cell the rows carried: no count
   reaches the render path any more (internal/web/trending.go), so a head over
   that column would name a column that is not beneath it. */
.trending .colhead {
  grid-template-columns: 3.25rem minmax(0, 1fr) auto;
}
.colhead .c-ident { grid-column: 2; }
.colhead .c-fig   { grid-column: 3; text-align: right; }
/* Below this the row collapses to two columns and a column head would be
   pointing at nothing, so it goes rather than lying. */
@media (max-width: 34rem) { .colhead { display: none; } }

/* A stated absence. The dashed edge is this stylesheet's existing word for
   "nothing is here" -- .art-none, .miss, .empty and .blocked all use it -- so an
   empty collection reads as the same KIND of fact as a card with no art, rather
   than as an essay about what the tier is for. */
.absent {
  display: flex; align-items: baseline; gap: .75rem; flex-wrap: wrap;
  margin: 1.25rem 0 0; padding: 1.1rem 1.2rem;
  border: 1px dashed var(--rule-strong); border-radius: 4px;
  color: var(--text-muted);
}
.absent b { color: var(--text); font-weight: 600; }

.finder { position: relative; max-width: 34rem; margin-top: 1.4rem; }
.finder input {
  width: 100%;
  font-family: var(--serif);
  font-size: 1.05rem;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--rule-strong);
  border-radius: 3px;
  padding: .85rem 1rem .85rem 2.5rem;
  line-height: 1.4;
}
.finder input::placeholder { color: var(--text-muted); }
.finder-gl {
  position: absolute;
  left: .95rem;
  top: 50%;
  transform: translateY(-50%);
  width: .85rem;
  height: .85rem;
  opacity: .5;
  pointer-events: none;
}

/* The homepage's finder is the page's one control and is sized as one; the
   search page's stays at the shared 34rem. */
.home .finder { max-width: 40rem; margin-top: 1.25rem; }
.home .finder input { font-size: 1.15rem; padding: .95rem 1rem .95rem 2.6rem; }
.home .finder-gl { left: 1rem; width: .95rem; height: .95rem; }
.home .results, .home .miss { max-width: 40rem; }

/* The /search form reuses .finder whole -- the same relative box, the same
   2.5rem gutter, the same magnifier sitting in it -- so the control a visitor
   meets on the search page is the control they met on the homepage. What is
   new is only what a form needs and a typeahead does not: a game to scope to,
   and a button to submit with.

   The button is neutral rather than gold. docs/12-design-system.md rations
   gold by area and spends it on the one action a page is asking for, and on a
   search page that action is following a result, not resubmitting the form.
   The shape is button.pause's (:949), which is this stylesheet's existing
   answer to "a real button that is not the call to action". */
.search-form { max-width: 34rem; margin: 1.4rem 0 2rem; }
.search-form .lab { display: block; margin-bottom: .35rem; }
.search-form .finder { margin-top: 0; }
.search-controls { display: flex; align-items: center; gap: .6rem; margin-top: .7rem; }
.search-controls .lab { margin: 0; }
.search-form select {
  /* Sans with its two siblings. .search-controls is a flex row of three --
     the .lab, this, and the submit button -- and the other two moved. */
  font-family: var(--sans); font-weight: 500; font-size: .72rem; letter-spacing: .06em;
  color: var(--text); background: var(--surface);
  border: 1px solid var(--rule-strong); border-radius: 2px;
  padding: .45rem .6rem; cursor: pointer;
}
.search-form button[type="submit"] {
  font-family: var(--sans); font-weight: 500; font-size: .72rem; letter-spacing: .1em;
  text-transform: uppercase; cursor: pointer;
  background: transparent; color: var(--text);
  border: 1px solid var(--rule-strong); border-radius: 2px;
  padding: .45rem .85rem; margin-left: auto;
}
.search-form button[type="submit"]:hover { border-color: var(--accent); color: var(--accent); }

.scope { margin: .55rem 0 0; display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; }
/* The design's crumb: a gold eyebrow, then the path in muted type, on one
   baseline with 14px between them. Ours carries the same two things -- the
   vertical, then what is in it -- so it takes the same treatment rather than a
   second one that means the same. */
.home .scope {
  align-items: baseline;
  gap: .875rem;
  margin: 1.1rem 0 0;
  font-size: .78rem;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-muted);
}
.home .scope .lab { color: var(--accent); font-size: .69rem; letter-spacing: .22em; }
/* The count, in the data face: .lab around it is sans and 600 is past that
   face's ceiling. See "figures inside a label". */
.scope b { font-family: var(--mono); font-weight: 600; color: var(--text); }
.scope .sep { color: var(--rule-strong); }
.scope button {
  font: inherit;
  font-family: var(--mono);
  font-weight: 500;
  font-size: .66rem;
  letter-spacing: .06em;
  background: none;
  border: 0;
  padding: 0;
  color: var(--accent);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.results {
  list-style: none;
  margin: .6rem 0 0;
  padding: 0;
  max-width: 34rem;
  border: 1px solid var(--rule-strong);
  border-radius: 3px;
  overflow: hidden;
  background: var(--surface);
}
.results:empty { display: none; }
.results li + li { border-top: 1px solid var(--rule); }
.results button {
  width: 100%;
  text-align: left;
  font: inherit;
  background: none;
  border: 0;
  padding: .65rem .95rem;
  cursor: pointer;
  display: flex;
  align-items: baseline;
  gap: .7rem;
  color: var(--text);
}
.results button:hover,
.results button:focus-visible { background: var(--sunken); }
.results .nm { font-family: var(--display); font-weight: 400; font-size: 1.2rem; }
.results .meta {
  margin-left: auto;
  font-family: var(--mono);
  font-weight: 500;
  font-size: .66rem;
  color: var(--text-muted);
  white-space: nowrap;
  font-variant-numeric: tabular-nums slashed-zero;
}
.results mark { background: transparent; color: var(--accent); font-weight: 700; }

/* An honest miss says what we do index. It never promises future value. */
.miss {
  max-width: 34rem;
  margin: .6rem 0 0;
  padding: .8rem .95rem;
  border: 1px dashed var(--rule-strong);
  border-radius: 3px;
  color: var(--text-muted);
  font-size: .88rem;
}
.miss b { color: var(--text); font-weight: 600; }

/* ---------- The browse strip ----------
   The second door in the masthead, in both auth states. Three axes of VALUES
   rather than of facet names -- nobody clicks "Subject", they click
   "Charizard" -- each axis named by a .lab and each value a link that resolves
   today.

   Inline groups, not a hanging indent. The anchors are bare siblings of the
   .lab inside one <p class="facet">, which is why an earlier version indented
   each row 7rem rather than gridding it. An inline-flex group solves the same
   problem without the gutter or the ruled rows: the label and its values are
   flex items of the group, the groups are flex items of the nav, and a group
   that does not fit beside the one before it drops to the next line whole.
   What this buys is the first screen: below 46rem the strip is ONE
   horizontally scrolling rail instead of six stacked lines, so the worked
   example lands on a phone's first screen. The rail scrolls inside its own
   box -- the page body never scrolls sideways -- and keeps a thin scrollbar
   as the cue that it does; it bleeds into the column's 1.25rem padding so the
   clipped chip at the edge is the other cue.

   The three Sets chips carry the set's mark on the light plate the set tiles
   use (.set-tile .plate): light in both themes because marks are drawn for
   light grounds. The mark comes from the set store (browse.go); a chip the
   store cannot answer for is exactly the text chip it was. Characters and
   print classes are text on purpose. */
.facets { margin: 1.25rem 0 0; display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1.5rem; }
.facet { margin: 0; padding: 0; display: inline-flex; flex-wrap: wrap; align-items: center; gap: .4rem .35rem; }
.facets .lab { white-space: nowrap; margin-right: .25rem; }

/* Neither of this stylesheet's two existing chips would do here, and both
   failures are design-system rules rather than taste.

   NOT GOLD. Every unstyled link inside <main> is gold, because `main a` (:308)
   makes it so, and .scope's buttons (:627) are gold as well. Every value here
   would then be a gold mark -- thirteen of them in one glance -- which is
   precisely docs/12-design-system.md:85's "a dozen gold buttons visible at
   once reads as a gold-striped page and undoes the argument". `color:
   inherit` is the escape from that inherited rule, and it is the same escape
   .card-cell a, .set-tile > a and .game-link already perform.

   NOT MONO UPPERCASE, which is what .chip and button.try-grail are.
   docs/13-typography-mark.md:9 splits the two faces by what they describe:
   Azeret Mono sets what a machine recorded, Literata sets what a person
   values -- prose and card names. "Charizard", "Prismatic Evolutions" and
   "Special Illustration Rare" are card, set and print-class names, so they
   take the serif in the case they are written in.

   The keyline is what marks these as links, not the hue: at inherited ink they
   are the same colour as the text around them, and WCAG 1.4.1 wants a carrier
   that is not colour. .more a is the same construction. */
.facets a {
  display: inline-flex; align-items: center; gap: .4rem;
  font-family: var(--serif); font-size: .88rem; line-height: 1.35;
  color: inherit; text-decoration: none; white-space: nowrap;
  border: 1px solid var(--rule-strong); border-radius: 3px;
  padding: .18rem .5rem;
  /* The mark plate's height, so a chip with a mark and a chip without sit on
     one line at one height. */
  min-height: 1.86rem;
}
.facets a:hover,
.facets a:focus-visible { border-color: var(--accent); color: var(--accent); }
.facets a.set-chip { padding-left: .18rem; }
.facets .mark {
  display: grid; place-items: center;
  width: 3.25rem; height: 1.5rem; padding: .12rem;
  background: #f4f4f2; border: 1px solid var(--rule); border-radius: 2px;
}
.facets .mark img { max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; display: block; }

@media (max-width: 46rem) {
  /* Vertical padding inside the scroll box, so a chip's focus ring is not
     clipped by it; the margin gives the padding back. */
  .facets {
    margin: .4rem -1.25rem 0; padding: .35rem 1.25rem .35rem;
    flex-wrap: nowrap; overflow-x: auto; gap: 0 1.1rem;
    scrollbar-width: thin;
  }
  .facet { flex: 0 0 auto; flex-wrap: nowrap; }
}

.verdict {
  margin-top: 1.5rem;
  border: 1px solid var(--rule-strong); border-radius: 5px;
  background: var(--surface);
  padding: 1rem 1.15rem 1.1rem;
}
.verdict[hidden] { display: none; }

.verdict-head { display: grid; grid-template-columns: 4rem minmax(0, 1fr); gap: 0 1rem; align-items: center; }
/* Identification scale: 4rem is the feed's own 64px thumbnail -- "a thumbnail
   beside a row, never a hero" (docs/09-legal-trust.md:17). The well is
   --sunken and never tinted (prohibition 5). */
.verdict .art {
  display: grid; place-items: center;
  width: 4rem; aspect-ratio: 245 / 342;
  background: var(--sunken); border: 1px solid var(--rule); border-radius: 3px;
  overflow: hidden;
}
.verdict .art img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* No art -- a card chosen from the typeahead, whose rows carry none by design,
   or an opening card the catalogue holds no image for -- shows the printed
   number, the fallback every row on the site already uses. */
.verdict .art .art-none {
  font-family: var(--mono); font-weight: 500; font-size: .7rem; letter-spacing: .02em;
  color: var(--text-muted); text-align: center;
}
/* Both children toggle by the hidden attribute, and the display rules above
   would defeat it. Last on purpose: equal specificity, later wins. */
.verdict .art [hidden] { display: none; }

.verdict .verdict-eyebrow { margin: 0 0 .15rem; line-height: 1.4; }
.verdict-ident { margin: 0 0 .1rem; font-size: 1.3rem; font-weight: 700; letter-spacing: -.02em; line-height: 1.2; }
.verdict-trail {
  margin: 0;
  color: var(--text-muted);
  font-family: var(--mono);
  font-weight: 500;
  font-size: .72rem;
  letter-spacing: .05em;
  font-variant-numeric: tabular-nums slashed-zero;
  font-feature-settings: "tnum" 1, "zero" 1;
  text-wrap: pretty;
}
.verdict .plate-lab { margin: 1rem 0 .4rem; }

.defaults { display: flex; align-items: center; gap: .45rem; flex-wrap: wrap; margin: 0; }
.chip {
  font-family: var(--mono);
  font-weight: 600;
  font-size: .63rem;
  letter-spacing: .07em;
  border: 1px solid var(--rule-strong);
  border-radius: 2px;
  padding: .28rem .5rem;
  font-variant-numeric: tabular-nums slashed-zero;
  white-space: nowrap;
}
/* Every property the product's bare .note (below) sets is reset here, not just
   the two that showed: that rule gives a note a hairline and a padded top, and
   on this page it reached the instrument's over-ride label. */
.defaults .note {
  color: var(--text-muted); font-size: .79rem; line-height: 1.4;
  margin: 0 0 0 .15rem; padding: 0; border: 0;
}

/* Three rows, so the answer reads without being read. The push row is
   carried by picture, word and size -- two carriers before colour touches it.
   Picture, one word, the cases: no count, because the cases are the count,
   and a row with none says "none" in the quiet ink. */
.verdict .outcomes { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--rule); }
.verdict .outcome {
  display: grid; grid-template-columns: 1.1rem auto minmax(0, 1fr);
  gap: 0 .6rem; align-items: baseline;
  padding: .45rem 0; border-bottom: 1px solid var(--rule);
}
.verdict .outcome .g { display: inline-flex; align-self: center; color: var(--text-muted); }
.verdict .outcome.push .g { color: var(--accent); }
.verdict .outcome .word {
  font-family: var(--sans); font-weight: 500; font-size: .62rem; letter-spacing: .1em;
  text-transform: uppercase; color: var(--text-muted); white-space: nowrap;
  min-width: 4.4rem;
}
.verdict .outcome.push .word { color: var(--text); }
.verdict .outcome .cases { font-size: .88rem; line-height: 1.4; color: var(--text-muted); }
.verdict .outcome.push .cases { color: var(--text); font-weight: 600; }
.verdict .outcome.nil .cases { font-style: italic; }
.verdict .outcome.nil .word, .verdict .outcome.nil .g { color: var(--text-muted); }

.verdict .grail-detail { margin: .75rem 0 0; }

/* The way on: the section's one action, in .more a's keyline shape. Neutral,
   never gold -- the page's gold is the signup CTA. */
.verdict-act { margin: .9rem 0 0; }
.verdict-act[hidden] { display: none; }
.verdict-act .grail-open {
  display: inline-block; padding: .5rem 1rem;
  border: 1px solid var(--rule-strong); border-radius: 3px;
  color: var(--text); text-decoration: none; font-size: .9rem;
}
.verdict-act .grail-open:hover,
.verdict-act .grail-open:focus-visible { border-color: var(--accent); color: var(--accent); }

/* Kept for the showcase and the grail page, whose three-count rows share the
   class: the keyline each count sits in, and the glyph's ink (the push and
   never inks are set with the showcase rules below). */
.bucket {
  border: 1px solid var(--rule-strong);
  border-radius: 3px;
  background: var(--surface);
  overflow: hidden;
}
.bucket .g { display: inline-flex; align-self: center; color: var(--text-muted); }

@media (max-width: 34rem) {
  .verdict { margin-top: .75rem; padding: .85rem .9rem 1rem; }
  .verdict .plate-lab { margin: .5rem 0 .2rem; }
  .verdict .outcome .cases { grid-column: 2 / -1; }
}

/* The illustrative label sits next to what it qualifies, never in a footer,
   and keeps the disclosure token's contrast floor. */
.illus {
  margin: .8rem 0 0;
  color: var(--text-disclosure);
  font-size: .79rem;
  line-height: 1.5;
  max-width: 62ch;
}

/* The one dark card in the body, and the page's only call to action.
   It is the plate again -- the third and last appearance of the band, after
   the masthead and the rail -- so a visitor reads "this is grai.ls asking"
   rather than "this is another section". Theme-invariant, so it redeclares the
   four tokens the rail declares at :164 plus --cta-edge: the gold fill is
   8.02:1 on the plate and the keyline light needs (docs/12-design-system.md:80)
   would be a hairline around a button that does not want one. */
.pitch {
  margin-top: 2.5rem;
  --text:      #F3EFE6;   /* 17.5:1 on the plate */
  --text-muted: #A69C8B;  /*  7.4:1 */
  --accent:    #D9B65E;   /* 10.4:1 */
  --cta-edge:  transparent;
  background: var(--plate);
  color: var(--text);
  border-radius: 6px;
  padding: 1.75rem 1.85rem;
  display: flex;
  gap: 1.5rem;
  align-items: center;
  flex-wrap: wrap;
}
.pitch ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(10.5rem, 1fr));
  gap: .3rem 1.4rem;
  flex: 1 1 20rem;
}
.pitch li { font-size: .9rem; display: flex; gap: .5rem; align-items: baseline; line-height: 1.45; }
.pitch li::before { content: "\25C6"; color: var(--accent); font-size: .55rem; flex: 0 0 auto; }
.pitch-act { flex: 0 0 auto; }
.pitch-act .lab { display: block; margin-bottom: .5rem; }

/* ---------- The band ----------
   The design's two-column stretch: a list of rows against a narrower second
   column. 1.15fr to 1fr is its ratio, and align-items:start keeps the two
   columns' headings on one line instead of stretching the shorter one. */
.two-col { margin-top: 2.5rem; }
@media (min-width: 62rem) {
  .two-col {
    display: grid;
    grid-template-columns: 1.15fr 1fr;
    gap: 0 3rem;
    align-items: start;
  }
  /* The sections carry their own top margin for the single-column case; inside
     the band the wrapper owns it, or the two columns start at different
     heights. */
  .two-col > .trending, .two-col > .guides-col { margin-top: 0; }
}
.guides-col { margin-top: 2.5rem; }

/* In the band the guides are a ruled list, not the card grid they were at the
   fold: a narrow column of four cards would be four boxes one above another,
   which is a list drawn the expensive way. The hairline is the same one
   .trend-row uses two feet to the left, so the two columns read as one band. */
.guides-col .guide-list { list-style: none; margin: 1rem 0 0; padding: 0; }
.guides-col .guide-list li { margin: 0; }
.guides-col .guide-list a {
  display: block;
  padding: .8rem 0;
  border-top: 1px solid var(--rule);
  font-family: var(--display);
  font-weight: 400;
  font-size: 1.28rem;
  line-height: 1.2;
  color: var(--text);
  text-decoration: none;
  text-wrap: pretty;
}
.guides-col .guide-list a:hover { color: var(--accent); }

.fold { margin-top: 3rem; padding-top: 1.75rem; border-top: 1px solid var(--rule-strong); }

/* One column now. The fold held two headings -- what grai.ls is, and the
   guides -- and the guides moved up into the band, so the grid that paired
   them would be a two-column layout with one column of content in it. */
.home .fold h2 { margin-top: 0; }
.home .guide-list li { margin: 0 0 .8rem; }

/* The guides, as the band's card grid.
   Four titles in a column of accent-coloured links was four gold lines in a
   row, which is the gold-striped page docs/12-design-system.md:85 names. As
   cards the title carries itself in the serif and the accent is spent on the
   edge that appears when a card is pointed at -- one mark, on the one card
   being read. No art: there is no picture of a guide that would not be
   invented, and docs/09-legal-trust.md:17 keeps images at identification
   scale beside a row in any case. */
@media (min-width: 46rem) {
  .home .fold .guide-list {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: .75rem;
    margin: .5rem 0 0;
  }
}
.home .fold .guide-list li { margin: 0; }
.home .fold .guide-list a {
  display: block;
  height: 100%;
  border-radius: 0;
  font-family: var(--display);
  font-size: 1.35rem;
  font-weight: 400;
  line-height: 1.25;
  color: var(--text);
  text-wrap: pretty;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 6px;
  padding: 1rem 1.1rem 1.15rem;
}
.home .fold .guide-list a:hover {
  border-color: var(--accent);
  color: var(--accent);
  text-decoration: none;
}

@media (max-width: 34rem) {
  /* The phone's first screen: the sentence, the finder, the chip rail and the
     worked card with its first verdict. Every margin here is spent on that. */
  .home h1 { margin-top: .5rem; }
  .home .lede { font-size: 1rem; line-height: 1.45; margin-bottom: .5rem; }
  .home .finder { margin-top: 1rem; }
  .home .scope { margin-top: .4rem; line-height: 1.5; }
  /* 1.15rem clips the placeholder mid-word inside 350px. */
  .home .finder input { font-size: 1.05rem; }
  /* The trail wraps to three lines beside a 4rem well on a phone; a size step
     down keeps the card and its identity on the first screen. */
  .verdict-trail { font-size: .66rem; letter-spacing: .03em; }
}

@media (max-width: 900px) {
  .pitch ul { grid-template-columns: 1fr; }
}

/* ==========================================================================
   Product surfaces — the feed and match detail
   --------------------------------------------------------------------------
   Moved out of a Go string literal in internal/web/templates.go, which carried
   its own copy of the token block and its own @font-face rules. There were four
   :root declarations in this repository and only two of them declared the
   faces, so the account screens rendered in Georgia and nobody could see it in
   a diff. One stylesheet, one token block, one set of faces.

   Everything below WAS scoped to classes the content pages do not use, and
   that assumption is gone. templates/pages/index.html is now one document in
   both auth states, so the feed's row primitives -- .match, .row, .ident,
   .title, .meta, .chip, .conf, .price, .flag, .right, .blocked -- render into
   the same DOM as the content site's .verdict, .outcomes, .curated, .trend-row
   and .facets. Nothing about the tokens changed; what changed is that a bare
   selector in either half now reaches the other half on the front page.

   IT HOLDS TODAY BY DESCENDANT SPECIFICITY, AND ONLY BY THAT. Each of these
   beats the bare rule it shares a name with, and each is load-bearing:

     .trend-row .meta   over  .meta   -- else every browse row goes mono
                                         uppercase with a top margin
     .results .meta     over  .meta   -- the typeahead's right-hand column
     .trend-row .terms  -- the recognition line under a name; a bare .terms
                                         does not exist and must not
     .defaults .note    over  .note   -- the instrument's over-ride label
     .trend-row .ident  over  .ident  -- the grid that lays the row out

   Simplify any one of them into a bare selector because "it only appears in
   one place" and it restyles a section three screens away, with nothing in the
   diff to say which. Two bare selectors that DID reach across are already
   fixed and must stay scoped: h1 -> .detail h1, and .quiet -> p.quiet.
   ========================================================================== */

.wrap { max-width:60rem; margin:0 auto; padding:0 1.5rem 5rem; }

header.bar {
  display:flex; align-items:center; gap:1rem; flex-wrap:wrap;
  padding:1.25rem 0; margin-bottom:1.5rem;
  border-bottom:1px solid var(--rule-strong);
}
.brand { font-size:1.35rem; font-weight:700; letter-spacing:-0.01em; text-decoration:none; color:var(--text); }
.brand .dot { color:var(--accent); }
.spacer { flex:1 1 auto; }
/* An account control, not a call to action: the product is the feed, and a
   loud Sign in competing with it would be selling the wrong thing. */
a.navlink { color:var(--text-muted); text-decoration:none; font-family:var(--sans);
  font-weight:500; font-size:0.68rem; letter-spacing:0.09em; text-transform:uppercase;
  padding:0.4rem 0.65rem; border:1px solid var(--rule); border-radius:2px;
  white-space:nowrap; }
a.navlink:hover { color:var(--text); border-color:var(--rule-strong); }
a.navlink:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }

/* Live state is a word plus a mark, never a colour alone. */
.live { font-family:var(--sans); font-weight:500; font-size:.7rem; letter-spacing:.14em;
  text-transform:uppercase; color:var(--text-muted); display:flex; align-items:center; gap:.5rem; }
.live .pip { width:.5rem; height:.5rem; border-radius:50%; background:var(--accent); }
@media (prefers-reduced-motion:no-preference) {
  .live[data-state="live"] .pip { animation:pulse 2.4s ease-in-out infinite; }
}
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:.35} }
.live[data-state="paused"] .pip { background:var(--text-muted); }

button.pause {
  font-family:var(--sans); font-weight:500; font-size:.72rem; letter-spacing:.1em;
  text-transform:uppercase; cursor:pointer;
  background:transparent; color:var(--text); border:1px solid var(--rule-strong);
  border-radius:2px; padding:.5rem .85rem;
}
button.pause:hover { border-color:var(--accent); color:var(--accent); }
:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }

.lede { color:var(--text-muted); margin:0 0 1.75rem; max-width:52ch; }

ol.feed { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:1px;
  background:var(--rule-strong); border:1px solid var(--rule-strong); border-radius:3px; overflow:hidden; }
li.match { background:var(--surface); }
li.match a.row {
  display:grid; grid-template-columns:64px 1fr auto; gap:1.1rem; align-items:start;
  padding:1.1rem 1.25rem; text-decoration:none; color:inherit;
}
li.match a.row:hover { background:var(--raised); }
/* The photo well stays near-neutral in both themes so a condition judgement
   never shifts with the theme. */
.thumb { width:64px; height:64px; border-radius:2px; background:#EDEAE4; object-fit:contain;
  border:1px solid var(--rule); }
.ident { font-weight:700; letter-spacing:-0.01em; }
.title { color:var(--text-muted); font-size:.9rem; margin-top:.15rem;
  display:-webkit-box; -webkit-line-clamp:1; -webkit-box-orient:vertical; overflow:hidden; }
.meta { margin-top:.5rem; display:flex; flex-wrap:wrap; gap:.4rem .75rem; align-items:center;
  font-family:var(--sans); font-weight:500; font-size:.68rem; letter-spacing:.08em;
  text-transform:uppercase; color:var(--text-muted); }
.chip { border:1px solid var(--rule-strong); border-radius:2px; padding:.16rem .45rem; }
/* Confidence is a glyph plus the literal word and never a colour: a
   colour-coded trust claim is what a sceptical four-figure buyer discounts. */
.conf { color:var(--text); }
.right { text-align:right; display:flex; flex-direction:column; align-items:flex-end; gap:.35rem; }
.price { font-size:1.15rem; font-weight:700; white-space:nowrap; }
.flag { font-family:var(--sans); font-weight:500; font-size:.62rem; letter-spacing:.12em;
  text-transform:uppercase; }
.flag.push { color:var(--accent); }
.flag.quiet { color:var(--text-muted); }
/* An ended row keeps its live flag, hidden, so a revival (feed.js) can show
   it again as the server drew it. .flag is inline-flex (:3441), which
   outranks the UA sheet's [hidden] -- the same pair the finder panel needs. */
.flag[hidden] { display: none; }
/* An ended listing stays in the feed as the comp it is, set back rather than
   gone: the collector learns the last asking price, and that it closed. The
   row sits on the sunken ground and carries the Ended flag; nothing is faded
   or struck, because opacity would push every text run in the row under AA
   and a struck price reads as void, which is the opposite of a comp. */
.match.ended { background:var(--sunken); }
.match.ended .thumb { filter:grayscale(1); }
.buy .ended { font-family:var(--sans); font-weight:500; font-size:.72rem; letter-spacing:.08em;
  text-transform:uppercase; color:var(--text-muted); margin:.5rem 0 .5rem; }
.buy .fine { color:var(--text-muted); font-size:.78rem; line-height:1.5; margin:0 0 1rem; }

/* Why a match did NOT interrupt is shown, not hidden. The worst failure in this
   product is a true grail the user never heard about, so suppression is always
   visible and always explained (docs/05-experience.md). */
.blocked { border-top:1px dashed var(--rule); padding:.6rem 1.25rem .85rem;
  color:var(--text-muted); font-size:.82rem; }
.blocked b { font-family:var(--sans); font-weight:500; font-size:.62rem; letter-spacing:.12em;
  text-transform:uppercase; color:var(--text); display:block; margin-bottom:.15rem; }

.empty { border:1px dashed var(--rule-strong); border-radius:3px; padding:3rem 1.5rem;
  text-align:center; color:var(--text-muted); }

/* ---------- match detail ---------- */
.detail { display:grid; grid-template-columns:minmax(0,1fr) 20rem; gap:2.5rem; align-items:start; }
@media (max-width:52rem) { .detail { grid-template-columns:1fr; } }
.hero { width:100%; max-width:22rem; border-radius:3px; background:#EDEAE4;
  border:1px solid var(--rule); }
/* Scoped deliberately, and it has to stay scoped. This was a bare `h1` declared
   nearly 600 lines below the real one at :287, and at equal specificity the
   later rule wins, so EVERY heading on the site rendered at 1.7rem rather than
   the Display step docs/13-typography-mark.md sets. That it was never meant to
   reach them is written in the stylesheet itself: .form-card h1, .onboard h1
   and .set-head h1 each had to re-override it for their own surfaces.

   Scoping it is not a no-op. Ten headings keep the size they had, because each
   already carries an override; the other fifteen -- the homepage headline, the
   card page, the feed, the content pages and the four guides -- move to the
   Display step and gain its 2.75rem top margin. That is the correction, not a
   side effect. Note .hero h1 at :402 does not soften the margin for them:
   class="hero" appears once in the whole tree, on match-detail.html's image.

   Unscope it again and all fifteen shrink with nothing in the diff to say why. */
.detail h1 { font-size:1.7rem; line-height:1.25; letter-spacing:-0.02em; margin:.25rem 0 .4rem;
  text-wrap:balance; }
.sub { color:var(--text-muted); margin:0 0 1.25rem; }
dl.facts { display:grid; grid-template-columns:auto 1fr; gap:.45rem 1.25rem; margin:1.5rem 0;
  padding-top:1.25rem; border-top:1px solid var(--rule-strong); }
dl.facts dt { font-family:var(--sans); font-weight:500; font-size:.68rem; letter-spacing:.1em;
  text-transform:uppercase; color:var(--text-muted); padding-top:.28rem; }
dl.facts dd { margin:0; }

aside.buy { background:var(--surface); border:1px solid var(--rule-strong); border-radius:3px;
  padding:1.35rem; position:sticky; top:1.5rem; }
aside.buy .price { font-size:1.9rem; display:block; margin-bottom:.15rem; }
aside.buy .where { color:var(--text-muted); font-size:.85rem; margin:0 0 1.15rem; }
a.cta {
  display:block; text-align:center; text-decoration:none;
  background:var(--cta-fill); color:var(--cta-label);
  /* The keyline is what makes this button conformant on light: the fill alone
     is 2.28:1 on paper and only the 5.49:1 boundary carries it. */
  border:1px solid var(--cta-edge); border-radius:2px;
  padding:.9rem 1.25rem;
  font-family:var(--sans); font-weight:500; font-size:.82rem; letter-spacing:.1em;
  text-transform:uppercase;
}
a.cta:hover { filter:brightness(1.06); }
/* The disclosure has its own contrast floor (8.84:1 light / 10.10:1 dark) and
   sits adjacent to the CTA, not in a footer: 16 CFR 255.0(f) asks that it be
   unavoidable, which a page-bottom line is not. */
.disclosure { color:var(--text-disclosure); font-size:.78rem; line-height:1.5; margin:.85rem 0 0; }
.note { color:var(--text-muted); font-size:.78rem; line-height:1.5; margin:1.1rem 0 0;
  padding-top:1.1rem; border-top:1px solid var(--rule); }
a.back { color:var(--accent); text-decoration:none; font-family:var(--sans); font-weight:500;
  font-size:.72rem; letter-spacing:.1em; text-transform:uppercase; }

/* The live region announcing new matches. This was a style attribute in the
   template; style attributes are blocked by style-src just as inline <style>
   blocks are, and 'unsafe-hashes' to keep one margin is not a trade worth
   making. */
.status { margin-bottom: 1rem; }

/* ==========================================================================
   Account screens — signup, verify, sign in, sign out
   --------------------------------------------------------------------------
   Moved out of a second Go string literal, which carried a third copy of the
   design tokens and — because it declared no @font-face at all — rendered every
   one of these screens in Charter and Menlo instead of the product's faces.
   Nothing in a diff showed it; the tokens named families the document had never
   loaded.

   Everything is scoped under .account / .form-card. The original rules styled
   bare h1, label, input, button and a, which was safe in a stylesheet serving
   five pages and is not safe in one serving the whole site. .form-card rather
   than .card because .card already means a bordered callout above.
   ========================================================================== */

.account {
  display: grid;
  place-items: center;
  min-height: 100vh;
  padding: 2rem 1.25rem;
}
.form-card { width: 100%; max-width: 26rem; }
.form-card h1 {
  font-size: 1.6rem; line-height: 1.2; letter-spacing: -.025em;
  margin: 0 0 .4rem; font-weight: 700;
}
.form-card p.lede { color: var(--text-muted); margin: 0 0 1.6rem; font-size: .95rem; }
.form-card label { display: block; margin: 0 0 1rem; }
.form-card .lab {
  font-family: var(--sans); font-weight: 500; font-size: .6rem;
  letter-spacing: .14em; text-transform: uppercase; color: var(--text-muted);
  display: block; margin-bottom: .35rem;
}
.form-card input {
  width: 100%; font-family: var(--serif); font-size: 1rem; color: var(--text);
  background: var(--surface); border: 1px solid var(--rule-strong);
  border-radius: 3px; padding: .7rem .85rem; line-height: 1.4;
}
.form-card input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.form-card .hint { color: var(--text-muted); font-size: .78rem; margin: .3rem 0 0; line-height: 1.45; }
.form-card button {
  width: 100%; background: var(--cta-fill); color: var(--cta-label);
  border: 0; border-radius: 2px; padding: .8rem 1.2rem;
  font-family: var(--sans); font-weight: 500; font-size: .78rem;
  letter-spacing: .09em; text-transform: uppercase; cursor: pointer; margin-top: .4rem;
}
.form-card button.secondary {
  background: none; color: var(--text-muted); border: 1px solid var(--rule-strong);
}
.form-card button.secondary:hover { color: var(--text); }
.form-card .err {
  background: var(--surface); border: 1px solid var(--text-disclosure);
  border-radius: 3px; padding: .7rem .85rem; color: var(--text-disclosure);
  font-size: .88rem; margin: 0 0 1.2rem;
}
.form-card .notice { color: var(--text-muted); font-size: .88rem; margin: 0 0 1.2rem; }
/* The privacy notice's answer to Forget me (pages/privacy.html), in the
   article's own measure: the same muted line the form card prints, under a
   paragraph rather than over a form. And the button itself, on a line of
   its own with a paragraph's rhythm -- a form cannot sit inside a <p>, the
   parser closes the paragraph at it. */
.prose .notice { color: var(--text-muted); font-size: .88rem; margin: 0 0 1.2rem; }
.prose .forget { margin: 0 0 1.2rem; }
.form-card .fine { color: var(--text-muted); font-size: .78rem; line-height: 1.5; margin: 1.4rem 0 0; }
/* No global `a` rule exists in this stylesheet by design, so each region
   colours its own or links fall through to the browser default. */
.form-card a { color: var(--accent); }
.form-card code.otp {
  font-family: var(--mono); font-weight: 600; letter-spacing: .3em; font-size: 1.2rem;
}

/* ==========================================================================
   Onboarding — naming a first grail
   --------------------------------------------------------------------------
   The fourth and last copy of the token block, and the second that declared no
   webfaces. Scoped under .onboard: the original styled bare h1, ul, li, input
   and a, which was survivable in a stylesheet serving one screen.
   ========================================================================== */

.onboard { max-width: 34rem; margin: 0 auto; }

/* The onboarding screen arrived with its own copies of `*` and `body`, unscoped,
   and 800 lines below the real ones at :116 and :118. Later rule wins, so EVERY
   page on the site was rendering at this screen's 17px/1.6 with 3rem of padding
   -- and silently losing `font-optical-sizing: none`, which the comment on the
   real rule says must never animate. Deleted rather than scoped: .onboard below
   already carries everything this screen actually needs. */

.onboard h1{font-size:1.7rem;line-height:1.2;letter-spacing:-.028em;margin:0 0 .4rem;font-weight:700}
.onboard p.lede{color:var(--text-muted);margin:0 0 1.6rem}
.onboard input[type=search]{width:100%;font-family:var(--serif);font-size:1.05rem;color:var(--text);
background:var(--surface);border:1px solid var(--rule-strong);border-radius:3px;
padding:.8rem .95rem}
.onboard input:focus-visible,.onboard button:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.onboard .lab{font-family:var(--sans);font-weight:500;font-size:.6rem;letter-spacing:.14em;
text-transform:uppercase;color:var(--text-muted)}
.onboard ul{list-style:none;margin:1rem 0 0;padding:0;border:1px solid var(--rule-strong);
border-radius:3px;overflow:hidden;background:var(--surface)}
.onboard li+li{border-top:1px solid var(--rule)}
.onboard li form{margin:0}
.onboard li button{width:100%;text-align:left;background:none;border:0;color:var(--text);
font:inherit;padding:.7rem .95rem;cursor:pointer;display:flex;gap:.7rem;align-items:baseline}
.onboard li button:hover{background:#201C16}
.onboard .nm{font-weight:600}
.onboard .met.onboard a{margin-left:auto;font-family:var(--mono);font-weight:500;font-size:.66rem;
color:var(--text-muted);white-space:nowrap;font-variant-numeric:tabular-nums}
.onboard .defaults{display:flex;gap:.4rem;flex-wrap:wrap;margin:1.4rem 0 0;align-items:center}
.onboard .chip{font-family:var(--mono);font-weight:600;font-size:.62rem;letter-spacing:.07em;
border:1px solid var(--rule-strong);border-radius:2px;padding:.26rem .5rem}
.onboard .note{color:var(--text-muted);font-size:.8rem;margin:.5rem 0 0;line-height:1.5}
.onboard .err{border:1px solid var(--accent);border-radius:3px;padding:.7rem .9rem;
color:var(--accent);font-size:.9rem;margin:0 0 1.2rem}
.onboard .empty{color:var(--text-muted);font-style:italic;padding:.9rem .95rem}
.onboard a{color:var(--accent)}

/* The feed's own controls. They used to sit in a second header bar; the rail is
   the only chrome now, so they sit with the thing they control. */
.feed-controls {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  margin: 0 0 1.25rem;
}
.feed-controls .live { margin: 0; }

/* ==========================================================================
   The admin — the operator surface
   --------------------------------------------------------------------------
   docs/07-architecture.md:127: "boring server-rendered pages ... none of it is
   product." So it borrows the account screens' form chrome and adds only what
   a list of grails needs. A broken anchor is marked, because a curated grail
   outlives a catalogue rename and the operator has to be able to see that.
   ========================================================================== */

.form-card.admin { max-width: 44rem; }

.admin-list { list-style: none; margin: 1.5rem 0 0; padding: 0; }

/* ---------- The field editor's row controls ----------
   `.form-card button` is width:100% because every other button in the admin is
   the one action on a page -- Save, Publish, Retire. A row in the field index
   has THREE controls beside each other (edit, up, down), and inheriting that
   width stacked them into three full-width bars per row and made twelve rows a
   page nobody would scroll. The forms are inline-block so they sit on one line,
   and the buttons size to their own text. */
.field-row-acts { display: flex; align-items: center; gap: .5rem; margin: .4rem 0 0; }
.field-row-acts form { margin: 0; }
.field-row-acts button { width: auto; margin-top: 0; padding: .35rem .7rem; font-size: .66rem; }

/* The help line under a field belongs to the field ABOVE it, and sat closer to
   the label below -- so "changing it would retire a published URL" read as an
   introduction to the name field rather than a warning about the address. */
.form-card .fine { margin: .35rem 0 1.4rem; }
/* An input that cannot be edited has to look like one. The address is rendered
   disabled because the handler ignores it either way; without this it reads as
   a field the operator simply has not noticed they can change. */
.form-card input:disabled {
  background: var(--sunken);
  color: var(--text-muted);
  cursor: not-allowed;
}
.admin-row {
  border: 1px solid var(--rule-strong);
  border-radius: 3px;
  padding: .9rem 1rem;
  margin-bottom: .75rem;
}
.admin-row.broken { border-color: var(--text-disclosure); }
.admin-line { margin: 0 0 .5rem; font-weight: 600; }
.admin-meta {
  display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  margin: 0 0 .5rem;
}
.admin-meta .chip {
  font-family: var(--sans); font-weight: 500; font-size: .62rem;
  letter-spacing: .07em; text-transform: uppercase;
  border: 1px solid var(--rule-strong); border-radius: 2px; padding: .2rem .45rem;
}
.admin-meta .lab {
  font-family: var(--sans); font-weight: 500; font-size: .62rem;
  letter-spacing: .1em; text-transform: uppercase; color: var(--text-muted);
}
.admin-meta a { color: var(--accent); }
.admin-note {
  margin: 0 0 .5rem; color: var(--text-muted);
  font-size: .85rem; line-height: 1.5;
}
.admin-row form { margin: 0; }
.admin-row button { width: auto; padding: .4rem .8rem; margin: 0; }
/* Several forms on one row -- move, unfeature, the status buttons -- sit in
   a line, as one row of controls. */
.admin-row form.inline { display: inline-flex; flex-wrap: wrap; align-items: center; gap: .4rem; margin: 0 .4rem .4rem 0; }
/* The labelling queue's one form a row holds a verdict's buttons and the
   fields to file with it; each field keeps a width its placeholder reads
   in, and the row wraps rather than clips (2026-09-19). */
.admin-row form.inline input[type="text"] { flex: 1 1 14rem; min-width: 12rem; }
.form-card.admin textarea, .form-card.admin select {
  width: 100%; font-family: var(--serif); font-size: 1rem; color: var(--text);
  background: var(--surface); border: 1px solid var(--rule-strong);
  border-radius: 3px; padding: .7rem .85rem; line-height: 1.4;
}
.form-card.admin textarea:focus-visible, .form-card.admin select:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}

/* The staging index's migration ledger: one row per file, one column per
   schema, so where a change has landed is read across rather than inferred. */
.admin-table { width: 100%; border-collapse: collapse; margin: 1rem 0 1.5rem; font-size: .85rem; }
.admin-table th, .admin-table td {
  text-align: left; padding: .45rem .5rem; border-bottom: 1px solid var(--rule-strong);
  vertical-align: top;
}
.admin-table th {
  font-family: var(--sans); font-weight: 500; font-size: .62rem;
  letter-spacing: .1em; text-transform: uppercase; color: var(--text-muted);
}
.admin-table tr.staged td { background: var(--sunken); }
.admin-table .chip {
  font-family: var(--sans); font-weight: 500; font-size: .62rem;
  letter-spacing: .07em; text-transform: uppercase;
  border: 1px solid var(--rule-strong); border-radius: 2px; padding: .15rem .4rem;
  margin-left: .4rem;
}

/* ==========================================================================
   Curated grails — the homepage showcase
   --------------------------------------------------------------------------
   The three buckets reuse the first-run instrument's vocabulary and glyphs on
   purpose: the instrument and the showcase must tell one story, or a visitor
   learns the rule twice.

   Two carriers throughout, per docs/12-design-system.md's first prohibition —
   the glyph and the word carry the verdict, and colour only reinforces it, so
   every state reads with hue removed.
   ========================================================================== */

.curated { margin-top: 2.5rem; padding-top: 1.25rem; border-top: 1px solid var(--rule); }
.curated-lede { color: var(--text-muted); margin: .35rem 0 1.5rem; max-width: var(--measure); }
.curated-fine {
  margin: 1.5rem 0 0; color: var(--text-muted);
  font-size: .78rem; line-height: 1.5; max-width: var(--measure);
}

/* ---------- The featured grails, as a card grid ----------
   The design's four-up section, on the front door only: /g/{slug} and the
   admin screens keep the row, which is the right shape for a list you are
   reading down rather than a set you are looking across.

   NO ART WELL, and this is the one place the design is not followed. Its cards
   are dominated by a square photograph, and docs/09-legal-trust.md:17 puts
   images at identification scale beside a row and "never a hero" -- a square
   at the head of a card is exactly a hero. showcase.go carries no image field
   for the same reason and showcase_test.go:105-112 fails the build on any <img>
   inside this section. The card is type, and the recognition line is what
   carries it.

   auto-fill rather than a fixed four tracks: the design has four items and we
   have as many as an operator has published. auto-fill keeps the empty tracks,
   so three grails read as three cards in a four-up grid rather than as three
   third-width cards -- the count stays legible either way. */
/* Four tracks, and a quantity query for the counts that would orphan a card.
   auto-fill was picking the column count from the WIDTH alone, so five
   published grails came out four-and-one: a lone card on a second row with
   three empty tracks beside it, which reads as a mistake rather than as a
   count. The operator decides how many are published, so the grid has to
   answer for whatever number arrives.

   `:has(> :nth-child(N):last-child)` is a quantity query -- it matches a list
   whose Nth child is also its last, i.e. a list of exactly N. Only the counts
   that leave exactly one over need re-tracking; 5 and 9 are the ones inside
   any plausible range, and 13 would orphan again, which is recorded here
   rather than papered over. */
.home .curated .grails {
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1.375rem;                        /* the design's 22px card gap */
}
.home .curated .grails:has(> :nth-child(5):last-child),
.home .curated .grails:has(> :nth-child(9):last-child) {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
@media (max-width: 68rem) {
  .home .curated .grails,
  .home .curated .grails:has(> :nth-child(5):last-child),
  .home .curated .grails:has(> :nth-child(9):last-child) {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (max-width: 46rem) {
  .home .curated .grails,
  .home .curated .grails:has(> :nth-child(5):last-child),
  .home .curated .grails:has(> :nth-child(9):last-child) {
    grid-template-columns: minmax(0, 1fr);
  }
}
.home .curated .grail {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: .4rem;
  padding: .875rem .875rem 1.375rem;   /* the design's 14/14/22 card */
  /* Square. The design keeps exactly two radii on the whole page -- the field
     and the panel that drops out of it -- and everything on the paper is cut.
     A radius on a card is a fourth way of saying "separate object" on top of
     the border, the fill and the gap that already say it. */
  border-radius: 0;
}
/* The recognition line is the card's title, so it takes the display serif at
   the Subhead step rather than the row's bold body. It is still the link to
   the grail's own page and still keeps the --rule-strong underline the row
   gives it, because five gold titles in a grid is the same gold-striped page
   the row rule at :1820 is avoiding. */
/* The card's face, in the design's proportions: a square well of art, then the
   origin, the name and the note. The well is square rather than the card's own
   2:3 -- the design's is, and a column of 2:3 wells would push the type of the
   fourth card below the fold on a laptop. object-fit:contain, so a portrait
   card sits inside it whole rather than being cropped to fill. */
.home .curated .grail-art {
  display: block;
  aspect-ratio: 1;
  background: var(--sunken);
  overflow: hidden;
}
.home .curated .grail-art img {
  width: 100%; height: 100%;
  object-fit: contain;
  display: block;
}
.home .curated .grail-art .art-none {
  display: grid; place-items: center;
  width: 100%; height: 100%;
  border: 1px dashed var(--rule-strong);
  font-size: .8rem; color: var(--text-muted);
}
.home .curated .grail-cat { color: var(--accent); margin: .35rem 0 -.15rem; }
/* The operator's own sentence, in the design's italic caption. It is display
   italic, which docs/13-typography-mark.md released for exactly this: a caption
   under a card is not a word inside a sentence and cannot be read as one. */
.home .curated .grail-cap {
  margin: 0;
  font-family: var(--display);
  font-style: italic;
  font-size: 1.08rem;
  line-height: 1.3;
  color: var(--text-muted);
  text-wrap: pretty;
}
.home .curated .grail .grail-line {
  font-family: var(--display);
  font-weight: 400;
  font-size: 1.38rem;         /* the design's 22px card title */
  line-height: 1.14;
}
.home .curated .grail .grail-buckets { margin: 0; gap: .4rem .8rem; }
/* justify-self is a grid property and this is a flex column now; without the
   reset the chip keeps the row's end-alignment and the details summary keeps
   the row's full-width span, neither of which means anything here. */
.home .curated .grail .grail-act,
.home .curated .grail .grail-detail { margin: 0; justify-self: auto; }

/* One column at every width: rows in the follow lane's rhythm, not cards. The
   face is the recognition line and the three counts; note and specimens sit
   inside the details. */
.grails { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }
.grail {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: .3rem 1rem; align-items: center;
  padding: .8rem 1rem;
  border: 1px solid var(--rule); border-radius: 5px; background: var(--surface);
}
/* The bare rules keep the margins grail.html relies on, where the same
   elements sit in running prose; the row's grid geometry is scoped to .grail.
   text-wrap: pretty keeps a ceiling's figure from orphaning on its own line
   where the engine supports it, and costs nothing where it does not. */
.grail-line { margin: 0 0 .4rem; font-weight: 600; line-height: 1.4; text-wrap: pretty; }
/* An editor's way into the catalogue of grails, drawn for staff only: a
   line under the note on a card page, a link under a search result. */
.grail-note.editor { font-size: .82rem; }
.card-cell .editor {
  display: block; font-size: .78rem; margin-top: .25rem;
  color: var(--accent); text-decoration: underline; text-underline-offset: .15em;
}
.grail .grail-line { grid-column: 1; grid-row: 1; margin: 0; }
/* The line IS the link to the grail's page. Inherited ink and an underline in
   --rule-strong rather than main a's gold -- five gold lines in one glance is
   the gold-striped page -- and the underline is the non-colour carrier WCAG
   1.4.1 wants. Same escape .facets a performs. */
.grail-line a {
  color: inherit; text-decoration: underline;
  text-decoration-color: var(--rule-strong); text-decoration-thickness: 1px; text-underline-offset: .2em;
}
.grail-line a:hover,
.grail-line a:focus-visible { color: var(--accent); text-decoration-color: currentColor; }
.grail-note { margin: 0 0 .75rem; color: var(--text-muted); font-size: .85rem; line-height: 1.5; }
.grail-detail .grail-note { margin: .6rem 0 0; }

.grail-buckets {
  list-style: none; margin: 0 0 .75rem; padding: 0;
  display: flex; flex-wrap: wrap; gap: .6rem .9rem;
}
.grail .grail-buckets { grid-column: 1; grid-row: 2; margin: 0; }
/* An empty bucket: the dashed quiet-state edge, muted throughout. */
.grail-buckets .bucket.nil { border-style: dashed; color: var(--text-muted); }
.grail-buckets .bucket.nil .n,
.grail-buckets .bucket.nil .g { color: var(--text-muted); }
.grail-buckets .bucket {
  display: flex; align-items: baseline; gap: .35rem;
  font-family: var(--sans); font-weight: 500; font-size: .62rem;
  letter-spacing: .09em; text-transform: uppercase; color: var(--text-muted);
}
/* Mono and an EXPLICIT weight. The markup is <b class="n tabular">, so the
   UA sheet's font-weight:bolder resolves against .bucket's 500 and lands on
   700 -- a weight this rule never names and the sans cannot draw. See
   "figures inside a label". */
.grail-buckets .n {
  font-family: var(--mono); font-weight: 600;
  font-size: .95rem; color: var(--text);
}
.bucket.push .g { color: var(--accent); }
.bucket.never .g { color: var(--text-muted); }

.grail-detail { margin: 0 0 .75rem; }
.grail .grail-detail { grid-column: 1 / -1; grid-row: 3; margin: .1rem 0 0; }
.grail-detail summary {
  font-family: var(--sans); font-weight: 500; font-size: .62rem;
  letter-spacing: .1em; text-transform: uppercase; color: var(--text-muted);
  cursor: pointer;
}
.grail-detail summary:hover { color: var(--text); }

.specimens { list-style: none; margin: .6rem 0 0; padding: 0; }
.specimens .spec {
  display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap;
  padding: .35rem 0; border-top: 1px solid var(--rule);
  font-size: .85rem; line-height: 1.45;
}
.specimens .g { flex: 0 0 auto; display: inline-flex; align-self: center; }
.specimens .spec.push .g { color: var(--accent); }
.specimens .rsn {
  color: var(--text-muted); font-size: .78rem;
  font-family: var(--mono); font-weight: 500;
}

.grail-act { grid-column: 2; grid-row: 1 / 3; margin: 0; justify-self: end; }
button.try-grail {
  width: auto; margin: 0; padding: .45rem .8rem;
  background: none; color: var(--text-muted);
  border: 1px solid var(--rule-strong); border-radius: 2px;
  font-family: var(--sans); font-weight: 500; font-size: .66rem;
  letter-spacing: .09em; text-transform: uppercase; cursor: pointer;
}
button.try-grail:hover { color: var(--text); border-color: var(--text-muted); }

@media (max-width: 34rem) {
  .grail { grid-template-columns: minmax(0, 1fr); }
  .grail-act { grid-column: 1; grid-row: 3; justify-self: start; }
  .grail-detail { grid-row: 4; }
}

/* ---------- The grail page ---------- */
.tune { margin: 1.5rem 0; }
.tune fieldset {
  border: 1px solid var(--rule); border-radius: 3px;
  padding: .7rem .9rem; margin: 0 0 .75rem;
}
.tune legend { padding: 0 .35rem; }
.tune .inline {
  display: inline-flex; align-items: baseline; gap: .35rem;
  margin: 0 1rem .25rem 0;
}
.tune input.narrow { width: 5rem; display: inline-block; }
.tune .hint { display: inline; margin-left: .5rem; }
/* The re-run control: a real button that is not the call to action -- this
   page's gold is "Save this grail" -- in the shape button.pause and the search
   page's submit already use, so the control a collector meets here is the one
   they met on those pages. It was the browser's default button. */
.tune button[type="submit"] {
  width: auto; margin: .25rem 0 0;
  font-family: var(--sans); font-weight: 500; font-size: .72rem; letter-spacing: .1em;
  text-transform: uppercase; cursor: pointer;
  background: transparent; color: var(--text);
  border: 1px solid var(--rule-strong); border-radius: 2px;
  padding: .5rem .85rem;
}
.tune button[type="submit"]:hover,
.tune button[type="submit"]:focus-visible { border-color: var(--accent); color: var(--accent); }

.verdict-live { margin: 2rem 0 0; }
.sub { color: var(--text-muted); margin: -.4rem 0 1.2rem; }

.inline-form { display: inline; margin: 0; }
button.linky {
  width: auto; margin: 0; padding: 0; background: none; border: 0;
  color: var(--accent); font: inherit; text-transform: none;
  letter-spacing: normal; cursor: pointer; text-decoration: underline;
}

/* The grail's own page, linked beside the instrument button. A link rather than
   a second button: it navigates, and it must work with scripting off. */
.grail-open {
  font-size: 0.9rem;
  color: var(--accent);
  text-underline-offset: 0.2em;
}

/* ---------------------------------------------------------------- listings */
/* Real rows on a card, bucketed by the gate. Deliberately not price-sorted:
   sorting by price makes this a shopping surface and promotes the cheapest
   listing, which is usually the worst one. */
.listings { margin-top: 2rem; }
.listings .rows { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.5rem; }
.listings .row {
  display: grid;
  grid-template-columns: 1.25rem minmax(0, 1fr) auto;
  gap: 0.25rem 0.6rem;
  align-items: baseline;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--rule);
  border-radius: 4px;
  background: var(--surface);
}
.listings .row .g { grid-row: span 2; color: var(--text-muted); display: inline-flex; align-self: center; }
.listings .row .gr { font-weight: 600; }
.listings .row .pr { font-variant-numeric: tabular-nums; margin-left: 0.5rem; }
.listings .row .fmt, .listings .row .mark {
  font-size: 0.8rem; color: var(--text-muted); margin-left: 0.5rem;
}
.listings .row .mark {
  border: 1px solid var(--rule-strong); border-radius: 3px;
  padding: 0 0.3rem; letter-spacing: 0.02em;
}
.listings .row .rsn { grid-column: 2; font-size: 0.9rem; color: var(--text-muted); }
.listings .row .src { grid-column: 3; grid-row: span 2; white-space: nowrap; }
.listings .row.alerts { border-color: var(--rule-strong); }
/* Deliberately two rules with the same declarations rather than one shared
   rule. .sample-banner states that the listings beneath it are fabricated;
   .quiet is an aside. A future softening of the aside must not reach the
   compliance surface by accident, and a shared selector is exactly how that
   happens with nothing in the diff to show it. */
.sample-banner {
  border: 1px solid var(--rule-strong);
  border-radius: 4px;
  padding: 0.75rem;
  background: var(--sunken);
}
/* Element-qualified, and it has to stay that way. grail.html:100's aside is
   the only <p class="quiet"> in the tree, but pages/index.html:251 and
   feed.html:36 both write <span class="flag quiet"> on a match that did not
   interrupt -- and a bare .quiet wrapped that two-word label in this box: a
   keyline, a 4px radius, 0.75rem of padding and a sunken ground, inside the
   right-hand column of a feed row. .flag.quiet (:987) is the rule that span
   wants, and it asks for a muted ink and nothing else.

   The two class names come from opposite halves of this file, which is exactly
   the reach the Product surfaces banner above describes; the merged home page
   is the first document that holds both. */
p.quiet {
  border: 1px solid var(--rule-strong);
  border-radius: 4px;
  padding: 0.75rem;
  background: var(--sunken);
}

/* ---------------------------------------------------- discovery boundary */
/* The one line on the home page that is a promise rather than a label, and
   the only thing between a collector's own cards and everybody's.

   docs/02-grail-profiles.md:49 requires a hard visual AND semantic boundary
   between the match layer and the discovery layer, and :51 makes what that
   boundary protects a system-wide contract: discovery items never interrupt,
   on any channel, in every case. The sentence carries the semantic half; this
   rule is the visual half, and a boundary a reader cannot see leaves the
   contract as something they have to take on trust.

   --rule-strong, NEVER --rule. This file's own header and
   docs/12-design-system.md:86 say the same thing: --rule is decorative at
   1.28:1 light and 1.31:1 dark and cannot carry a functional boundary. A
   semantic layer boundary is functional, so it takes the 3.59:1 token, the
   same one .fold and .site-footer use to close a document.

   The label takes full ink while the sentence stays muted, which is .absent's
   move (:561): the mono uppercase word is the marker, the sentence is the
   rule it marks. Signed-in only -- a visitor with no account has no personal
   band above the line, so there would be nothing to demarcate. */
.layer {
  margin: 2.75rem 0 0;
  padding-top: 1.1rem;
  border-top: 1px solid var(--rule-strong);
  max-width: var(--measure);
  color: var(--text-muted);
  font-size: .88rem;
  line-height: 1.55;
}
.layer .lab { color: var(--text); margin-right: .55rem; }

/* ---------------------------------------------------------------- trending */
/* One row per card: art, identity and figures. The identity is type and
   carries the row on its own -- the thumbnail is an aid to recognition, not the
   thing being read, which is also the posture docs/09-legal-trust.md asks for. */
.trending { margin-top: 2.5rem; }
/* The collector's own grails, built from the same rows and kept in the same
   rhythm as .trending above. The margin is stated here rather than left to the
   <h2>'s own margin collapsing out through the section box, which is the only
   thing spacing this section today and stops the moment it grows a border, a
   padding, or a first child that is not a heading. It holds the <h2>, a
   .trend-rows list of .trend-row, and a .curated-fine line.

   The section renders in two documents whose ancestry differs, and the rule is
   deliberately independent of both: inside <main> on pages/index.html, where
   the merge put it, and after </main> on feed.html. One thing does differ and
   it is not this rule's to fix -- the grail name is an <a>, `main a` (:308)
   colours it inside <main>, and there is no global `a` rule in this file, so
   the same anchor on feed.html falls through to the browser default. */
.mine { margin-top: 2.5rem; }
.trend-rows { list-style: none; margin: 1rem 0 0; padding: 0; display: grid; gap: 0; }
/* A RULED LIST, not a stack of cards.
   Each row used to be a bordered --surface box with its own radius, which put
   a control edge around something that is not a control and made eight cards
   in a column read as eight separate offers. A hairline between rows says the
   same thing -- these are separate items -- with one line instead of four, and
   it is what lets a heading alone divide one section from the next above.
   --rule and not --rule-strong: this is a decorative separator inside one
   list, which is exactly the distinction docs/12-design-system.md:86 draws.
   The row's own edges are gone, so the horizontal padding goes too and the
   identity starts on the column's own left margin; .colhead's padding tracks
   it below. */
.trend-row {
  display: grid;
  /* Three tracks: art, identity, figures. The fourth held a collector count,
     and that count no longer reaches the render path (trending.go) -- see
     .demand below for the one cell that still asks for a fourth, and why it
     asks on itself rather than here. */
  grid-template-columns: 3.25rem minmax(0, 1fr) auto;
  gap: 0 1rem;
  align-items: center;
  padding: 0.85rem 0;
  border-top: 1px solid var(--rule);
}
/* No border of its own goes on .colhead -- the paragraph at :700 argues that
   out, and it still holds: --rule is invisible at caption scale and
   --rule-strong reads as a control edge. What changed is that it no longer
   needs one. The list below it is ruled now, so the first row's own top
   border IS the line under the head, drawn by the rows' rule rather than by a
   second one that would have to match it. */
.trend-row .art { display: block; }
.trend-row .art img {
  display: block; width: 3.25rem; height: auto; border-radius: 3px;
  /* Identification scale. Deliberately small: this is the size the fair-use
     reasoning in docs/09-legal-trust.md actually supports. */
}
/* The fallback when a source supplies no art -- One Piece, and 7,209 other
   cards. The number is the recognisable thing, so it stands in for the image. */
.trend-row .art-none {
  display: grid; place-items: center;
  width: 3.25rem; height: 4.55rem;
  border: 1px dashed var(--rule-strong); border-radius: 3px;
  font-size: 0.7rem; color: var(--text-muted); text-align: center;
}
.trend-row .ident { display: grid; gap: 0.1rem; min-width: 0; }
.trend-row .nm { font-family: var(--display); font-weight: 400; font-size: 1.28rem; }
/* The name is the row's link to the card's page (game.html, index.html's
   Following block): since 2026-09-18 a followed card is reachable from
   the page that names it, not only from its set. Plain, like the set
   grid's cell; the hover says it is a link. */
.trend-row .nm a { color: inherit; text-decoration: none; }
.trend-row .nm a:hover { text-decoration: underline; }
/* DECIDED: browse-row meta is the serif, muted. It inherited the feed's bare
   .meta -- mono, uppercase, tracked, with a top margin and a flex box -- by
   accident, and every property that rule sets is overridden here on purpose
   rather than by whichever declaration happened to win. Set names and print
   classes are what a person values (docs/13-typography-mark.md:9); the
   number inside the line is what a machine recorded and stays in Azeret.
   Reaches feed.html, game.html and collection.html rows, which is the intent
   there too. */
.trend-row .meta {
  display: block; margin: 0;
  font-family: var(--serif); font-weight: 400; font-size: .85rem;
  letter-spacing: 0; text-transform: none; color: var(--text-muted);
}
.trend-row .meta .tabular { font-family: var(--mono); font-weight: 500; font-size: .78rem; letter-spacing: .02em; }
/* The collector's own terms under the name: the recognition line, serif and
   muted, wrapping as the sentence it is. */
/* Reset against the feed's bare .ident (:1075), which the row's own .ident
   inherits from -- bold and tracked -- and which reached this line by descent. */
.trend-row .terms {
  font-size: .85rem; font-weight: 400; letter-spacing: 0; line-height: 1.45;
  color: var(--text-muted); text-wrap: pretty;
}
.mine .figures .lab { white-space: nowrap; }
.trend-row .figures { display: grid; gap: 0.1rem; justify-items: end; text-align: right; }
.trend-row .pr { font-size: 1.02rem; font-variant-numeric: tabular-nums; }
.trend-row .n, .trend-row .none { font-size: 0.8rem; color: var(--text-muted); }
/* Sign, glyph and word together, so a drop reads with hue removed
   (docs/12-design-system.md). Colour is never the only carrier. */
.trend-row .drop { font-size: 0.8rem; color: var(--text-muted); }
.trend-row .drop .g { color: var(--accent); }
/* The fourth cell. collection.html:36 is the only template that still writes
   one -- a "Watch for another" link on a card the collector already owns --
   and the column is declared on the cell rather than back in .trend-row's
   track list so that the rows which have three children get exactly three
   tracks and no trailing gap. `grid-column: 4` against a three-track template
   creates one implicit column, sized `auto` as the explicit fourth track was,
   and only in the rows that carry this span.

   .demand b and .pin came out with it. Both were feed.html:105-106's, both
   read trendingRow.Collectors and .Pinned, and neither field exists on that
   type any more; trending_test.go:60 asserts class="demand" and class="pin"
   never appear on / by name. */
.trend-row .demand {
  grid-column: 4;
  font-size: 0.8rem; color: var(--text-muted);
  text-align: right; white-space: nowrap;
}

@media (max-width: 34rem) {
  /* The reference designs are phone-first. Figures move under the identity
     rather than being squeezed beside it. Two tracks here, so nothing to drop:
     the fourth column never existed at this width. The .demand line below is
     what cancels its `grid-column: 4` -- same specificity, later source -- so
     collection.html's cell stacks under the identity like the figures do
     rather than opening an implicit column on a phone. */
  .trend-row { grid-template-columns: 3.25rem minmax(0, 1fr); row-gap: 0.4rem; }
  .trend-row .figures, .trend-row .demand {
    grid-column: 2; justify-items: start; text-align: left;
  }
}

/* -------------------------------------------------------------------- sets */
/* A game's sets, newest first. Text rather than links: there is no per-set
   route, and a dead link is worse than plain type. */
.sets { margin-top: 2.5rem; }
/* ------------------------------------------------------------ set tiles --
   A grid, not a list, and the one place this product surface admits an image
   larger than a row thumbnail. The subject is the SET MARK -- the logo printed
   on the booster -- which is how a collector recognises a set, and it is a
   different asset from the card artwork docs/09-legal-trust.md:17 governs:
   an identification mark rather than the illustration it sells. */
.set-tiles {
  list-style: none; margin: 0.75rem 0 0; padding: 0;
  display: grid; gap: 0.75rem;
  grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr));
}
.set-tile {
  display: grid; gap: 0.3rem; align-content: start;
  min-width: 0;
}

/* The plate is light in BOTH themes on purpose. Set marks are drawn for light
   grounds -- Scryfall's symbols are black glyphs -- so a plate that followed
   the theme would erase every Magic set on the dark product surface. */
.set-tile .plate {
  position: relative;
  display: grid; place-items: center;
  aspect-ratio: 16 / 9;
  padding: 0.9rem;
  background: #f4f4f2;
  border: 1px solid var(--rule);
  border-radius: 5px;
  overflow: hidden;
}
.set-tile .plate img {
  max-width: 100%; max-height: 100%;
  width: auto; height: auto;
  object-fit: contain;
  display: block;
}
/* No mark: the code carries the tile. Lorcana and One Piece supply none at
   all, so this is those whole games rather than an edge case. */
/* Mono, not sans: this prints {{.Code}} (templates/game.html:54) and carries
   .tabular. The sweep moved it on its uppercase and was wrong to -- a set code
   is machine data, and .set-tile .code below is the same string in the same
   face. */
.set-tile .plate-none {
  font-family: var(--mono); font-size: 0.95rem;
  color: #6b6b66; text-transform: uppercase; letter-spacing: 0.04em;
}
/* The release date rides the plate, as it does on the reference. Dark on
   light: it sits on the plate, not on the page. */
.set-tile .when {
  position: absolute; top: 0.35rem; right: 0.4rem;
  font-size: 0.66rem; color: #55554f;
  background: rgba(255, 255, 255, 0.86);
  border-radius: 3px; padding: 0.1rem 0.3rem;
  font-variant-numeric: tabular-nums;
}
.set-tile .nm {
  font-weight: 500; font-size: 0.92rem; line-height: 1.25;
  /* Set names run long -- "Scarlet & Violet: Prismatic Evolutions" -- and a
     tile grid needs a predictable height, so two lines then ellipsis. */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.set-tile .meta {
  display: flex; gap: 0.5rem; align-items: baseline; flex-wrap: wrap;
  font-size: 0.78rem; color: var(--text-muted);
}
.set-tile .code { font-family: var(--mono); font-size: 0.72rem; }
.set-tile .tabular { font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------- /sets --
   The index above the four games. One row per game, same vocabulary as the
   set rows below it so moving between the two feels like one surface. */
.game-rows { list-style: none; margin: 1.25rem 0 0; padding: 0; display: grid; gap: 0.5rem; }
.game-row { margin: 0; }
.game-link {
  display: grid;
  grid-template-columns: 3.4rem minmax(0, 1fr) auto;
  gap: 0 1rem;
  align-items: center;
  padding: 0.8rem 0.9rem;
  border: 1px solid var(--rule);
  border-radius: 5px;
  background: var(--surface);
  color: inherit;
  text-decoration: none;
}
.game-link:hover, .game-link:focus-visible { border-color: var(--rule-strong); }
/* Same light plate as the set tiles, and for the same reason: this image is a
   set MARK, and Magic's is a black glyph. */
.game-link .art {
  display: grid; place-items: center;
  width: 3.4rem; height: 2.4rem; padding: 0.3rem;
  background: #f4f4f2; border: 1px solid var(--rule); border-radius: 3px;
}
.game-link .art img {
  max-width: 100%; max-height: 100%; width: auto; height: auto;
  object-fit: contain; display: block;
}
.game-link .art-none {
  font-family: var(--mono); font-size: 0.72rem; color: #6b6b66;
}
.game-link .ident { display: grid; gap: 0.15rem; min-width: 0; }
.game-link .nm { font-size: 1.05rem; font-weight: 500; }
.game-link .meta { font-size: 0.82rem; color: var(--text-muted); }
.game-link .figures {
  display: grid; gap: 0.1rem; justify-items: end; text-align: right; min-width: 0;
}
.game-link .figures .nm { font-size: 0.9rem; font-weight: 400; }
.game-link .tabular { font-variant-numeric: tabular-nums; }

@media (max-width: 34rem) {
  /* The newest-set column is the first thing to go: the game and its set
     count are what the row is for. */
  .game-link { grid-template-columns: 3.4rem minmax(0, 1fr); }
  .game-link .figures { display: none; }
}

/* The rail nav carries four game names at .71rem uppercase with .11em
   letter-spacing, in a narrow column. "One Piece" is the longest and fits, but
   balance keeps any two-word name breaking evenly rather than orphaning a word
   if the rail narrows or a fifth game arrives. */
.site-nav a { text-wrap: balance; }

/* A year heading inside the set list. Grouping is what makes "all of them"
   scannable rather than a wall -- a year is the coarsest label that is both
   always available and what a collector actually scans for. */
.set-year {
  display: flex; align-items: baseline; gap: 0.6rem;
  margin: 1.6rem 0 0.5rem; font-size: 0.95rem;
}
.set-year .n {
  font-size: 0.78rem; color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------- set page --
   One set's cards. The last step before /g/{slug}. */
.crumb { font-size: 0.8rem; color: var(--text-muted); margin: 0 0 0.75rem; }
.crumb a { color: inherit; }

.set-head {
  display: grid; grid-template-columns: 6rem minmax(0, 1fr); gap: 0 1rem;
  align-items: center; margin-bottom: 1.25rem;
}
/* Light plate, same rule as the tiles: Scryfall's marks are black glyphs and
   would vanish on the dark product surface. */
.set-head .plate {
  display: grid; place-items: center; aspect-ratio: 16 / 9; padding: 0.5rem;
  background: #f4f4f2; border: 1px solid var(--rule); border-radius: 5px;
}
.set-head .plate img { max-width: 100%; max-height: 100%; width: auto; height: auto; }
/* Mono, for the reason .set-tile .plate-none is: it prints the same
   {{.Code}} (templates/set.html:8) that .set-head .code prints, and one header
   showing one code in two faces is the defect this avoids. */
.set-head .plate-none {
  font-family: var(--mono); font-size: 0.7rem; color: #6b6b66; text-transform: uppercase;
}
.set-head .ident { display: grid; gap: 0.2rem; min-width: 0; }
.set-head h1 { margin: 0; font-size: 1.5rem; line-height: 1.15; }
.set-head .meta {
  display: flex; gap: 0.6rem; flex-wrap: wrap;
  font-size: 0.82rem; color: var(--text-muted);
}
.set-head .code { font-family: var(--mono); }
.set-head .tabular { font-variant-numeric: tabular-nums; }

/* The card grid. auto-fill rather than a fixed column count: the largest set
   holds 5,579 cards and the median 34, so the same grid has to read at both. */
.card-grid {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: 0.75rem;
  grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
}
.card-cell {
  min-width: 0;

  /* Populate on scroll, without withholding anything from the response.
     content-visibility: auto lets the browser skip layout, paint and style for
     a cell that is nowhere near the viewport, and do that work as it scrolls
     into range. The bytes still arrive in one gzipped document -- 5,579 cards
     is 2.2 MB of HTML and 393 KB compressed -- so a crawler still receives the
     whole set.

     That distinction is the reason this is CSS and not an infinite scroll:
     10-acquisition.md:53 makes complete server-rendered HTML non-negotiable
     for a catalogue URL, because "Googlebot's render queue and non-JS AI
     crawlers make socket-delivered content invisible". Fetching cards over JS
     as the reader scrolls would hide all but the first screen from exactly the
     crawlers this surface exists for. This gets the same fast first paint and
     costs nothing at the network layer.

     contain-intrinsic-size gives the browser a placeholder size so the
     scrollbar is honest before anything renders; `auto` makes it remember each
     cell's real size once measured, so scrolling back up does not reflow. The
     value is the tile's own proportions: an 8rem column at 245/342 is about
     11rem of art plus two short lines. */
  content-visibility: auto;
  contain-intrinsic-size: auto 14rem;
}
.card-cell a {
  display: grid; gap: 0.25rem; color: inherit; text-decoration: none;
}
.card-cell .art {
  display: grid; place-items: center;
  /* The proportions of a trading card, so a missing image leaves a cell the
     same shape as its neighbours rather than a collapsed row. */
  aspect-ratio: 245 / 342;
  background: var(--surface); border: 1px solid var(--rule); border-radius: 4px;
  overflow: hidden;
}
.card-cell .art img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.card-cell .art-none {
  font-family: var(--mono); font-size: 0.75rem; color: var(--text-muted);
}
.card-cell .nm {
  font-size: 0.82rem; line-height: 1.2;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.card-cell .meta {
  font-size: 0.72rem; color: var(--text-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.card-cell .tabular { font-variant-numeric: tabular-nums; }
.card-cell a:hover .art, .card-cell a:focus-visible .art { border-color: var(--rule-strong); }

/* The tile is a link now; it must not look like plain type. */
.set-tile > a { display: grid; gap: 0.3rem; color: inherit; text-decoration: none; }
.set-tile > a:hover .plate, .set-tile > a:focus-visible .plate { border-color: var(--rule-strong); }

/* The next-page control. An anchor, not a button: it is what a crawler follows
   and what a reader without JavaScript clicks. set.js hijacks it into an
   append-on-scroll and removes it when the set is exhausted. */
.more { margin: 1.25rem 0 0; text-align: center; }
.more a {
  display: inline-block; padding: 0.55rem 1.1rem;
  border: 1px solid var(--rule-strong); border-radius: 5px;
  color: inherit; text-decoration: none; font-size: 0.85rem;
}
.more a:hover, .more a:focus-visible { border-color: var(--accent); }

/* The confidence discs (docs/13-typography-mark.md), drawn on the wordmark's
   own 24-unit grid: the filled disc IS the mark's period lifted out.

   No colour rule here, on purpose. currentColor means the glyph inherits
   whatever ink the row is set in, which makes "match confidence is never
   colour-coded" structurally true rather than a convention. The word beside it
   is the carrier that stands alone; the disc is the second, and there is no
   third. */
.cg { width: .72rem; height: .72rem; flex: 0 0 auto; vertical-align: -.09em; }
.conf { display: inline-flex; align-items: center; gap: .32rem; }

/* The gate's outcome pictograms (partials.html's og/*): a bell, two rows, a
   struck circle. Same construction as the discs -- inline SVG, currentColor,
   the word beside them is the carrier -- one size everywhere they appear,
   and centred in the flex rows that lay them out beside a baseline-aligned
   word. */
.og { width: .95rem; height: .95rem; flex: 0 0 auto; vertical-align: -.2em; align-self: center; }

/* The gate glyph on the interrupt flag. Before this the flag carried the word
   plus a COLOUR, which is one of {word, glyph, size} and therefore a breach of
   the two-carrier rule -- and colour can never be the carrier. */
.flag { display: inline-flex; align-items: baseline; gap: .35rem; }
.flag .g { display: inline-flex; align-self: center; }

/* Sign out lost its reassurance paragraph, which was also its top margin. */
.form-card h1 + form { margin-top: 1.5rem; }

/* ==========================================================================
   THE FRONT DOOR
   Serves /, /preview/homepage and the staff preview /s/home
   (internal/web/frontdoor.go): the design's main as four rails of listing
   cards since 2026-09-20 (docs/ops/front-door-rails.md). Every rule is
   scoped under .mockup, and that is not tidiness: .card, .row, .title,
   .note, .price and .placeholder all already mean something else in this
   file, and unscoping any one of them would restyle a real page three
   screens away. The values are the reference design's own, converted from
   its 14px body to our 18px one where they are type and kept as stated
   where they are geometry.
   ========================================================================== */
.mockup .eyebrow {
  font-family: var(--sans); font-weight: 500; font-size: .69rem;
  letter-spacing: .22em; text-transform: uppercase; color: var(--accent);
  display: block; margin: 0;
}
.mockup .label {
  font-family: var(--sans); font-weight: 500; font-size: .625rem;
  letter-spacing: .2em; text-transform: uppercase; margin: 0;
}
.mockup .muted { color: var(--text-muted); }

/* The design's hatch: the stand-in for a photograph it does not have. Kept as
   literals rather than tokens because it is not a surface of ours -- it is a
   picture of an absent picture, and it should read as one in either theme. */
.mockup .placeholder {
  background: repeating-linear-gradient(135deg, #E9E6DF 0 8px, #E2DFD7 8px 16px);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--mono); font-size: .625rem; color: rgba(20, 20, 20, .45);
}

.mockup .section { padding: 0 0 3.5rem; }
.mockup .crumb { display: flex; align-items: baseline; gap: .875rem; margin: 2rem 0 1.75rem; }
/* Sans BY HAND, not by the sweep: this rule sets no text-transform, so the
   rule that moved the labels did not reach it. The design's own crumb path is
   its --sans and this inherited the body serif, which was the one place the
   two documents disagreed on family without disagreeing on case. */
.mockup .crumb .path { font-family: var(--sans); font-size: .78rem; color: var(--text-muted); }
.mockup .section-head {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 2rem; margin-bottom: 1.75rem;
}
.mockup .section-head h2 {
  font-family: var(--display); font-weight: 300; font-size: 2.5rem;
  line-height: 1.1; margin: 0;
}
.mockup .section-head .note {
  font-family: var(--display); font-style: italic; font-size: 1.15rem;
  color: var(--text-muted);
}

.mockup .card {
  background: var(--surface); border: 1px solid var(--rule);
  border-radius: 0; padding: .875rem .875rem 1.375rem; margin: 0; min-width: 0;
}
.mockup .card .placeholder { aspect-ratio: 1; }
/* The well: the listing's own photograph, or our own file, in the same
   square well the design drew its picture in, whole rather than cropped
   (the showcase's .grail-art makes the same choice), on --sunken and never
   tinted (docs/12-design-system.md prohibition 5). The well is the card's
   hop since 2026-09-19 -- a block anchor, so the picture and not a text
   baseline is what a pointer finds; the title is a link that reads as the
   design's title; a draft on the staff preview has a dashed edge. */
.mockup .card a.well { display: block; }
.mockup .card a.well:hover { text-decoration: none; } /* the hatch's printed number is not a word to underline */
.mockup .card .art, .mockup .card .photo {
  display: block; width: 100%; aspect-ratio: 1; object-fit: contain;
  background: var(--sunken);
}
.mockup .card .title a, .mockup .row .title a, .mockup .sale .name a { color: inherit; text-decoration: none; }
.mockup .card .title a:hover, .mockup .row .title a:hover, .mockup .sale .name a:hover { text-decoration: underline; }
.mockup .card.draft { border-style: dashed; }
.mockup .card .label { margin-top: .5rem; color: var(--text-muted); }
.mockup .card a.out { display: block; color: var(--accent); text-decoration: none; }
.mockup .card a.out:hover { text-decoration: underline; }
.mockup .card .cat { color: var(--accent); margin: 1.125rem 0 .375rem; }
.mockup .card .title { font-family: var(--display); font-size: 1.375rem; line-height: 1.1; }
.mockup .card .note {
  font-family: var(--display); font-style: italic; font-size: 1rem;
  color: var(--text-muted); margin-top: .375rem; line-height: 1.3;
}

/* THE RAIL (docs/ops/front-door-rails.md, "The rail"): native horizontal
   scroll -- a flex row that overflows sideways and snaps near a card --
   in fractional columns, so the next card peeks and says there is more
   without a "View all" there is no index to land on: 2.4 on a desktop,
   1.3 on a tablet, 1.1 on a phone, where the row bleeds into the gutter
   as .facets does. The picture fills the card's width, so the column
   count is its size: these are twice the picture the first rails drew at
   4.4 and 2.4, and on a phone, where twice would be wider than the
   screen, as large as still leaves the next card peeking. The arrows sit in the head and stay hidden until
   rails.js finds the row overflowing; the cards' anchors are in tab
   order whatever the arrows do. The list's own marks and margins are
   reset: the cards are the list. A [hidden] rule beside each display rule
   below, since a display set here would otherwise win over the attribute
   the script toggles. */
.mockup .lane .section-head { flex-wrap: wrap; align-items: baseline; gap: .75rem 2rem; }
.mockup .lane .section-head .note { flex: 1 1 auto; }
.mockup .lane button.pause { flex: 0 0 auto; }
.mockup .lane button.pause[aria-pressed="true"] { border-color: var(--accent); color: var(--accent); }
.mockup .lane .arrows { display: flex; gap: .375rem; flex: 0 0 auto; }
.mockup .lane .arrows[hidden] { display: none; }
.mockup .lane .arrow {
  font-family: var(--mono); font-size: .8rem; line-height: 1; cursor: pointer;
  background: transparent; color: var(--text); border: 1px solid var(--rule-strong);
  border-radius: 2px; padding: .45rem .7rem;
}
.mockup .lane .arrow:hover { border-color: var(--accent); color: var(--accent); }
.mockup .lane .arrow[disabled] { opacity: .4; cursor: default; }
.mockup .lane .arrow[disabled]:hover { border-color: var(--rule-strong); color: var(--text); }
.mockup .track {
  --rail-gap: 1.375rem;
  --rail-cols: 2.4;
  display: flex; gap: var(--rail-gap); margin: 0; padding: 0 0 .5rem; list-style: none;
  overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x proximity; scrollbar-width: thin;
}
.mockup .track > .card {
  flex: 0 0 calc((100% - (var(--rail-cols) - 1) * var(--rail-gap)) / var(--rail-cols));
  scroll-snap-align: start;
  display: flex; flex-direction: column;
}
/* THE PAIRS. Under the well the card's text runs one fact at each edge --
   the set and the grade, the figure and the countdown, the format and the
   hop -- with the name alone on its line between them. A pair too wide for
   its card wraps to two lines. The head's chips and the foot's hop keep the
   right edge when they wrap, so the hop is never on the left of one card
   and the right of the next; a countdown that wraps returns to the left,
   under its figure, where the card always set it. The
   head is one chip high whether or not it holds a chip, and is drawn
   though it hold nothing, so every name in a rail starts at one height and
   none sits on the well. The foot takes the card's spare height above it,
   so a rail's hops share a baseline wherever none wraps.

   The countdown's box is as wide as the longest rung of the ladder at the
   size step's larger size ("ends in 23 h 59 min", "ends in 59 min 59 s":
   nineteen of Azeret's .69em advances, its .04em tracking included), and
   its text starts at the box's left. A box sized to its text, set against
   the right edge, would slide "ends in" sideways each time the figure
   lost a digit, which is the reflow docs/13-typography-mark.md bars; and
   a width set in rem rather than em holds through the size step, so the
   pair wraps or does not by the figure alone, once, when the page is
   drawn, and never mid-view. */
.mockup .card .pair {
  display: flex; flex-wrap: wrap; justify-content: space-between;
  align-items: baseline; gap: .3rem 1rem;
}
.mockup .card .pair > * { min-width: 0; }
.mockup .card .head > :last-child:not(:first-child),
.mockup .card .foot > :last-child:not(:first-child) { margin-left: auto; }
.mockup .card .head { align-items: center; min-height: 1.6rem; margin: 1.125rem 0 .375rem; }
.mockup .card .head .cat { margin: 0; }
.mockup .card .deal { margin-top: .6rem; }
.mockup .card .deal .ends { min-width: calc(19 * .69 * .88rem); text-align: left; }
.mockup .card .foot { margin-top: auto; padding-top: .55rem; }
.mockup .card .pair .figure, .mockup .card .pair .ends,
.mockup .card .pair .meta, .mockup .card .pair .label { margin-top: 0; }
/* The chips row: the grade, and "New" on a listing under an hour old --
   facts in mono, no hue (docs/12-design-system.md). */
.mockup .card .chips { display: flex; flex-wrap: wrap; gap: .35rem; }
.mockup .card .chips:empty { display: none; }
.mockup .card .chip { font-size: .58rem; padding: .22rem .45rem; }
/* The mono line: the format, the bids, the seller's offer, the sample
   word -- what a machine recorded, so the machine's face. */
.mockup .card .meta {
  display: block; font-family: var(--mono); font-size: .66rem; letter-spacing: .04em;
  text-transform: none; color: var(--text-muted); margin-top: .55rem;
}
/* The figure in the price face, and the word for what it is beside it. */
.mockup .card .figure { margin-top: .5rem; display: flex; align-items: baseline; gap: .45rem; flex-wrap: wrap; }
.mockup .card .figure .price { font-family: var(--display); font-size: 1.5rem; font-weight: 400; line-height: 1.1; }
.mockup .card .figure .word { font-size: .72rem; color: var(--text-muted); }
/* THE COUNTDOWN, in mono tabular from the listing's own close, under
   WCAG 2.2.1's real-time exception (an auction is that standard's own
   example). A size step under an hour (.near, which the server sets and
   the script keeps) is the second carrier beside the word, and there is
   no third: urgency gets no hue, in the last minute least of all
   (docs/12-design-system.md prohibition 3; docs/13-typography-mark.md's
   two-carrier rule). Azeret Mono holds its advance at every weight, so
   the figure never reflows its own column as it ticks. */
.mockup .card .ends {
  font-family: var(--mono); font-size: .72rem; letter-spacing: .04em;
  color: var(--text); margin-top: .45rem;
}
.mockup .card .ends.near { font-size: .88rem; }
/* A card whose auction closed under an open page: the script writes
   "closed", swaps the photograph for our art and points the anchors at
   the search (rails.js). Its figures are muted and its picture dimmed;
   the anchors keep their contrast, since they still go somewhere, and
   the card is never removed from under a thumb. */
.mockup .card.closed .art, .mockup .card.closed .placeholder { opacity: .6; }
.mockup .card.closed .ends, .mockup .card.closed .meta, .mockup .card.closed .figure { color: var(--text-muted); }

@media (max-width: 68rem) {
  .mockup .track { --rail-cols: 1.3; }
}
@media (max-width: 46rem) {
  .mockup .track {
    --rail-cols: 1.1;
    margin: 0 -1.25rem; padding: 0 1.25rem .5rem;
    scroll-snap-type: x mandatory; scroll-padding-inline: 1.25rem;
  }
  .mockup .lane .section-head { flex-direction: column; align-items: flex-start; gap: .5rem; }
}

/* THE PAGE BEFORE THE RAILS -- the featured block's grid and the design's
   two columns (home/sections in design-home.html; frontdoorlegacy.go),
   drawn while migration 0026 is staged and not promoted, so the live
   schema renders as it did until the promotion. Every rule from here to
   the end of the section goes with it. */
.mockup .cards { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1.375rem; }
.mockup .two-col .curated-fine { margin-top: 1rem; }
.mockup .two-col { display: grid; grid-template-columns: 1.15fr 1fr; gap: 3rem; padding: 0 0 3.5rem; }
.mockup .two-col > div { min-width: 0; }
.mockup .two-col .eyebrow { margin-bottom: .875rem; }
.mockup .row {
  display: grid; grid-template-columns: minmax(0, 1fr) 6.875rem 3rem; gap: .875rem;
  padding: .875rem 0; border-top: 1px solid var(--rule); align-items: baseline;
}
.mockup .row .title { font-family: var(--display); font-size: 1.28rem; line-height: 1.15; }
.mockup .row .sub { font-size: .72rem; color: var(--text-muted); margin: .125rem 0 0; }
.mockup .row .price { font-size: .82rem; text-align: right; font-weight: 400; }
.mockup .row .ago { font-size: .69rem; color: var(--text-muted); text-align: right; }

.mockup .sale {
  display: grid; grid-template-columns: 5.75rem minmax(0, 1fr); gap: 1rem;
  padding: .875rem 0; border-top: 1px solid var(--rule);
}
.mockup .sale .date { font-family: var(--display); font-size: 1.28rem; white-space: nowrap; }
.mockup .sale .name { font-size: .88rem; }
.mockup .sale .where { font-size: .69rem; color: var(--text-muted); margin-top: .2rem; }

.mockup .pulse-head { margin-top: 2.5rem; }
.mockup .pulse { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 1.5rem; }
.mockup .pulse > a {
  color: inherit; text-decoration: none;
  padding: .75rem 0; border-top: 1px solid var(--rule);
  display: flex; justify-content: space-between; align-items: baseline; gap: .625rem;
}
.mockup .pulse > a > span:first-child { font-size: .78rem; color: var(--text-muted); }
.mockup .pulse > a:hover > span:first-child { text-decoration: underline; }
.mockup .pulse .n { white-space: nowrap; }
.mockup .pulse > a > span:last-child { font-family: var(--display); font-size: 1.625rem; }


@media (max-width: 68rem) {
  .mockup .cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .mockup .two-col { grid-template-columns: minmax(0, 1fr); gap: 2.5rem; }
}
@media (max-width: 46rem) {
  .mockup .cards { grid-template-columns: minmax(0, 1fr); }
  .mockup .pulse { grid-template-columns: minmax(0, 1fr); }
}
