/* ---------------------------------------------------------------------
   NinePocke — the public page.

   Every decision here traces to docs/design/public-site.md; nothing is
   invented. Section references below are that document's.

   The governing idea (§1): the desk changes with the light, the paper never
   does. Everything that is not paper lives on --desk and flips with the
   system theme; --page / --pocket / --ink / --gold are identical in both,
   because a binder page does not turn black at night.
   --------------------------------------------------------------------- */

/* §7. First in the file, so the intent is impossible to miss; the
   !important flags mean position is not what makes it win. Nothing becomes
   unavailable under reduced motion — only the interpolation goes. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* §2.2 type scale and §3 spacing. Its own scale, not the Binder's 14px
   dashboard scale. Declared before the four role blocks below, which must
   stay contiguous and in order. */
:root {
  --t-hero:    clamp(44px, 9vw, 84px);
  --t-display: clamp(30px, 4.4vw, 44px);
  --t-lead:    clamp(20px, 2.4vw, 26px);
  --t-title:   19px;
  --t-body:    17px;
  --t-body-sm: 15px;
  --t-meta:    13px;
  --t-micro:   11px;

  --s-1:  4px;
  --s-2:  8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 24px;
  --s-6: 32px;
  --s-7: 48px;
  --s-8: 64px;
  --s-9: 96px;
  --s-10: 128px;
}

/* ---------------------------------------------------------------------
   §9.3. Four blocks follow, in exactly this order — added values, :root,
   the dark media query, .paper. Nothing may be inserted between them.
   .paper wins over the dark block only because it comes after it: both are
   specificity (0,1,0), so reordering would silently invert paper in dark
   mode, which is the one thing §1 exists to prevent.
   --------------------------------------------------------------------- */

/* The five values the site adds. §9.2 says why each exists.
   These live here, not in the copied tokens.css, so the copy stays a faithful
   copy of api/public/binder/tokens.css and its drift comment stays true. */
:root {
  --desk-light: #E2DCCF;   /* the daylight desk        */
  --gold-deep:  #895306;   /* gold as type, and the light button fill */
  --gold-lit:   #D39945;   /* gold in dark mode        */
  --ok-lit:     #A3B86B;   /* success in dark mode     */
  --sheet-edge: #C9C0AE;   /* the hero sheet's outer edge, light mode only */
}

:root {
  color-scheme: light dark;

  --desk:           var(--desk-light);
  --desk-ink:       var(--ink);        /* #1C1917  12.80:1 on the desk */
  --desk-ink-soft:  var(--ink-soft);   /* #57534E   5.58:1 */
  --desk-hairline:  rgba(28,25,23,.14);
  --accent:         var(--gold);       /* marks only — 3.60:1 */
  --accent-text:    var(--gold-deep);  /* type      — 4.65:1 */
  --btn-fill:       var(--gold-deep);
  --btn-fill-hover: #7A4A05;
  --btn-fill-press: #6C4105;
  --on-accent:      var(--page);       /* 5.68:1 on the fill */
  --btn-focus:      var(--ink);
  --danger:         #9A3412;           /* 5.35:1 on the desk */
  --ok:             var(--bucket-sleeve);
  --focus:          var(--gold);
  /* §4.2 demands two different sheet shadows and §9.3 forbids a per-theme
     override below these blocks. A role token is the only thing that
     satisfies both. Three layers here: --page on the sand desk is 1.22:1,
     so the boundary is carried by the drop shadow AND the 1px cut edge
     together, and neither may be dropped. */
  --sheet-shadow:   0 14px 34px rgba(60,48,30,.20),
                    0 2px 5px rgba(60,48,30,.14),
                    0 0 0 1px var(--sheet-edge);
}

@media (prefers-color-scheme: dark) {
  :root {
    --desk:           var(--surface);      /* #1A1714 */
    --desk-ink:       var(--chrome-ink);   /* #E7E2D9  13.84:1 */
    --desk-ink-soft:  var(--chrome-soft);  /* #A8A096   6.91:1 */
    --desk-hairline:  var(--hairline-dark);
    --accent:         var(--gold-lit);     /* 7.15:1 */
    --accent-text:    var(--gold-lit);
    --btn-fill:       var(--gold-lit);
    --btn-fill-hover: #DEA857;
    --btn-fill-press: #C08739;
    --on-accent:      var(--surface);      /* 7.15:1 on the fill */
    --btn-focus:      var(--page);
    --danger:         var(--alert);        /* #F87171  6.45:1 */
    --ok:             var(--ok-lit);       /* #A3B86B  8.18:1 */
    --focus:          var(--gold-lit);
    /* Two layers, not three: the dark sheet measures 15.95:1 against the
       desk and takes no ring at all. §4.2 */
    --sheet-shadow:   0 18px 40px rgba(0,0,0,.45),
                      0 2px 6px rgba(0,0,0,.30);
  }
}

/* Paper does not invert. §1. */
.paper {
  --desk:           var(--page);
  --desk-ink:       var(--ink);
  --desk-ink-soft:  var(--ink-soft);
  --desk-hairline:  rgba(28,25,23,.14);
  --accent:         var(--gold);
  --accent-text:    var(--gold-deep);
  --danger:         #9A3412;
  --ok:             var(--bucket-sleeve);
  --focus:          var(--gold);
}
.paper--recessed { --desk: var(--pocket); }

/* ---------------------------------------------------------------------
   Below this line every rule reads var(--desk) / var(--desk-ink) and is
   correct in both themes and both registers. A raw hex here is a defect,
   with the exceptions §9.3 documents: rgba() shadow, weld and hairline
   values (alpha over whatever is behind, correct by construction), the
   pocket mouth's white lip, and the theme-independent paper tokens used
   inside a pocket.
   --------------------------------------------------------------------- */

*,
*::before,
*::after { box-sizing: border-box; }

/* An author `display` beats the UA's [hidden] rule, and the form, its error
   and the success slip all toggle `hidden` while carrying one. */
[hidden] { display: none !important; }

html {
  scroll-behavior: smooth;          /* §7 */
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--desk);
  color: var(--desk-ink);
  font-family: var(--font-body);
  font-size: var(--t-body);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

h1,
h2 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 600;
}

p { margin: 0; }

ol,
ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* §3. Section rhythm and gutter are set independently — never a padding
   shorthand, which would couple them. */
section {
  padding-block: clamp(var(--s-7), 9vw, var(--s-9));
  background: var(--desk);
  color: var(--desk-ink);
}

.wrap {
  max-width: 1080px;
  margin-inline: auto;
  padding-inline: 20px;
}

@media (min-width: 640px) { .wrap { padding-inline: 32px; } }
@media (min-width: 1024px) { .wrap { padding-inline: 48px; } }

/* §2.2 measure. A heading never gets a wider column than the prose beneath
   it, so 34rem governs headings, leads and paragraphs alike. */
.column { max-width: 34rem; }

/* §5.2 puts the thesis's heading, lead and prose in one *centred* column.
   `scan` is organised the same way — its status line is already centred and
   its three steps are a symmetric 3-up grid — so it takes the same treatment.
   Below ~640px the 34rem cap does not bind and this centring is inert. */
.column--centred { margin-inline: auto; }

h2 {
  font-size: var(--t-display);
  line-height: 1.1;
  letter-spacing: -.01em;
  text-wrap: balance;
}

/* The motto and every section's lead are the same voice at the same size, so
   they share one declaration rather than two that can drift apart. */
.lead,
.motto {
  max-width: 30rem;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--t-lead);
  line-height: 1.45;
  letter-spacing: 0;
}

.lead { margin-top: var(--s-4); }

.column p + p { margin-top: var(--s-4); }
.column .lead + p { margin-top: var(--s-5); }

.eyebrow,
.page-number,
.hero-card-label,
.tail-label,
.step-number {
  font-family: var(--font-data);
  font-weight: 600;
  font-size: var(--t-micro);
  line-height: 1.2;
  letter-spacing: .10em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
}

.status-line,
.tail-note,
.tab-rule,
footer p {
  font-family: var(--font-data);
  font-weight: 500;
  font-size: var(--t-meta);
  line-height: 1.5;
  letter-spacing: .02em;
  font-variant-numeric: tabular-nums;
}

/* §6.1. The underline is always present; hover changes its weight, not its
   colour — a colour-shift hover reads as software. */
.text-link {
  display: inline-block;
  /* §10 binds a 44px hit area on this inline link. §5.1's `padding-block: 6px`
     alone does not reach it — 17px at the inherited 1.6 is a 27.2px line box,
     so the measured height was 39.2px. An explicit 32px line box makes the
     arithmetic exact and stays on the 4px grid: 32 + 2 × 6 = 44. */
  padding-block: 6px;
  line-height: var(--s-6);
  color: var(--accent-text);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
  transition: text-decoration-thickness 160ms ease-out;
}

.text-link:hover { text-decoration-thickness: 2px; }

/* §6.4. Never removed. The `input` and `button` selectors have nothing to
   match yet — Task 4 builds both controls — and are declared here precisely
   so that task cannot inherit a wrong default. */
a:focus,
button:focus,
input:focus,
a:focus-visible,
button:focus-visible,
input:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: inherit;
}

a:focus:not(:focus-visible),
button:focus:not(:focus-visible),
input:focus:not(:focus-visible) { outline: none; }

/* §6.4's one exception, and it must be implemented: on the gold-filled button
   a gold ring is invisible, so the button takes --btn-focus — --ink in light,
   --page in dark. What that ring is measured against is the 2px of bare desk
   the offset exposes, not the fill: 12.80:1 light, 15.95:1 dark. Directly on
   the fill it would measure 1.91–2.77:1 and fail 3:1, so `outline-offset: 2px`
   is load-bearing on this control and may not be collapsed to 0. */
button:focus,
button:focus-visible { outline-color: var(--btn-focus); }

/* =====================================================================
   1. masthead — §5.1, and the 3×3 sheet of §4
   ===================================================================== */

.eyebrow {
  color: var(--accent-text);
  margin-bottom: var(--s-3);
}

h1 { margin: 0; }

.wordmark {
  display: block;
  font-size: var(--t-hero);
  line-height: .98;
  letter-spacing: -.02em;
}

.motto {
  display: block;
  margin-top: var(--s-5);
  color: var(--desk-ink);
}

.status-line { color: var(--desk-ink-soft); }
.masthead-text .status-line { margin-top: var(--s-5); }
.masthead-action { margin-top: var(--s-4); }

.sheet-wrap { margin-top: var(--s-7); }

/* §4.2. The sheet is a manufactured object: a page ground, hard-stop weld
   seams down the middle of every gap, a barely-there sheen, and a cut outer
   edge. Without the welds, eight empty pockets read as eight rectangles. */
.sheet {
  --sheet-pad: 14px;
  --sheet-gap: 10px;
  --weld: rgba(28,25,23,.10);

  /* The two seam positions, expressed once and substituted into both
     gradients. The 100% resolves along each gradient's own axis — width for
     `to right`, height for `to bottom` — and the grid's rows and columns are
     laid out by the same (available − padding − gaps) / 3 arithmetic, so one
     expression is correct on both axes. */
  --seam-a: calc(var(--sheet-pad) + (100% - 2 * var(--sheet-pad) - 2 * var(--sheet-gap)) / 3 + var(--sheet-gap) / 2);
  --seam-b: calc(var(--sheet-pad) + 2 * (100% - 2 * var(--sheet-pad) - 2 * var(--sheet-gap)) / 3 + 1.5 * var(--sheet-gap));

  position: relative;
  padding: var(--sheet-pad);
  border-radius: 6px;
  background-color: var(--page);
  background-image:
    linear-gradient(to right,
      transparent calc(var(--seam-a) - .5px), var(--weld) calc(var(--seam-a) - .5px),
      var(--weld) calc(var(--seam-a) + .5px), transparent calc(var(--seam-a) + .5px),
      transparent calc(var(--seam-b) - .5px), var(--weld) calc(var(--seam-b) - .5px),
      var(--weld) calc(var(--seam-b) + .5px), transparent calc(var(--seam-b) + .5px)),
    linear-gradient(to bottom,
      transparent calc(var(--seam-a) - .5px), var(--weld) calc(var(--seam-a) - .5px),
      var(--weld) calc(var(--seam-a) + .5px), transparent calc(var(--seam-a) + .5px),
      transparent calc(var(--seam-b) - .5px), var(--weld) calc(var(--seam-b) - .5px),
      var(--weld) calc(var(--seam-b) + .5px), transparent calc(var(--seam-b) + .5px));
  background-repeat: no-repeat;
  box-shadow: var(--sheet-shadow);
}

/* The one allowed gradient, §4.2. If it is noticeable as a gradient it is
   too strong. */
.sheet::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(135deg, rgba(255,255,255,.5) 0%, transparent 40%);
  opacity: .14;
  pointer-events: none;
}

.sheet-grid {
  position: relative;
  display: grid;
  /* minmax(0, 1fr) rather than 1fr: a bare 1fr floors at the item's
     min-content width, and the centre pocket contains words. Without this
     the sheet stops shrinking below ~360px and the page scrolls sideways —
     §10's claim that nothing here has a fixed min-width. */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sheet-gap);
}

/* 5:7 is 2.5in × 3.5in and is non-negotiable — a square pocket stops reading
   as a trading card page instantly. The mouth is open: hairlines on three
   sides, a soft lip across the top, and rounder top corners, because a mouth
   opened thousands of times is rounder than the sealed base. */
.pocket {
  aspect-ratio: 5 / 7;
  border-radius: 6px 6px 4px 4px;
  background: var(--pocket);
  box-shadow:
    inset  1px 0 0 rgba(28,25,23,.09),
    inset -1px 0 0 rgba(28,25,23,.09),
    inset  0 -1px 0 rgba(28,25,23,.09),
    inset  0 2px 2px -1px rgba(255,255,255,.55);
}

/* A filled pocket is brighter than an empty well, exactly as in the Binder,
   and keeps the same lip — which then reads as the card sitting a little
   below the mouth, which is what a card in a pocket does. */
.pocket--filled { background: var(--page); }

/* §4.1. A typographic card carrying the thesis rather than a card identity —
   it invents nothing, which a plausible 1989 Griffey would not manage.
   The pocket is paper in both themes, so this uses the paper tokens
   directly rather than the register's --accent / --desk-ink. */
.hero-card {
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  height: 100%;
  padding: 8px;
  overflow: hidden;
  hyphens: none;
}

.hero-card .subject {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 17px;
  line-height: 1.3;
  color: var(--ink);
  text-wrap: balance;
}

.hero-card-rule {
  width: 24px;
  height: 1px;
  margin-block: var(--s-2);
  background: var(--gold);
}

.hero-card-label { color: var(--ink-soft); }

.page-number {
  margin-top: var(--s-3);
  text-align: center;
  color: var(--accent-text);
}

@media (min-width: 640px) {
  .sheet {
    --sheet-pad: 18px;
    --sheet-gap: 12px;
  }
  .hero-card { padding: 10px; }
  .hero-card .subject { font-size: var(--t-title); }
  .sheet-wrap {
    max-width: 420px;
    margin-inline: auto;
  }
}

@media (min-width: 900px) {
  .masthead-layout {
    display: grid;
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
    gap: var(--s-8);
    align-items: center;
  }
  .sheet-wrap {
    margin-top: 0;
    margin-inline: 0;
    justify-self: end;
  }
}

/* =====================================================================
   2. thesis — §5.2, and the long-tail rule
   ===================================================================== */

/* Mobile is the rotated device: two columns, five rows, and the 1fr spacer
   row is the whole mechanism that holds the two clusters apart. Nothing is
   absolutely positioned, so the clusters cannot collide — if the text grows,
   row 3 collapses first and then the grid simply grows past its min-height. */
.tail-rule {
  display: grid;
  grid-template-columns: 10px minmax(0, 1fr);
  grid-template-rows: auto auto 1fr auto auto;
  column-gap: 26px;
  row-gap: var(--s-2);
  min-height: 220px;
  margin-top: var(--s-7);
}

.tail-rule .marks {
  position: relative;
  grid-column: 1;
  grid-row: 1 / -1;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
}

.tail-rule .marks::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 1px;
  transform: translateX(-50%);
  background: rgba(28,25,23,.14);
}

/* Not new iconography: a filled square and an outlined square are already
   how this product tells two handling outcomes apart. The site's only icons
   are the Binder's squares. §5.2 */
.mark {
  width: 10px;
  height: 10px;
}

.mark--filled { background: var(--accent); }
.mark--outlined { box-shadow: inset 0 0 0 1.5px var(--ink-soft); }

.tail-label { color: var(--ink-soft); }
.tail-note--start { color: var(--accent-text); }
.tail-note--end { color: var(--ink-soft); }

.tail-label--start { grid-area: 1 / 2; }
.tail-note--start  { grid-area: 2 / 2; }
.tail-spacer       { grid-area: 3 / 2; }
.tail-note--end    { grid-area: 4 / 2; }
.tail-label--end   { grid-area: 5 / 2; }

/* The ticks exist to connect an annotation to a point on a horizontal axis.
   When the axis is vertical each annotation already sits on its own row
   beside its own mark, so they are dropped rather than rotated. §5.2 */
@media (min-width: 640px) {
  .tail-rule {
    grid-template-columns: 100%;
    grid-template-rows: auto auto auto;
    column-gap: 0;
    row-gap: 0;
    min-height: 0;
  }

  .tail-rule .marks {
    grid-column: 1;
    grid-row: 2;
    flex-direction: row;
    width: 100%;
    height: 10px;
  }

  .tail-rule .marks::before {
    top: 50%;
    bottom: auto;
    left: 0;
    right: 0;
    width: auto;
    height: 1px;
    transform: translateY(-50%);
  }

  .tail-note {
    position: relative;
    padding-bottom: 12px;
  }

  .tail-note::after {
    content: "";
    position: absolute;
    bottom: -5px;
    width: 1px;
    height: 17px;
    background: currentColor;
    opacity: .55;
  }

  .tail-note--start {
    grid-area: 1 / 1;
    justify-self: start;
    max-width: 38%;
  }
  .tail-note--start::after { left: 0; }

  /* Right edge at 78% along the rule: justify-self puts the margin box's
     right edge at the container's, and the 22% margin walks it back. The
     two max-widths plus that margin sum to 100%, so the pair cannot overlap
     at any width. */
  .tail-note--end {
    grid-area: 1 / 1;
    justify-self: end;
    max-width: 40%;
    margin-right: 22%;
    text-align: right;
  }
  .tail-note--end::after { right: 0; }

  .tail-label {
    grid-row: 3;
    grid-column: 1;
    margin-top: var(--s-2);
  }
  .tail-label--start { justify-self: start; }
  .tail-label--end { justify-self: end; }

  .tail-spacer { display: none; }
}

/* =====================================================================
   3. scan — §5.3
   ===================================================================== */

/* A recessed spread between two brighter ones. */
#scan {
  box-shadow:
    inset 0 1px 0 rgba(28,25,23,.06),
    inset 0 -1px 0 rgba(28,25,23,.06);
}

.steps {
  display: grid;
  gap: var(--s-6);
  margin-top: var(--s-7);
}

.step {
  display: grid;
  grid-template-columns: 96px minmax(0, 1fr);
  column-gap: var(--s-4);
  align-items: start;
}

.step > .glyph { grid-area: 1 / 1 / span 3 / span 1; }
.step-number,
.step-title,
.step-text { grid-column: 2; }

.step-number { color: var(--accent-text); }

/* §2.2's 34rem governs every text column on the page, and in the row layout
   the step's text lane is otherwise unbounded — measured 45.19rem at 899px. */
.step-title,
.step-text { max-width: 34rem; }

.step-title {
  margin-top: var(--s-2);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--t-title);
  line-height: 1.3;
}

.step-text {
  margin-top: var(--s-2);
  font-size: var(--t-body-sm);
  line-height: 1.55;
  color: var(--desk-ink-soft);
}

/* Each glyph is a miniature of the hero sheet — the page's whole icon set is
   this, the corner brackets, the index tab and the two squares. No icon
   library, no emoji, and nothing that depicts a card that does not exist. */
/* All three glyph boxes are the same size so the three step numbers sit on one
   line. Two separate things were needed for that.

   min-height: the stack's front sheet is 12px narrower than the other two
   minis, so its content comes up about 5px short of theirs.

   display: flow-root: .glyph--stack .mini--front carries `margin-top: 12px`
   and is the first IN-FLOW child of .glyph — the back and mid minis are
   absolutely positioned — so with no block formatting context that margin
   collapsed out of .glyph and became .glyph's own, dropping step 03 by 12px.
   It only showed at >=900px, because below that .glyph is a grid item and grid
   items already establish an independent formatting context. getComputedStyle
   reports marginTop: 0px either way, so this is only findable by measuring the
   rendered boxes. */
.glyph {
  position: relative;
  display: flow-root;
  width: 96px;
  min-height: 125px;
}

.mini {
  padding: 7px;
  border-radius: 4px;
  background: var(--page);
  box-shadow:
    0 1px 2px rgba(28,25,23,.10),
    0 0 0 1px rgba(28,25,23,.08);
}

.mini-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 5px;
}

.mini-pocket {
  aspect-ratio: 5 / 7;
  border-radius: 2px;
  background: var(--pocket);
  box-shadow: inset 0 0 0 1px rgba(28,25,23,.06);
}

/* Pocket positions, not data. No card name, no set, no value. */
.mini-pocket--read {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--page);
  box-shadow: inset 0 0 0 1px rgba(28,25,23,.12);
  font-family: var(--font-data);
  font-size: 9px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-soft);
}

/* A viewfinder is corner brackets. It is explicitly not a dashed rectangle:
   capture-and-review.md §1 calls that "the universal shorthand for camera
   target" and bans it, and CLAUDE.md #14 bans unmotivated dashes generally.
   There is no dash anywhere on this site. */
.bracket {
  position: absolute;
  width: 14px;
  height: 14px;
  border: 0 solid var(--accent);
}

.bracket--tl { top: -8px; left: -8px; border-top-width: 2px; border-left-width: 2px; }
.bracket--tr { top: -8px; right: -8px; border-top-width: 2px; border-right-width: 2px; }
.bracket--bl { bottom: -8px; left: -8px; border-bottom-width: 2px; border-left-width: 2px; }
.bracket--br { bottom: -8px; right: -8px; border-bottom-width: 2px; border-right-width: 2px; }

.glyph--stack .mini { width: calc(100% - 12px); }

.glyph--stack .mini--back {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 12px;
}

.glyph--stack .mini--mid {
  position: absolute;
  top: 6px;
  right: 6px;
  bottom: 6px;
}

.glyph--stack .mini--front {
  position: relative;
  margin-top: 12px;
  margin-right: 12px;
}

.index-tab {
  position: absolute;
  top: 10px;
  left: 0;
  width: 3px;
  height: 22px;
  border-radius: 0 2px 2px 0;
  background: var(--accent);
}

.status-line--centred {
  margin-top: var(--s-5);
  text-align: center;
}

@media (min-width: 900px) {
  .steps {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--s-7);
  }
  .step { display: block; }
  .step > .glyph {
    width: 108px;
    min-height: 142px;
    margin-bottom: var(--s-5);
  }
}

/* =====================================================================
   4. groups — §5.4
   ===================================================================== */

.tabs {
  display: grid;
  gap: var(--s-4);
  margin-top: var(--s-7);
}

/* Not three cards. --pocket on --page is 1.11:1, which is the definition of
   grey-on-grey, so the tab carries a 3px gold edge, a staggered indent that
   breaks the grid, and two voices inside it — display serif over mono. §8 */
.tab {
  padding: 14px 18px;
  background: var(--pocket);
  border-left: 3px solid var(--accent);
  border-radius: 0 4px 4px 0;
  box-shadow: inset 0 0 0 1px rgba(28,25,23,.05);
}

.tab-name {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--t-title);
  line-height: 1.3;
  color: var(--ink);
}

/* Every rule printed here is executable by api/src/rules/predicate.ts as it
   exists today — eq, contains, in, and, or, not over real fact columns.
   There is no range operator and no acquisition field, so no rule on this
   page may use one. §5.4 */
.tab-rule {
  margin-top: var(--s-1);
  color: var(--ink-soft);
}

@media (min-width: 900px) {
  .groups-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--s-8);
    align-items: start;
  }
  .groups-layout .column { max-width: 30rem; }
  .tabs {
    margin-top: 0;
    gap: var(--s-5);
  }
  /* Index tabs down the edge of a binder sit at different depths; the
     stagger is what stops three stacked rectangles reading as a list. It is
     dropped rather than scaled below 900px, where 40px buys nothing. */
  .tab:nth-child(2) { margin-left: 20px; }
  .tab:nth-child(3) { margin-left: 40px; }
}

/* =====================================================================
   5. subscribe — §5.5, with its states from §6.2 and §6.3.
   ===================================================================== */

.subscribe-layout {
  max-width: 30rem;
  margin-inline: auto;
}

.subscribe-layout > p { margin-top: var(--s-4); }

.subscribe-form {
  display: grid;
  gap: var(--s-3);
  margin-top: var(--s-6);
  transition: opacity 120ms ease-in;
}

.subscribe-form.is-leaving { opacity: 0; }

.subscribe-form label {
  font-family: var(--font-data);
  font-weight: 600;
  font-size: var(--t-micro);
  line-height: 1.2;
  letter-spacing: .10em;
  text-transform: uppercase;
  color: var(--desk-ink-soft);
  /* The grid gap is 12px; the label sits 8px above its input. */
  margin-bottom: calc(var(--s-2) - var(--s-3));
}

/* You write on paper, in both themes. The stone border is required, not
   decorative: --page on the light desk is 1.22:1, so without it the control
   has no boundary. #78716C is 4.29:1 on --page and 3.51:1 on the desk. */
.subscribe-form input {
  min-height: 48px;
  padding-inline: var(--s-3);
  font: inherit;
  font-size: var(--t-body);
  background: var(--page);
  color: var(--ink);
  border: 1px solid #78716C;
  border-radius: 4px;
}

.subscribe-form input[aria-invalid="true"] { border-color: var(--danger); }

/* §6.2. The only filled element on the site. Hover moves away from the
   ground and active moves back toward it, so the button presses in. */
.subscribe-form button {
  min-height: 48px;
  /* Wide enough for either label, so "Sending…" does not shrink the button
     and shove the input sideways mid-submit. */
  min-width: 9rem;
  padding-inline: var(--s-5);
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 16px;
  color: var(--on-accent);
  background: var(--btn-fill);
  border: 0;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 160ms ease-out, transform 160ms ease-out;
}

.subscribe-form button:hover { background: var(--btn-fill-hover); transform: translateY(-1px); }

.subscribe-form button:active {
  background: var(--btn-fill-press);
  transform: translateY(0);
  transition-duration: 100ms;
}

/* §6.3. Busy, not unavailable: .72 keeps "Sending…" readable. */
.subscribe-form button:disabled,
.subscribe-form button:disabled:hover {
  background: var(--btn-fill-press);
  transform: none;
  opacity: .72;
  cursor: wait;
}

@media (min-width: 640px) {
  .subscribe-form { grid-template-columns: 1fr auto; }
  .subscribe-form label,
  .subscribe-form .field-error { grid-column: 1 / -1; }
}

/* Never colour alone: each message is led by the Binder's 10px filled
   square, and the words carry the whole meaning without it. */
.field-error,
.form-status.is-error,
.slip-title {
  display: flex;
  align-items: baseline;
  gap: var(--s-2);
}

.field-error::before,
.form-status.is-error::before,
.slip-title::before {
  content: "";
  flex: none;
  width: 10px;
  height: 10px;
  background: currentColor;
}

.slip-title::before { background: var(--ok); }

.field-error,
.form-status { font-size: var(--t-meta); line-height: 1.5; }

.field-error,
.form-status.is-error { color: var(--danger); }

.form-status { color: var(--desk-ink-soft); }
.subscribe-layout > .form-status { margin-top: var(--s-3); }
.form-status:empty { display: none; }

.success-slip {
  margin-top: var(--s-6);
  padding: var(--s-5);
  background: var(--page);
  border-radius: 4px;
  animation: slip-in 180ms ease-out;
}

.slip-title {
  font-family: var(--font-display);
  font-size: var(--t-title);
  color: var(--ink);
}

.slip-note {
  margin-top: var(--s-2);
  font-family: var(--font-data);
  font-weight: 500;
  font-size: var(--t-meta);
  line-height: 1.5;
  letter-spacing: .02em;
  color: var(--ink-soft);
}

/* The two Buttondown redirect targets, /subscribed/ and /confirmed/. The
   slip alone on the desk, centred in the viewport; it arrives already
   decided, so it does not animate in. */
.landing {
  display: grid;
  align-content: center;
  min-height: 80vh;
  min-height: 80svh;
  padding-block: var(--s-8);
}

.landing h1.slip-title { font-weight: 400; }
.landing .subscribe-layout > .eyebrow { margin-top: 0; }
.landing .success-slip { margin-top: 0; animation: none; }
.landing .subscribe-layout > .landing-action { margin-top: var(--s-4); }

@keyframes slip-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* =====================================================================
   footer — §5.6. No links, no year.
   ===================================================================== */

footer {
  padding-block: var(--s-6);
  border-top: 1px solid var(--desk-hairline);
  background: var(--desk);
  color: var(--desk-ink-soft);
  text-align: center;
}
