/* ============================================================================
   briefing.css — daily briefing render (Stage 1 chunk 1.2; dark re-theme chunk 3).
   Smart-Brevity card stack on the dark #0e0e0e system. Brand tokens +
   @font-face declarations come from global.css; this file is layout only.
   Every franchise card is a visual PEER — the fact card is NOT a hero.
   ============================================================================ */

.bf-page {
  min-height: 100svh;
  background: var(--bg); /* warm beige in light mode */
  /* + status-bar/camera inset on top so the wordmark clears the notch / Dynamic
     Island (the Daily header is normal-flow, so no top:0 bar selector covers it). */
  padding: calc(28px + var(--safe-top)) 18px 64px;
}

.bf-wrap { max-width: 620px; margin: 0 auto; }

/* ── Header — ONE utility row (the paper carries the nameplate/date/edition now):
   culture pill left, icon-only calendar trigger right. ───────────────────── */
.bf-header { text-align: center; margin: 2px 0 26px; }
.bf-header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  /* Positioned ancestor for .ar-cal-panel on /briefing ONLY (see the
     .bf-header-row .ar-calbar override below) — full content-column width,
     so the calendar centers on the column instead of on the narrow
     flush-right trigger. /archive's standalone .ar-calbar (not nested here)
     is untouched. */
  position: relative;
}
/* .bf-wordmark / .bf-kicker / .bf-nav: no longer used by /briefing (the daily
   card's own nameplate carries them now) — kept only because archive.html's
   header still renders this trio and shares this stylesheet. Do not delete
   without checking archive.html first. */
.bf-wordmark {
  font-family: 'Fraunces', Georgia, serif;
  font-weight: 600;
  font-size: 1.5rem;
  letter-spacing: -0.01em;
  color: var(--text-primary);
  margin: 0 0 6px;
}
.bf-kicker {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.72rem;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--text-muted); /* dark-theme small text is never lime — Wave D follow-up; light stays --accent-ink below */
  margin: 0 0 4px;
}
html.theme-light .bf-kicker { color: var(--accent-ink); }
/* WO-CONV-3 (§8.3 register 3c) — the standing ambient rule line atop the
   archive/Collection headers. Neutral ink always (§3a: ambient affordances on
   a reading-primary view are never lime), quieter than .bf-kicker since it's
   a supporting line, not the page's own title. */
.ar-rule-line {
  font-family: var(--font-body);
  font-size: 0.8rem;
  color: var(--text-subtle);
  margin: 0 0 4px;
}
.bf-nav { margin: 10px 0 0; }
.bf-nav a {
  font-family: var(--font-body);
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--text-primary); /* dark-theme small text is never lime — Wave D follow-up; light stays --accent-ink below */
  text-decoration: none;
}
html.theme-light .bf-nav a { color: var(--accent-ink); }
.bf-nav a:hover { text-decoration: underline; }
.bf-nav a:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; border-radius: 4px; }

/* ── Culture selector (tease + seam) — subtle editorial row, not an app switcher.
   Live culture is selectable; not-yet-built ones show a muted "Soon" badge. ── */
.bf-culture { margin: 2px 0 8px; }
.bf-cults { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; }
.bf-cult {
  display: inline-flex; align-items: center; gap: 6px;
  background: none; border: none; cursor: pointer;
  font-family: var(--font-body); font-size: 0.8rem; color: var(--text-subtle);
  padding: 3px 4px; border-radius: 8px;
}
.bf-cult.is-active {
  position: relative;
  color: var(--text-primary); font-weight: 700;
}
/* FLAT ink underline (not a cupped/pill shape): a straight 2px rule the width of
   the active tab's text, barely-rounded ends, sitting just below the word.
   Neutral, not lime — the loved-state heart is the card's one signal (§3a
   punctuation law: lime is never decoration/tab-indicator chrome). */
.bf-cult.is-active::after {
  content: ''; position: absolute; left: 4px; right: 4px; bottom: 0;
  height: 2px; border-radius: 1px; background: var(--text-primary);
}
.bf-cult.is-locked { color: var(--text-subtle); cursor: default; opacity: 0.6; }
/* Locked cultures recede — dim "SOON" pill so the active Czech clearly leads. */
.bf-cult-badge {
  font-size: 0.6rem; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--text-disabled); background: var(--border);
  border-radius: 999px; padding: 1px 6px;
}
.bf-cult:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
.bf-cults-hint {
  font-family: var(--font-body); font-size: 0.74rem; color: var(--text-subtle);
  text-align: center; margin: 6px 0 0; min-height: 1em;
}

/* ── Culture DROPDOWN (Daily) — compact trigger pill + bottom sheet ──────────
   Replaces the row above on /briefing (DB-driven: all is_live cultures). Trigger
   centres in the header (.bf-header is text-align:center). The sheet reuses the
   notif-sheet safe-area pattern: the backdrop's bottom padding carries
   var(--safe-bottom, 0px) so the sheet floats above the home indicator. ── */
.bf-cult-trigger {
  display: inline-flex; align-items: center; gap: 8px;
  background: var(--surface-2); border: 1px solid var(--border-strong);
  border-radius: 999px; cursor: pointer; padding: 7px 14px;
  min-height: 44px; /* a11y medium-pass (2026-07-11): was ~35.6px */
  font-family: var(--font-numeric);
  font-size: 0.9rem; font-weight: 700; color: var(--text-primary);
  -webkit-tap-highlight-color: transparent;
}
.bf-cult-trigger-flag { font-size: 1.05rem; line-height: 1; }
.bf-cult-trigger-name { line-height: 1; max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bf-cult-trigger-caret { font-size: 0.85rem; color: var(--text-subtle); margin-left: 2px; }
.bf-cult-trigger:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
/* §ED176 — the selector sits at the top LEFT of .bf-header-row and the calendar
   trigger at the top RIGHT, and only one of them was raised. These four
   declarations are COPIED VERBATIM from .ar-cal-trigger (briefing.css:1789) —
   the same resting shadow, the same hover pair, the same 0.97 press, the same
   transition list. Nothing invented: no new shadow, no new radius, no new
   token. What is deliberately NOT copied is .ar-cal-trigger's SHAPE — its 13px
   radius, 15px padding and --border (the selector keeps its 999px pill,
   --border-strong and 44px min-height). Jake asked for raised and hovering
   like the calendar button, not for the selector to become one.
   ONE RULE SERVES BOTH SURFACES — no is-app fork. The shadows are plain
   rgba(0,0,0,·) rather than a theme token, which is exactly what the calendar
   button already does in both themes on both surfaces; forking here would mean
   inventing an elevation the calendar button does not have. */
.bf-cult-trigger {
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
  transition: background 140ms ease, transform 120ms ease, box-shadow 140ms ease;
}
.bf-cult-trigger:hover { background: var(--surface-3); box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5); }
.bf-cult-trigger:active { transform: scale(0.97); }

.cult-sheet-backdrop {
  position: fixed; inset: 0; z-index: 9200;
  background: var(--scrim);
  display: flex; align-items: flex-end; justify-content: center;
  padding: 0 0 var(--safe-bottom, 0px);
  opacity: 0; visibility: hidden;
  transition: opacity 0.3s ease, visibility 0.3s ease;
}
.cult-sheet-backdrop.is-visible { opacity: 1; visibility: visible; }
.cult-sheet {
  width: 100%; max-width: 480px;
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: 24px 24px 0 0; padding: 20px 16px 28px;
  transform: translateY(100%);
  transition: transform 0.35s cubic-bezier(0.32, 0.72, 0, 1);
}
.cult-sheet-backdrop.is-visible .cult-sheet { transform: translateY(0); }
.cult-sheet-title {
  font-family: var(--font-display);
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-subtle); text-align: center; margin: 0 0 12px;
  text-wrap: balance;
}
.cult-sheet-list { display: flex; flex-direction: column; gap: 4px; }
.cult-sheet-item {
  display: flex; align-items: center; gap: 12px; width: 100%;
  background: none; border: none; cursor: pointer; text-align: left;
  padding: 13px 12px; border-radius: 14px;
  font-family: var(--font-numeric);
  font-size: 1rem; font-weight: 600; color: var(--text-primary);
  -webkit-tap-highlight-color: transparent;
}
.cult-sheet-item:hover,
.cult-sheet-item.is-current { background: var(--surface-2); }
.cult-sheet-item-flag { font-size: 1.3rem; line-height: 1; flex-shrink: 0; }
.cult-sheet-item-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cult-sheet-item-check { color: var(--accent-ink); font-size: 1rem; flex-shrink: 0; }
.cult-sheet-item:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; }

/* ── WEB: culture picker is an ANCHORED POPOVER, not a bottom sheet ──────────
   html.is-app (native, see briefing.html's capability-gate script) keeps every
   rule above untouched — the sheet still rises from the bottom exactly as
   before. On web (html:not(.is-app)) js/card.js's mountCultureDropdown appends
   .cult-sheet into #bf-culture itself (now position:relative here) instead of
   nesting it inside .cult-sheet-backdrop, so it can anchor top:100% under the
   trigger pill the same way .ar-cal-panel anchors under its own trigger above
   — same floats-over-the-feed mechanic, just a narrower box. .cult-sheet-item
   rows are reused completely unstyled here; only the outer container differs.
   .cult-sheet-backdrop is repurposed into a plain full-viewport, invisible
   click-outside catcher (no dimming — a popover this small doesn't earn a
   scrim), decoupled from the panel entirely so its own position:fixed never
   becomes the panel's containing block. ── */
/* §ED529 — `html:not(.is-app) #bf-culture { position: relative; }` DELETED.
   It was the anchor for a position:absolute .cult-sheet nested inside it. The
   sheet is a <body> child now and anchors off a measured rect, so this rule has
   nothing left to position. */
html:not(.is-app) .cult-sheet-backdrop {
  background: transparent; -webkit-backdrop-filter: none; backdrop-filter: none;
  display: block; padding: 0;
  opacity: 1; visibility: visible; pointer-events: none;
  transition: none;
}
html:not(.is-app) .cult-sheet-backdrop.is-visible { pointer-events: auto; }
/* §ED529 — THE COMMENT ON THE OLD z-index WAS FALSE, AND THAT IS THE WHOLE BUG.
   It read "above .cult-sheet-backdrop's 9200 — clicks on the panel never fall
   through to the click-catcher". They fell through every time. .cult-sheet-
   backdrop is appended to <body>, so its 9200 is in the ROOT stacking context;
   .cult-sheet was appended into #bf-culture, i.e. inside .bf-header (a stacking
   context at z-index 20) inside .bf-wrap (a stacking context at z-index 1), so
   its 9210 was scoped inside .bf-header and never competed with 9200 at all.
   The transparent, pointer-events:auto catcher therefore painted over every
   row and swallowed the click: elementFromPoint() at the centre of a culture
   row returned `DIV.cult-sheet-backdrop is-visible`, e.target WAS the backdrop
   so its own `if (e.target !== backdrop) return` guard did not fire, and the
   handler just closed the sheet. That is the reported "clicking a culture does
   nothing at all" — nothing to do with the membership lock.
   FIX: js/card.js appends the sheet to <body> on web too, position:fixed, with
   top/left written by positionSheet(). Now 9210 and 9200 are siblings in one
   context and the comment is true. See scripts/audit-ed529-chrome.js. */
html:not(.is-app) .cult-sheet {
  position: fixed; top: 0; left: 0; right: auto; margin: 0;
  width: max-content; min-width: 220px; max-width: min(320px, calc(100vw - 32px));
  border-radius: 16px; padding: 10px;
  z-index: 9210;
  border: 1px solid var(--border-strong); box-shadow: var(--shadow-hero);
  opacity: 0; visibility: hidden; pointer-events: none;
  transform: translateY(-6px) scale(0.97); transform-origin: top left;
  transition: opacity 160ms ease-in, transform 160ms ease-in, visibility 0s linear 160ms;
}
html:not(.is-app) .cult-sheet.is-visible {
  opacity: 1; visibility: visible; pointer-events: auto;
  transform: translateY(0) scale(1);
  transition: opacity 160ms ease-out, transform 180ms cubic-bezier(0.22, 1, 0.36, 1), visibility 0s;
}
html:not(.is-app) .cult-sheet-title { margin: 0 0 8px; }
/* §ED529 — the sheet itself is tabindex="-1" and takes focus on open so Tab
   continues into the rows (it is a <body> child now, not the trigger's DOM
   neighbour). That is a programmatic focus on a container, never a target the
   reader tabbed to, so it must not paint a ring: the ring belongs to the rows,
   which keep their own :focus-visible outline. The element is still focused,
   so the role="dialog"/aria-label announcement is unaffected. */
html:not(.is-app) .cult-sheet:focus { outline: none; }

/* ── Cards (all franchises are coequal — same weight) ───────────────────── */
.bf-stack { display: flex; flex-direction: column; gap: 14px; }

.bf-card {
  background: var(--surface);
  border: 1.5px solid var(--border-strong);
  border-radius: 14px;
  padding: 20px 20px 18px;
}

/* Light mode: --surface is only ~10% relatively brighter than --bg (cream on
   tan), versus dark mode's --surface being ~64% relatively brighter than
   --bg (#171717 on #0e0e0e) — the same absolute gap reads far flatter once
   both are pushed into the light-luminance range, so a border alone doesn't
   give the card enough figure-ground separation. --shadow-card already
   exists for exactly this (global.css theme-light block, olive-brown per
   PREMIUM-POLISH-SPEC §8 "Elevation on paper") but was never wired to a
   selector — .bf-daily gets --shadow-hero already; this gives the other
   (non-hero) cards the same paper-over-paper lift instead of reading flat.
   Dark mode untouched — .bf-card there already reads elevated via contrast
   alone. */
html.theme-light .bf-card {
  box-shadow: var(--shadow-card);
}

/* Collection shelf — a kept fact's card is a tap target to its day's edition
   (js/collection.js). Same hover/press language as .ar-worth-row's clickable
   rows (briefing.css) rather than inventing a new affordance. */
.bf-card-linked { cursor: pointer; transition: transform 140ms ease, border-color 140ms ease; }
.bf-card-linked:hover { border-color: var(--border-accent); }
.bf-card-linked:active { transform: scale(0.99); }
.bf-card-linked:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }

.bf-label {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.72rem;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--text-subtle);
  margin: 0 0 10px;
}

/* One fact unit inside a card (a card may hold >1 item). Transparent border at
   rest (not "none") so the loved-state green outline below doesn't shift
   layout when it appears — same treatment as .bf-guessmoment further down. */
.bf-item {
  border: 1.5px solid transparent;
  border-radius: 10px;
  transition: border-color 160ms ease;
}
.bf-item + .bf-item { margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--border); }
.bf-actions { margin-top: 2px; }

/* Tier-2 'love' reaction (fact cards only) — light, on-brand, distinct from Report. */
.bf-love {
  flex: 0 0 auto;
  background: none;
  border: none;
  cursor: pointer;
  padding: 0 12px 0 0;
  font-size: 1.05rem;
  line-height: 1;
  color: var(--text-muted);
  transition: color 0.12s ease, transform 0.08s ease;
}
.bf-love:hover { color: var(--text-subtle); }
.bf-love.is-on { color: var(--accent-ink); }
.bf-love:active { transform: scale(1.15); }
.bf-love:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; border-radius: 4px; }

.bf-reported {
  display: inline-block;
  margin-left: 14px;
  font-family: var(--font-body);
  font-size: 0.78rem;
  color: var(--text-primary); /* dark-theme small text is never lime — Wave D follow-up; light stays --accent-ink below */
}
html.theme-light .bf-reported { color: var(--accent-ink); }

.bf-text {
  font-family: var(--font-body);
  font-size: 1.05rem;      /* ~16–17px lead — the "what + why it matters" */
  line-height: 1.6;
  color: var(--text-primary);
  margin: 0 0 12px;
}

.bf-source {
  font-family: var(--font-body);
  font-size: 0.8rem;
  color: var(--text-subtle);
  margin: 0 0 10px;
  overflow-wrap: break-word;
}

/* ARCH-CORRECTION notice (R6.3) — deliberately NOT an alarm/warning
   treatment (no red, no icon): a correction notice states a fact, it does
   not apologize or alarm. Same neutral register as .bf-source, set apart
   only by a hairline top rule so it reads as an appended editorial note. */
.bf-correction {
  margin: 10px 0 0;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}
.bf-correction-label {
  font-family: var(--font-body);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-subtle);
  margin: 0 0 4px;
}
.bf-correction-body,
.bf-correction-replacement {
  font-family: var(--font-body);
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--text-subtle);
  margin: 0 0 4px;
}
.bf-correction-replacement:last-child { margin-bottom: 0; }

/* ── Unified daily card — "B · The Masthead" (dark · premium-editorial) ──────── */
.bf-daily {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  overflow: hidden;
  /* Promoted to the shared --shadow-hero token (global.css Phase A) — the
     top-weighted lime glow this card originated, now shared app-wide. Carries
     its own light-mode olive-glow variant automatically. */
  box-shadow: var(--shadow-hero);
  transition: transform 160ms ease, box-shadow 160ms ease;
}
/* Gentle, subtle hover-lift — the card rises a touch and the glow firms up. */
.bf-daily:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-hero-hover);
}
/* Masthead — ONE publication nameplate: elevated by LUMINANCE (stands proud of
   the card body) + a lime top-edge, now with --edge-light (T0) as its catch
   light. Row 1 = the Culturily wordmark itself (the "The Daily" title that
   used to sit under it is retired — the wordmark alone carries the nameplate
   now, per the strict Fraunces rule: Fraunces stays ONLY on this wordmark,
   nowhere else on the card); Row 2 = the dateline folio under a hairline —
   it absorbs the old corner edition number, page date, and culture marker. */
.bf-masthead {
  display: flex;
  flex-direction: column;
  padding: 14px 28px 12px;
  background: var(--surface-3);
  border-top: 2px solid var(--border-strong);        /* structural edge, not a lime flourish */
  box-shadow: var(--edge-light);
}
.bf-mast-wordmark {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 1.45rem; font-weight: 600;
  letter-spacing: -0.01em;
  text-wrap: balance;
  color: var(--text-primary);
  margin: 0 0 4px;
}
.bf-mast-dateline {
  font-family: var(--font-body);
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  color: var(--text-subtle);
  margin: 8px 0 0;
  padding-top: 8px;
  border-top: 1px solid var(--border);
}
/* Past-day (isolated single-day view, /briefing only) additions to the dateline.
   Dark-theme small text is never lime — Wave D follow-up; light stays --accent-ink. */
.bf-mast-archive { color: var(--text-subtle); letter-spacing: 0.06em; }
html.theme-light .bf-mast-archive { color: var(--accent-ink); }
.bf-mast-back {
  position: relative; /* a11y medium-pass (2026-07-11): hit-slop base, see ::before */
  align-self: flex-start;
  margin-top: 8px;
  background: none; border: none; padding: 0; cursor: pointer;
  font-family: var(--font-body); font-size: 0.75rem; font-weight: 700;
  color: var(--text-primary);
  -webkit-tap-highlight-color: transparent;
}
html.theme-light .bf-mast-back { color: var(--accent-ink); }
/* Was ~18px tall (12px text * 1.5 line-height, zero padding). Invisible
   hit-slop instead of real padding — same pattern as .pf-icon-btn's
   ::before{inset:-4px} — so the visible label/spacing doesn't shift. */
.bf-mast-back::before { content: ''; position: absolute; inset: -13px 0; }
.bf-mast-back:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; border-radius: 4px; }

/* ── §ED509 — THE HONEST STATE ────────────────────────────────────────────────
   "Today's edition isn't ready yet — this is the last one published."

   NOT AN ERROR, and styled so nothing suggests one: no red, no warning tint, no
   alert glyph. Nothing has broken from the reader's side — they are on the
   current page, looking at the most recent edition that exists. NOT AN ARCHIVE
   TAG either: .bf-mast-archive is a small-caps marker for a day the reader
   CHOSE, and this reader chose nothing, so the two must never share a treatment.

   The grammar is an editorial aside inside the paper: neutral ink, a rule on the
   leading edge, sitting under the dateline's own hairline. One step firmer than
   the dateline so it cannot be skimmed past, and no firmer than that.

   AT THE §ED306 TYPE FLOOR (1rem/16px) DELIBERATELY. This is prose a reader has
   to actually read and act on, not furniture. .bf-mast-dateline above it sits at
   0.75rem and predates the floor (§ED475's unswept backlog) — that is a debt,
   not a precedent, and matching it here would deepen it.

   Fully token-driven, so light and dark are covered by global.css's own
   palettes rather than by a second theme rule here. */
.bf-mast-stale {
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: 1.45;
  color: var(--text-secondary);
  margin: 10px 0 0;
  padding: 8px 0 2px 12px;
  border-left: 3px solid var(--border-strong);
  text-wrap: pretty;
}
.bf-mast-stale-lede { color: var(--text-primary); font-weight: 700; }

/* Ribbon slot (item #8, R4) — structural shape only; ships hidden/empty here
   (composeHookCard sets [hidden]). The tutorial build populates the content
   and un-hides it — this is the shell it drops into, not a live element yet. */
.bf-ribbon-slot {
  background: var(--accent-dim);
  padding: 10px 28px;
  font-family: var(--font-body);
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--accent-ink);
  text-align: center;
}

/* Top padding tightened (was 30px) — the masthead/nameday/rootline group above
   the fact reads as one compact block now; the fact headline's own generous
   lead-in (.bf-quote's padding-top) is where the deliberate air lives instead. */
.bf-daily-body { padding: 22px 32px 28px; }

/* Standing root-line: the daily emotional frame — warm muted italic dek. Quiet
   context, not competing with the hero below. Nudged back up slightly (was
   0.95rem/--text-subtle) — the declutter pass had softened this past
   legibility; still a step short of the pre-declutter weight/size. */
.bf-rootline {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 500;
  font-size: 1rem;
  line-height: 1.4;
  color: var(--text-muted);
  margin: 0 0 10px;
}

/* Name-day folio — a franchise line between the masthead's dateline and the
   root-line (moved out of the old bf-frame metadata row, which is gone: the
   date it carried is now the dateline folio's job). The dot is neutral now —
   decoration doesn't get to be lime (§3a); the card's one signal is the loved
   heart. Label + name nudged back up slightly (were 0.68rem/500/--text-subtle
   and 0.8rem/500) — still short of the original 700/600 weights, just past
   the point the declutter pass had softened them to. */
.bf-namefolio { display: flex; align-items: center; gap: 7px; margin: 0 0 10px; }
.bf-namefolio-label {
  font-family: var(--font-display);
  font-variant: small-caps;
  font-size: 0.72rem;
  font-weight: 580;
  letter-spacing: 0.06em;
  color: var(--text-muted);
}
.bf-namefolio-dot { width: 4px; height: 4px; border-radius: 50%; background: var(--text-muted); }
.bf-namefolio-name { font-family: var(--font-body); font-size: 0.85rem; font-weight: 560; color: var(--text-primary); } /* dark-theme small text is never lime — Wave D follow-up; light stays --accent-ink below */
html.theme-light .bf-namefolio-name { color: var(--accent-ink); }
/* Edge case only — a day with a name-day but NO fact block, so composeCards has
   no paper to fold the folio into (renderNameDayCard fallback). Not a bordered
   peer card, just enough surface so the line doesn't float bare on the page. */
.bf-namefolio-standalone { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-card); padding: 18px 20px; }
.bf-namefolio-standalone .bf-namefolio { margin: 0; }

/* Name-day save toggle (114_user_saved_namedays.sql) — signed-in readers only.
   Each name in the folio becomes its own tap target: same font/size/weight as
   the plain .bf-namefolio-name it replaces, so a day with nothing saved reads
   IDENTICALLY to before. Calm/almanac styling per the ruling: no lime, no
   celebration, no counter — just a quiet ring that fills when saved. */
.bf-namefolio-names { font-family: var(--font-body); font-size: 0.85rem; font-weight: 560; }
.bf-nameday-toggle {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: none;
  border: none;
  padding: 2px 1px;
  margin: 0;
  font: inherit;
  color: var(--text-primary);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
html.theme-light .bf-nameday-toggle { color: var(--accent-ink); }
.bf-nameday-toggle:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; border-radius: 4px; }
.bf-nameday-icon { display: inline-flex; color: var(--text-subtle); }
.bf-nameday-toggle.is-saved .bf-nameday-icon { color: currentColor; }
.bf-nameday-toggle.is-saved .bf-nameday-ring { fill: currentColor; }

/* Free-tier cap (MONETIZATION-MODEL.md §7-R2, 115_user_saved_namedays_free_
   limit.sql) — an unsaved name once a free reader already has 3. Dimmed, not
   hidden or struck through: still reads as a name, not a broken control. */
.bf-nameday-toggle.is-locked { opacity: 0.5; }

/* "Today is X — one of yours" — the personal-touch line, quiet and
   editorial like .bf-rootline (never a badge, never colored, no icon). Sits
   between the folio and the root-line only when it has something to say. */
.bf-namefolio-mine {
  font-family: var(--font-body);
  font-style: italic;
  font-size: 0.8125rem;
  color: var(--text-muted);
  margin: -4px 0 10px;
}

/* Folio invitation (Part 3, "make membership feel like membership",
   2026-07-23) — a member's OTHER culture edition, mentioned as one quiet
   masthead line, same register as .bf-namefolio-mine (italic, muted, no
   badge). It IS tappable (in-place culture switch), unlike that line, so it
   gets cursor:pointer + a slightly bolder arrow as the one tap affordance —
   still no lime, no border, no card: this is furniture, not a CTA. */
.bf-culture-invite {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  padding: 0;
  font-family: var(--font-body);
  font-style: italic;
  font-size: 0.8125rem;
  color: var(--text-muted);
  margin: -4px 0 10px;
  cursor: pointer;
}
.bf-culture-invite:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; border-radius: 4px; }
.bf-culture-invite-arrow { font-style: normal; font-weight: 700; color: var(--text-primary); }
html.theme-light .bf-culture-invite-arrow { color: var(--accent-ink); }

/* Free-cap note — appended once, after the folio, on the proactive-lock tap
   or the server rejection. Same quiet register as .bf-namefolio-mine: one
   plain sentence, no badge, no button, a plain-text link is the only accent. */
.bf-nameday-limit-note {
  font-family: var(--font-body);
  font-size: 0.8125rem;
  color: var(--text-muted);
  margin: -4px 0 10px;
}
.bf-nameday-limit-link { color: var(--text-primary); text-decoration: underline; }
html.theme-light .bf-nameday-limit-link { color: var(--accent-ink); }

/* Hook — the daily fact, marked by a short neutral horizontal DASH above it
   (was lime — decoration doesn't get to be lime, §3a). Set for SENTENCES (the
   editorial-voice hook reads ~8-10 wrapped lines on mobile, not a poster
   fragment). .is-short (card.js, text ≤ ~90 chars) steps back up to a poster
   size for short punchy facts. The hook stays the hero in both themes. Font
   §ED430 · back to Fraunces, at 400. Jake's FINAL ruling makes fact titles
   Fraunces 400, which supersedes the old "Fraunces is the wordmark only"
   reservation (VISUAL-IDENTITY §2) for this element. --font-display now
   RESOLVES to Fraunces, so the token is already correct and only the weight
   moves (480 -> 400). Fraunces' opsz axis is live again (9-144, measured);
   browsers apply font-optical-sizing:auto by default, so it tracks font-size
   without the hand-set opsz 40 this comment used to describe.
   Generous padding-top here (was 2px) is deliberate — the hero's arrival
   needs clear air after the quiet pre-fact stack above (masthead/nameday/
   rootline), not a snug transition; the size bump below is only half the
   "hero" effect, the breathing room is the other half. */
.bf-quote { padding-top: 26px; }
.bf-accent-dash {
  display: block;
  width: 36px; height: 4px;
  background: var(--text-muted);
  border-radius: 999px;
  margin: 0 0 14px;
}
.bf-hook-text {
  position: relative;
  font-family: var(--font-display);
  font-weight: 400;   /* §ED430 · ruled */
  font-size: 2rem;
  line-height: 1.18;
  letter-spacing: -0.015em;
  color: var(--text-primary);
  margin: 0;
  overflow-wrap: break-word;   /* long Czech names never overflow the card */
  text-wrap: balance;
}
.bf-hook-text.is-short { font-size: 2.4rem; }

/* Fact deck (§1.7, FACT-ANATOMY-SPEC.md 2026-07-15 amendment) — fact_text,
   demoted from hero to deck ONLY when an approved title takes the hero slot
   above it (card.js renderFactRead: appended only when fact.title is
   present). Body-serif, first-class (full text_primary, not the debrief's
   muted tone) — never truncated, always renders in full.
   (Renamed "standfirst" -> "deck" in this comment 2026-07-23: that word now
   names a DIFFERENT, new block — see .bf-standfirst below, the placement
   line between the title and THIS deck — so reusing it here would read as
   the same thing twice.) */
.bf-fact-deck {
  font-family: var(--font-body);
  font-size: 1.2rem;
  line-height: 1.5;
  color: var(--text-primary);
  max-width: 62ch;
  margin: 14px 0 0;
}

/* Standfirst (§1.7 amendment, 2026-07-23) — a placement line for the card's
   LEAD entity, between the hero title and the deck above: "Benjamin
   Franklin — a statesman of American independence (1706-1790)." Deliberately
   subordinate to .bf-hook-text in every dimension (smaller, uppercase-tracked
   label register instead of display type, muted color) — a second headline
   here would reopen the emotional-spine two-subject law (§1.7's own register
   bans exist for the same reason). Server-gated to render only alongside an
   approved, hash-fresh title (10-assemble-daily.js's gateRenderableStandfirst),
   so it never appears floating with no title above it. */
.bf-standfirst {
  font-family: var(--font-body);
  font-size: 0.92rem;
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: 0.01em;
  color: var(--text-muted);
  max-width: 62ch;
  margin: 10px 0 0;
}

/* Hook → story seam — the classic head/dek separator: one quiet hairline, no
   second lime flourish (the dash stays the column's only one). */
.bf-hook-seam { width: 24px; height: 1px; background: var(--border); margin: 20px auto; border: none; }

/* Debrief — grounded background/story below the hook. */
.bf-debrief {
  font-family: var(--font-body);
  font-size: 1.02rem;
  line-height: 1.62;
  color: var(--text-muted);
  max-width: 62ch;
  margin: 16px 0 0;
}

/* Trust line — standing provenance, ALWAYS visible (never tap-gated behind
   Read-more, which keeps the URL/subdomain/report affordances). No lime here —
   the hook's accent-dash stays the column's one flourish. */
.bf-trust { font-family: var(--font-body); font-size: 0.78rem; color: var(--text-subtle); margin: 16px 0 0; }

/* Reaction row — love pill (live count) + social proof. */
.bf-reaction { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; margin-top: 26px; }
.bf-love-pill {
  display: inline-flex; align-items: center; gap: 9px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 9999px;
  padding: 11px 18px 11px 15px; cursor: pointer;
  min-height: 44px; /* a11y low-pass (2026-07-11): was ~42px on desktop */
  transition: transform 140ms ease, background 140ms ease;
}
.bf-love-pill:hover { transform: translateY(-1px); background: var(--surface-3); }
.bf-love-pill:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
/* §ED240 — the save pill's glyph is the bookmark now; the heart went to
   .bf-like-pill. Same ink it always carried, on the new mark. */
.bf-love-pill .bf-bookmark { color: var(--accent-ink); }
.bf-love-lbl { font-family: var(--font-body); font-size: 0.84rem; font-weight: 700; color: var(--text-primary); }
.bf-love-count { font-family: var(--font-body); font-size: 0.78rem; color: var(--text-subtle); }
.bf-love-pill.is-on { background: var(--accent); border-color: var(--accent); }   /* loved → lime fill, dark text */
.bf-love-pill.is-on .bf-bookmark { color: var(--accent-text); fill: var(--accent-text); }
.bf-love-pill.is-on .bf-love-lbl { color: var(--accent-text); }
.bf-love-pill.is-on .bf-love-count { color: color-mix(in srgb, var(--accent-text) 55%, transparent); }
/* LIKE (§ED240) — the reaction, beside the save. Same pill geometry so the
   two read as one control group, but NEVER lime-filled in either state: the
   view's one lime action is the save's is-on fill, and a second filled pill
   beside it would breach the palette invariant every time a reader did both.
   The on-state says its piece through the heart's ink (--accent-ink, exactly
   the treatment .bf-love-pill already gives that glyph on this surface) and
   a strengthened border — not a fill. Label ink is --text-primary in both
   states, so nothing here rests on a muted tone for legibility. */
.bf-like-pill {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 11px 16px; border-radius: 999px;
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--text-primary);
  font-family: var(--font-body); font-size: 0.86rem; font-weight: 600;
  cursor: pointer;
  transition: background 120ms ease, border-color 120ms ease, transform 120ms ease;
}
.bf-like-pill:hover { transform: translateY(-1px); background: var(--surface-3, var(--surface-2, var(--surface))); }
.bf-like-pill:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
.bf-like-pill .bf-heart { color: var(--text-subtle); transition: color 120ms ease; }
.bf-like-pill.is-on { border-color: var(--accent-ink); }
.bf-like-pill.is-on .bf-heart { color: var(--accent-ink); }
.bf-like-pill .bf-like-lbl { color: var(--text-primary); }

/* Green "kept" outline on the fact's own OUTER card, toggled in card.js's
   paint() via row.closest('.bf-daily, .bf-item'). Matches --accent, the same
   lime used for .bf-masthead's border-top "lime top-edge" (the card's own
   top-bar accent) — was --success (a darker, distinct mint/forest green)
   until this pass; changed to read as the SAME brand accent as the rest of
   the card rather than a separate, slightly-too-dark green. Border-color
   only (.bf-item already carries a transparent border at rest to color
   here, and .bf-daily carries its own non-transparent border already) — no
   fill, kept deliberately subtle. .bf-guessmoment is deliberately NOT
   targeted here — it always nests inside .bf-daily/.bf-item, so coloring it
   drew the outline on an inner box instead of framing the actual card (see
   card.js paint()). */
.bf-daily.is-loved,
.bf-item.is-loved {
  border-color: var(--accent);
}

/* A2 — love-pill micro-interaction. Both classes are added by card.js only on
   the tap-driven transition into loved (never on the async server-
   reconciliation repaint), so they never replay spuriously. Reduced-motion is
   handled globally (global.css collapses animation-duration app-wide). */
@keyframes bf-heart-pop {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.35); }
  65%  { transform: scale(0.9); }
  100% { transform: scale(1); }
}
.bf-heart.bf-pop { animation: bf-heart-pop 250ms ease-out; }

@keyframes bf-count-rise {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}
.bf-love-count.bf-rise { animation: bf-count-rise 220ms ease-out; }
.bf-social { font-family: var(--font-body); font-size: 0.78rem; color: var(--text-subtle); }

/* ── BLUR + OVERLAY guess gate (calendar-scrim pattern) ──────────────────────
   The lead fact renders BLURRED behind an overlay (filter:blur, same approach as
   the calendar scrim); a guess panel hovers on top with the thin stem + 4 tiles.
   On a guess the overlay drops and the blur clears (the fact reads sharp), and a
   small result chip lands at the BOTTOM. Masthead/date above stay sharp. */
/* Transparent border at rest (matches .bf-item's treatment) — this is the
   closest()-matched container for every TESTABLE fact (hero or secondary),
   so it needs its own border to color; .is-gated's veil pseudo-element uses
   the same 16px radius, kept in sync here for a consistent outline. */
.bf-guessmoment {
  margin-top: 22px;
  border: 1.5px solid transparent;
  border-radius: 16px;
  transition: border-color 160ms ease;
}

/* GATED — the fact renders NOW but blurred unreadable; the overlay panel stacks in
   the SAME grid cell on top. */
.bf-guessmoment.is-gated { display: grid; }
.bf-guessmoment.is-gated > .bf-gated,
.bf-guessmoment.is-gated > .bf-guess-overlay { grid-area: 1 / 1; }
.bf-gated { filter: blur(0); transition: filter 520ms cubic-bezier(0.22, 1, 0.36, 1); }
/* Veiled fact — FULL natural height (the grid cell stretches it); blurred unreadable.
   §ED137: THIS RULE IS THE SIGHTED READER'S GATE ONLY, and nothing more. `filter`
   is a paint-time effect and `pointer-events` a hit-testing rule; neither removes
   the subtree from the accessibility tree or the tab order, so for four months a
   screen reader read the whole answer before the guess. The AT gate is the `inert`
   (+ aria-hidden) attribute card.js sets on .bf-gated at build time and clears in
   reveal() — see js/card.js's buildGuessMoment. Do not conclude from this rule
   that the gate is CSS's job: CSS closes one channel of two. */
.bf-guessmoment.is-gated .bf-gated { filter: blur(10px); pointer-events: none; user-select: none; }
/* VEIL — a translucent tint over the blurred fact (calendar-scrim pattern lineage),
   covering the FULL gated block and extending BEYOND the floating panel. Above the
   fact (z0), below the panel (z2). R5: the backdrop-filter (blur+brightness) layer
   that used to live here is GONE — a perf fix (backdrop-filter is expensive in
   WKWebView, and this was stacking a SECOND blur on top of .bf-gated's own
   `filter: blur(10px)`, for one veil). The content layer's own blur + this plain
   translucent tint now carry the whole effect. Device-verify (dark + light): if
   the veil visibly cheapens without the extra brightness lift, that's the one
   thing in this commit to revert. */
.bf-guessmoment.is-gated::before {
  content: ''; grid-area: 1 / 1; z-index: 1; pointer-events: none; border-radius: 16px;
  background: color-mix(in srgb, var(--text-primary) 6%, transparent);
}
.bf-guess-overlay { z-index: 2; align-self: start; }
/* A1 — animated exit: card.js adds .is-exiting on commit() and removes the node
   ~200ms later (matching this duration) instead of an instant removeChild.
   prefers-reduced-motion is handled globally (global.css collapses transition
   durations app-wide), so this degrades to an effectively-instant removal there. */
.bf-guess-overlay.is-exiting {
  opacity: 0;
  transform: scale(0.94);
  transition: opacity 200ms ease-out, transform 200ms ease-out;
  pointer-events: none;
}

/* The guess panel — a frosted "door": TRANSLUCENT (--glass-panel, T0/R5 token —
   was a hand-written rgba + a theme-light override, now one declaration) so the
   blurred story shows through underneath (teasing shape/length) while staying
   unreadable; lime-tinted hairline is the panel's interaction affordance, the
   ambient shadow lifts it off the blur (var(--shadow-float), already
   per-theme) — the lime-tinted GLOW that used to ride along the shadow is
   gone (§3a: no glow-at-scale). The old dark-mode-only text-shadow is gone —
   it existed to keep text legible over the FROST layer's brightness boost
   (removed above), which no longer applies. */
.bf-guess {
  background: var(--glass-panel);
  border: 1px solid var(--border-accent); border-radius: 18px;
  padding: 24px 22px;
  box-shadow: var(--shadow-float);
}
.bf-guess-eyebrow {
  display: flex; align-items: center; gap: 8px; margin: 0 0 14px;
  font-family: var(--font-body); font-size: 0.66rem; font-weight: 700;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--text-muted); /* dark-theme small text is never lime — Wave D follow-up; light stays --accent-ink below */
}
html.theme-light .bf-guess-eyebrow { color: var(--accent-ink); }
.bf-guess-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--text-muted); flex: 0 0 auto; }
.bf-guess-stem {
  font-family: var(--font-body); font-size: 1.16rem; line-height: 1.45; font-weight: 500;
  color: var(--text-primary); margin: 0 0 20px;
}
.bf-guess-hint {
  font-family: var(--font-body); font-size: 0.74rem; color: var(--text-muted);
  margin: 16px 0 0; text-align: center;
}

/* Option tiles — letter badge + text rows. Translucent (--glass-tile, T0/R5
   token) so the blurred story reads continuously across the panel; border
   reuses the standard --border/--border-strong ladder instead of one-off
   alpha values, which retires the theme-light override entirely. */
.bf-opts { display: flex; flex-direction: column; gap: 10px; }
.bf-opt {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  background: var(--glass-tile); border: 1.5px solid var(--border);
  border-radius: 13px; padding: 13px 14px; cursor: pointer; color: var(--text-primary);
  transition: border-color 120ms ease, background 120ms ease, transform 100ms ease;
}
.bf-opt:hover { border-color: var(--border-strong); }
.bf-opt:active { transform: scale(0.99); }
.bf-opt:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
.bf-opt:disabled { cursor: default; }
.bf-opt-badge {
  flex: 0 0 auto; width: 26px; height: 26px; display: inline-flex; align-items: center; justify-content: center;
  border-radius: 8px; background: var(--surface-3); color: var(--text-subtle);
  font-family: var(--font-display); font-weight: 700; font-size: 0.875rem;
  /* 14px bold clears the WCAG "large text" line — needed so the
     is-wrong state's --error-ink-on-fill ratio (~4.2:1 in dark theme)
     qualifies under the 3:1 large-text tier instead of failing 4.5:1. */
}
.bf-opt-text { font-family: var(--font-body); font-size: 0.95rem; font-weight: 500; }

/* INLINE correct/wrong OPTION FEEDBACK (card.js commit()) — the SAME pattern
   the arcade quiz uses (guest-lesson.js/lesson.js .option-card is-correct/
   is-wrong/is-reveal/is-dimmed), reused here on .bf-opt instead of a centered
   overlay or a page-level toast — both broke on-device (the toast overlapped
   the header, the overlay never appeared). This page doesn't load
   components.css, so the same brand-locked red values are duplicated here
   rather than pulled in wholesale — colors match exactly, just scoped to
   .bf-opt's own badge/text structure instead of .option-card's. Correct now
   uses the semantic --success green (was brand lime) — quiz feedback is
   functional state, not brand punctuation; the loved heart stays the card's
   one lime signal (§3a), and this also brings is-correct in line with
   is-wrong below, which already used semantic --error. */
.bf-opt.is-correct {
  border-color: var(--success);
  background: var(--success-dim);
  box-shadow: 0 0 0 1px var(--success) inset, 0 2px 10px color-mix(in srgb, var(--success) 12%, transparent);
}
.bf-opt.is-correct .bf-opt-badge { background: color-mix(in srgb, var(--success) 16%, transparent); color: var(--success); }
/* Reveal — the right answer shown inline after a WRONG pick (replaces the old
   "Not quite — it was X" copy; the correct option itself IS the answer now). */
.bf-opt.is-reveal {
  border-color: var(--success);
  background: var(--success-dim);
}
.bf-opt.is-reveal .bf-opt-badge { background: color-mix(in srgb, var(--success) 14%, transparent); color: var(--success); }
.bf-opt.is-wrong {
  border-color: var(--error);
  background: var(--error-dim);
  box-shadow: 0 0 0 1px var(--error) inset, 0 2px 10px color-mix(in srgb, var(--error) 12%, transparent);
}
.bf-opt.is-wrong .bf-opt-badge { background: color-mix(in srgb, var(--error) 16%, transparent); color: var(--error-ink); }
.bf-opt.is-dimmed { opacity: 0.4; }

/* Result banner — COMPACT but readable at a glance, at the BOTTOM after the read.
   THREE shapes (card.js buildResultChip): GUESS (STREAK ONLY now — the verdict
   badge/headline and wrong-answer reveal live inline on the guess options
   above, see .bf-opt.is-correct/.is-wrong/.is-reveal), READ completion (R4 — no correctness, flame badge + the
   PROMOTED streak cell), and DAY-1 (R4 — full-width band, self-limiting). Not
   the old full-screen flame; the reading stays the hero. Entrance = BLOOM
   (.m-bloom, global.css T0 primitive, applied via Motion.bloom() in card.js)
   — replaces the old plain-CSS slide-up. */
.bf-result {
  display: flex; align-items: center; gap: 13px;
  margin-top: 18px; padding: 13px 15px; border-radius: 14px; border: 1px solid;
  font-family: var(--font-body);
}
.bf-result.is-read    { background: var(--accent-dim); border-color: var(--border-accent); }
.bf-result-badge {
  flex: 0 0 auto; width: 34px; height: 34px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-weight: 800; font-size: 1.15rem; line-height: 1;
}
.bf-result-badge.is-flame { background: var(--accent); color: var(--accent-text); }
.bf-result-mid { flex: 1 1 auto; min-width: 0; }
.bf-result-head {
  font-family: var(--font-display); font-weight: 700; font-size: 1.05rem; line-height: 1.1; margin: 0;
  text-wrap: balance;
}
.bf-result.is-read    .bf-result-head { color: var(--text-primary); } /* dark-theme small text is never lime — Wave D follow-up; light stays --accent-ink below */
html.theme-light .bf-result.is-read .bf-result-head { color: var(--accent-ink); }
.bf-result-sub { font-size: 0.86rem; color: var(--text-muted); margin: 3px 0 0; }
.bf-result-streak {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: 5px;
  color: var(--text-subtle); font-size: 0.8rem; font-weight: 600;
  font-family: var(--font-numeric);   /* the streak digit — numbers use --font-numeric, never --font-display */
}
.bf-result-flame { color: var(--accent-ink); }
.bf-result-flame svg { width: 16px; height: 16px; }

/* GUESS result, streak-only — this chip no longer signifies right/wrong (that
   moved to the top banner), so shrink it to a modest pill and drop the
   green/red tint. Scoped to correct/wrong only — is-day1 and is-read keep the
   base .bf-result sizing/background above untouched. */
.bf-result.is-correct,
.bf-result.is-wrong {
  padding: 8px 12px;
  margin-top: 10px;
  background: var(--surface-well);
  border-color: var(--border);
}

/* READ completion (R4) — the PROMOTED streak cell: "Day N" is the thing the
   eye lands on (a reward, not a footnote), the self-narrative label below it. */
.bf-result-cell {
  flex: 0 0 auto; display: flex; flex-direction: column; align-items: flex-end; gap: 2px; text-align: right;
}
.bf-result-cell-n {
  font-family: var(--font-numeric);
  font-weight: 800; font-size: 1.05rem; line-height: 1; color: var(--text-primary); /* dark-theme small text is never lime — Wave D follow-up; light stays --accent-ink below */
}
html.theme-light .bf-result-cell-n { color: var(--accent-ink); }
.bf-result-cell-label { font-family: var(--font-body); font-size: 0.72rem; color: var(--text-subtle); }

/* DAY-1 (R4) — self-limiting, full-width Beat-2 band; shared by whichever path
   (guess or read) first reaches streakCount===1. Replaces the shapes above. */
.bf-result.is-day1 { flex-direction: column; align-items: flex-start; gap: 6px; width: 100%; background: var(--accent-dim); border-color: var(--border-accent); }
.bf-result-day1-head { display: flex; align-items: center; gap: 10px; }
.bf-result.is-day1 .bf-result-head { color: var(--text-primary); } /* dark-theme small text is never lime — Wave D follow-up; light stays --accent-ink below */
html.theme-light .bf-result.is-day1 .bf-result-head { color: var(--accent-ink); }
.bf-result.is-day1 .bf-result-sub { color: var(--text-muted); margin: 0; }

/* ANON STREAK NUDGE — logged-out only, sits quietly under the result banner. No
   fill, subtle text: it must read as a footnote to the banner, not a peer.
   font-family was ALREADY var(--font-body) (Montserrat) — checked git history,
   it has been since this rule was first introduced (2026-06-27) and there is
   no other CSS rule or theme override touching --font-body/--font-display
   that could flip it; per DESIGN-INVARIANTS.md this is correct as-is. Size
   bumped per the 2026-07-08 design pass — 0.78rem read as too small for a
   CTA line, not just a caption. */
.bf-streak-nudge {
  display: inline-block;
  font-family: var(--font-body);
  font-size: 0.92rem;
  font-weight: 600;
  color: var(--text-subtle);
  text-decoration: none;
  margin: 10px 0 0 2px;
  padding: 2px 0;
  transition: color 120ms ease;
}
.bf-streak-nudge:hover { color: var(--text-muted); }

/* DAILY COLLECTION NUDGE — logged-in equivalent of the anon streak nudge above,
   but promoted to a proper button-weight tap target (was a bare inline-text
   footnote — too subtle to act on). Matches the existing .pf-change-culture-
   link row pattern (profile.html) rather than a loud lime CTA: padded, a
   subtle border/surface tint, calm/editorial text color. */
.bf-collection-nudge {
  display: flex;
  align-items: center;
  width: 100%;
  margin-top: 14px;
  padding: 12px 15px;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: var(--surface-well);
  font-family: var(--font-body);
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--text-primary);
  text-decoration: none;
  transition: background 120ms ease, border-color 120ms ease;
}
.bf-collection-nudge:hover { background: var(--surface-2); border-color: var(--border-strong); }

/* CONVERSION WALL (ONBOARDING-CONVERSION-AUDIT §2) — dual-trigger anon signup
   ask. Same visual family as .bf-result.is-day1 (--accent-dim band,
   border-accent) per PREMIUM-POLISH-SPEC's result-chip convention. */
.bf-reaction-wrap { display: block; }
.bf-wall-slot:not([hidden]) { display: block; }

/* WO-CONV-3 (§8.3/§8.7) — the first-save disclosure, once ever. Neutral ink,
   no lime (§3a: an ambient affordance on a reading-primary view). */
.bf-first-save {
  display: flex; align-items: center; gap: 10px;
  margin-top: 10px; padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
}
.bf-first-save-text {
  font-family: var(--font-body);
  font-size: 0.8rem;
  color: var(--text-subtle);
  margin: 0;
  flex: 1;
}
.bf-first-save-dismiss {
  flex: 0 0 auto;
  border: none;
  background: transparent;
  color: var(--text-muted);
  font-family: var(--font-body);
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
  padding: 4px 6px;
}

/* ── "This one's ours" — the reader's own one-line note (119_user_fact_notes.sql).
   The FIRST feature that puts the reader's OWN WORDS on a card, so a note must
   read UNMISTAKABLY as theirs, never as sourced/verified content. Three signals
   carry the distinction at once: (1) an explicit "Your note" label, (2) a
   RECESSED well — sourced text sits on the flat card surface, the note sits
   inset in a well — and (3) a NEUTRAL grey left rule, deliberately NOT the olive
   source-accent (.bf-accent-dash / .bf-trust), so the two can never blur. Calm,
   no arcade, no celebration. Theme-aware via the shared tokens (well/border/ink). */
.bf-note-zone { margin: 10px 0 2px; }

/* Empty state — a quiet ambient trigger, not a heavy button. */
.bf-note-add {
  display: inline-flex; align-items: center; gap: 6px;
  border: none; background: transparent; cursor: pointer;
  font-family: var(--font-body); font-size: 0.8rem; font-weight: 600;
  color: var(--text-subtle); padding: 2px 0;
}
.bf-note-add:hover { color: var(--text-muted); }
.bf-note-add-ic { display: inline-flex; }
.bf-note-add:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; border-radius: 4px; }

/* The saved note — recessed well + neutral left rule + label. */
.bf-note {
  padding: 9px 12px 9px 13px;
  background: var(--surface-well);
  border-left: 3px solid var(--border-strong);
  border-radius: 0 8px 8px 0;
}
.bf-note-label {
  display: block;
  font-family: var(--font-body);
  font-size: 0.66rem; font-weight: 700;
  letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--text-subtle);
  margin: 0 0 3px;
}
.bf-note-text {
  font-family: var(--font-body);
  font-size: 0.9rem; line-height: 1.5;
  color: var(--text-primary);
  margin: 0; overflow-wrap: break-word;
}
.bf-note-edit {
  margin-top: 6px;
  border: none; background: transparent; cursor: pointer;
  font-family: var(--font-body); font-size: 0.75rem; font-weight: 600;
  color: var(--text-subtle); padding: 0; text-decoration: underline;
}
.bf-note-edit:hover { color: var(--text-muted); }

/* Inline editor — one line, calm. */
.bf-note-editor { display: flex; flex-direction: column; gap: 8px; }
.bf-note-input {
  width: 100%; box-sizing: border-box;
  font-family: var(--font-body); font-size: 0.9rem; line-height: 1.5;
  color: var(--text-primary); background: var(--surface);
  border: 1px solid var(--border-strong); border-radius: 8px; padding: 9px 11px;
}
.bf-note-input::placeholder { color: var(--text-subtle); }
.bf-note-input:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
.bf-note-actions { display: flex; gap: 8px; align-items: center; }
.bf-note-save, .bf-note-cancel, .bf-note-delete {
  font-family: var(--font-body); font-size: 0.78rem; font-weight: 600; cursor: pointer;
  border-radius: 7px; padding: 6px 14px;
}
.bf-note-save { border: none; background: var(--accent-ink); color: #fff; }       /* olive-ink action, calm (matches the stepped-down email CTA), never raw lime */
.bf-note-cancel { border: 1px solid var(--border-strong); background: transparent; color: var(--text-muted); }
.bf-note-delete { margin-left: auto; border: none; background: transparent; color: var(--text-subtle); text-decoration: underline; padding: 6px 2px; }
.bf-note-save:focus-visible, .bf-note-cancel:focus-visible, .bf-note-delete:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }

/* Calm free-cap note — mirrors .bf-nameday-limit-note exactly. */
.bf-note-limit {
  font-family: var(--font-body); font-size: 0.8125rem;
  color: var(--text-muted); margin: 8px 0 0;
}
.bf-note-limit-link { color: var(--text-primary); text-decoration: underline; }
html.theme-light .bf-note-limit-link { color: var(--accent-ink); }
.bf-wall {
  position: relative;
  display: flex; flex-direction: column; align-items: flex-start; gap: 10px;
  width: 100%; margin-top: 12px; padding: 14px 40px 14px 15px;
  border-radius: 14px; border: 1px solid var(--border-accent);
  background: var(--accent-dim);
  font-family: var(--font-body);
  transition: opacity 200ms ease, transform 200ms ease;
}
.bf-wall.is-dismissing { opacity: 0; transform: translateY(-4px); }
/* a11y follow-up (2026-07-11): explicit, local reduced-motion guard for the
   wall's slide/fade — see the identical rationale comment on TURN
   (global.css, near mTurnIn/mTurnOut): the blanket `*` rule (global.css
   § Reduced motion) already forces this to near-instant today, so this is
   defense-in-depth/legibility, not a behavior change. Dismissal (the
   interaction) is untouched — only its motion is suppressed. */
@media (prefers-reduced-motion: reduce) {
  .bf-wall { transition-duration: 0.01ms; }
}
.bf-wall-head {
  margin: 0; font-size: 0.92rem; line-height: 1.35; color: var(--text-primary); /* dark-theme small text is never lime — Wave D follow-up; light stays --accent-ink below */
}
html.theme-light .bf-wall-head { color: var(--accent-ink); }
.bf-wall-cta {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 14px 16px; border-radius: 10px;
  background: var(--accent); color: var(--accent-text);
  font-family: var(--font-body); font-size: 0.86rem; font-weight: 700;
  text-decoration: none; cursor: pointer; border: none;
  transition: background 120ms ease, transform 120ms ease;
}
.bf-wall-cta:hover { background: var(--accent-hover); }
.bf-wall-cta:active { transform: scale(0.97); }
.bf-wall-close {
  position: absolute; top: 10px; right: 10px;
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 50%; border: none; padding: 0;
  background: transparent; color: var(--text-subtle); cursor: pointer;
}
.bf-wall-close:hover { color: var(--text-muted); background: color-mix(in srgb, var(--text-primary) 6%, transparent); }
/* Hit-slop only — the visible 26px circle is unchanged; -9px on every side
   brings the actual tappable area to 44x44 (same technique as .pf-icon-btn). */
.bf-wall-close::before {
  content: '';
  position: absolute;
  inset: -9px;
}
.bf-wall-close-ic svg { display: block; width: 14px; height: 14px; }

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

/* Reduced motion — the blur clears with a simple fade, no filter transition.
   a11y follow-up (2026-07-11), resolved: global.css's universal reduced-
   motion kill-switch (`*{transition-duration:0.01ms !important}`) wins over
   this rule's 160ms regardless of specificity, since !important beats
   non-!important outright — so this fade collapses to near-instant, same as
   everywhere else. Confirmed correct, not a gap: a media query that only
   ever suppresses/shortens motion never changes the guess-reveal LOGIC
   (content still appears the instant it's un-gated, just without an
   animated fade) — no device test needed for that class of change, only
   for anything that could alter WHEN/WHETHER content appears, which this
   doesn't. Left as-is rather than adding !important to force the 160ms
   through, since that would mean MORE motion for reduced-motion users than
   the already-compliant instant reveal, not less. */
@media (prefers-reduced-motion: reduce) {
  .bf-gated { transition: none; }
  .bf-guessmoment:not(.is-gated) .bf-gated { animation: bf-fade-in 160ms linear both; }
}

/* Inline icons. */
.bf-ic { display: inline-flex; align-items: center; flex: 0 0 auto; }
.bf-ic svg { display: block; }
/* WO-VIS Wave B: IC_LINK (card.js) used to hardcode stroke="#a89a7e" regardless
   of theme; now stroke="currentColor", scoped here to the muted tone it had. */
.bf-row-src .bf-ic { color: var(--text-subtle); }

/* Read-more disclosure — quiet inline; depth hidden by default (MOD 1). */
.bf-depth { margin-top: 24px; }
.bf-depth-summary {
  cursor: pointer; list-style: none;
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--font-body); font-size: 0.84rem; font-weight: 600;
  color: var(--text-primary); padding: 12px 0; /* a11y medium-pass: was 4px (~28px tall); dark-theme small text is never lime — Wave D follow-up, light stays --accent-ink below */
}
html.theme-light .bf-depth-summary { color: var(--accent-ink); }
.bf-depth-summary::-webkit-details-marker { display: none; }
.bf-depth-summary:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; border-radius: 4px; }
.bf-chev { color: var(--accent-ink); transition: transform 160ms ease; }
.bf-depth[open] .bf-chev { transform: rotate(180deg); }
.bf-depth-rows {
  margin-top: 14px; padding-top: 20px;
  border-top: 1px solid var(--border);
  display: flex; flex-direction: column; gap: 2px;
}
.bf-depth-context {
  font-family: var(--font-body); font-size: 0.7rem; font-weight: 600;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--text-subtle); margin: 0 0 6px;
}
.bf-depth-row { display: flex; align-items: center; gap: 9px; padding: 11px 0; text-decoration: none; }
a.bf-depth-row { cursor: pointer; }
.bf-depth-sep { height: 1px; background: var(--border); }
.bf-row-text { font-family: var(--font-body); font-size: 0.84rem; color: var(--text-muted); overflow-wrap: break-word; }
.bf-row-strong { color: var(--text-primary); font-weight: 600; overflow-wrap: break-word; }
.bf-row-muted { color: var(--text-subtle); }
/* Source link — provenance, deliberately quiet (not a loud link); the title
   underlines subtly only on hover/focus. */
.bf-row-src { text-decoration: none; }
.bf-row-src .bf-row-strong { text-decoration: none; }
.bf-row-src:hover .bf-row-strong,
.bf-row-src:focus-visible .bf-row-strong { text-decoration: underline; }
.bf-row-src:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; border-radius: 4px; }

/* ENTITY ARCHIVE — "More on <entity> — N pieces in the archive" (below the
   seam, after Read-more). Same disclosure shape as .bf-depth (details/
   summary, chevron rotates open) so it reads as one family of "there's more
   here, tap to see" affordances rather than a second, competing pattern. */
.bf-entity-archive { margin-top: 18px; }
.bf-entity-archive-summary {
  cursor: pointer; list-style: none;
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--font-body); font-size: 0.84rem; font-weight: 600;
  color: var(--text-primary); padding: 12px 0;
}
html.theme-light .bf-entity-archive-summary { color: var(--accent-ink); }
.bf-entity-archive-summary::-webkit-details-marker { display: none; }
.bf-entity-archive-summary:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; border-radius: 4px; }
.bf-entity-archive[open] .bf-chev { transform: rotate(180deg); }
.bf-entity-archive-rows {
  margin-top: 10px; padding-top: 14px;
  border-top: 1px solid var(--border);
  display: flex; flex-direction: column; gap: 2px;
}
.bf-entity-archive-row {
  display: flex; align-items: center; gap: 10px;
  /* wrap so the R2 attribution line drops BELOW the hook rather than competing
     with it for the row's width — see .bf-entity-archive-source */
  flex-wrap: wrap;
  padding: 11px 0;
  text-decoration: none;
  font-family: var(--font-body); font-size: 0.84rem; color: var(--text-muted);
}
.bf-entity-archive-row:not(:last-child) { border-bottom: 1px solid var(--border); }
.bf-entity-archive-row:hover { color: var(--text-primary); }
.bf-entity-archive-row:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; border-radius: 4px; }
.bf-entity-archive-hook { flex: 1; overflow-wrap: break-word; }
.bf-entity-archive-row.is-locked .bf-entity-archive-hook { color: var(--text-subtle); }
/* R2 attribution on an archive row (CORPUS-RIGHTS.md R2) — its own full-width
   line under the hook, deliberately quieter than the hook it credits. Renders
   only when the endpoint supplied a source, so there is no empty line. */
.bf-entity-archive-source {
  flex: 1 0 100%;
  margin-top: 3px;
  font-size: 0.74rem; line-height: 1.35;
  color: var(--text-subtle);
  overflow-wrap: break-word;
}
.bf-entity-archive-lock { flex: 0 0 auto; color: var(--text-disabled); display: inline-flex; }
.bf-entity-archive-lock svg { display: block; }

/* Report flag — muted outline by default; on report the ICON fills olive
   (like the loved heart, both themes). The "Reported — thanks" TEXT goes
   neutral on dark (small text is never lime — Wave D follow-up) and stays
   olive on light. */
.bf-flag { color: var(--text-subtle); }
.bf-row-report.is-reported .bf-flag { color: var(--accent-ink); }
.bf-row-report.is-reported .bf-flag svg path { fill: currentColor; }
.bf-row-report.is-reported .bf-row-text { color: var(--text-primary); font-weight: 600; }
html.theme-light .bf-row-report.is-reported .bf-row-text { color: var(--accent-ink); }
.bf-row-soon {
  font-family: var(--font-body); font-size: 0.66rem; font-weight: 600;
  letter-spacing: 0.05em; text-transform: uppercase; color: var(--text-subtle);
}

/* On This Day — year lead + lived-memory ("25 years ago today") badge. */
.bf-otd-year { color: var(--text-primary); }
.bf-otd-badge {
  display: inline-block;
  font-family: var(--font-body);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--text-muted); /* dark-theme small text is never lime — Wave D follow-up; light stays --accent-ink below. Background wash (--accent-dim) is translucent, not a solid fill, so the button-fill exemption doesn't apply here */
  background: var(--accent-dim);
  border-radius: 999px;
  padding: 2px 9px;
  margin: 0 0 8px;
}
html.theme-light .bf-otd-badge { color: var(--accent-ink); }

.bf-report {
  display: inline-block;
  margin-left: 14px;
  font-family: var(--font-body);
  font-size: 0.78rem;
  color: var(--text-subtle);
  text-decoration: underline;
}
.bf-report:hover { color: var(--text-primary); }

/* ── States ─────────────────────────────────────────────────────────────── */
.bf-state {
  text-align: center;
  font-family: var(--font-body);
  color: var(--text-subtle);
  padding: 40px 16px;
}
.bf-state .bf-state-h {
  font-family: var(--font-display);
  font-size: 1.3rem;
  color: var(--text-primary);
  margin: 0 0 8px;
  text-wrap: balance;
}
.bf-retry {
  margin-top: 14px;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.9rem;
  padding: 9px 16px;
  border: 1.5px solid var(--border-strong);
  border-radius: 10px;
  background: var(--surface-4);
  color: var(--text-primary);
  cursor: pointer;
}

/* A6 — masthead-shaped loading skeleton, replacing the plain "Loading…" text.
   Mirrors .bf-daily's shape — imprint + nameplate + dateline bars in the mast,
   then hook/debrief lines in the body — so the page doesn't jump when the real
   card lands. Shimmer recipe ported from components.css's .skeleton (NOT
   loaded on briefing.html/archive.html, which only load global.css +
   briefing.css) — same GPU-friendly transform-sweep, scoped under bf- so it
   can't collide if components.css is ever added here later. Archive's
   #ar-loader reuses this same treatment (hardcoded HTML, still just
   .bf-skel-kicker — a single bar is enough for a loading TILE, not a full page
   mount, so it's untouched here). */
.bf-skeleton {
  border-radius: 22px;
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--border);
}
.bf-skel-mast {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 16px 28px 14px;
  background: var(--surface-3);
  border-top: 2px solid var(--border-accent);
}
.bf-skel-imprint { width: 22%; height: 9px; }
.bf-skel-title { width: 46%; height: 20px; margin-top: 2px; }
.bf-skel-dateline { width: 64%; height: 11px; margin-top: 8px; }
.bf-skel-body { padding: 30px 32px 28px; display: flex; flex-direction: column; gap: 12px; }
.bf-skel-line {
  position: relative;
  overflow: hidden;
  border-radius: 6px;
  background: var(--surface-2);
}
.bf-skel-line::after {
  content: '';
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--text-primary) 6%, transparent) 50%, transparent 100%);
  animation: bf-skel-sweep 1.3s ease-in-out infinite;
}
/* color-mix(var(--text-primary)) inverts per theme automatically (white tint
   dark, dark tint light), so the loading sweep stays visible without a
   separate light override. */
@keyframes bf-skel-sweep { to { transform: translateX(100%); } }
.bf-skel-kicker  { width: 34%; height: 12px; }
.bf-skel-hook    { width: 92%; height: 26px; margin-top: 6px; }
.bf-skel-hook-2  { width: 68%; height: 26px; }
.bf-skel-debrief   { width: 100%; height: 14px; margin-top: 10px; }
.bf-skel-debrief-2 { width: 82%; height: 14px; }

/* ── Bottom anonymous / signup prompt ───────────────────────────────────── */
.bf-foot { margin-top: 26px; text-align: center; }
.bf-signup {
  background: var(--surface);
  border: 1.5px solid var(--border-strong);
  border-radius: 14px;
  padding: 18px 20px;
}
.bf-signup p { font-family: var(--font-body); }
.bf-signup-h {
  font-family: var(--font-display);
  font-size: 1.02rem;
  font-weight: 600;
  color: var(--text-primary);
  margin: 0 0 4px;
}
.bf-signup-sub { font-size: 0.84rem; color: var(--text-subtle); margin: 0 0 14px; }

/* Waitlist capture — email + lime "Join the waitlist" (reuses waitlist-signup). */
.bf-wl { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; }
.bf-wl-email {
  flex: 1 1 220px; max-width: 300px;
  font-family: var(--font-body); font-size: 0.92rem;
  padding: 11px 14px;
  border: 1.5px solid var(--border-strong);
  border-radius: 12px;
  background: var(--surface-4);
  color: var(--text-primary);
}
.bf-wl-email:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
.bf-wl-btn {
  font-family: var(--font-display); font-weight: 600; font-size: 0.92rem;
  padding: 11px 20px; border: none; border-radius: 12px;
  background: var(--accent);      /* sanctioned lime fill: dark text on lime */
  color: var(--accent-text); cursor: pointer;
}
.bf-wl-btn:hover { background: var(--accent-hover); }
.bf-wl-btn:disabled { opacity: 0.6; cursor: default; }
/* Native variant's CTA is an <a>, not a <button> — .bf-wl-btn covers the shared
   fill/type; this just makes the link render like one. Sized up from the
   shared .bf-wl-btn baseline (2026-07-08 design pass) — this is the button an
   anon SAMPLE reader actually converts through, so it earns a bigger tap
   target + more visual weight than the plain "Join the waitlist" web form
   button it shares a base class with; that one is untouched. Cascade note:
   this rule must stay AFTER .bf-wl-btn in source order (same specificity —
   single class each) for these overrides to win. */
.bf-signup-cta {
  display: inline-block;
  text-decoration: none;
  font-size: 1.05rem;
  font-weight: 700;
  padding: 15px 30px;
}
.bf-wl-msg { font-family: var(--font-body); font-size: 0.82rem; margin: 10px 0 0; min-height: 1em; }
.bf-wl-msg.is-ok { color: var(--text-primary); font-weight: 600; } /* dark-theme small text is never lime — Wave D follow-up; light stays --accent-ink below */
html.theme-light .bf-wl-msg.is-ok { color: var(--accent-ink); }
.bf-wl-msg.is-err { color: var(--error); }
.bf-anon {
  font-family: var(--font-body);
  font-size: 0.76rem;
  color: var(--text-subtle);
  margin: 12px 0 0;
}

/* ── The Archive — LEDGER OF DAYS GROUPED BY MONTH (2026-07-17 rebuild) ────────
   Index altitude, not reading altitude: a compact row per published day, not a
   card. Zero CTAs, zero lime anywhere in this block — the shelf is the
   argument (MONETIZATION-MODEL §8.1.1, amended); the one membership utterance
   on the page is the standing rule line in the header, not here. */
.ar-ledger { display: flex; flex-direction: column; gap: 34px; }
.ar-month { display: flex; flex-direction: column; }
.ar-month-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 0 2px 9px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 2px;
}
.ar-month-title {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.08rem;
  color: var(--text-primary);
  margin: 0;
}
/* Older-month count badge ("June 2026 — 30 pieces") — the title-depth
   fallback's compensating signal: past ARCHIVE_TITLE_DEPTH_MONTHS a section
   shows no titles (list-briefings.js withholds them server-side), so the
   count is what proves cadence/scale instead. Absent on a recent (titled)
   section — see js/archive.js's appendRow. */
.ar-month-count {
  font-family: var(--font-body);
  font-size: 0.78rem;
  color: var(--text-subtle);
}
.ar-month-rows { display: flex; flex-direction: column; }

/* Rows are LINKS, not surfaces — a real <a href="/briefing.html?date=…">, no
   JS-intercepted click. Tapping a locked row opens the day view, which
   carries the spine + membership wall THERE (§8.1.1); this row never does. */
.ar-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 11px 2px;
  border-bottom: 1px solid var(--border);
  text-decoration: none;
  color: inherit;
  transition: background 120ms ease;
}
.ar-row:last-child { border-bottom: none; }
.ar-row:hover { background: var(--border); }
.ar-row:active { opacity: 0.7; }
.ar-row:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; border-radius: 4px; }
.ar-row-date {
  flex: 0 0 40px;
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1.15;
}
.ar-row-daynum { font-family: var(--font-display); font-weight: 600; font-size: 1rem; color: var(--text-primary); }
.ar-row-weekday { font-family: var(--font-body); font-size: 0.6rem; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-subtle); }
/* Title dominates when present (recent months); absent past the depth
   threshold — a date-only row is deliberately lighter, not padded to match. */
.ar-row-title {
  flex: 1;
  min-width: 0;
  font-family: var(--font-display);
  font-size: 0.94rem;
  line-height: 1.35;
  color: var(--text-primary);
  overflow: hidden;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}
/* Today's badge is the ONLY visual difference an unlocked today's row ever
   carries. Neutral ink — never lime; this isn't a CTA. */
.ar-row-badge {
  flex: 0 0 auto;
  margin-left: auto;
  font-family: var(--font-body);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-subtle);
  white-space: nowrap;
}
/* The lock mark — a small seal, not a button; the pitch lives on the day view
   (briefing.html?date=), never here. */
.ar-row-lock { flex: 0 0 auto; margin-left: auto; color: var(--text-disabled); display: inline-flex; }
.ar-row-lock svg { display: block; }

.ar-loader {
  text-align: center;
  font-family: var(--font-body);
  font-size: 0.82rem;
  color: var(--text-subtle);
  padding: 18px 0;
}
#ar-sentinel { height: 1px; }

/* Locked teaser (WO-CONV-1) — the spine standard: hook + source, no body, no
   blur. Neutral ink throughout — the lime budget reserves the accent for a
   view's own membership-intent action (WO-CONV-2), not this passive read. */
.ar-locked {
  background: var(--surface);
  border: 1.5px dashed var(--border-strong);
  border-radius: 16px;
  padding: 28px 22px;
  text-align: center;
}
.ar-locked-hook {
  font-family: var(--font-display);
  font-size: 1.15rem;
  color: var(--text-primary);
  margin: 0 0 8px;
  text-wrap: balance;
}
/* Standfirst on the LOCKED spine (Ruling 1, 2026-07-29) — the identification
   line a reader meets before any detail, carried into the teaser so a locked
   day names its subject instead of only headlining it. Subordinate to
   .ar-locked-hook in exactly the way .bf-standfirst is to .bf-hook-text
   (smaller, body-face, muted) — the two-subject law applies here too. */
.ar-locked-standfirst {
  font-family: var(--font-body);
  font-size: 0.88rem;
  font-weight: 600;
  line-height: 1.4;
  color: var(--text-muted);
  max-width: 46ch;
  margin: 0 auto 10px;
  /* Same rag treatment as .ar-locked-hook above — a centred three-line clause
     otherwise orphans its closing date on a line of its own at desktop width. */
  text-wrap: balance;
}
.ar-locked-source {
  font-family: var(--font-body);
  font-size: 0.82rem;
  color: var(--text-subtle);
  margin: 0 0 10px;
}
/* WO-CONV-3: the Collection shelf's quiet sub-line under a frozen kept piece
   ("Kept 12 July · members reopen every piece") — archive's lockedTeaser
   calls omit this (subLine unset), so it never renders there. */
.ar-locked-subline {
  font-family: var(--font-body);
  font-size: 0.78rem;
  color: var(--text-subtle);
  font-style: italic;
  margin: 0 0 10px;
}
/* THE SEAL (§ED240) — the shape of the withheld body, so a locked spine can
   no longer be mistaken for a short piece. Grammar lifted from index.html's
   .lv2-alm-seal (labelled sealed block, uppercased in the sheet, action
   beneath). ONE deliberate difference: the border is SOLID and the fill is
   surface-2, where the landing's seal is dashed — .ar-locked is itself a
   dashed box, and a dashed box directly inside a dashed box reads as a
   rendering fault rather than as nesting. Neutral ink throughout: no lime
   here, the budget is spent by the membership card once it is summoned. */
.ar-seal {
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface-2, var(--surface));
  padding: 14px 16px 16px;
  margin: 0 auto 16px;
  max-width: 34ch;
  text-align: left;
}
.ar-seal-label {
  display: block;
  font-family: var(--font-body);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  /* Ratified as the label "THE STORY" (index.html:1635) — cased here, not in
     the DOM, for the same reason it is cased there. */
  text-transform: uppercase;
  color: var(--text-subtle);
  margin: 0 0 10px;
}
.ar-seal-shape { display: flex; flex-direction: column; gap: 7px; }
.ar-seal-rule {
  display: block;
  height: 7px;
  border-radius: 3px;
  background: var(--border-strong);
}
.ar-locked-today {
  font-family: var(--font-body);
  font-size: 0.86rem;
  font-weight: 600;
  color: var(--text-primary);
  text-decoration: underline;
  margin: 0 0 14px;
  display: inline-block;
}
/* Tap-reveal for the membership card — deliberately NEUTRAL, not lime (see
   lockedTeaser's comment: the lime budget is reserved for the card's own CTA
   once summoned, never for the reveal action itself). */
.ar-locked-reveal {
  display: block;
  margin: 0 auto;
  padding: 10px 16px;
  border-radius: 10px;
  border: 1px solid var(--border-strong);
  background: transparent;
  color: var(--text-subtle);
  font-family: var(--font-body);
  font-size: 0.82rem;
  cursor: pointer;
}
.ar-locked-reveal:hover { background: color-mix(in srgb, var(--text-primary) 5%, transparent); }

/* THE MEMBERSHIP CARD (WO-CONV-2, §8.1/§8.3) — one component, every wall.
   Neutral concrete throughout except the single lime CTA (the palette
   invariant's "one primary action per view" — this card's only chromatic
   element). Lives inside .ar-locked, directly under the spine + the
   "read today free" link, never a modal. */
.bf-mwall {
  border-top: 1px solid var(--border);
  margin-top: 6px;
  padding-top: 16px;
  text-align: left;
}
.bf-mwall-head {
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 400;
  color: var(--text-primary);
  margin: 0 0 8px;
  text-wrap: balance;
}
html.theme-light .bf-mwall-head { color: var(--accent-ink); }
.bf-mwall-body {
  font-family: var(--font-body);
  font-size: 0.86rem;
  line-height: 1.45;
  color: var(--text-subtle);
  margin: 0 0 10px;
}
.bf-mwall-status {
  font-family: var(--font-body);
  font-size: 0.78rem;
  color: var(--text-muted);
  margin: 0 0 14px;
}
.bf-mwall-cta {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 13px 18px; border-radius: 10px;
  background: var(--accent); color: var(--accent-text);
  font-family: var(--font-body); font-size: 0.86rem; font-weight: 700;
  border: none; cursor: pointer;
  transition: background 120ms ease, transform 120ms ease;
}
.bf-mwall-cta:hover { background: var(--accent-hover); }
.bf-mwall-cta:active { transform: scale(0.97); }
/* §8-A free-account card's secondary action — plain text, never a second
   lime element alongside .bf-mwall-cta in the same view (palette invariant). */
.bf-mwall-login {
  display: block; margin-top: 12px;
  font-family: var(--font-body); font-size: 0.8rem;
  color: var(--text-subtle); text-decoration: underline;
}
.bf-mwall-login:hover { color: var(--text-primary); }
/* Quiet link to /membership.html (the sales-gap fix, ruled 2026-07-17) — same
   neutral-ink treatment as .bf-mwall-login, for the identical reason: never a
   second lime element alongside the card's own CTA/plan buttons. */
.bf-mwall-more {
  display: block; margin-top: 12px;
  font-family: var(--font-body); font-size: 0.8rem;
  color: var(--text-subtle); text-decoration: underline;
}
.bf-mwall-more:hover { color: var(--text-primary); }
.bf-mwall-form {
  display: flex; gap: 8px; flex-wrap: wrap;
}
.bf-mwall-email {
  flex: 1 1 200px; min-width: 0;
  padding: 12px 14px; border-radius: 10px;
  border: 1px solid var(--border-strong);
  background: var(--surface-2, var(--surface));
  color: var(--text-primary);
  font-family: var(--font-body); font-size: 0.86rem;
}
.bf-mwall-email:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.bf-mwall-submit {
  padding: 12px 16px; border-radius: 10px;
  background: var(--accent); color: var(--accent-text);
  font-family: var(--font-body); font-size: 0.86rem; font-weight: 700;
  border: none; cursor: pointer; white-space: nowrap;
}
.bf-mwall-submit:hover { background: var(--accent-hover); }
.bf-mwall-submit:disabled { opacity: 0.65; cursor: default; }
.bf-mwall-error { font-family: var(--font-body); font-size: 0.78rem; color: var(--error, #b3261e); margin: 8px 0 0; }
.bf-mwall-error:empty { margin: 0; }
.bf-mwall-done {
  font-family: var(--font-body); font-size: 0.86rem; font-weight: 600;
  color: var(--text-primary); margin: 0;
}

/* Art.16(m) immediate-access withdrawal-waiver consent (terms.html "Right of
   withdrawal (EU/UK)") — sits between the status line and the plan picker,
   so it's read and actively checked BEFORE a plan tap can fire checkout.
   Plain concrete/border checkbox, no lime (this is an affirmation control,
   not the primary action — the plan buttons remain the CTAs). */
.bf-mwall-consent {
  display: flex; align-items: flex-start; gap: 8px;
  margin: 0 0 12px; cursor: pointer;
}
.bf-mwall-consent-box {
  flex: 0 0 auto; margin: 2px 0 0;
  width: 16px; height: 16px;
  accent-color: var(--accent-ink, var(--accent));
  cursor: pointer;
}
.bf-mwall-consent-text {
  font-family: var(--font-body); font-size: 0.78rem; line-height: 1.45;
  color: var(--text-subtle);
}
.bf-mwall-consent-text a {
  color: var(--text-primary); text-decoration: underline; text-underline-offset: 2px;
}
.bf-mwall-consent-text a:hover { color: var(--accent-ink, var(--accent)); }

/* Monthly/annual checkout picker — two co-equal choices, so all neutral (no
   lime): the palette invariant's "one primary action per view" budget
   doesn't apply to a menu with no single primary among them. The annual
   option carries the founding-rate offer (MONETIZATION-MODEL §7-P): a quiet
   struck-through list price + the active founding price + a small label —
   deliberately restrained (no red slash, no badge chrome) per the calm
   editorial register; still no lime, same budget reasoning as above. */
.bf-mwall-picker {
  display: flex; gap: 8px; flex-wrap: wrap;
}
.bf-mwall-plan {
  display: flex; flex-direction: column; align-items: flex-start; gap: 3px;
  padding: 12px 16px; border-radius: 10px;
  background: transparent; color: var(--text-primary);
  border: 1px solid var(--border-strong);
  font-family: var(--font-body); font-size: 0.86rem;
  cursor: pointer; text-align: left;
  transition: border-color 120ms ease, background 120ms ease;
}
.bf-mwall-plan:hover { border-color: var(--accent); }
.bf-mwall-plan:disabled { opacity: 0.55; cursor: default; }
.bf-mwall-plan-label { font-weight: 600; }
.bf-mwall-plan-price {
  display: flex; align-items: baseline; gap: 6px;
  font-family: var(--font-numeric); font-weight: 700;
}
.bf-mwall-plan-strike {
  font-weight: 400; font-size: 0.82rem;
  color: var(--text-subtle); text-decoration: line-through;
}
.bf-mwall-plan-note {
  font-family: var(--font-body); font-weight: 400; font-size: 0.72rem;
  color: var(--text-subtle);
}

@media (max-width: 480px) {
  .bf-mwall-form { flex-direction: column; }
  /* flex-basis:200px on .bf-mwall-email means WIDTH in the desktop row
     layout above, but the SAME basis reads as HEIGHT once flex-direction
     flips to column here — reset to auto or the input stretches tall. */
  .bf-mwall-email { flex: 0 0 auto; }
  .bf-mwall-cta, .bf-mwall-submit { width: 100%; }
  .bf-mwall-picker { flex-direction: column; }
  .bf-mwall-plan { width: 100%; }
}

/* ── Responsive ─────────────────────────────────────────────────────────────
   Desktop keeps the generous editorial feel above. On phones (~≤480px) the card
   scales down, padding tightens, and every tap target stays ≥44px. The shared
   card.js means this covers BOTH the /briefing card and the /archive scroll. */
@media (max-width: 480px) {
  .bf-page { padding: calc(12px + var(--safe-top)) 12px 52px; }   /* + camera/status-bar inset (phone width → this rule wins) */
  .bf-wrap { max-width: 100%; }

  /* Mobile-only: tighter nameplate padding, same left-aligned two-row layout. */
  .bf-masthead { padding: 11px 18px 10px; }
  .bf-mast-wordmark { font-size: 1.2rem; }
  .bf-daily-body { padding: 16px 18px 22px; }   /* tightened top — less pre-fact scroll */

  .bf-quote { padding-top: 20px; }   /* still generous, just a touch tighter than desktop's 26px */
  .bf-accent-dash { margin-bottom: 11px; }   /* slightly tighter on phones */
  .bf-hook-text { font-size: 1.75rem; line-height: 1.2; }         /* smaller but still the hero */
  .bf-hook-text.is-short { font-size: 2rem; }
  .bf-standfirst { font-size: 0.86rem; }
  .bf-fact-deck { font-size: 1.05rem; line-height: 1.48; }
  .bf-debrief { font-size: 0.98rem; line-height: 1.58; }

  .bf-reaction { gap: 10px; margin-top: 22px; }
  .bf-love-pill { padding: 12px 18px; }                     /* ≥44px touch target */
  .bf-like-pill { padding: 12px 18px; }                     /* §ED240 — same */
  .bf-social { font-size: 0.76rem; }

  /* a11y medium-pass: base rule's padding:12px already covers mobile too — this
     10px override would have REGRESSED it below the new 44px floor. Removed. */
  .bf-depth-row { padding: 13px 0; }                        /* ≥44px touch target */

  /* Culture selector wraps and stays tappable on narrow screens. */
  .bf-cults { gap: 6px; }
  .bf-cult { padding: 6px 6px; }
}

/* Larger phones / small tablets — a gentle middle step. */
@media (min-width: 481px) and (max-width: 640px) {
  .bf-daily-body { padding: 18px 24px 24px; }
  .bf-hook-text { font-size: 2.1rem; }
}

/* Desktop width for .bf-wrap lives entirely in the DESKTOP LAYER block
   below (WO-WEB-2, ≥900px → 720px) — that rule comes later in the
   cascade and already wins over any ≥768px/≥1024px value here, so the
   old pre-WO-WEB-2 steps that used to live at this point were dead code
   (only ever "live" in the narrow 768-899px gap, which is not one of
   this app's supported tiers — see docs/APP-WEB-ARCHITECTURE.md's single
   900px breakpoint rule). Removed rather than left shadowed. */

/* ── Archive jump-to-date calendar (design direction C · "The Strip") ────────
   A dropdown navigation aid within the archive scroll (not a page). Olive header
   band, cream grid, lime dots on briefing days, olive-filled today, legend, and a
   "Worth revisiting" list of the most-loved past dailies. */
/* §ED529 — `z-index: 45 /* trigger + panel above the scrim (40) *\/` DELETED.
   It never did that. On /briefing .ar-calbar is a flex item of .bf-header-row,
   so the 45 did create a stacking context — but one nested inside .bf-header's
   own context at 20, so neither the trigger nor the panel was ever above the
   scrim. A declaration measured never to have had its stated effect is deleted,
   not re-commented. position:relative stays: /archive still centres the bar. */
.ar-calbar { text-align: center; margin: 0 0 18px; position: relative; }
/* /briefing ONLY: .ar-calbar sits inside .bf-header-row (flex, space-between,
   "culture pill left, calendar trigger right"), so it's shrunk to the
   trigger's own width and flush against the row's right edge — .ar-cal-panel
   centering "within" that narrow box pushed the panel off the right edge of
   the content column. Un-positioning it here hands .ar-cal-panel's containing
   block up to .bf-header-row (the full-width row, see its position:relative
   above), so the panel centers on the column instead. /archive's .ar-calbar
   is a standalone full-width block (not nested in .bf-header-row) — this
   selector doesn't match there, so its calendar is untouched. */
.bf-header-row .ar-calbar { position: static; }
.ar-cal-trigger {
  display: inline-flex; align-items: center; gap: 10px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 13px;
  padding: 10px 15px; cursor: pointer;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
  font-family: var(--font-body); font-size: 0.88rem; font-weight: 600; color: var(--text-primary);
  transition: background 140ms ease, transform 120ms ease, box-shadow 140ms ease;
}
.ar-cal-trigger:hover { background: var(--surface-3); box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5); }
.ar-cal-trigger:active { transform: scale(0.97); }
.ar-cal-trigger:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
/* R5: the two icon SVGs hardcoded #4A6300/#8a7f68 strokes regardless of theme
   (never adapted for dark mode) — now `stroke="currentColor"` + token-driven
   color here instead. */
.ar-cal-ico { color: var(--accent-ink); }
.ar-cal-chev { color: var(--text-subtle); transition: transform 160ms ease; }
.ar-cal-trigger.is-open .ar-cal-chev { transform: rotate(180deg); }

/* Scrim — soft blurred layer over the feed while the calendar floats. R5:
   background moved to T0's --scrim token (the guess-panel's OWN backdrop-
   filter is gone, R5 item #2, but this small blur(2px) modal scrim is a
   separate, much cheaper case — left as-is). */
/* §ED529 — z-index RAISED 40 -> 9100, AND THE PANEL MOVED OUT FROM UNDER IT.
   The 40 was never the number that decided this. The panel carried z-index 50
   and still rendered UNDER the scrim, dimmed and blurred with the rest of the
   page, because it lived inside .bf-header — which is a stacking context three
   times over (position:sticky + z-index:20, transform:translateZ(0), and
   backdrop-filter). Its 50 was scoped INSIDE that context; against the root the
   whole header painted at 20, below this scrim's 40. Measured, not read:
   elementFromPoint() at the centre of the open panel's own month header
   returned `DIV.ar-cal-scrim is-open`.
   Raising a z-index alone could not have fixed that, so js/card.js's
   mountCalendar now reparents BOTH this scrim and .ar-cal-panel to <body>,
   where they are siblings in the root stacking context and these numbers
   finally mean what they say. See scripts/audit-ed529-chrome.js. */
.ar-cal-scrim {
  position: fixed; inset: 0; z-index: 9100;
  background: var(--scrim);
  -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity 200ms ease-in, visibility 0s linear 200ms;
}
.ar-cal-scrim.is-open { opacity: 1; visibility: visible; pointer-events: auto; transition: opacity 200ms ease-out, visibility 0s; }

/* Panel — FLOATS over the feed (does not push it), anchored below the trigger.
   §ED529 — position:absolute -> fixed, and top/left are now written by
   mountCalendar's positionPanel() rather than by this sheet. The element is a
   <body> child now (see the scrim note above), so it has no positioned ancestor
   to anchor to; the anchor box is measured at open time from .bf-header-row on
   /briefing and .ar-calbar on /archive — the SAME two boxes that used to be its
   containing block, so the resting geometry is unchanged in both places.
   z-index 9110 sits just above the scrim's 9100, and both are now in the root
   stacking context where that comparison is actually performed. */
.ar-cal-panel {
  position: fixed; top: 0; left: 0;
  width: min(380px, calc(100vw - 28px));
  z-index: 9110; text-align: left;
  opacity: 0; visibility: hidden; pointer-events: none;
  transform: translateY(-6px) scale(0.96); transform-origin: top center;
  transition: opacity 200ms ease-in, transform 200ms ease-in, visibility 0s linear 200ms;
}
.ar-cal-panel.is-open {
  opacity: 1; visibility: visible; pointer-events: auto;
  transform: translateY(0) scale(1);
  transition: opacity 200ms ease-out, transform 200ms cubic-bezier(0.22, 1, 0.36, 1), visibility 0s;
}
.ar-cal {
  background: var(--surface); border: 1px solid var(--border); border-radius: 20px; overflow: hidden;
  /* Matches the daily card exactly — the shared --shadow-hero token (global.css
     Phase A), which this panel's glow recipe was promoted FROM. */
  box-shadow: var(--shadow-hero);
}
/* header band — elevated by luminance */
.ar-cal-head { background: var(--surface-3); padding: 16px 20px; display: flex; align-items: center; justify-content: space-between; }
.ar-cal-nav {
  width: 44px; height: 44px; /* a11y medium-pass (2026-07-11): was 34x34 */
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 10px; background: var(--border); border: none; cursor: pointer;
  color: var(--text-subtle); /* WO-VIS Wave B: CAL_CHEV_L/R (card.js) used to
     hardcode stroke="#EAF4C8" regardless of theme; now stroke="currentColor" */
  transition: background 120ms ease, transform 120ms ease;
}
.ar-cal-nav:hover { background: var(--border-strong); }
.ar-cal-nav:active { transform: scale(0.9); }
.ar-cal-nav:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
.ar-cal-titlewrap { display: flex; align-items: baseline; gap: 8px; }
.ar-cal-month { font-family: var(--font-display); font-weight: 500; font-size: 1.4rem; color: var(--text-primary); }
.ar-cal-year { font-family: var(--font-body); font-size: 0.8rem; color: var(--text-subtle); }

.ar-cal-body { padding: 16px 20px 14px; }
.ar-cal-dow { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; margin-bottom: 6px; }
.ar-cal-dowd { text-align: center; font-family: var(--font-body); font-size: 0.62rem; font-weight: 600; letter-spacing: 0.04em; color: var(--text-subtle); }
.ar-cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.ar-cal-cell {
  position: relative; height: 44px; /* a11y medium-pass (2026-07-11): was 38px */
  display: flex; align-items: center; justify-content: center;
  border-radius: 9px; font-family: var(--font-body); font-size: 0.86rem; color: var(--text-subtle);
  transition: background 120ms ease, transform 120ms ease;
  /* Non-selectable days (empty / future / still-loading) are NOT inputs — show the
     default arrow, never a text I-beam. Only .has-dot / .is-today opt back into a
     pointer below. Kills the "typing cursor on a blank calendar" look. */
  cursor: default; user-select: none; -webkit-user-select: none;
}
.ar-cal-cell.is-blank { visibility: hidden; }
.ar-cal-cell.is-empty { color: var(--text-subtle); }
.ar-cal-cell.is-future { color: var(--text-disabled); opacity: 0.5; }
.ar-cal-cell.has-dot { color: var(--text-primary); font-weight: 600; cursor: pointer; }
.ar-cal-cell.has-dot:hover { background: var(--surface-3); }
.ar-cal-cell.is-today { background: var(--accent); color: var(--accent-text); font-weight: 700; cursor: pointer; }
.ar-cal-cell.is-today:hover { background: var(--accent-hover); }
.ar-cal-cell.has-dot:active, .ar-cal-cell.is-today:active { transform: scale(0.9); }
.ar-cal-cell:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
/* The day currently shown in the isolated single-day view. Explicit state (set via
   mountCalendar.setSelected) — an ink ring, distinct from today's lime fill.
   (Was a bare-lime ring; thin strokes need --accent-ink on light, §3a.) */
.ar-cal-cell.is-selected { box-shadow: inset 0 0 0 2px var(--accent-ink); color: var(--text-primary); font-weight: 700; cursor: pointer; }

/* The has-edition marker repeats on every day with content — dozens can be on
   screen at once, so it's a density/wayfinding mark, not "the" signal; neutral
   ink. Today's own marker stays lime (is-today, above) — it's the one that
   matters on this view. */
.ar-cal-dot { position: absolute; bottom: 5px; left: 50%; transform: translateX(-50%); width: 5px; height: 5px; border-radius: 50%; background: var(--text-muted); }

.ar-cal-legend { display: flex; align-items: center; gap: 14px; padding: 0 20px 14px; }
.ar-cal-leg { display: inline-flex; align-items: center; gap: 6px; font-family: var(--font-body); font-size: 0.72rem; color: var(--text-subtle); }
.ar-cal-leg-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--text-muted); }
.ar-cal-leg-today { width: 11px; height: 11px; border-radius: 4px; background: var(--accent); }

/* "Worth revisiting" — most-loved past dailies */
.ar-worth { background: var(--surface-2); border-top: 1px solid var(--border); padding: 16px 20px 18px; }
.ar-worth-label { font-family: var(--font-body); font-size: 0.66rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-subtle); margin: 0; }
.ar-worth-list { display: flex; flex-direction: column; margin-top: 10px; }
.ar-worth-row {
  display: flex; align-items: baseline; gap: 12px; width: 100%; text-align: left;
  background: none; border: none; border-bottom: 1px solid var(--border); padding: 9px 8px; cursor: pointer;
  border-radius: 8px;
  transition: background 120ms ease, opacity 120ms ease;
}
.ar-worth-row:last-child { border-bottom: none; }
.ar-worth-row:hover { background: var(--border); }
.ar-worth-row:hover .ar-worth-t { text-decoration: underline; }
.ar-worth-row:active { opacity: 0.65; }
.ar-worth-row:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; border-radius: 4px; }
.ar-worth-d { flex: 0 0 34px; font-family: var(--font-display); font-size: 0.88rem; font-weight: 600; color: var(--text-primary); } /* dark-theme small text is never lime — Wave D follow-up; light stays --accent-ink below */
html.theme-light .ar-worth-d { color: var(--accent-ink); }
.ar-worth-t { flex: 1; font-family: var(--font-display); font-size: 0.92rem; line-height: 1.3; color: var(--text-primary); }

/* Archive door inside the calendar panel (Daily only) — the deleted page-header
   "View all daily facts →" link now lives inside the time-travel control. */
.ar-cal-foot { border-top: 1px solid var(--border); padding: 12px 20px; text-align: center; }
.ar-cal-foot-link {
  font-family: var(--font-body);
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--text-primary); /* dark-theme small text is never lime — Wave D follow-up; light stays --accent-ink below */
  text-decoration: none;
}
html.theme-light .ar-cal-foot-link { color: var(--accent-ink); }
.ar-cal-foot-link:hover { text-decoration: underline; }
.ar-cal-foot-link:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; border-radius: 4px; }

/* Brief highlight when the calendar jumps to a day's row (jumpToDate). */
@keyframes ar-flash { 0% { box-shadow: 0 0 0 3px transparent; } 25% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 55%, transparent); } 100% { box-shadow: 0 0 0 3px transparent; } }
.ar-row-flash { animation: ar-flash 1.6s ease; border-radius: 10px; }

/* Calendar — respect reduced motion (fade only, no transform/scale animation). */
@media (prefers-reduced-motion: reduce) {
  .ar-cal-panel, .ar-cal-panel.is-open { transform: none; transition: opacity 120ms linear, visibility 0s; }
  .ar-cal-panel:not(.is-open) { transition: opacity 120ms linear, visibility 0s linear 120ms; }
  .ar-cal-scrim, .ar-cal-scrim.is-open { transition: opacity 120ms linear, visibility 0s; }
  .ar-cal-cell, .ar-cal-nav, .ar-worth-row, .ar-cal-trigger, .ar-cal-chev, .ar-row { transition: none; }
  .ar-cal-cell:active, .ar-cal-nav:active, .ar-cal-trigger:active { transform: none; }
  /* §ED176: the selector now carries the calendar trigger's transition and
     press, so it must carry its reduced-motion exemption too. */
  .bf-cult-trigger { transition: none; }
  .bf-cult-trigger:active { transform: none; }
  .ar-row-flash { animation: none; }
  .bf-daily { transition: none; }
  .bf-daily:hover { transform: none; }
}

/* Calendar — comfortable tap targets on phones (≥44px). */
@media (max-width: 480px) {
  /* a11y medium-pass (2026-07-11): .ar-cal-cell/.ar-cal-nav's base rules are
     now 44px themselves — the 40px .ar-cal-nav override here would have
     REGRESSED it below the floor on mobile specifically. Removed; .ar-cal-cell's
     44px is now redundant with the base rule but harmless, left as-is. */
  .ar-cal-cell { height: 44px; }
  .ar-worth-row { padding: 12px 8px; }
  .ar-cal-trigger { padding: 12px 16px; }
}

/* ── Shared bottom nav (chunk 6) ─────────────────────────────────────────────
   The Daily now carries the ONE shared bottom nav (rendered by js/bottom-nav.js).
   These rules are a self-contained copy of the .bottom-nav block from
   components.css — the Daily pages load global.css (for the vars) + briefing.css
   but NOT components.css, so the nav styling lives here. Keep in sync if the
   nav chrome ever changes. Also reserve bottom space so the fixed nav never
   covers the footer / last card. */
/* --nav-bottom-h already includes the home-indicator inset, so it's not added
   again here (would double the bottom gap). */
.bf-page { padding-bottom: calc(var(--nav-bottom-h) + 28px); }
@media (max-width: 480px) {
  .bf-page { padding-bottom: calc(var(--nav-bottom-h) + 20px); }
}

/* Floating pill — kept in lockstep with the .bottom-nav block in components.css.
   Outer = inset carrier (height + padding-bottom preserve the safe-area contract,
   untouched); the pill chrome lives on .bottom-nav-inner. */
.bottom-nav {
  position: fixed; bottom: 0; left: 0; right: 0;
  height: var(--nav-bottom-h);
  background: transparent;        /* was var(--surface) */
  border-top: none;               /* was 1px solid var(--border) */
  z-index: var(--z-sticky);
  display: flex; align-items: center; justify-content: center;
  padding-bottom: var(--safe-bottom, 0px);
  padding-left: 16px; padding-right: 16px;   /* gutter from screen edge */
  pointer-events: none;           /* transparent margins never eat taps */
  overflow: visible;              /* active circle pops above the pill */
  /* Do NOT add view-transition-name — see the .bottom-nav note in
     components.css. Cross-document View Transitions are now fully disabled
     (global.css), so this is moot for the current jump, but re-enabling VT
     without also re-solving the cross-page snapshot mismatch would bring it
     back regardless of naming. */
}
.bottom-nav-inner {
  display: flex; width: 100%; max-width: var(--app-width); margin-inline: auto;
  height: 58px; padding: 0 8px;
  border-radius: var(--r-full);
  /* §ED441 §7b / DELETED §ED451: `background: rgba(18,18,20,.92)` lived here.
     §ED441 rendered it in all four surfaces (web light, web dark, shell light,
     shell dark) and the literal LOST every time — global.css's
     --c-glass-chrome wins on the web, app-nav.css's --surface in the shell. It
     painted nowhere. Removed rather than left as clutter because the failure
     mode is invisible: it is one specificity bump away from repainting the bar
     near-black, and a colour that did not change looks exactly like a colour
     that was not meant to change (§ED440 T1).

     THE `border` AND `box-shadow` BELOW ARE LEFT IN PLACE DELIBERATELY. Both
     are also overridden (global.css's specular-edge block on the web,
     app-nav.css §5 in the shell), so both are very probably dead too — but
     §ED441 §7b MEASURED the background only, and this pass deletes what was
     measured, not what is inferred. Filed as a follow-up rather than taken on
     reasoning. */
  border: 1px solid rgba(255, 255, 255, 0.08);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28);
  pointer-events: auto; overflow: visible;
}
.bottom-nav-item {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  cursor: pointer; color: rgba(255, 255, 255, 0.68);
  transition: color var(--t-fast) var(--ease);
  border: none; background: none; padding: var(--sp-1) 2px;
  -webkit-tap-highlight-color: transparent; touch-action: manipulation; position: relative;
  text-decoration: none; overflow: visible;
}
.bottom-nav-item:hover { color: rgba(255, 255, 255, 0.9); }
.bottom-nav-item.is-active {
  outline: none;   /* glyph is fixed lime + label hidden, so no item colour needed */
}
.bottom-nav-item.is-active .bottom-nav-label { visibility: hidden; }

/* Round 4 — kept in lockstep with components.css (which is the canonical copy;
   read the NAV-JUMP HISTORY comment there for the full round 1-4 history). The
   active tab's pop circle is now an ALWAYS-PRESENT, ALWAYS-SAME-POSITION layer
   (.bottom-nav-pop) for every tab, on every page — only its opacity toggles.
   Nothing about its position ever changes on any state, so there's no position
   for any transition mechanism (CSS, native, or unknown) to ever mis-render. */
.bottom-nav { transform: none !important; }
.bottom-nav-item { transform: none !important; touch-action: manipulation; }
.bottom-nav-item:active { transform: none !important; }
.bottom-nav-item:focus:not(.is-active) { transform: none !important; outline: none; }
.bottom-nav-item:active:not(.is-active) { outline: none; }
.bottom-nav-icon {
  line-height: 1; position: relative; display: inline-flex; align-items: center; justify-content: center;
  transform: none !important; transition: none !important;
}
.bottom-nav-icon-rest { transition: opacity 100ms ease !important; }
.bottom-nav-item.is-active .bottom-nav-icon-rest { opacity: 0; }
/* -6px (structural rearchitecture pass, was -18px): kept in lockstep with the
   .bottom-nav-pop note in components.css — the item stretches to the pill's
   full height, so its top edge is the pill's top edge; -6px contains the
   circle inside the pill (~6px protrusion) instead of floating above it. */
/* §ED441 §7c / DELETED §ED451 — the disc chrome only. The layer, its
   `position: absolute`, its opacity pair and its 100ms transition stay: they
   are the regular->fill cross-fade that IS the active state on both surfaces.
   See the fuller note at the same rule in css/components.css. */
.bottom-nav-pop {
  position: absolute;
  display: flex; align-items: center; justify-content: center;
  opacity: 0; pointer-events: none;
  transition: opacity 100ms ease !important;
}
.bottom-nav-item.is-active .bottom-nav-pop { opacity: 1; }
.bottom-nav-item.is-active .bottom-nav-icon { transform: none !important; }
.bottom-nav-item * { transform: none !important; }
/* var(--accent), not --accent-ink: the pill is a fixed near-black floating
   dock in BOTH themes (not theme-following), so the icon always needs the
   bright tier for contrast — --accent-ink is tuned for LIGHT backgrounds,
   which this pill never has. */
.bottom-nav-svg-icon { display: block; color: var(--accent); }
/* §ED441 §7b / DELETED §ED451: the `font-size: 9px` on .bottom-nav-label
   resolved nowhere — 11px on the web, 10px in the shell. See the fuller note
   at the same rule in css/components.css. */
.bottom-nav-label { font-weight: 500; text-transform: uppercase; letter-spacing: 0.06em; line-height: 1; }

/* ── DESKTOP LAYER (WO-WEB-2) — additive ≥900px only; base/mobile rules above are LAW, never edited ── */
@media (min-width: 900px) {
  .bottom-nav { display: none; }

  .bf-page { padding-top: 64px; padding-bottom: 104px; }
  .bf-wrap { max-width: 720px; }

  .bf-masthead { padding: 24px 64px 18px; }
  .bf-mast-wordmark { font-size: 1.75rem; }
  .bf-daily-body { padding: 40px 64px 48px; }

  .bf-quote { padding-top: 32px; }
  .bf-hook-text { font-size: 2.75rem; line-height: 1.15; letter-spacing: -0.02em; }
  .bf-hook-text.is-short { font-size: 3.5rem; line-height: 1.08; letter-spacing: -0.025em; }
  .bf-rootline { font-size: 1.125rem; }
  .bf-standfirst { font-size: 1rem; margin-top: 12px; }
  .bf-fact-deck { font-size: 1.25rem; line-height: 1.52; }
  .bf-debrief { font-size: 1.0625rem; line-height: 1.65; }
  .bf-trust, .bf-mast-dateline { font-size: 0.8125rem; }

  .bf-stack { gap: 24px; }

  /* Entrance — one-shot fade + rise on the stack's direct children, staggered
     by nth-child in 80ms steps and capped (a long stack never queues a late
     card behind a multi-second delay). Reduced motion already collapses
     globally (global.css's universal kill-switch further down this cascade),
     but this stays wrapped in its own no-preference guard too — same
     defense-in-depth convention as .bf-wall's local guard above — so
     reduced-motion users never even see the keyframes referenced. */
  @media (prefers-reduced-motion: no-preference) {
    @keyframes bf-desktop-enter {
      from { opacity: 0; transform: translateY(12px); }
      to   { opacity: 1; transform: translateY(0); }
    }
    .bf-stack > * {
      animation: bf-desktop-enter 450ms cubic-bezier(0.22, 1, 0.36, 1) both;
    }
    .bf-stack > *:nth-child(1) { animation-delay: 0ms; }
    .bf-stack > *:nth-child(2) { animation-delay: 80ms; }
    .bf-stack > *:nth-child(3) { animation-delay: 160ms; }
    .bf-stack > *:nth-child(4) { animation-delay: 240ms; }
    .bf-stack > *:nth-child(5) { animation-delay: 320ms; }
    .bf-stack > *:nth-child(n+6) { animation-delay: 400ms; }
  }
}

/* ═══ SCREEN 1 · THE CONTINUITY GATE (§ED110, RE-INHERITED §ED112) ══════════
   The screen an anonymous reader meets AFTER spending their one complete
   edition (§ED109). Ruled by Jake to inherit the landing system WHOLESALE
   (§ED108-4).

   §ED110 MIRRORED four landing rulesets into this file, because briefing.html
   loaded global.css + briefing.css only and landing-v2.css was not on the
   page. §ED112 fixed THE CAUSE, not the symptom: briefing.html now links
   css/landing-v2.css, and the gate's markup (js/card.js buildContinuityGate)
   carries the landing's OWN classes — .lv2-eyebrow, .lv2-h1, .lv2-cta,
   .lv2-cta-secondary. The four mirrored rulesets (.bf-gate-eyebrow, -h1,
   -cta, -secondary) are DELETED. There is now ONE source of truth for the
   landing identity: css/landing-v2.css. Do not re-copy them here.

   WHAT THAT CHANGED, AND IT IS THE WHOLE POINT: .bf-gate-h1 had eased the
   landing's clamp one step down — clamp(1.875rem, 5vw, 2.75rem) against
   .lv2-h1's clamp(2.5rem, 6.5vw, 4rem). That easing is what Jake read as
   "small". It is gone. The gate headline is now the landing hero's scale
   exactly: 30px → 40px at 390px wide, 44px → 64px at desktop.

   WHAT SURVIVES HERE, AND ONLY THIS: the wrapper, which has no landing
   equivalent — the landing hero sits at the top of a cold page; this sits at
   the foot of a reading column that has already run its full length and needs
   its own rule and rhythm — plus two POSITION-ONLY overrides, named below.
   Face, size, weight, colour and tracking all still come from landing-v2.css.

   LIME BUDGET (VISUAL-IDENTITY §3a): exactly ONE lime element on this screen —
   the .lv2-cta fill, inherited with its ACHROMATIC shadow (§4 bans coloured
   object-shadows). Eyebrow, headline and secondary link carry no accent.
   ═════════════════════════════════════════════════════════════════════════ */
.bf-gate {
  text-align: center;
  margin: var(--sp-10) auto 0;
  padding: var(--sp-10) var(--sp-5) var(--sp-8);
  max-width: 560px;
  border-top: 1px solid var(--border);
}
/* POSITION-ONLY №1. The landing puts .lv2-sub between h1 and CTA (a further
   32px of air); the gate CUTS .lv2-sub (js/card.js says why), so the h1's own
   20px bottom margin would leave the CTA crowded. Spacing, not type. */
.bf-gate .lv2-h1 { margin-bottom: var(--sp-8); }
/* POSITION-ONLY №2. .lv2-cta-secondary is inline-block on the landing, sitting
   beside other inline content; in the gate it is the only thing on its line. */
.bf-gate .lv2-cta-secondary { display: block; }

/* ═══ SCREEN 2 · THE MEMBERSHIP OFFER (§ED112) ══════════════════════════════
   The screen that explains what membership IS. It did not exist before
   §ED112: a new free reader went from signup straight into the product with
   no moment that ever said what the paid tier gives.

   SHAPE IS RULED, NOT CHOSEN. MONETIZATION-MODEL.md §366: "An inline card —
   never a modal, never an interstitial, never navigation away." So this is a
   surface card appended at the FOOT of a finished edition, never a thing
   between a reader and today's card (THE-BREAKOUT-THESIS §1a — see the timing
   block in js/card.js, which is where the ruling is enforced).

   IDENTITY IS INHERITED, NOT REBUILT. Shell values are .lv2-almanac's
   (landing-v2.css: --surface, 1px --border, radius 12, 32px 28px). Everything
   inside carries the landing's own classes: .lv2-h1 (headline), .lv2-sub
   (body), .lv2-cta (the one lime action), .lv2-cta-micro (status). No new
   type scale is invented here and none is mirrored — same rule as Screen 1.

   LIME BUDGET: one — the .lv2-cta fill. The dismiss control is a hairline
   glyph button carrying no accent, reusing .bf-wall-close's grammar.
   ═════════════════════════════════════════════════════════════════════════ */
.bf-offer {
  position: relative;
  text-align: center;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 32px 28px;
  margin: var(--sp-10) auto 0;
  max-width: 620px;
}
/* Card rhythm — the landing hero's 20px/32px cadence compressed one step
   because this sits inside a bordered card, not on open page. Spacing only. */
.bf-offer .lv2-h1 { margin-bottom: 16px; }
.bf-offer .lv2-sub { margin-bottom: 20px; }
/* MONETIZATION-MODEL §366 puts the status line ABOVE the intent tap;
   .lv2-cta-micro is authored for below one, so this is a placement flip. */
.bf-offer-status { margin: 0 0 18px; }
.bf-offer-close {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 34px;
  height: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  border-radius: 8px;
  color: var(--text-subtle);
  cursor: pointer;
}
.bf-offer-close:hover { color: var(--text-primary); }
.bf-offer-close-ic { width: 18px; height: 18px; }

/* ── §ED262 THE READER ANSWER SURFACE ───────────────────────────────────────
   The bf-note LOOK, deliberately: recessed well, neutral left rule, quiet
   uppercase label, calm actions. A reader should read the answer block and the
   note block as the same KIND of thing — their own words on their own card —
   because they are. What is NOT copied is bf-note's plumbing: notes are
   auth.uid()-only with a hard cap of three (119), and this surface is
   anonymous-first and uncapped, written only through a service-role function.

   NO ARCADE. No celebration, no progress bar, no fill, no tick, no badge. The
   countdown is a sentence, not a meter — R12 asks for the missing thing to be
   NAMED, and a bar would be the "1 of 3 complete" framing in graphic form.

   CONTRAST: every pair below is measured in scripts/test-ed262-reader-answer.js
   against the real token values from css/global.css, in BOTH themes, asserted
   >= 4.5:1. Nothing here is eyeballed. --text-subtle is used only where
   test-ed262 proves it clears the bar on the surface it actually sits on. */
.ba-zone { margin: 12px 0 2px; }
.ba-zone.is-busy { opacity: 0.6; pointer-events: none; }   /* in flight — never a spinner, never a message */

/* The question. First-class — it is the card speaking, not a label.
   §ED430 · Fraunces 400, matching the .ba-zone > .ba-question declaration
   below so the question does not change face if the child combinator ever
   misses (reader-answer.js always mounts it inside .ba-zone today). */
.ba-question {
  font-family: var(--font-display);
  font-size: 0.95rem; line-height: 1.45; font-weight: 400;
  color: var(--text-primary);
  margin: 0 0 10px;
}
.ba-question.is-answered-q { font-weight: 400;   /* §ED430 · ruled */ color: var(--text-muted); margin-bottom: 8px; }

/* §ED548 FOLLOW-UP — PIN THE QUESTION while a take is live (recording or
   paused). Measured live: at realistic mobile heights the question scrolled
   fully off-screen while paused. position:sticky, not fixed: it reserves its
   own height in normal flow, so nothing jumps or gets covered while it is
   NOT stuck, and it only overlaps scrolled content once actually stuck —
   which is the point of pinning it. Toggled by mountRecorder()'s setState();
   at rest and in review the class is absent and .ba-question behaves exactly
   as above. Background is handled per-variant below: the corpus path's
   `.ba-zone > .ba-question` already carries a solid lime band, the `.is-bank`
   path does not (see the §ED440 block) and needs an explicit opaque one. */
.ba-question.ba-question-pinned {
  position: sticky;
  top: 0;
  z-index: 5;
}

/* The taps. A wrapping row of quiet pills — five siblings, and the write-in is
   one of them, NOT a control set apart from them. Ratified: the write-in is a
   fifth option, and styling it as a link or a form toggle would demote it. */
.ba-options { display: flex; flex-wrap: wrap; gap: 8px; }
.ba-option {
  font-family: var(--font-body);
  font-size: 0.85rem; line-height: 1.35; font-weight: 600;
  text-align: left; cursor: pointer;
  color: var(--text-primary);
  background: var(--surface-well);
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  padding: 8px 15px;
}
.ba-option:hover { border-color: var(--accent-ink); }
.ba-option:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
/* A terminal option ("We didn't", "I don't know") is NOT dimmed or demoted.
   27-generate-question.js:280 is explicit that a reader with no counterpart is
   DONE, and that pushing them further is how invented answers happen — so the
   honest answer must not look like the lesser one. Same weight, same colour. */
.ba-option.is-terminal { }
.ba-option-writein { border-style: dashed; }

/* The inline write-in. Multi-line: a spoken answer runs to several sentences,
   and a one-line input would make speech look like the wrong way to use it. */
.ba-editor { display: flex; flex-direction: column; gap: 8px; margin-top: 4px; }
.ba-input-row { display: flex; align-items: flex-end; gap: 8px; }
.ba-input {
  flex: 1 1 auto; width: 100%; box-sizing: border-box; resize: vertical;
  font-family: var(--font-body); font-size: 0.9rem; line-height: 1.5;
  color: var(--text-primary); background: var(--surface);
  border: 1px solid var(--border-strong); border-radius: 8px; padding: 9px 11px;
}
.ba-input::placeholder { color: var(--text-subtle); }
.ba-input:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }
/* §ED572 — the declarative half of "the writing box clears the fixed nav".
   js/reader-answer.js's clearBottomChrome() scrolls the box clear on focus by
   measuring the bar's live rect; this makes the BROWSER's own scrolling agree
   with it, so scrollIntoView(), a fragment jump, sequential focus navigation
   and a test driver's auto-scroll all stop at the same line instead of parking
   the box under the bar. --nav-bottom-h is calc(64px + var(--safe-bottom)) —
   the token, never a literal, so a notched device's taller bar is covered. */
.ba-input { scroll-margin-bottom: calc(var(--nav-bottom-h, 64px) + 12px); }

/* The mic. Present ONLY where the Web Speech API is — reader-answer.js never
   creates this node otherwise, so there is no disabled state to style and no
   "not supported" affordance to design. A reader without it sees a text box
   and no evidence anyone else got more. */
.ba-mic {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; cursor: pointer;
  color: var(--text-muted);
  background: var(--surface-well);
  border: 1px solid var(--border-strong); border-radius: 50%;
}
.ba-mic:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
/* Held. A colour + border change, no pulse and no waveform: a live recording
   animation is theatre, and this surface promises the opposite of theatre.

   SUPERSEDED BY §ED440 PHASE E, PART 4 — and the reasoning is what changes,
   not just the verdict. "Theatre" is the right objection to the LANDING's
   waveform, which is seeded-random bars with no audio behind them (R8): a
   performance of listening. It is the wrong objection to a waveform driven by
   an AnalyserNode on the reader's own live input, because that is not a
   performance, it is the only EVIDENCE the reader gets that the microphone is
   actually hearing them. A mic button that changes colour proves the button
   was pressed. Bars that move when you speak prove the mic is on — which is
   the single most useful thing this surface can tell someone who has just
   been asked to talk about their family.
   The new treatment is in the Phase E block at the foot of this sheet. It
   never renders bars without data behind them: no analyser degrades to one
   pulsing indicator, never to invented bars. */
.ba-mic.is-live { color: var(--accent-ink); border-color: var(--accent-ink); }
.ba-mic-ic { display: inline-flex; }

/* Actions — same shapes and same words as the note editor (card.js:470/472). */
.ba-actions { display: flex; gap: 8px; align-items: center; }
.ba-save, .ba-cancel {
  font-family: var(--font-body); font-size: 0.78rem; font-weight: 600; cursor: pointer;
  border-radius: 7px; padding: 6px 14px;
}
.ba-save { border: none; background: var(--accent-ink); color: #fff; }
.ba-cancel { border: 1px solid var(--border-strong); background: transparent; color: var(--text-muted); }
.ba-save:focus-visible, .ba-cancel:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }

/* The CONFIRMED answer — echoed only after the server issued a row id. Same
   recessed well as .bf-note, so a reader's answer and a reader's note sit in
   visibly the same register. */
.ba-answer {
  padding: 9px 12px 9px 13px;
  background: var(--surface-well);
  border-left: 3px solid var(--border-strong);
  border-radius: 0 8px 8px 0;
}
.ba-answer-value {
  font-family: var(--font-body); font-size: 0.9rem; font-weight: 600; line-height: 1.45;
  color: var(--text-primary); margin: 0; overflow-wrap: break-word;
}
.ba-answer-text {
  font-family: var(--font-body); font-size: 0.9rem; line-height: 1.5;
  color: var(--text-primary); margin: 6px 0 0; overflow-wrap: break-word;
  white-space: pre-wrap;   /* VERBATIM — the reader's own line breaks survive */
}
/* The tap the reader already committed, shown while they add words to it. */
.ba-chosen {
  font-family: var(--font-body); font-size: 0.85rem; font-weight: 600;
  color: var(--text-muted); margin: 0 0 8px;
}
/* "Add words to this" — a quiet ambient trigger, exactly .bf-note-add's weight.
   It must never look like an unfinished step: the answer is already complete. */
.ba-more {
  display: inline-flex; align-items: center; margin-top: 8px;
  border: none; background: transparent; cursor: pointer;
  font-family: var(--font-body); font-size: 0.8rem; font-weight: 600;
  color: var(--text-muted); padding: 2px 0; text-decoration: underline;
}
.ba-more:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; border-radius: 4px; }

/* ── COUNTDOWN AND PAYOFF ──────────────────────────────────────────────────
   No meter, no bar, no numerals-as-score. --text-muted, not --text-subtle:
   this is the line that carries the promise, so it is read, not skimmed. */
.ba-progress { margin-top: 10px; }
.ba-countdown {
  font-family: var(--font-body); font-size: 0.8125rem; line-height: 1.45;
  color: var(--text-muted); margin: 0;
}
/* The payoff. Set apart by a rule, not by a colour flood or a card — the
   ratified line is the reward, and decorating it would cheapen it. */
.ba-progress.is-payoff {
  border-top: 1px solid var(--border-strong);
  padding-top: 10px;
}
/* §ED267 — THE SAME LINE, BEFORE THE FIRST ANSWER. It is the promise here and
   the payoff there, so it is the same words in the same face at the same size:
   a reader who reaches three must recognise the sentence they were shown at
   zero, and re-styling it would break that recognition.
   WHAT DIFFERS IS THE SEPARATOR, and only that. --border (the subtle one), not
   --border-strong: at zero this line follows the options and is part of the
   ask, so it is joined to it. At three it follows a confirmed answer and closes
   the entry, so it is ruled off. Same type, different punctuation. */
.ba-progress.is-promise {
  border-top: 1px solid var(--border);
  padding-top: 10px;
  margin-top: 12px;
}
/* §ED553 · 0.875rem (14px) → 1rem. THIS WAS A LIVE §ED440 BREACH AND IT IS
   FIXED ON BOTH SURFACES, NOT SCOPED TO THE ONE THAT FOUND IT.

   §ED440's type floor is 16px and this line sat two under it. It was invisible
   for as long as the only instrument pointed at the floor was the landing
   probe and this rule rendered only inside the app; §ED553 mounts the answer
   surface on the landing, scripts/audit-ed549.js §6 measured every element
   carrying text, and .ba-payoff @14px was the single failure.

   IT IS CHANGED IN briefing.css, SO IT CHANGES /briefing.html TOO — stated
   plainly rather than discovered later. That is deliberate: the alternative was
   a landing-only override, which would be two type ramps for one component and
   the same fork this pass refuses everywhere else. The line it governs is the
   ratified payoff ("One answer a day, kept private…"), read by exactly the
   audience the 16px floor exists for. Nothing else about the rule moves. */
.ba-payoff {
  font-family: var(--font-body); font-size: 1rem; line-height: 1.5; font-weight: 600;
  color: var(--text-primary); margin: 0;
}
/* The claim prompt rides ALONGSIDE the payoff, never in front of it: the payoff
   shows anonymously (ratified), so the account is an offer, not a toll. */
.ba-claim {
  display: inline-block; margin-top: 8px;
  font-family: var(--font-body); font-size: 0.8125rem; font-weight: 600;
  color: var(--text-primary); text-decoration: underline;
}
html.theme-light .ba-claim { color: var(--accent-ink); }   /* mirrors .bf-note-limit-link exactly */

/* Wider viewports: the options row would otherwise stretch a five-pill line
   across a full-width card and read as a toolbar. Cap it at the measure of the
   prose it belongs to. */
@media (min-width: 900px) {
  .ba-options { max-width: 34rem; }
  .ba-input-row { max-width: 34rem; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   §ED271 — THE READER'S ANSWERS ON /record  (#rec-answers-section)

   Lives here rather than in css/record.css because record.html already loads
   briefing.css and this build's write permissions cover briefing.css only.
   Purely additive: every selector below is namespaced .rec-answers / .ra-*, so
   no page that loads briefing.css at an older cache version is affected.

   COLOUR: no new literals. Question and write-in take --text-primary (the
   app's primary body pairing); the count takes --text-subtle, the exact token
   the already-shipped .rec-section-empty uses on this same page. The tap chip
   is a --border outline with --text-primary text — an outline, not a fill, so
   it introduces no new foreground/background pair to verify.
   ═══════════════════════════════════════════════════════════════════════════ */
.rec-answers {
  margin-top: 26px;
}

.rec-answers-count {
  color: var(--text-subtle);
  font-family: var(--font-body);
  font-size: 0.88rem;
  margin: 0 0 12px;
}
.rec-answers-count:empty { display: none; }

#rec-answers-list > .ra-card + .ra-card { margin-top: 12px; }

/* §ED285 — the card was reading "like a Word document": one body face at one
   size for dateline, question and answer, on the same surface as every other
   card. Type and colour only; no element is added, moved or reworded.

   SURFACE: --surface-well (dark #101010, the deepest surface token defined —
   deeper than .bf-card's --surface #1D1D1D) so the answer cards sit as wells
   the reader's own words live inside, not as more floating chrome. Light
   INVERTS by the theme's own elevation law (global.css:350 "white is the
   ceiling, nesting descends"), so light takes --surface — which is what
   .bf-card already resolved to there, i.e. light's card face is unchanged and
   keeps its shipped --shadow-card lift. */
.ra-card {
  padding: 18px 20px;
  background: var(--surface-well);
}
html.theme-light .ra-card { background: var(--surface); }

/* The question, verbatim from the snapshot stored at answer time.

   It is semantically a heading and is now typeset as one: --font-display
   (§ED430: Fraunces 400) at display size. The ELEMENT stays a <p>
   (js/record-answers.js:119) — promoting it to <h3> is a markup change in a
   file outside this build's write permissions, and is reported rather than
   made. Cormorant defaults to old-style figures, which would set any year or
   number in a question at x-height against the lining figures of the dateline
   and answer beneath it; lining-nums holds one numeral system across the card. */
.ra-question {
  color: var(--text-primary);
  font-family: var(--font-display);
  font-size: 1.5rem;
  line-height: 1.22;
  font-weight: 400;   /* §ED430 · ruled */
  letter-spacing: -0.005em;
  font-variant-numeric: lining-nums;
  margin: 8px 0 0;
}

/* The tap. Rendered only when its label resolves — see js/record-answers.js.
   An outline chip, deliberately quieter than the write-in: the reader's own
   words outrank the option they picked. */
/* §ED469 / Jake's ruling: this WAS its own one-off accent treatment
   (--accent-ink text on an --accent-dim wash) — a second, different way of
   saying "this is what you picked" from the one already ruled for exactly
   that idea (css/landing-a2.css .p-opt:first-child, "the one the reader
   took" — lifted into the app as .ba-zone .ba-option.is-chosen /
   .ba-zone .ba-answer above). Two treatments for the same idea is how the
   two-limes defect happened in the first place; this is now that same
   recipe, verbatim — lime tint at 22% over the surface, the border at 52%
   into the hairline, ink at the primary tier, not accent-ink. COLOUR AND
   BORDER ONLY changed; size/shape are .ra-value's own, unrelated to the
   lime budget.
   STATIC FALLBACK FIRST, same shape as the other two color-mix sites in
   this file (briefing.css ~2939/3168): an engine without color-mix
   (WKWebView below iOS 16.4) keeps a flat tint + the chosen-row's own
   solid accent-ink border instead of losing the border declaration
   entirely (an invalid color-mix() invalidates the whole `border`
   shorthand, not just the colour). */
.ra-value {
  display: inline-block;
  color: var(--c-ink);
  background: #EEF7CC;                      /* light fallback, pre-color-mix */
  font-family: var(--font-body);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  line-height: 1.2;
  border: 1px solid var(--c-accent-ink);
  border-radius: 999px;
  padding: 5px 11px;
  margin: 10px 0 0;
}
html:not(.theme-light) .ra-value,
html[data-theme="dark"] .ra-value { background: #23291A; }  /* dark fallback */
@supports (background: color-mix(in oklab, red 22%, blue)) {
  .ra-value,
  html:not(.theme-light) .ra-value,
  html[data-theme="dark"] .ra-value {
    background: color-mix(in oklab, var(--c-accent) 22%, var(--c-surface));
    border-color: color-mix(in oklab, var(--c-accent) 52%, var(--c-line));
  }
}

/* The write-in reuses .bf-note wholesale (label + text), so it matches the
   reader's notes elsewhere on this page byte for byte. Only the top margin is
   set here, to sit clear of the chip above it. */
.ra-note { margin-top: 12px; }

/* CONSEQUENCE of deepening the card: .bf-note's own background is
   --surface-well (briefing.css:1041), which on a --surface-well card would
   erase the write-in's figure entirely in dark. Scoped to .ra-card only, the
   note steps back UP the ladder (--surface) so the reader's words keep the
   inset-block reading they have everywhere else. Light is untouched by that
   inversion — there the card is --surface, so the note keeps --surface-well
   and stays the recessed one. Same two tokens either way, swapped. */
.ra-card .bf-note { background: var(--surface); }
html.theme-light .ra-card .bf-note { background: var(--surface-well); }

/* Print — the page IS the print artifact (css/record.css @media print). The
   chip's border survives; nothing here is interactive, so nothing is hidden. */
@media print {
  .rec-answers-count { color: #000; }
  .ra-question, .ra-value { color: #000; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   §ED286 — THE ANSWER CARD: A FILLED BAND OVER A CONTRASTING BODY

   ONE object, two states. /briefing asks (band + tap options in the body);
   /record remembers (band + chosen label + the reader's words). The shell —
   radius, border, band fill, band ink, body surface, lift — is declared ONCE
   across both selector sets below so the two ends of the loop cannot drift
   apart. Only the bleed geometry differs, and even that is driven off the
   same two custom properties.

   THE BAND'S INK IS --accent-text, NOT --accent-ink. This is the load-bearing
   correction of this build. In LIGHT, --accent-ink is #586F00 (global.css:394)
   and pairs with lime at 4.84:1 — legible. But in DARK, global.css:110 sets
   `--accent-ink: var(--accent)`, i.e. #CCFF00 — the identical value as the
   fill. A band of --accent carrying --accent-ink text renders lime-on-lime at
   1.00:1 in dark: an invisible question. --accent-text (#161616, global.css:53)
   is declared in :root and is NOT overridden in the light block, so it resolves
   to the same value in BOTH themes, and its own comment names it "the ink used
   for text-on-lime in both themes," verified 15.40:1. That single token is what
   makes the light band shippable without inventing a hex: the light band is not
   a weaker cousin of the dark one, it is the SAME band. Recomputed here from
   the token values by WCAG relative luminance: #161616 on #CCFF00 = 15.40:1,
   both themes, AAA.

   ZERO colour literals: every declaration below is a var() reference.
   ═══════════════════════════════════════════════════════════════════════════ */

/* THE SHELL. --card-pad-* are the single source of truth for the inset: the
   band bleeds by negating exactly them, so padding can never fall out of step
   with the bleed. overflow:hidden is what clips the band into the radius —
   without it the fill would square off the top two corners. */
.ra-card,
.ba-zone {
  --card-pad-x: 20px;
  --card-pad-y: 18px;
  padding: var(--card-pad-y) var(--card-pad-x);
  border: 1.5px solid var(--border-strong);
  border-radius: 14px;
  overflow: hidden;
  background: var(--surface-well);
}

/* Light inverts the elevation ladder (global.css:350 — "white is the ceiling,
   nesting descends"), so the body takes --surface there, exactly as §ED285
   ruled for .ra-card. Soft lift is the --shadow-card family and light-only:
   .bf-card already establishes that dark needs no shadow because luminance
   contrast alone elevates it. --shadow-accent is deliberately NOT used — it
   resolves to `none` on web light (global.css:440), so it would have made the
   lift asymmetric between themes by accident rather than by design. */
html.theme-light .ra-card,
html.theme-light .ba-zone {
  background: var(--surface);
  box-shadow: var(--shadow-card);
}

/* THE BAND — fill and ink, shared by every band piece on both surfaces.
   .is-answered-q is included explicitly: it carries its own colour at equal
   specificity, and must not pull the answered question off the band.
   §ED469 / Jake's ruling: .rec-fact-date REMOVED from this group. §ED460
   counted date band + question band + answer chip as three stacked accent
   elements on one /record or vault card against the briefing's one; Jake
   ruled the QUESTION keeps the lime as the licensed answer-zone exception
   and the date band loses it — see the neutral rule below. */
.ra-card > .ra-question,
.ba-zone > .ba-question,
.ba-zone > .ba-question.is-answered-q {
  background: var(--accent);
  color: var(--accent-text);
  margin-left: calc(var(--card-pad-x) * -1);
  margin-right: calc(var(--card-pad-x) * -1);
  padding-left: var(--card-pad-x);
  padding-right: var(--card-pad-x);
}

/* The question, both surfaces, one declaration: §ED430 Fraunces 400 at the
   display size (was Cormorant 500) — §ED285's one-declaration structure is
   kept, only the face and weight move. lining-nums is RETAINED and still
   load-bearing: Fraunces, like Cormorant, defaults to old-style figures,
   which would set a year in the question at x-height against the lining
   figures of the body beneath it. Verified on the shipped file — the fvar
   carries opsz 9-144 and wght 100-900. */
.ra-card > .ra-question,
.ba-zone > .ba-question,
.ba-zone > .ba-question.is-answered-q {
  font-family: var(--font-display);
  font-size: 1.5rem;
  line-height: 1.22;
  font-weight: 400;   /* §ED430 · ruled */
  letter-spacing: -0.005em;
  font-variant-numeric: lining-nums;
}

/* /record's dateline eyebrow — WAS the top half of a two-piece band filled
   identically with the question below it (Jake's §ED469 ruling: that made
   the date its own separate accent element, one of three stacked on a
   single card against the briefing's one). Now plain card content: the
   shell's own top inset (.ra-card's padding, no negative-margin bleed
   trick), small/uppercase/tracked exactly as record.css's standalone
   .rec-fact-date already sets it — record.html gets the same declaration
   twice (harmless, same value); vault.html, which never loads record.css,
   was getting NO typography for this element outside the old band rule and
   needs it declared here to keep the eyebrow legible at all. */
.ra-card > .rec-fact-date {
  font-family: var(--font-body);
  /* THE 16px FLOOR (§ED306) — identical to css/record.css's .rec-fact-date by
     intent, not by coincidence. §ED469 copied that rule here at 0.78rem
     (12.48px) and carried its sub-floor size with it; both are now on the
     floor token.

     CASCADE — MEASURED, NOT ASSUMED. record.html links BOTH sheets and so
     carries both rules, which looks like a specificity race. It is not one:
     `.ra-card` is never rendered on record.html at all. §ED426 retired
     js/record-answers.js from that page (see the comment at record.html's
     .rec-answers section) and moved the answer cards to /vault.html, so the
     only .rec-fact-date record.html produces is js/record.js's, inside a
     plain .bf-card. The two rules are therefore DISJOINT in practice, one
     per surface:
         vault.html   -> THIS rule (vault never loads record.css)
         record.html  -> record.css's .rec-fact-date (no .ra-card exists here)
     Were an .ra-card ever reintroduced to record.html, this selector (0,2,0)
     would take it from record.css's (0,1,0) on specificity, independent of
     link order — record.html loads briefing.css FIRST, so load order alone
     would have handed it the other way. Verified by
     scripts/measure-ed475-rec-fact-date.js, which asserts the absence rather
     than trusting it.

     THE REAL HAZARD IS THE COPY, NOT THE CASCADE. Two disjoint rules that
     must stay identical are a divergence waiting to happen, and had already
     diverged: this one used --c-ink-3 while record.css's used --text-subtle,
     so /record drew its dateline in a different ink from /vault. Unified on
     --c-ink-3. audit-ed440-tokens.js's type-floor check now reads BOTH
     files, so a future sub-floor edit to either fails the audit. */
  font-size: var(--c-t-micro);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--c-ink-3);
  margin: 0 0 6px;
}
/* The question is now the band ON ITS OWN (Jake's ruling), but unlike
   /briefing's .ba-question it is NOT the first thing in the card — the
   neutral dateline sits above it in normal flow. It therefore does NOT
   bleed to the card's top the way .ba-question does (that trick assumes
   nothing sits above it; here it would overlap the dateline). It keeps its
   existing bottom bleed unchanged and gets its own top inset instead of
   inheriting the eyebrow's old padding-top:0/margin-top:0 hand-off. */
.ra-card > .ra-question {
  margin-top: 6px;
  padding-top: var(--card-pad-y);
  padding-bottom: var(--card-pad-y);
  margin-bottom: var(--card-pad-y);
}

/* /briefing's band is the question alone — it bleeds top and sides itself. */
.ba-zone > .ba-question,
.ba-zone > .ba-question.is-answered-q {
  margin-top: calc(var(--card-pad-y) * -1);
  padding-top: var(--card-pad-y);
  padding-bottom: var(--card-pad-y);
  margin-bottom: var(--card-pad-y);
}

/* .ba-zone's shipped outer margin stays; it is a nested well inside the daily
   card, not a page-level card, and this build does not restructure /briefing. */
.ba-zone { margin: 12px 0 2px; }

/* CONSEQUENCE, the §ED285 defect generalised to /briefing. §ED285 found that
   .bf-note's own background is --surface-well, which vanishes once the CARD is
   --surface-well in dark, and stepped the note up to --surface. /briefing was
   never checked for the same collision — and it has TWO of them: .ba-option
   (briefing.css:2321) and .ba-answer (briefing.css:2380) are both
   --surface-well, and both now sit on a --surface-well body in dark. Both step
   up the same way, dark only. Light needs no step: there the body is --surface
   and --surface-well is already the recessed one, so the shipped tokens are
   already correct — same two tokens either way, swapped, exactly as §ED285.
   .ba-input is already --surface and so stays legible on the dark body
   untouched; the write-in's figure is verified, not assumed. */
html:not(.theme-light) .ba-zone .ba-option,
html:not(.theme-light) .ba-zone .ba-answer {
  background: var(--surface);
}

/* ══════════════════════════════════════════════════════════════════════════
   §ED440 · THE OVERRIDE LAYER — LAST THING IN THIS FILE. APPEND NOTHING AFTER.
   ══════════════════════════════════════════════════════════════════════════

   WHAT THIS IS. The landing's design language (prototype/prototype-a2.css,
   ratified) applied to the Daily, by mapping this sheet's existing classes
   onto the --c-* tokens in css/tokens.css. It restyles ONLY the classes
   §ED440 Phase B names.

   WHY IT IS AN APPEND AND NOT A REWRITE — and what that costs. This sheet is
   ~131KB with no test coverage; rewriting it in place with nothing to catch a
   regression is the worse risk. The cost is real and is recorded as technical
   debt T1 in docs/ED440-DESIGN-LANGUAGE.md: the same classes are now styled
   in TWO places, and an edit in the body of the file can be silently
   overridden a hundred kilobytes further down. A future "briefing.css
   consolidation" pass folds this block back in and deletes the superseded
   literals. Do not attempt it without a screenshot harness green first —
   shots/ed440/phase-b/ is the seed of one.

   HOUSE RULES FOR THIS BLOCK, so it stays reversible:
     - It wins by SOURCE ORDER at equal specificity. No !important anywhere.
     - Where a longer selector was unavoidable, the rule it beats is named.
     - Every declaration names the token it applies. This block is a MAPPING
       from old literal to new token, never a second design.
     - Delete the block and the Daily returns exactly to its pre-§ED440 look.

   NOT IN SCOPE, deliberately: no new copy, no new mechanism, no new lock
   reasons, no change to who sees what. Nohemi is NOT referenced — it is
   unreachable in production (netlify.toml force-404s /prototype/*) and the
   landing's card interior is Hanken + mono anyway (§ED398), both of which
   this app already ships. So the card is a full-fidelity lift as-is.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── THE PAGE GROUND — L0 ───────────────────────────────────────────────── */
.bf-page { background: var(--c-bg); }

/* ── THE EDITION CARD — L2 ──────────────────────────────────────────────
   Was: --shadow-hero, a top-weighted LIME GLOW. That glow is the single
   loudest "developer tool" signal on the surface and DESIGN-INVARIANTS.md
   already bans lime glow at scale on web. It is replaced by real elevation:
   a warm hairline plus the L2 ambient lift. */
.bf-daily {
  background: var(--c-surface);
  border: var(--c-e2-border);
  border-radius: var(--c-e2-radius);
  box-shadow: var(--c-e2-shadow);
  /* transform+opacity only, and the hover lift reads --c-amp-sm so reduced
     motion nulls the distance rather than each rule guarding itself. */
  transition: box-shadow var(--c-m-expand) var(--c-e-out),
              transform  var(--c-m-expand) var(--c-e-out);
}
.bf-daily:hover {
  transform: translateY(calc(-1 * var(--c-amp-sm)));
  box-shadow: var(--c-e3-shadow);
}

/* ── MASTHEAD ──────────────────────────────────────────────────────────── */
.bf-masthead { background: var(--c-surface); border-bottom: 1px solid var(--c-line-2); }
.bf-mast-wordmark { color: var(--c-ink); }
.bf-mast-dateline { color: var(--c-ink-2); font-size: var(--c-t-micro); }
.bf-mast-archive  { color: var(--c-ink-3); font-size: var(--c-t-micro); }

/* ── DESIGNED EMPTY / ERROR / OFFLINE ───────────────────────────────────
   ONE renderer (js/briefing.js showState) serves all three. Offline and
   error carry byte-identical copy by §ED181-2 ruling and that is unchanged
   here — §ED440 restyles, it does not write copy.
   No red card: --c-danger is spent on the retry control's focus ring only. */
.bf-state { color: var(--c-ink-2); font-size: var(--c-t-body); }
.bf-state .bf-state-h {
  font-family: var(--c-f-display);
  color: var(--c-ink);
  font-size: 1.5rem;
  line-height: 1.2;
}
.bf-retry {
  font-family: var(--c-f-body);
  font-size: var(--c-t-micro);
  padding: 11px 20px;
  border: var(--c-e1-border);
  border-radius: var(--c-r-sm);
  background: var(--c-surface-2);
  color: var(--c-ink);
  box-shadow: var(--c-e1-shadow);
  transition: transform var(--c-m-tap) var(--c-e-standard),
              background-color var(--c-m-tap) var(--c-e-standard);
}
.bf-retry:hover  { background: var(--c-bg-deep); }
.bf-retry:active { transform: scale(.97); }
.bf-retry:focus-visible { outline: 2px solid var(--c-focus); outline-offset: 2px; }
/* Action-waiting -> inline progress on the control itself, never a page
   spinner (js/briefing.js sets .is-retrying for the duration of the load). */
.bf-retry.is-retrying { opacity: .65; pointer-events: none; }

/* ── SKELETON — content-waiting, so it must match the REAL layout ───────
   The skeleton now models the question block too (js/briefing.js
   renderSkeleton), because modelling only masthead + body made the page
   visibly GROW when the options arrived. A skeleton of the wrong shape is
   worse than none. */
.bf-skeleton {
  background: var(--c-surface);
  border: var(--c-e1-border);
  border-radius: var(--c-e2-radius);
  box-shadow: var(--c-e1-shadow);
}
.bf-skel-mast {
  background: var(--c-surface);
  border-top: none;                    /* was a 2px lime --border-accent rule */
  border-bottom: 1px solid var(--c-line-2);
}
.bf-skel-line { background: var(--c-skeleton); border-radius: var(--c-r-sm); }
.bf-skel-line::after {
  background: linear-gradient(90deg, transparent 0%, var(--c-skeleton-sheen) 50%, transparent 100%);
}
/* The question block's own bars. */
.bf-skel-ask   { padding: 0 32px 28px; display: flex; flex-direction: column; gap: 12px; }
.bf-skel-q     { width: 86%; height: 18px; margin-top: 4px; }
.bf-skel-opts  { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }
.bf-skel-opt   { height: 34px; border-radius: var(--c-r-full); }
.bf-skel-opt-1 { width: 42%; } .bf-skel-opt-2 { width: 34%; }
.bf-skel-opt-3 { width: 52%; } .bf-skel-opt-4 { width: 28%; }

/* ── THE QUESTION — "THE BAND" ──────────────────────────────────────────
   THE BAND IS RATIFIED SHIPPED DESIGN (§ED285, face re-ruled §ED430): the
   question sits on a full-bleed lime band, shared by /record and the Daily.
   §ED440 does NOT change it — this pass restyles, it does not redesign, and
   the band is not in its remit. See the note in
   docs/ED440-DESIGN-LANGUAGE.md flagging the band against the palette
   invariant; that tension is Jake's to rule on, not this pass's to resolve.

   WHAT IS FIXED HERE IS A SPECIFICITY BUG THIS LAYER WALKED INTO. The first
   draft styled bare `.ba-question` (0,1,0), which LOSES to the band's
   `.ba-zone > .ba-question` (0,2,0) — so every declaration in it was dead and
   the band went on reading global.css's --accent/--accent-text instead of the
   token layer. That is precisely the failure mode T1 warns about, hit inside
   the very block that documents it.
   The selector below MATCHES the band's own specificity and wins on source
   order, so the band now reads --c-* like everything else. It is a pure token
   mapping: same fill, same ink, same geometry. --c-on-accent on --c-accent
   measures 16.77:1. */
.ba-zone > .ba-question,
.ba-zone > .ba-question.is-answered-q {
  background: var(--c-accent);
  color: var(--c-on-accent);
  font-family: var(--c-f-display);
}

/* ── THE OPTION ROWS — lifted from the landing's .p-opt ─────────────────
   Landing geometry: 12/18 padding, --r-md, 1px hairline, body-size text.
   The app's pills (999px radius, 0.85rem, --surface-well) become rows. */
.ba-option {
  font-family: var(--c-f-body);
  font-size: var(--c-t-body);
  line-height: 1.55;
  font-weight: 400;
  color: var(--c-ink-2);
  background: var(--c-surface);
  border: var(--c-e1-border);
  border-radius: var(--c-r-md);
  padding: 12px 18px;
  box-shadow: var(--c-e1-shadow);
  transition: transform    var(--c-m-tap)    var(--c-e-standard),
              border-color var(--c-m-toggle) var(--c-e-out),
              background-color var(--c-m-toggle) var(--c-e-out);
}
.ba-option:hover { border-color: var(--c-accent-ink); background: var(--c-surface-2); }
.ba-option:active { transform: scale(.98); }
.ba-option:focus-visible { outline: 2px solid var(--c-focus); outline-offset: 2px; }

/* THE CHOSEN ROW — the landing's .p-opt:first-child treatment.
   STATIC FALLBACK FIRST, then the color-mix. An engine without color-mix
   (WKWebView below iOS 16.4) keeps the flat tint above instead of falling
   through to a transparent row; a modern engine takes the mix. The two
   fallbacks are the mix resolved against the light and dark surfaces. */
/* SPECIFICITY, MEASURED NOT ASSUMED. briefing.css:2705 carries
     html:not(.theme-light) .ba-zone .ba-option,
     html:not(.theme-light) .ba-zone .ba-answer { background: var(--surface); }
   a §ED285-derived DARK-ONLY step-up (the option/answer wells collide with a
   --surface-well body in dark). At (0,3,1) it beats a bare `.ba-option`
   (0,1,0) and a `html:not(.theme-light) .ba-answer` (0,2,1) — so the first
   draft of this layer lost BOTH in dark: the option kept global.css's
   #1D1D1D, and the answered/celebration tint never appeared at all. Light was
   fine, so every light screenshot looked correct and hid it. It was caught by
   scripts/audit-ed440-tokens.js reading computed style, not by eye.
   The selectors below MATCH (0,3,1) and win on source order. The step-up
   BEHAVIOUR is preserved exactly — only its tokens change. */
html:not(.theme-light) .ba-zone .ba-option,
html[data-theme="dark"] .ba-zone .ba-option { background: var(--c-surface); }

.ba-zone .ba-answer {
  background: #EEF7CC;                       /* light fallback, pre-color-mix */
  border-left: 3px solid var(--c-accent-ink);
  border-radius: 0 var(--c-r-sm) var(--c-r-sm) 0;
  padding: 11px 14px;
}
html:not(.theme-light) .ba-zone .ba-answer,
html[data-theme="dark"] .ba-zone .ba-answer { background: #23291A; }  /* dark fallback */
@supports (background: color-mix(in oklab, red 22%, blue)) {
  .ba-zone .ba-answer,
  html:not(.theme-light) .ba-zone .ba-answer,
  html[data-theme="dark"] .ba-zone .ba-answer {
    background: color-mix(in oklab, var(--c-accent) 22%, var(--c-surface));
  }
}
.ba-answer-value { color: var(--c-ink); font-weight: 560; font-size: var(--c-t-body); }
.ba-answer-text  { color: var(--c-ink-2); font-size: var(--c-t-body); line-height: var(--c-lh-body); }

/* ── THE CELEBRATION (§ED440) ───────────────────────────────────────────
   THE one spring in the app, spent on the one act that matters: the reader
   answering. It happens once a day and was completely unmarked before this
   — grep Motion js/reader-answer.js returned nothing.

   IT FIRES ON SERVER-CONFIRMED SAVE, NEVER ON TAP. js/reader-answer.js
   passes celebrate:true only from send()'s commit callback, i.e. only after
   a genuine 2xx write. The 409 (answer already existed) and 503 (edit
   refused) branches both route through adoptExisting() and return BEFORE
   commit, so a refused or failed save can never celebrate. Restoring a
   prior answer never celebrates either.

   TRANSFORM AND OPACITY ONLY. No width, no height, no top/left. */
@keyframes bf-answer-land {
  0%   { transform: scale(.94); opacity: 0; }
  60%  { transform: scale(1.02); opacity: 1; }
  100% { transform: scale(1);    opacity: 1; }
}
@keyframes bf-tick-in {
  from { opacity: 0; transform: scale(.7); }
  to   { opacity: 1; transform: scale(1); }
}
.ba-answer.is-just-saved {
  animation: bf-answer-land var(--c-m-celebrate) var(--c-e-spring) both;
  transform-origin: left center;
}
.ba-answer.is-just-saved .ba-answer-value::before {
  content: '';
  display: inline-block;
  width: 15px; height: 15px;
  margin-right: 8px;
  vertical-align: -2px;
  background: var(--c-accent-ink);
  /* The landing's .p-opt-tick path, as a mask so it inherits the accent. */
  -webkit-mask: var(--c-tick) center / contain no-repeat;
          mask: var(--c-tick) center / contain no-repeat;
  animation: bf-tick-in var(--c-m-celebrate) var(--c-e-spring) both;
}
/* REDUCED MOTION. The token layer already flattens --c-e-spring to an
   ease-out and nulls the amplitudes, so this degrades on its own. What is
   pinned here is the END STATE: the confirmation must still HAPPEN — the
   answer and its tick arrive, just without the overshoot. Duration is
   collapsed by global.css's universal kill; `none` is never used, because
   `none` would skip the end state and could leave the block at opacity 0. */
@media (prefers-reduced-motion: reduce) {
  .ba-answer.is-just-saved,
  .ba-answer.is-just-saved .ba-answer-value::before {
    animation-name: bf-tick-in;      /* opacity-led, no scale overshoot */
    transform: none;
  }
}

/* ── EDIT / SAVE CONTROLS ──────────────────────────────────────────────── */
.ba-save {
  background: var(--c-accent);
  color: var(--c-on-accent);
  border: none;
  border-radius: var(--c-r-sm);
  font-family: var(--c-f-body);
  transition: transform var(--c-m-tap) var(--c-e-standard);
}
.ba-save:active { transform: scale(.97); }
.ba-cancel { border: var(--c-e1-border); color: var(--c-ink-2); background: transparent; }
.ba-save:focus-visible, .ba-cancel:focus-visible { outline: 2px solid var(--c-focus); outline-offset: 2px; }
.ba-more   { color: var(--c-ink-2); font-size: var(--c-t-micro); }
.ba-chosen { color: var(--c-ink-2); font-size: var(--c-t-micro); }
.ba-input  { background: var(--c-surface); border: var(--c-e1-border); border-radius: var(--c-r-sm); color: var(--c-ink); }

/* ── STREAK + PROGRESS ─────────────────────────────────────────────────
   NEUTRAL INK, INCLUDING THE NUMERALS. DESIGN-INVARIANTS.md bans small lime
   text on the dark theme even where the raw contrast ratio is high, and a
   streak numeral is exactly the "small numeral" that law names. No exception
   is made for the one number that would look good in lime. The light theme
   keeps --c-accent-ink for small text, which that law explicitly permits. */
.ba-progress .ba-countdown { color: var(--c-ink-2); font-size: var(--c-t-micro); }
.ba-progress.is-payoff { border-top: 1px solid var(--c-line); }
.ba-progress.is-payoff .ba-countdown { color: var(--c-ink); }
html.theme-light .ba-progress.is-payoff .ba-countdown,
html[data-theme="light"] .ba-progress.is-payoff .ba-countdown { color: var(--c-accent-ink); }

/* ── THE LOCKED STATE — RESTYLE ONLY (§ED240 seal preserved) ────────────
   No new mechanism, no new copy, no new lock reasons, nothing about who
   sees what. The seal — ruled lines standing in the shape of the withheld
   body — is kept exactly as ruled; it gets the warm palette and nothing
   else. The lime budget stays where lockedTeaser put it: the reveal action
   is NEUTRAL, and lime arrives once, on the membership card, only after a
   reader taps for it. Many locked spines can share one scroll. */
.ar-locked {
  background: var(--c-surface);
  border: 1px dashed var(--c-line);
  border-radius: var(--c-e2-radius);
  box-shadow: var(--c-e1-shadow);
}
.ar-locked-hook { font-family: var(--c-f-display); color: var(--c-ink); }
.ar-locked-standfirst { color: var(--c-ink-2); font-size: var(--c-t-body); }
.ar-locked-source, .ar-locked-subline { color: var(--c-ink-3); font-size: var(--c-t-micro); }
.ar-seal {
  background: var(--c-surface-2);
  border: var(--c-e1-border);
  border-radius: var(--c-r-md);
}
.ar-seal-label { color: var(--c-ink-3); font-size: var(--c-t-micro); }
.ar-seal-rule  { background: var(--c-line); }
.ar-locked-today { color: var(--c-ink); font-size: var(--c-t-micro); }
.ar-locked-reveal {
  border: var(--c-e1-border);
  border-radius: var(--c-r-sm);
  color: var(--c-ink-2);
  font-size: var(--c-t-micro);
  background: transparent;
  transition: background-color var(--c-m-toggle) var(--c-e-out),
              transform var(--c-m-tap) var(--c-e-standard);
}
.ar-locked-reveal:hover  { background: var(--c-surface-2); }
.ar-locked-reveal:active { transform: scale(.98); }
.ar-locked-reveal:focus-visible { outline: 2px solid var(--c-focus); outline-offset: 2px; }

/* ── MODALS / SHEETS — L3. SCRIM, NO BLUR ──────────────────────────────
   Backdrop blur is licensed to ONE consumer in this product (a sticky
   header over scrolling content) and the calendar panel is not it. */
.ar-cal-scrim {
  background: var(--c-e3-scrim);
  -webkit-backdrop-filter: none;
          backdrop-filter: none;
}
.ar-cal-panel {
  background: var(--c-surface);
  border: var(--c-e3-border);
  border-radius: var(--c-e3-radius);
  box-shadow: var(--c-e3-shadow);
  transition: transform var(--c-m-sheet) var(--c-e-out),
              opacity   var(--c-m-sheet) var(--c-e-out);
}
.ar-cal-trigger {
  background: var(--c-surface);
  border: var(--c-e1-border);
  border-radius: var(--c-r-full);
  color: var(--c-ink-2);
  box-shadow: var(--c-e1-shadow);
  transition: transform var(--c-m-tap) var(--c-e-standard);
}
.ar-cal-trigger:active { transform: scale(.97); }
.ar-cal-trigger:focus-visible { outline: 2px solid var(--c-focus); outline-offset: 2px; }

/* ── THE ONE LICENSED BLUR: the sticky header over scrolling content ──── */
/* §ED440 PHASE E — TWO DEFECTS FIXED HERE, BOTH FOUND BY THE STRUCTURAL
   CHECK IN scripts/audit-ed440-scroll-perf.js AND NEITHER VISIBLE BY EYE.
   1. NO SATURATE. Phase D shipped `blur(var(--c-blur-header))` alone, so this
      surface was the frosted-plastic version of the material while the
      masthead and the nav had the full recipe. It now reads the assembled
      --c-glass-filter token, which cannot be used without its saturate.
   2. IT IS NOT STICKY. Computed position was `static`, with no fixed or
      sticky ancestor — so this is a blurred element INSIDE the scroller, the
      one case Part 3 forbids on performance grounds, re-running its backdrop
      pass every frame. The comment above has claimed "sticky header" since
      Phase D while the element was never positioned. Named a header, priced
      as one, and behaving as neither. */
.bf-header {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--c-glass-chrome);
  -webkit-backdrop-filter: var(--c-glass-filter);
          backdrop-filter: var(--c-glass-filter);
  border-bottom: 1px solid var(--c-glass-chrome-line);
  box-shadow: inset 0 -1px 0 var(--c-specular-under);
  transform: translateZ(0);
}
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .bf-header { background: var(--c-surface); }
}

/* ── FOOT / SIGNUP ─────────────────────────────────────────────────────── */
.bf-signup-h   { font-family: var(--c-f-display); color: var(--c-ink); }
.bf-signup-sub { color: var(--c-ink-2); font-size: var(--c-t-body); }
.bf-anon       { color: var(--c-ink-3); font-size: var(--c-t-micro); }
.bf-signup-cta {
  background: var(--c-accent);
  color: var(--c-on-accent);
  border-radius: var(--c-r-sm);
  transition: transform var(--c-m-tap) var(--c-e-standard);
}
.bf-signup-cta:active { transform: scale(.98); }

/* ══════════════════════════════════════════════════════════════════════════
   §ED440 PHASE D · ITEM 1 — THE OPTIMISTIC ANSWER
   The highest-priority fix of this pass. Part 1 mechanic 4.
   ══════════════════════════════════════════════════════════════════════════ */

/* THE DIM IS GONE. `.ba-zone.is-busy { opacity:.6 }` (briefing.css:2305) meant
   the one feedback a reader got for tapping their answer was the ENTIRE
   question block fading out, while the row they chose stayed unmarked until
   the server replied. On anything worse than office wifi that is a dead tap.
   The old class is left defined above — nothing sets it any more — and
   reader-answer.js now sets `.is-committing`, which blocks a second press and
   CHANGES NOTHING VISIBLE. The chosen row is the feedback. */
.ba-zone.is-committing { pointer-events: none; }

/* THE CHOSEN ROW, PAINTED WITHIN ONE FRAME OF THE TAP.
   Deliberately the SAME treatment as the confirmed .ba-answer below — the
   lime tint at 22% over the surface, the accent hairline — because the
   optimistic state is not a "pending" state with its own look. It is the
   answer, shown immediately, which the server then either confirms (the
   block rebuilds and the 640ms spring fires) or takes back (the class comes
   off and .ba-failed appears). A distinct third appearance for "probably
   saved" would be inventing a state the reader does not have.
   COLOUR AND BORDER ONLY — no transform, no opacity — so there is nothing
   here to drop a frame on and nothing for reduced-motion to kill.
   Specificity matches the (0,3,1) dark step-up at briefing.css:2895 and wins
   on source order; see that rule's note for why a bare .ba-option loses. */
.ba-zone .ba-option.is-chosen,
html:not(.theme-light) .ba-zone .ba-option.is-chosen,
html[data-theme="dark"] .ba-zone .ba-option.is-chosen {
  background: #EEF7CC;                      /* light fallback, pre-color-mix */
  border-color: var(--c-accent-ink);
  color: var(--c-ink);
}
html:not(.theme-light) .ba-zone .ba-option.is-chosen,
html[data-theme="dark"] .ba-zone .ba-option.is-chosen { background: #23291A; }
@supports (background: color-mix(in oklab, red 22%, blue)) {
  .ba-zone .ba-option.is-chosen,
  html:not(.theme-light) .ba-zone .ba-option.is-chosen,
  html[data-theme="dark"] .ba-zone .ba-option.is-chosen {
    background: color-mix(in oklab, var(--c-accent) 22%, var(--c-surface));
  }
}

/* THE TAKE-BACK. Shown only where an optimistic paint was reverted and
   nothing re-rendered over it. Warm danger token, at the reading floor —
   this is a sentence a reader must actually read. */
.ba-failed {
  margin: 10px 0 0;
  color: var(--c-danger);
  font-size: var(--c-t-micro);
  line-height: 1.5;
}

/* Inline progress on the write-in's Save — an ACTION waiting, not a toggle. */
.ba-save.is-saving { opacity: .72; cursor: default; }

/* ── §ED487 · THE UNSAVED WRITE-IN ────────────────────────────────────────
   Shown when the server refused an edit (503) or already held an answer
   (409) and the reader had typed prose. js/reader-answer.js adopts the
   STORED answer above this block; this is the paragraph that did NOT save,
   put back on screen so the reader can still read it and copy it.

   NO GROUND OF ITS OWN — deliberately, and for the reason §ED70 names.
   .ba-answer's base rule paints `--surface-well`, which belongs to the
   OLD token layer (global.css), while every colour in this §ED440 block
   comes from the NEW one (tokens.css --c-*). Mixing the two would set
   light ink on a dark well in one theme and never be noticed until a
   reader met it. So this block inherits the zone's ground and borrows its
   ink from exactly the rules that already ship beside it: --c-danger from
   .ba-failed above, --c-ink / --c-ink-2 from .ba-answer-value and
   .ba-answer-text at briefing.css:3016.

   IT MUST NOT READ AS A SECOND ANSWER. .ba-answer is the lime-tinted
   confirmed block; this is a DOTTED danger edge with no fill, so the two
   are distinguishable by shape as well as by colour — the dotted rule
   survives forced-colours and greyscale, which a tint does not.

   TYPE FLOOR (§ED306): the reader's own words are set at --c-t-body, not
   at a caption size. This is the last place that paragraph exists. */
.ba-unsaved {
  margin: 12px 0 0;
  padding: 2px 0 2px 13px;
  border-left: 3px dotted var(--c-danger);
}
.ba-unsaved-lead {
  margin: 0;
  color: var(--c-danger);
  font-size: var(--c-t-micro);
  line-height: 1.5;
}
.ba-unsaved-label {
  margin: 10px 0 0;
  color: var(--c-ink-2);
  font-size: var(--c-t-micro);
  line-height: 1.5;
}
.ba-unsaved-text {
  margin: 4px 0 0;
  color: var(--c-ink);
  font-size: var(--c-t-body);
  line-height: var(--c-lh-body);
  overflow-wrap: break-word;
  white-space: pre-wrap;   /* VERBATIM — the reader's own line breaks survive */
}

/* ══════════════════════════════════════════════════════════════════════════
   §ED440 PHASE D · ITEMS 2, 3, 4, 5 — THE ROOM, THE GROUND, THE OPTIONS,
   THE BAND.

   SCOPING, DELIBERATE. Everything below hangs off `.bf-page`, which exists on
   briefing / record / vault / archive and on NEITHER index.html NOR
   membership.html — both of which load this sheet. membership.html is
   explicitly out of scope this pass (it gets its own, and becomes theme-aware
   then) and index.html is the landing. Scoping to .bf-page excludes both
   structurally rather than by hoping nobody notices.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── ITEM 2 · THE ROOM SYSTEM, SWITCHED ON ───────────────────────────────
   The tokens have existed since Phase A and NOTHING HAS EVER READ THEM. The
   app did not lack a macro rhythm system; it had one and had never switched
   it on. Measured before this rule (Phase C, 1440x900):

     landing section gaps   90 · 135 · 216 · 216 · 134 · 135   THREE ranks
     app block gaps         10 · 11 · 12 · 14 · 16 · 20 · 26   NO ranks

   The app's LARGEST macro gap was 3.5x SMALLER than the landing's SMALLEST
   section gap, so nothing on the page was ranked and it read as one
   undifferentiated run. That is the mechanical answer to "cramped".

   AND IT HOLDS AT 375. The ladder is viewport-HEIGHT-derived
   (clamp(132px, 24vh, 280px)), so it scales with the reading surface rather
   than collapsing with the width. The old rules did the opposite: they more
   than HALVED the card's interior air on the surface most readers use
   (.bf-daily-body was 40/64/48 at 1440 and 16/18/22 at 375). Those two
   overrides, at briefing.css:1822 and :1848, are what this replaces. */
.bf-page {
  background: var(--c-bg);
  padding-top: var(--c-room-sm);
  padding-left: var(--c-gutter);
  padding-right: var(--c-gutter);
  padding-bottom: calc(var(--c-room-lg) + var(--nav-bottom-h, 0px));
}
/* MASTHEAD CLEARANCE. .app-topnav is position:fixed, 70px, and exists only
   at >=900px (global.css:1092). Without this the culture pill slides UNDER
   the fixed bar — which is exactly what the first draft of this layer did,
   caught in an injected 1440 frame during Phase C. */
@media (min-width: 900px) {
  .bf-page { padding-top: calc(70px + var(--c-room-sm)); }
}

/* ITEM 2 · MEASURE. Text ran the full 620/720px column at every width. The
   landing ranks WIDTH as well as room: --c-measure for sections,
   --c-measure-narrow for the reading column. The card is the reading
   column. */
.bf-page .bf-wrap { max-width: var(--c-measure-narrow); }

/* THE CARD IS SEATED, NOT BUTTED. The dominant beat on the page gets room
   above it and nothing else does — room is a RANK, spent once. --room-sm
   under the utility row, --room-md before the foot. The app has ONE beat,
   not the landing's six, so it spends one rank down from --room-xl. */
.bf-page .bf-header { margin: 0 0 var(--c-room-sm); }
.bf-page .bf-foot   { margin-top: var(--c-room-md); }
/* Interior rank: the reading block separates from the answer zone by more
   than its own paragraphs separate from each other. */
.bf-page .bf-reaction-wrap { margin-top: var(--c-room-sm); }
.bf-page .bf-daily-body { padding: 40px 64px 48px; }
@media (max-width: 899px) {
  /* HOLDS instead of halving — the whole point of a vh-derived ladder. */
  .bf-page .bf-daily-body { padding: 32px 24px 36px; }
}

/* ── ITEM 3 · THE SHELL ONTO WARM TOKENS ─────────────────────────────────
   Measured before this rule: page ground #FFFFFF light / #101010 (S 0%)
   dark, .ba-zone and .bf-signup on pure #FFFFFF. Part 1 REFUSES pure #000
   and #FFF grounds and refuses cool greys anywhere. Phase B moved the CARD
   to warm tokens and the SHELL never followed, which is the located cause of
   "the surroundings look default" — a warm card on an achromatic ground. */
/* SPECIFICITY, MEASURED NOT ASSUMED — this rule lost its first draft and the
   loss was LIGHT-ONLY, which is the T1 defect class exactly: every dark frame
   was correct and hid it. briefing.css:2618 carries
     html.theme-light .ra-card, html.theme-light .ba-zone
       { background: var(--surface); box-shadow: var(--shadow-card); }
   at (0,2,1), which beats a bare `.bf-page .ba-zone` (0,2,0). Caught by
   scripts/audit-ed440-tokens.js reading computed style, not by eye.
   The leading `html` brings this to (0,2,1) and it wins on source order.
   Its box-shadow is overridden too, deliberately: --shadow-card is
   `0 6px 20px rgba(22,22,22,.16)` — one shadow at 3x the landing's alpha,
   cool-tinted and with no contact edge, i.e. the exact geometry Part 1
   mechanic 1 rules against. L1 is the correct elevation for this surface. */
html .bf-page .ba-zone,
html .bf-page .bf-signup {
  background: var(--c-surface);
  border-color: var(--c-line);
  box-shadow: var(--c-e1-shadow);
}

/* (c) THE CULTURE SELECTOR — a brand moment, not a control.
   Was: bg #FAFAFA (achromatic), a rgba(22,22,22,.16) border — DOUBLE the
   landing's .13 hairline and cool-tinted — and box-shadow rgba(0,0,0,.4),
   a pure-black 40% shadow on warm paper, the single cheapest declaration
   measured this session. Its label was 14.4px, under the floor.
   The LIME hairline is what makes it read as identity rather than chrome;
   the landing's .p-chip does the same and carries no shadow at all. */
.bf-page .bf-cult-trigger {
  background: var(--c-surface);
  border: 1px solid color-mix(in srgb, var(--c-accent-ink) 30%, transparent);
  box-shadow: none;
  border-radius: var(--c-r-full);
  padding: 10px 18px;
  font-size: var(--c-t-micro);
  font-weight: 500;
  color: var(--c-ink);
  transition: border-color var(--c-m-toggle-out) var(--c-e-standard),
              background   var(--c-m-toggle-out) var(--c-e-standard),
              transform    var(--c-m-tap)        var(--c-e-standard);
}
.bf-page .bf-cult-trigger:hover {
  border-color: var(--c-accent-ink);
  background: var(--c-surface-2);
  box-shadow: none;
  transition-duration: var(--c-m-toggle), var(--c-m-toggle), var(--c-m-tap);
}
.bf-page .bf-cult-trigger:active { transform: scale(.98); }
.bf-page .ar-cal-trigger {
  background: var(--c-surface);
  border: 1px solid var(--c-line);
  box-shadow: var(--c-e1-shadow);
  border-radius: var(--c-r-full);
  color: var(--c-ink-2);
}

/* (e) THE VOICE AFFORDANCE — voice is the differentiator and looked like an
   afterthought: a 38px circle on --surface-well (#101010) with
   --border-strong, i.e. old graphite tokens, reading as a secondary icon
   button beside a text field.
   NO WAVEFORM. §ED438 R8 forbids porting the landing's FAKE .p-wave over a
   real player, and reader-answer.js's own comment already rules a live
   recording animation out as theatre. This is a target and a colour, not a
   performance. */
.bf-page .ba-mic {
  width: auto; min-width: 44px; height: 44px; padding: 0 16px; gap: 8px;
  background: var(--c-surface);
  border: 1px solid color-mix(in srgb, var(--c-accent-ink) 34%, transparent);
  border-radius: var(--c-r-full);
  color: var(--c-accent-ink);
  font-size: var(--c-t-micro);
  font-weight: 500;
  transition: transform var(--c-m-tap) var(--c-e-standard),
              border-color var(--c-m-toggle-out) var(--c-e-standard);
}
.bf-page .ba-mic:hover  { border-color: var(--c-accent-ink); background: var(--c-accent-wash); }
.bf-page .ba-mic:active { transform: scale(.97); }
.bf-page .ba-mic.is-live {
  background: var(--c-band-fill); border-color: var(--c-band-fill); color: var(--c-on-accent);
}

/* ── ITEM 4 · D3 — THE OPTIONS TO THE PRIMARY INK TIER ───────────────────
   NOT a contrast fix. Measured: 7.74:1 light / 7.40:1 dark — both PASS AA
   comfortably. It is a HIERARCHY defect: .ba-option was set to --c-ink-2,
   the SECONDARY tier, while the prose it asks the reader to act on sits on
   the primary tier — so the options rendered at ~45% of the contrast of the
   body copy and read as secondary text.

   THE LIFT WAS THE ERROR, AND IT WAS MINE. Phase B took --ink-2 faithfully
   from the landing's .p-opt. But the landing's options are a STATIC PICTURE
   of a control inside a marketing page — decorative, never tapped. The app's
   are the live control a reader must read and choose between. This is the
   concrete case Part 2 names: where the landing's value cannot port, say so.
   Matches the (0,3,1) dark step-up and wins on source order. */
.bf-page .ba-zone .ba-option,
html:not(.theme-light) .bf-page .ba-zone .ba-option,
html[data-theme="dark"] .bf-page .ba-zone .ba-option { color: var(--c-ink); }

/* ── ITEM 5 · THE BAND, DIALLED (P4b) ────────────────────────────────────
   Jake ruled P4b. The lip STAYS A FULL-BLEED LIP; only the lime's lightness
   moves, and only in dark. --c-band-fill resolves to #CCFF00 in light and
   #A8D400 in dark — see css/tokens.css for the measurement and for why the
   rejected P4 tint was wrong. Specificity matches the band's own
   .ba-zone > .ba-question (0,2,0) and wins on source order; a bare
   .ba-question (0,1,0) would lose silently, which is the T1 defect. */
.bf-page .ba-zone > .ba-question { background: var(--c-band-fill); }

/* ── §ED469 · THE SAME BAND, ON /record AND THE VAULT ────────────────────
   §ED460 measured this as a separate defect from the one above, not a
   duplicate: the shell comment at line ~2612 already explains ONE band
   shared by .ra-card (/record, the vault) and .ba-zone (the Daily) — but
   only .ba-zone ever got the P4b dial (item 5 above). .ra-card's band was
   still reading the undialled .ra-question rule two screens up
   (var(--accent)/var(--accent-text), flat #CCFF00 in both themes) — a
   reader's own private vault blasting full-brightness lime the briefing
   deliberately dims in dark. Same fix, same tokens, same reason for the
   higher (0,3,0) specificity: .ra-question shares (0,2,0) with
   .ba-zone > .ba-question, and this must not lose silently the way the T1
   defect already did once in this file.
   Every /record- and vault-rendered card sits under <main class="bf-page">
   (record.html, vault.html — verified), so this scope matches exactly.
   .rec-fact-date is DELIBERATELY absent here — Jake's ruling took it off
   the band entirely (see the neutral rule at line ~2694); it no longer
   reads any accent token at all. */
.bf-page .ra-card > .ra-question {
  background: var(--c-band-fill);
  color: var(--c-on-accent);
}


/* ══════════════════════════════════════════════════════════════════════════
   §ED440 PHASE E · THE LOCKED STATE — EXECUTION REBUILD (Part 1)

   THE CONCEPT IS §ED240'S AND IT IS KEPT: the withholding must be VISIBLE, and
   what stands in for the body is its SHAPE. A gate nobody can see is not a
   gate, it is a disappointing edition. Nothing about the mechanism, the four
   lock reasons, the entitlement logic or the copy moves in this block.

   WHAT MOVES IS THE EXECUTION, because the seal was conceptually right and
   visually dead: a headline, then four flat grey rules. Two-dimensional. In
   light mode especially — warm paper, grey rules, nothing lit — it read as a
   rendering failure rather than as a wall.

   THE REBUILD, IN FOUR PARTS:
     a) THE SPINE RENDERS SHARP, AT FULL INK, in the treatment it carries when
        unlocked. This is the largest single change and it costs nothing: the
        server was already sending real hook, title, standfirst and source and
        the old seal spent them at reduced size and reduced ink, as though
        they were also being withheld. They are not. They are the hook.
     b) THE BODY PREVIEW DISSOLVES — real prose, sharp at the top, ramping
        into blur and out to the ground. Two stacked copies with
        complementary masks (see js/card.js lockedTeaser for why one element
        cannot do this).
     c) THE SEAL LINES CONTINUE THAT DISSOLVE below the preview, gradient-
        masked into the ground instead of sitting as flat rules. They now
        read as the text carrying on past where it can still be resolved,
        which is what they were always meant to mean.
     d) A GLASS CARD FLOATS OVER THE BOUNDARY where sharp becomes faded. It
        is the ONLY element at chrome elevation on this view.

   LIGHT AND DARK DO NOT SHIP AT THE SAME SETTINGS, and that is Part 1's
   explicit instruction. On a dark ground a fade toward the ground is doing
   most of the work for free — ink and ground are far apart, so the ramp is
   legible over a short distance. On warm paper the ink/ground distance is
   smaller, the fade runs out of contrast almost immediately, and the same
   numbers produce a preview that looks merely faint. Light therefore gets a
   LONGER sharp region, a SHALLOWER blur, and a stronger glass edge. Every
   divergence below is marked LIGHT: or DARK: and none of it is symmetric.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── (1a) THE SPINE, SHARP ───────────────────────────────────────────────
   Phase D's .ar-locked-hook was --c-f-display at inherited size. It now takes
   the hero treatment: the same face, weight and tracking .bf-hook-text
   carries on an unlocked read, so a locked card and an open one present their
   headline identically. Nothing here is dimmed, tinted or reduced. */
.bf-page .ar-locked-hook,
.ar-locked.has-preview .ar-locked-hook {
  font-family: var(--c-f-display);
  font-weight: 400;
  font-size: clamp(1.5rem, 4.4vw, 2rem);
  line-height: 1.2;
  letter-spacing: -0.012em;
  color: var(--c-ink);
  font-variant-numeric: lining-nums;
}
.bf-page .ar-locked-standfirst {
  color: var(--c-ink-2);
  font-size: var(--c-t-body);
  line-height: var(--c-lh-body);
}
/* .ar-locked-source IS DELIBERATELY NOT IN THIS RULE. It was, and that was a
   self-inflicted specificity defeat: this selector is (0,2,0) and the citation
   amendment's `html .ar-locked-source` is (0,1,1), so THIS rule won and the
   locked state's source line rendered --c-ink-3 grey while every other
   citation in the app carried the accent. Jake's amendment names the locked
   state's source_title explicitly — it is one of the few sharp elements on
   that screen and it should carry weight. Caught on frame 15, confirmed by
   computed style, not by argument. The citation block at the foot of this
   sheet owns this element's colour and size now. */
.bf-page .ar-locked-subline { color: var(--c-ink-3); font-size: var(--c-t-micro); }

/* ── (1b) THE DISSOLVE ───────────────────────────────────────────────────
   THE STACK. Two copies of the preview occupy the same box. The sharp one is
   in flow and gives the container its height; the blurred one is absolutely
   positioned on top of it and is inert (aria-hidden in the DOM, pointer-
   events:none here, never selectable).

   WHY overflow IS NOT hidden. Clipping would cut the blur's own spread and
   put a hard edge exactly where the design needs none. The masks end the
   content instead, which is what a mask is for. */
/* ONE MEASURE FOR THE WHOLE DISSOLVING COLUMN — the preview and the seal
   lines beneath it must be the same width or the "text continuing past where
   it can be resolved" reading breaks at the seam. 34ch is the seal's own
   §ED240 value, kept.
   THE MEASURE HOLDS: this is narrower than --c-measure-narrow and is a ch
   unit, so it tracks the body face and cannot be widened by a layout. */
.ar-locked { --ar-lock-measure: 34ch; }

.ar-locked-body {
  position: relative;
  margin: 18px auto 0;
  max-width: var(--ar-lock-measure);
  /* LEFT-ALIGNED, INSIDE A CENTRED BLOCK. .ar-locked centres its spine (a
     three-line hook and a short standfirst read as a centred clause, which is
     §ED240's composition and is not this pass's to change). Body prose is not
     a clause: centred body text has a ragged LEFT edge, and a ragged left
     edge under a fade reads as damage rather than as continuation. Part 1a
     also asks for the spine to render as it would when unlocked, and an
     unlocked debrief (.bf-debrief) is left-aligned. So the column is left-set
     and the BLOCK is centred — the card's composition survives and the prose
     gets a real rag. */
  text-align: left;
  /* The blurred copy bleeds a few px past the text box; give it room so the
     bleed lands on ground rather than on the next element. */
  padding-bottom: 6px;
}
.ar-locked-preview,
.ar-locked-preview-soft {
  margin: 0;
  font-family: var(--c-f-body);
  font-size: var(--c-t-body);
  line-height: var(--c-lh-body);
  color: var(--c-ink);
  /* The measure holds inside the dissolve exactly as it does in open prose —
     a paywall is not a reason to reflow the column. */
  max-width: 100%;
}
.ar-locked-preview-soft {
  position: absolute;
  inset: 0;
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
}

/* LIGHT: the sharp copy holds full ink to 48% and does not finish until 82%.
   The long plateau is the "more contrast in the sharp region" Part 1 calls
   for — on paper the reader needs to have genuinely READ something before it
   starts to go. */
.ar-locked-preview {
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 48%, rgba(0,0,0,.35) 68%, transparent 82%);
          mask-image: linear-gradient(to bottom, #000 0%, #000 48%, rgba(0,0,0,.35) 68%, transparent 82%);
}
/* LIGHT: a SHALLOWER blur (3.5px). On paper a deep blur turns text into a
   grey smear that reads as damage; the ink/ground distance is small enough
   that a light touch already destroys legibility while keeping the TEXTURE
   of prose, which is the thing that must survive. */
.ar-locked-preview-soft {
  filter: blur(3.5px);
  -webkit-mask-image: linear-gradient(to bottom, transparent 26%, #000 58%, #000 74%, transparent 97%);
          mask-image: linear-gradient(to bottom, transparent 26%, #000 58%, #000 74%, transparent 97%);
}

/* DARK: the ramp starts earlier and runs deeper. Ink #F5F2EC on ground
   #100E0C is a 17:1 separation — the fade has contrast to spend and can
   afford to begin at 38%. The blur goes to 5px for the same reason: light
   text on dark blurs into a GLOW rather than a smear, and a shallow blur
   there just looks like a focus error. */
html:not(.theme-light) .ar-locked-preview,
html[data-theme="dark"] .ar-locked-preview {
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 38%, rgba(0,0,0,.30) 62%, transparent 78%);
          mask-image: linear-gradient(to bottom, #000 0%, #000 38%, rgba(0,0,0,.30) 62%, transparent 78%);
}
html:not(.theme-light) .ar-locked-preview-soft,
html[data-theme="dark"] .ar-locked-preview-soft {
  filter: blur(5px);
  -webkit-mask-image: linear-gradient(to bottom, transparent 20%, #000 50%, #000 70%, transparent 95%);
          mask-image: linear-gradient(to bottom, transparent 20%, #000 50%, #000 70%, transparent 95%);
}

/* REDUCED MOTION does not apply here — nothing moves. But REDUCED
   TRANSPARENCY does, and it is a real user setting that this design must
   answer: a reader who has asked the OS to reduce transparency is telling us
   that layered, low-contrast material is hard for them to parse. Drop the
   blurred copy entirely and let the sharp one end at its mask. The wall still
   works; the dissolve just becomes a fade. */
@media (prefers-reduced-transparency: reduce) {
  .ar-locked-preview-soft { display: none; }
  .ar-locked-preview {
    -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 62%, transparent 92%);
            mask-image: linear-gradient(to bottom, #000 0%, #000 62%, transparent 92%);
  }
}

/* ── (1c) THE SEAL LINES, CONTINUING THE DISSOLVE ────────────────────────
   The rules keep their §ED240 job — the shape of the withheld body — and
   stop being flat. They carry REAL INK at the top and are masked out into
   the ground, so the column reads as continuing past the point where it can
   still be resolved rather than as four grey bars parked under the text.

   The label keeps full ink and is NOT masked: it is the one string here that
   must stay readable, because it is what names the group for a screen reader
   and what tells a sighted reader what the shape stands for. */
/* THE SEAL STOPS BEING A BOX. §ED240 gave it a bordered surface-2 card so the
   rules read as a deliberate sealed block rather than as a rendering fault —
   correct then, wrong now: with real prose dissolving directly above it, a
   boxed panel CUTS the column exactly where the design needs it to continue.
   The dissolve is what now says "sealed", so the box is redundant and it was
   the second reason the old state read as furniture.
   Background, border and padding all go; the shape lines stay. */
.bf-page .ar-seal {
  border: 0;
  padding: 0;
  background: none;
  margin: 0 auto;
  max-width: var(--ar-lock-measure);
  text-align: left;
}
.bf-page .ar-seal-label {
  color: var(--c-ink-3);
  font-size: var(--c-t-micro);
  letter-spacing: .10em;
  text-transform: uppercase;
}
.bf-page .ar-seal-shape {
  display: flex;
  flex-direction: column;
  gap: 9px;
  margin-top: 12px;
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, rgba(0,0,0,.55) 45%, transparent 100%);
          mask-image: linear-gradient(to bottom, #000 0%, rgba(0,0,0,.55) 45%, transparent 100%);
}
/* Real ink, not a grey. --c-ink-2 at 38% on paper is the weight of a line of
   set text seen out of focus, which is what these stand for. The old value
   was a flat --border hairline and it is the single reason the seal read as
   dead: a rule at hairline weight is FURNITURE, and furniture is not what a
   withheld paragraph looks like. */
.bf-page .ar-seal-rule {
  height: 9px;
  border-radius: 3px;
  background: color-mix(in srgb, var(--c-ink-2) 38%, transparent);
}
html:not(.theme-light) .bf-page .ar-seal-rule,
html[data-theme="dark"] .bf-page .ar-seal-rule {
  background: color-mix(in srgb, var(--c-ink-2) 30%, transparent);
}

/* ── (1d) THE GLASS CARD OVER THE BOUNDARY ───────────────────────────────
   THE ONE CHROME-ELEVATION ELEMENT ON THIS VIEW, and the full Part 3 recipe:
   backdrop blur AND saturate, warm tint, hairline, specular top edge, warm
   thickness line beneath. It is glass because it is chrome — it floats above
   content — which is exactly the division tokens.css §5 draws.

   THE NEGATIVE MARGIN IS THE DESIGN, not a nudge. The card is pulled UP so it
   straddles the point where the preview stops being sharp. Placing it below
   the dissolve would make it a footer; placing it across the dissolve makes
   it the thing that INTERRUPTED the reading. */
/* THE CARD MUST CLEAR THE TAB BAR. Caught on frame 12 at 375: the floating
   nav pill covered the "read today's edition" link entirely, so the locked
   state shipped with its secondary action unreachable on every phone. The
   nav is fixed and 64px + safe-area, and .bf-page's own bottom padding was
   sized for a state that did not end in a card. */
.ar-locked { padding-bottom: calc(var(--safe-bottom, 0px) + 96px); }

.ar-lock-card {
  position: relative;
  z-index: 2;
  /* Default: a normal gap. THE OVERLAP IS OPT-IN and belongs to the preview
     variant only. Frame 13 showed why: with no preview the column is ~120px
     shorter, so a fixed -34px pull swallowed all but two of the seal's shape
     lines and the no-preview option was being photographed WORSE THAN IT IS.
     A comparison Jake is asked to rule on must not be rigged by a constant
     tuned for the other option. */
  margin-top: 22px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 22px 20px 18px;
  border-radius: var(--c-r-lg);
  background: var(--c-glass-chrome);
  -webkit-backdrop-filter: var(--c-glass-filter);
          backdrop-filter: var(--c-glass-filter);
  transform: translateZ(0);
  border: 1px solid var(--c-glass-chrome-line);
  box-shadow: var(--c-specular-edge), var(--c-e2-shadow);
}
/* THE OVERLAP — only where there is a dissolve to straddle. */
.ar-locked.has-preview .ar-lock-card { margin-top: -34px; }

/* LIGHT: a STRONGER edge, per Part 1. Warm paper behind warm glass gives very
   little luminance separation, so on light the card's own outline is most of
   what makes it read as a separate pane at all. The hairline goes to a real
   1.5px and picks up a second, warmer outer ring so the card has a visible
   lip against the paper. Dark needs neither — there the tint alone separates. */
html.theme-light .ar-lock-card,
html[data-theme="light"] .ar-lock-card {
  /* THE TINT GOES DARKER THAN THE SURFACE IT FLOATS ON, and this is the fix
     the first render forced. --c-glass-chrome is rgba(255,253,250,.66) and
     the card beneath it is --c-surface #FFFDFA: white glass on white paper.
     The card was invisible — a lime button and a lock apparently floating on
     nothing. Frame 12 showed it; no amount of edge tuning would have saved a
     fill with nowhere to sit.
     Real glass over white paper does not read by its FILL, it reads by being
     slightly deeper than the sheet under it and by casting a shadow onto it.
     So light gets its own tint one step down the warm ramp (toward
     --c-bg-deep) rather than the shared chrome white. Dark needs none of
     this: there the tint is already lighter than its ground and separates on
     its own. */
  background: rgba(247, 243, 236, .72);
  border-width: 1.5px;
  /* L3, not L2. This is the only floating pane on the view and on paper the
     SHADOW is most of what says "floating" — the fill can only do so much
     when ground and surface are four points apart in lightness. */
  box-shadow: var(--c-specular-edge),
              0 0 0 1px rgba(20, 18, 14, .05),
              var(--c-e3-shadow);
}
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  html.theme-light .ar-lock-card,
  html[data-theme="light"] .ar-lock-card { background: #F7F3EC; }
}

/* MANDATORY FALLBACK — verified, not assumed. Without a backdrop filter the
   glass card would be a 66% tint sitting directly on top of the preview text:
   text over text, and the least readable thing on the screen. Fully opaque. */
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .ar-lock-card { background: var(--c-surface); }
}

/* ── (1e) THE LOCK AFFORDANCE — TWO OPTIONS ──────────────────────────────
   Neither is a grey padlock glyph on a flat field, which is what Part 1e
   rules out and what every product ships. */
.ar-lock-mark {
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  border-radius: var(--c-r-full);
  flex: 0 0 auto;
}
.ar-lock-mark svg { width: 21px; height: 21px; display: block; }

/* OPTION 1 — THE WELL (default). A circular dish pressed INTO the glass. The
   light source is above, so the inside of the top edge catches it and the
   floor of the dish falls into shadow — which is the inverse of the specular
   on a raised pane, and is why it reads as pressed rather than applied. Same
   one light source; the geometry is what changes, not the lighting.
   It spends NO accent, which is why it is the default: the lime budget on
   this view belongs to the unlock action (Part 1d). */
.ar-lock-mark.is-well {
  background: color-mix(in srgb, var(--c-ink) 5%, transparent);
  color: var(--c-ink-2);
  box-shadow: inset 0 2px 3px rgba(20, 18, 14, .13),
              inset 0 -1px 0 var(--c-specular);
}
html:not(.theme-light) .ar-lock-mark.is-well,
html[data-theme="dark"] .ar-lock-mark.is-well {
  background: rgba(0, 0, 0, .28);
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, .45),
              inset 0 -1px 0 rgba(255, 252, 245, .07);
}

/* OPTION 2 — THE HALO. The lime at low alpha behind the mark, so the accent
   is present at the LOCK and not only at the action beneath it. The glyph
   takes --c-accent-ink (the readable olive at the same 72° hue), never the
   raw lime, which is unreadable as a stroke on paper. */
.ar-lock-mark.is-halo {
  background: radial-gradient(closest-side,
                rgba(204, 255, 0, .30) 0%,
                rgba(204, 255, 0, .12) 62%,
                rgba(204, 255, 0, 0) 100%);
  color: var(--c-accent-ink);
  box-shadow: var(--c-specular-edge-soft);
}
html:not(.theme-light) .ar-lock-mark.is-halo,
html[data-theme="dark"] .ar-lock-mark.is-halo {
  background: radial-gradient(closest-side,
                rgba(204, 255, 0, .22) 0%,
                rgba(204, 255, 0, .08) 62%,
                rgba(204, 255, 0, 0) 100%);
  color: var(--c-accent);
}

/* ── (1f) THE ACTIONS ────────────────────────────────────────────────────
   Copy unchanged; both strings are the ones this teaser already shipped. */
.ar-lock-card .ar-locked-reveal {
  order: 1;
  width: 100%;
  max-width: 320px;
  padding: 15px 22px;
  border: 1px solid var(--c-line);
  border-radius: var(--c-r-sm);
  background: transparent;
  color: var(--c-ink);
  font-family: var(--c-f-body);
  font-size: var(--c-t-micro);
  font-weight: 600;
  cursor: pointer;
}
.ar-lock-card .ar-locked-today {
  order: 2;
  color: var(--c-ink-2);
  font-size: var(--c-t-micro);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* THE LIME, SPENT ONCE. .is-solo is set by lockedTeaser only for a caller
   whose wallSource is a briefing surface — i.e. a view that renders exactly
   ONE of these. The archive and the Collection stack many and keep §ED240's
   entirely-neutral treatment, which is the ruling this must not break.
   See the note at the call site in js/card.js. */
.ar-lock-card.is-solo .ar-locked-reveal {
  background: var(--c-accent);
  color: var(--c-on-accent);
  border-color: transparent;
  /* The specular belongs on the lime too — it is a raised object under the
     same light as everything else, and without the edge it is the one flat
     element on a lit screen. */
  box-shadow: var(--c-specular-edge-soft), var(--c-e1-shadow);
}
html:not(.theme-light) .ar-lock-card.is-solo .ar-locked-reveal,
html[data-theme="dark"] .ar-lock-card.is-solo .ar-locked-reveal {
  /* Dark spends the BAND token, not raw lime — same divergence and same
     emission reason as the answer band (§ED440 F1 / item 5). */
  background: var(--c-band-fill);
}

/* Pressed state — Part 5b. The card's actions are tappable and must answer. */
.ar-lock-card .ar-locked-reveal:active { transform: scale(.97); }


/* ══════════════════════════════════════════════════════════════════════════
   §ED440 PHASE E · THE CITATION LINE (Jake's amendment, mid-pass)

   WHY IT MOVES. "Published, sourced material first" is the product's central
   claim, and the line that carries it was set as body prose — the same ink,
   the same weight, the same everything as the sentence above it. A trust
   signal that is typographically indistinguishable from the text it vouches
   for is not doing its job. This is a HIERARCHY correction.

   IT IS ONE DECLARATION AND IT IS CORRECT IN BOTH THEMES, because
   --c-accent-ink is already the per-theme TEXT tier of the one accent:
       LIGHT  #3D4D00   olive     8.38:1 on paper   (AAA at small sizes)
       DARK   #A8D400   H 72.45°  11.08:1 on ground
   VERIFIED IN THE SHEET, not assumed from the amendment: css/tokens.css:367
   carries #A8D400 as the dark --c-accent-ink and states its measurement.

   THIS DOES NOT BREAK ONE-ACCENT-PER-VIEW. Olive and #A8D400 are hue 72 —
   the same colour at different lightness, which is the rule tokens.css §1
   already states ("dark mode is this same hue at a different lightness,
   never a second palette"). There is no second accent hue anywhere here.

   IT DOES AMEND THE LIME-TEXT LAW, and that amendment is written into
   docs/DESIGN-INVARIANTS.md in the same commit — a law left contradicting
   the code is how the next pass reverts this without knowing it was ruled.
   Amended: lime is permitted as small text on dark ONLY at --c-accent-ink or
   lower emission, and ONLY on the citation role. Every other small-text role
   on dark stays --c-ink. STREAK NUMERALS IN PARTICULAR STAY NEUTRAL — with
   the citation carrying the accent, a lime streak would put two accent
   elements in one view, which is the thing the budget exists to prevent.

   SCOPE — EVERY CITATION ROLE IN THE APP, greppped rather than guessed:
     .bf-source                  briefing secondary fact, collection, /record
     .bf-trust                   briefing HERO fact ("Sourced from … ·
                                 cross-checked") — the same role, other branch
                                 of the same if/else at js/card.js:1483/1486,
                                 so the two can never both appear for one fact
     .ar-locked-source           the locked state
     .bf-entity-archive-source   archive entity rows
     .vault-fact-source          the vault
   NOT .lv2-alm-source: that is the LANDING's almanac, a different surface
   with its own ratified design language, and this amendment is scoped to the
   app. Flagged in the report rather than changed silently.
   ══════════════════════════════════════════════════════════════════════════ */
html .bf-source,
html .bf-trust,
html .ar-locked-source,
html .bf-entity-archive-source,
html .vault-fact-source {
  color: var(--c-accent-ink);
  /* THE 16px FLOOR. Measured before this pass: these roles were set below it
     (see the §ED440 Phase E report / D2). A citation set at 12-14px on an
     audience that skews middle-aged reads as an afterthought no matter what
     colour it is — the size was as much of the problem as the ink, so both
     move or neither is fixed. --c-t-micro is the 16px floor in rem. */
  font-size: var(--c-t-micro);
  /* Slightly tighter tracking than body: the line is now coloured AND at the
     floor, and without this it starts to shout. It is a credential, not a
     headline. */
  letter-spacing: -0.004em;
  /* OPACITY 1 IS LOAD-BEARING, NOT TIDINESS. vault.html:134 sets
     `.vault-fact-source { opacity: .62 }` in a page-level <style>. Opacity is
     a different PROPERTY from colour, so nothing above overrides it: the
     amendment's #A8D400 would have been composited to 62% against the ground,
     landing near #6E8A00 — a different lightness on the same hue, at roughly
     half the ruled contrast, and it would have measured as "shipped" while
     being nothing of the sort. That is the present-but-null defect class this
     repo already has a name for. The ink tier does the recession now; the
     opacity was doing it twice. */
  opacity: 1;
}

/* ══════════════════════════════════════════════════════════════════════════
   §ED440 PHASE E · OPTICAL SIZING (Part 5a)

   WHAT THE SHIPPED FILE ACTUALLY CARRIES — read from the woff2's fvar table,
   not from the foundry's spec sheet:

       assets/fonts/fraunces.woff2      opsz  9 -> 144   (default   9)
                                        wght  100 -> 900 (default 900)
       assets/fonts/fraunces-ext.woff2  identical
       assets/fonts/hanken-grotesk*     wght  100 -> 900 only
       CabinetGrotesk-Variable          wght  100 -> 900 only
       cormorant-garamond               wght  300 -> 700 only

   THERE IS NO `SOFT` AXIS AND NO `WONK` AXIS IN THE SHIPPED FILE. The full
   Fraunces from the foundry has both; this subset does not carry them.
   Confirmed twice, by two independent methods: the fvar table lists two axes,
   and `font-variation-settings: 'wonk' 1` renders byte-identically to no
   setting at all (311.344px both ways, measured). So the WONK evaluation Part
   5a asks for CANNOT be rendered from this file — there is nothing to turn on.
   It is reported rather than faked; see the §ED440 Phase E report.

   THE OTHER PART 5a PREMISE IS ALSO FALSE, AND IN OUR FAVOUR: "we almost
   certainly ship one static instance". We do not. `font-optical-sizing: auto`
   is the browser default, the axis is live, and it is already working —
   measured at 40px, `auto` renders 311.344px wide against `none` at
   320.609px. The axis has been doing its job unremarked.

   SO WHAT IS ACTUALLY LEFT TO WIN, AND IT IS REAL. `auto` sets opsz to the
   font size IN POINTS, which is px x 0.75. A 40px display title therefore
   asks for opsz 30 — out of a range that runs to 144. It is barely into
   display territory. Measured at 40px:

       opsz   9  (text end)        320.609px
       opsz  30  (what auto picks) 311.344px
       opsz 144  (display end)     265.141px   <- 17% narrower than auto

   That is not a scale change; the letterforms physically differ — tighter
   fit, higher stroke contrast, smaller apertures, which is what a display cut
   IS. Below, the display roles ask for the display end of the axis explicitly
   instead of accepting the point-size mapping.

   BOTH AXES ARE ALWAYS NAMED TOGETHER, AND THAT IS LOAD-BEARING.
   `font-variation-settings` replaces the whole variation state, so naming
   opsz alone would drop wght to the fvar DEFAULT — and Fraunces' wght default
   is 900, not 400. A one-axis declaration here would have silently set every
   display string on the app in Black. Named pairs cannot do that.

   72 AND NOT 144. The extreme end is a poster cut; at 32-40px on a phone its
   apertures close up and it starts to cost legibility on an audience that
   skews older, which is the same audience the 16px floor exists for. 72 is
   the middle of the display half and takes most of the difference.
   RENDERED BOTH WAYS for Jake — shots/ed440/phase-e/16-opsz-*.
   ══════════════════════════════════════════════════════════════════════════ */
html .bf-hook-text,
html .ar-locked-hook,
html .ra-card > .ra-question,
html .ba-zone > .ba-question,
html .ba-zone > .ba-question.is-answered-q {
  font-variation-settings: 'opsz' 72, 'wght' 400;
}
/* The masthead wordmark is the one display string that should take the axis
   to its end: it is set once, large, and it is a LOGO — a closed aperture is
   an identity there rather than a legibility cost. */
html .app-topnav-brand {
  font-variation-settings: 'opsz' 144, 'wght' 400;
}

/* ══════════════════════════════════════════════════════════════════════════
   §ED440 PHASE E · THE VOICE AFFORDANCE (Part 4)

   Voice is the differentiator and it looked like an afterthought: a 15px glyph
   in a row of grey buttons, indistinguishable from "cancel". This makes it
   read as the PREMIUM path — which is what it is, because a recorded voice is
   the thing the reader's family will actually want in twenty years and a typed
   sentence is not.

   THE THREE STATES ARE IN js/waveform.js; this sheet only dresses them.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── (4a) IDLE — the mic reads as the premium path ───────────────────────
   Lime-tinted, specular-edged, unmistakably tappable, and visibly a
   DIFFERENT KIND of control from the neutral actions beside it. */
html .bf-page .ba-mic {
  background: linear-gradient(to bottom,
                color-mix(in srgb, var(--c-accent) 16%, var(--c-surface)),
                var(--c-surface));
  border: 1px solid color-mix(in srgb, var(--c-accent-ink) 42%, transparent);
  /* The specular. Same one light source as every other lit object. */
  box-shadow: var(--c-specular-edge-soft), var(--c-e1-shadow);
}
html:not(.theme-light) .bf-page .ba-mic,
html[data-theme="dark"] .bf-page .ba-mic {
  background: linear-gradient(to bottom,
                color-mix(in srgb, var(--c-accent-ink) 22%, var(--c-surface)),
                var(--c-surface));
}

/* ── THE HELD STATE MUST OUTRANK THE IDLE ONE. A SPECIFICITY DEFEAT, CAUGHT ──
   MEASURED, not spotted by eye. The dark idle rule above is
   `html:not(.theme-light) .bf-page .ba-mic` = (0,3,1); Phase D's held rule is
   `.bf-page .ba-mic.is-live` = (0,3,0). Same class count, but the idle rule
   carries an element selector too, so IT WINS — and in dark the mic kept its
   resting gradient for the entire recording. Probed computed style while
   holding the button: background-color rgba(0,0,0,0) and a live background-
   image, i.e. the held rule was dead. Light was unaffected (its idle rule is
   (0,2,1) and loses correctly), so this was a DARK-ONLY silent failure — the
   exact shape §ED440 T1 records, and the exact reason this pass verifies by
   computed style instead of by looking at frames.

   `background` and not `background-color`: the idle rule sets a gradient,
   which lives in background-image, so overriding only the colour would leave
   the gradient painted on top of it and the fix would look like it failed.
   The shorthand resets both. */
html .bf-page .ba-mic.is-live,
html:not(.theme-light) .bf-page .ba-mic.is-live,
html[data-theme="dark"] .bf-page .ba-mic.is-live {
  background: var(--c-band-fill);
  border-color: var(--c-band-fill);
  color: var(--c-on-accent);
}

/* ── (4b) THE WAVEFORM TRACK ─────────────────────────────────────────────
   28 DOM bars. Height is driven by a custom property so the only thing that
   changes per frame is a transform on the compositor — no layout, no paint,
   which is what makes 60fps affordable on a phone that is also recording.

   `scaleY` on a bottom-anchored bar, NOT `height`. Animating height is a
   layout write per bar per frame — 28 layouts per frame, and on a mid-range
   Android that alone would drop the recording. */
.wv {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  height: 34px;
  padding: 0 2px;
  flex: 1 1 auto;
  min-width: 0;
  /* --wv-p is playback progress, 0..1. Unset outside playback. */
}
.wv-bar {
  flex: 1 1 0;
  min-width: 2px;
  height: 100%;
  border-radius: var(--c-r-full);
  background: color-mix(in srgb, var(--c-accent-ink) 30%, transparent);
  transform-origin: bottom center;
  transform: scaleY(var(--wv-h, .2));
  transition: transform 90ms linear, background-color var(--c-m-toggle) var(--c-e-standard);
}
html:not(.theme-light) .wv-bar,
html[data-theme="dark"] .wv-bar {
  background: color-mix(in srgb, var(--c-accent-ink) 38%, transparent);
}

/* IDLE is DECORATIVE AND SAYS SO. The resting form is a fixed hand-set shape
   (js/waveform.js REST), not data and not random. It is beautiful at rest,
   which is the entire requirement, and it claims nothing. */
.wv:not(.is-live):not(.is-playback) .wv-bar {
  background: color-mix(in srgb, var(--c-ink-3) 34%, transparent);
}

/* ── (4c) RECORDING — real data, so it gets the accent ───────────────────
   The bars only take the lime while they are carrying live input. That is the
   distinction the supersession note above turns on: colour marks DATA here,
   not activity. */
.wv.is-live .wv-bar {
  background: var(--c-accent-ink);
  /* 60ms, not the 90ms rest value: at analyser frame rate a longer ease
     smears consecutive frames together and the bars stop tracking speech. */
  transition: transform 60ms linear;
}
html:not(.theme-light) .wv.is-live .wv-bar,
html[data-theme="dark"] .wv.is-live .wv-bar { background: var(--c-band-fill); }

/* THE RULED DEGRADATION — no AnalyserNode, or reduced motion. ONE indicator,
   pulsing. Never fake bars. The bars hold the resting form and stop being the
   thing that carries the state; the dot carries it instead. */
.wv.is-pulse .wv-bar { transition: none; }
.wv.is-pulse::after {
  content: '';
  position: absolute;
  right: 6px; top: 50%;
  width: 9px; height: 9px;
  margin-top: -4.5px;
  border-radius: var(--c-r-full);
  background: var(--c-accent-ink);
  animation: wvPulse 1.4s var(--c-e-inout) infinite;
}
.wv.is-pulse { position: relative; padding-right: 22px; }
@keyframes wvPulse { 0%,100% { opacity: .35; } 50% { opacity: 1; } }
/* Under reduced motion the dot stops pulsing and simply STAYS LIT — the state
   is still conveyed, which is the rule (opacity end-states survive, motion
   does not). Recording is unaffected either way; see js/waveform.js. */
@media (prefers-reduced-motion: reduce) {
  .wv.is-pulse::after { animation: none; opacity: 1; }
}

/* ── (4d) PLAYBACK — peaks from the real buffer, with real progress ──────
   The played portion takes the accent; the rest stays quiet. One gradient on
   the TRACK rather than a class per bar, so progress costs no DOM writes. */
/* THE PLAYED BARS ARE THE UNPLAYED BARS. The split is carried entirely by the
   wash below, not per bar — one element for progress rather than 28 DOM
   writes, which is the whole reason --wv-p is a custom property.
   A GRADIENT LAYER PINNED TO `background-size: 0 100%` LIVED HERE AND HAD
   NEVER RENDERED A PIXEL: zero width, and nothing ever resized it. §ED527b's
   law is to delete a declaration found never to have applied, so it is gone.
   Proven inert rather than assumed — arm 3 pixel-diffs the bars against the
   old rule and gets 0 differing pixels. */
.wv.is-playback .wv-bar {
  background: color-mix(in srgb, var(--c-ink-3) 30%, transparent);
}
.wv.is-playback {
  --wv-p: 0;
  -webkit-mask-image: none;
}
/* The played/unplayed split, scaled from the left edge by --wv-p, which
   js/waveform.js writes from the audio element's own currentTime — so the
   progress is the audio's, never a timer's.

   TRANSFORM, NOT WIDTH — §ED537 defect 3. This animated `width` until now,
   which is a layout write per frame on the one element that is on screen
   precisely because something is playing. §ED440 permits transform and
   opacity only. The breach was INERT while --wv-p never moved off 0 for a
   paused element (§ED537 defect 2); fixing the progress is what turned a
   dormant breach into a live one, which is why the two land together.
   MEASURED (scripts/audit-ed537-waveform.js arm 3): the scaled fill reaches
   the same pixel as the width fill at p = 0, .25, .5, .75 and 1 — worst edge
   delta 0px — with the track box and all 28 bar boxes identical between the
   two arms, which is the layout-shift half of that arm.

   `overflow:hidden` went with it: the pseudo-element has no children and
   never clipped anything.

   THE 10px `border-radius` WENT TOO, AND THAT IS A DELIBERATE VISUAL CHANGE
   RATHER THAN A SIDE EFFECT. scaleX squashes horizontal curvature: a 10px
   radius under scaleX(.25) draws as 2.5px across, so the fill's leading edge
   would taper differently at every progress value — MEASURED at 0.85% of the
   track's pixels, peak channel delta 73, all of it on that one corner. A
   progress fill's leading edge should be a clean vertical boundary anyway,
   and the wash sits behind 28 bars in a 34px track where its own corners were
   never legible. Removing it is what makes the transform provably identical
   to the width fill — arm 3a diffs the two at 0 pixels. Arm 3b prices the
   radius removal itself against the rule as it shipped, separately, so the
   intended change is never hidden inside the mechanical one.

   THE WASH IS ACCENT PLUS A WARM NEUTRAL, AND THE NEUTRAL IS LIGHT-ONLY —
   §ED537 defect 4. MEASURED on the shipped rule: the accent wash alone
   separates played from unplayed by ΔE 27.8 against the dark ground but only
   ΔE 13.6 against paper. The same wash reads twice as strongly in dark as in
   light, which is the whole complaint. THE FIX SPENDS NO MORE ACCENT: hue 72
   stays at 18% in both themes and carries the IDENTITY of the played side.
   The legibility is bought with --c-ink-3 — the same warm neutral the
   unplayed bars are already made of, so no colour is invented and no cool
   grey enters. Light lands at ΔE 22.9 and the two themes come within 1.21x
   of each other. */
.wv.is-playback::before {
  content: '';
  position: absolute;
  inset: 0;
  transform-origin: left center;
  transform: scaleX(var(--wv-p, 0));
  pointer-events: none;
  background-color: color-mix(in srgb, var(--c-accent-ink) 18%, transparent);
  background-image: linear-gradient(color-mix(in srgb, var(--c-ink-3) 24%, transparent) 0 100%);
  transition: transform 120ms linear;
}
html:not(.theme-light) .wv.is-playback::before,
html[data-theme="dark"] .wv.is-playback::before {
  /* Dark already separates at ΔE 27.8 on the accent alone — measured. Adding
     the neutral here would only mute the accent to fix nothing, and --c-ink-3
     inverts to a LIGHT warm grey in dark, so it would lift the wash rather
     than deepen it. */
  background-image: none;
}
.wv.is-playback { position: relative; }

/* ── (4e) THE VOICE ROW ──────────────────────────────────────────────────
   Mic and waveform side by side: the affordance and the evidence, together.
   At 375 the waveform takes what the mic leaves and never pushes it off the
   row (min-width:0 + flex on the track). */
.ba-voice-row {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-width: 0;
  margin-top: 10px;
}
/* The track fills whatever the mic leaves and never pushes it off the row.
   `min-width:0` on a flex child is the part everyone omits: without it the
   default `min-width:auto` refuses to shrink below content size and the
   overflow reappears at 375. */
.ba-voice-row .wv { flex: 1 1 auto; min-width: 0; }

/* ── (4e-ii) THE REFUSED MIC — §ED525 ─────────────────────────────────────
   The line js/reader-answer.js writes under the voice row when getUserMedia
   or the recognizer refuses. Until §ED525 that refusal was swallowed
   entirely, which is how netlify.toml's `microphone=()` outlived the voice
   build by two months (§ED524).

   NOT AN ERROR TREATMENT, ON PURPOSE. No --c-danger, no dotted rule, no box
   — those belong to `.ba-unsaved`, where the reader's own prose is at stake
   and the alarm is earned. Nothing is lost here: the answer box is still
   there, still typed into, still saved. So this is set as quiet prose in the
   caption voice, the same --c-ink-2 / --c-t-micro pairing `.ba-chosen` and
   `.ba-unsaved-label` use. It should read as the app saying what happened,
   not as the app going wrong.

   STYLED AT ALL, which is the point of writing it down. `.ba-refused` — the
   §ED424 R3 refusal line — has no rule anywhere in this repo and has been
   flagged as an orphan twice (§ED430, §ED487); it renders as a bare browser
   default <p>. This class does not repeat that.

   SCOPE MATCHES `.ba-voice-row` ABOVE, which is the only place the note is
   ever inserted (as its next sibling). briefing.html, archive.html and
   vault.html all load this sheet and are the only three pages that mount a
   reader answer block. */
.ba-voice-note {
  margin: 8px 0 0;
  color: var(--c-ink-2);
  font-size: var(--c-t-micro);
  line-height: 1.5;
  overflow-wrap: break-word;   /* long UA strings never widen the card */
}

/* ── (4f) PLAYBACK — §ED463, REUSING vault.html's affordance verbatim ──────
   §ED440-DESIGN-LANGUAGE.md R8: `.vault-audio` is the REAL player (genuine
   empty and error substates, native <audio controls>, the real decoded-buffer
   waveform via window.Waveform) and the landing's `.p-wave` is a picture of
   one — R8 names this "the one place the app leads" and forbids downgrading
   it. This is that component, not a second one: identical class names,
   identical rules, so js/reader-answer.js's play button (anonymous and
   signed-in readers alike, §ED463) and js/vault.js's are the same visual
   vocabulary, not two.

   DUPLICATED HERE, NOT MOVED. vault.html:80-143 keeps these inline in its own
   <style> block, deliberately namespaced to that page. briefing.html (where
   reader-answer.js actually mounts an answer block) loads briefing.css, not
   vault.html — so the rules are restated here, verbatim, rather than adding a
   new stylesheet link to a page ed462-landing owns. Keep both copies in sync
   by hand if either changes; a future pass may want to promote both to a
   shared sheet, which is a decision about vault.html this file does not make. */
.vault-media { margin-top: 12px; }
.vault-media-item { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.vault-media-label { font-size: .75rem; text-transform: uppercase; letter-spacing: .04em; opacity: .6; }
.vault-media-play { font: inherit; font-size: .875rem; padding: 4px 12px; border-radius: 999px; border: 1px solid currentColor; background: none; color: inherit; cursor: pointer; }
.vault-media-gone { font-size: .8125rem; opacity: .7; margin: 6px 0 0; }
.vault-audio { width: 100%; margin-top: 8px; }

/* ══════════════════════════════════════════════════════════════════════════
   §ED440 PHASE E · PART 2 — THE DESKTOP SPLIT LAYOUT. RULED 2026-08-20, SHIPPED.

   Jake compared three renders (L1 companion rail, L2 split, L3 split+rail)
   and took L2 — read on the left, respond on the right, both visible at
   once, no scrolling between the fact and the question. Shipped
   UNCONDITIONALLY below at >=1024px: the html[data-ed440-layout] attribute
   this comparison used is gone from these rules, because the comparison is
   over.

   L1 AND L3 ARE DROPPED, NOT DELETED. Both put the signup's lime CTA on
   screen at the same time as the lime citation band, which the
   one-lime-per-view budget cannot absorb — see docs/ED440-DESIGN-LANGUAGE.md
   R10, the same law that dropped the HALO lock affordance. L1 is additionally
   blocked structurally: its rail promises streak, today's-progress and a
   vault-preview module, none of which exist as components today (see "WHAT
   THE RAIL CONTAINS, STATED HONESTLY" below) — worth rebuilding if those
   three get built, recorded here so the work is not re-derived. Their rules
   stay below, still gated on html[data-ed440-layout="L1"|"L3"], reachable
   only by the render script — see docs/ED440-DESIGN-LANGUAGE.md R11.

   THE DIAGNOSIS IS CORRECT AND MEASURED. At 1440 the card is 820px inside a
   1440 viewport: ~620px doing nothing. But --c-measure-narrow is RIGHT for
   reading and wrong for the PAGE, and widening it would damage the column to
   solve a layout problem. L2 keeps the measure and uses the space by
   splitting fact and response into two columns instead.

   COLLAPSES BELOW 1024px to today's single column, untouched here — mobile
   at 375 is already good and does not change.

   WHAT THE RAIL CONTAINS, STATED HONESTLY (kept for L1/L3, dormant below).
   Part 2 described a rail carrying streak, today's progress, a vault preview
   and the culture selector. Only some of that exists as a component today:
   the culture selector and the calendar trigger (.bf-header) and the account
   card (.bf-foot) are real and are what the rail was built from. A streak
   module, a progress module and a vault preview DO NOT EXIST on this page —
   .bf-result is nested inside .bf-daily-body and cannot be grid-placed out
   of it without a DOM change, which a render-only pass must not make.
   ══════════════════════════════════════════════════════════════════════════ */
@media (min-width: 1024px) {

  /* THE SHIP: L2. The page gets the wide measure, the PROSE COLUMN DOES NOT.
     --c-measure (1080) is the page rail; the reading column stays capped by
     the card's own padding and by --ar-lock-measure / --c-measure-narrow
     wherever those already apply. Split happens INSIDE the card so the card
     stays one object.

     §ED527b — `max-width: 1240px` WAS HERE AND NEVER APPLIED. It is 0-1-0 and
     `.bf-page .bf-wrap` at :3388 is 0-2-0; a media query adds no specificity,
     so source order was never consulted and this declaration was discarded on
     every render for the whole life of L2. It is DELETED rather than repaired
     in place: left here it is a live trap, one edit to :3388 away from
     silently widening the rail on every desktop edition. The rail now belongs
     to the §ED527b block below, which owns it conditionally and at a
     specificity that actually wins. */
  .bf-wrap {
    display: grid;
    align-items: start;
    column-gap: clamp(24px, 3vw, 48px);
    grid-template-columns: minmax(0, 1fr);
  }
  .bf-daily-body {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(300px, 380px);
    column-gap: clamp(28px, 3vw, 56px);
    align-items: start;
  }
  /* Everything defaults to the reading column; only the answer zone crosses. */
  .bf-daily-body > * { grid-column: 1; }
  .bf-reaction-wrap {
    grid-column: 2;
    grid-row: 1 / span 99;
    position: sticky;
    top: 92px;
  }
  /* THE BAND IS FULL-BLEED BY RULING and bleeds to the CARD's edge. In a
     split it must bleed to its own COLUMN's edge instead, or it tears across
     the gutter into the prose. This is the kind of thing only a render shows. */
  .ba-zone { border-radius: var(--c-r-lg); overflow: hidden; }

  /* ── L1 · COMPANION RAIL — DROPPED, kept for the render script only ─────
     The card holds the measure and sits centred-left; the rail carries the
     page's context modules. Attribute-gated: html[data-ed440-layout] is set
     by nothing but scripts/render-ed440-screens.js. */
  html[data-ed440-layout="L1"] .bf-wrap {
    grid-template-columns: minmax(0, 820px) minmax(240px, 320px);
    grid-template-areas: 'card rail' 'card railfoot';
    grid-template-rows: auto 1fr;
  }
  html[data-ed440-layout="L1"] .bf-stack  { grid-area: card; }
  html[data-ed440-layout="L1"] .bf-header { grid-area: rail; position: static; }
  html[data-ed440-layout="L1"] .bf-foot   { grid-area: railfoot; display: block !important; margin-top: 20px; }

  /* The rail's modules become cards rather than a toolbar row. */
  html[data-ed440-layout="L1"] .bf-header-row,
  html[data-ed440-layout="L3"] .bf-header-row {
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
  }
  html[data-ed440-layout="L1"] .bf-cult-trigger,
  html[data-ed440-layout="L3"] .bf-cult-trigger { width: 100%; justify-content: flex-start; }
  html[data-ed440-layout="L1"] .bf-header,
  html[data-ed440-layout="L3"] .bf-header {
    padding: 18px;
    border-radius: var(--c-r-lg);
    border: var(--c-e1-border);
    box-shadow: var(--c-e1-shadow);
  }

  /* ── L3 · SPLIT + RAIL — the bento — DROPPED, kept for the render script
     only. L2's split, plus a thin rail for the context modules. */
  html[data-ed440-layout="L3"] .bf-wrap {
    max-width: 1400px;
    grid-template-columns: minmax(0, 1fr) minmax(220px, 280px);
    grid-template-areas: 'card rail' 'card railfoot';
    grid-template-rows: auto 1fr;
  }
  html[data-ed440-layout="L3"] .bf-stack  { grid-area: card; }
  html[data-ed440-layout="L3"] .bf-header { grid-area: rail; position: static; }
  html[data-ed440-layout="L3"] .bf-foot   { grid-area: railfoot; display: block !important; margin-top: 20px; }
  html[data-ed440-layout="L3"] .bf-daily-body {
    grid-template-columns: minmax(0, 1fr) minmax(280px, 340px);
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   §ED527b · THE DESKTOP COLUMN — L2 AMENDED

   §ED527 measured the shipped desktop edition page and found the prose
   rendering in a 267px column at 1440 with a 380px near-empty column beside
   it. Three defects, all fixed here; fixing only the first leaves the second
   and third live.

   D1 · THE PAGE RAIL LOST ON SPECIFICITY.
        `.bf-page .bf-wrap { max-width: var(--c-measure-narrow) }` (:3388,
        0-2-0) outranked L2's `.bf-wrap { max-width: 1240px }` (0-1-0). A MEDIA
        QUERY ADDS NO SPECIFICITY, so source order was never reached. The
        `grid-template-columns` half of the same L2 block had no competitor and
        DID apply: the split shipped, the widening did not, and an 820px card
        that used to give prose ~692px gave it 267px. Strictly worse than
        before §ED440. See DESIGN-INVARIANTS.md I-1.

   D2 · A COLUMN RESERVED FOR A ZONE THAT IS USUALLY ABSENT.
        With no approved reader_question, column two holds a 71px pill row in
        380px of reserved width — the state for 56% of the last 111 editions.

   D3 · THE SPLIT WAS GATED BELOW THE WIDTH AT WHICH IT FITS.
        L2 opened the split at 1024px. `.bf-wrap` resolves to (viewport − 102px)
        capped at its max-width, so the 1240px rail is only reachable at
        viewport >= 1342. Fixing D1 alone would have left 1024–1340 rendering a
        381–604px column.

   THE SHAPE OF THE FIX: ONE COLUMN IS THE DESKTOP DEFAULT; THE SPLIT IS THE
   OPT-IN. Below, the reading measure lands at ~690px in BOTH states, so the
   answer rail becomes ADDITIVE width rather than subtractive — which is what
   L2's own comment said it wanted ("L2 keeps the measure and uses the space").

   MECHANISM, STATED. The split is conditional on `:has(.ba-zone)`.

     · Why :has() and not a JS-set class — .ba-zone is built synchronously by
       js/reader-answer.js:buildAnswerBlock inside .bf-reaction-wrap, and this
       stays a render-only change: no DOM edit, no class to keep in sync, and
       the condition re-evaluates for free if the zone is ever mounted late.

     · Why the polarity is POSITIVE. §ED527's first draft wrote the inverse,
       `.bf-wrap:has(.bf-daily-body:not(:has(.ba-zone)))` — :has() CANNOT NEST,
       so that selector is invalid and a parser DROPS IT SILENTLY. Making the
       single column the default means the conditional only ever needs a flat,
       positive `:has(.ba-zone)`. There is nothing here that can silently drop.

     · What an engine without :has() does. It discards these three rules and
       renders one 690px column at every desktop width — the correct fallback,
       not a broken one. scripts/audit-ed527-layout.js asserts CSS.supports(
       'selector(:has(.ba-zone))') anyway, so a green can never mean "the
       selector was never evaluated".

   SPECIFICITY, COUNTED, because that is the bug being fixed:
     .bf-page .bf-daily-body                              0-2-0
     .bf-page .bf-daily-body:has(.ba-zone)                0-3-0  wins
     .bf-page .bf-daily-body > .bf-reaction-wrap          0-3-0
     .bf-page .bf-daily-body:has(.ba-zone) > .bf-reaction-wrap  0-4-0  wins
     .bf-page .bf-wrap                            (:3388) 0-2-0
     .bf-page .bf-wrap:has(.bf-daily-body .ba-zone)       0-4-0  wins
   Every override in this block beats its competitor on SPECIFICITY, not on
   source order — so it survives being moved, and survives another rule being
   appended after it.

   GUARDED BY scripts/audit-ed527-layout.js, which renders briefing.html with
   real payloads at 8 widths x 2 themes x both edition kinds and asserts the
   prose column never falls below 590px (62ch, .bf-debrief's own declared
   measure) on desktop and never shrinks as the viewport grows. Its --plant
   arm re-creates D1 and is expected to go red.

   MOBILE IS UNTOUCHED. Nothing here applies below 1024px.
   ══════════════════════════════════════════════════════════════════════════ */
@media (min-width: 1024px) {

  /* D1 + D2 · ONE COLUMN IS THE DESKTOP DEFAULT. The 820px card and its 64px
     side padding give the prose ~692px — the measure --c-measure-narrow was
     chosen for, and what the page rendered before L2's split took 380px + a
     43px gutter out of a card that never grew to pay for them. */
  .bf-page .bf-daily-body {
    display: block;
  }
  /* Sticky is meaningless in block flow and would pin the pill row to the
     viewport as the reader scrolls past it. */
  .bf-page .bf-daily-body > .bf-reaction-wrap {
    position: static;
    top: auto;
  }
}

@media (min-width: 1380px) {

  /* D3 · THE SPLIT, ONLY WHERE THE RAIL FITS AND ONLY WHEN THERE IS SOMETHING
     TO PUT IN IT.

     1380, NOT 1360. §ED527's report put the threshold at 1360 from the formula
     ".bf-wrap = viewport − 102px". That formula was read off a 1440 render and
     does not hold below 1440. The real one is

         .bf-wrap = ICB − 2 x clamp(20px, 5vw, 72px)        (--c-gutter)

     so the gutter is still SCALING at 1360 (68px a side, not the capped 72),
     the rail lands at 1224px rather than 1240, and the prose column dips to
     673px. 0.9 x ICB >= 1240 solves to ICB >= 1377.8; 1380 is that with 2px of
     slack. scripts/audit-ed527-layout.js measures 1379 and 1380 either side of
     this line, and it is what caught the 1360 error.

     The threshold is measured against the INITIAL CONTAINING BLOCK, since a
     media feature excludes a classic scrollbar. A window narrow enough that a
     scrollbar pushes the ICB under 1380 reads as one column, which is the
     right answer at that size anyway.

     The grid tracks and alignment are NOT restated — they are still L2's at
     :4375 and were the half of that block that always worked. */
  /* `:has(.bf-daily-body .ba-zone)`, NOT `:has(.ba-zone)`. The bare form
     over-reaches, and the page it breaks is a LIVE one. js/reader-answer.js
     is not the only thing that mounts a .ba-zone into .bf-wrap:

       js/card.js:2473          inside .bf-daily-body        <- the split's zone
       js/card.js:1610/2026/2106  other card shapes in .bf-stack
       js/briefing.js:471       §ED424's locked-day question, appended
                                straight to #bf-stack

     On a locked day (LADDER_MODE is on in production) the last of those puts
     a .ba-zone in .bf-wrap with no .bf-daily-body anywhere on the page. The
     bare selector would widen that page's rail to 1240px with nothing to
     split, running the locked teaser's prose to ~1112px — a NEW defect of
     exactly the kind this block exists to remove. Requiring the zone to be
     inside .bf-daily-body ties the widening to the thing that consumes it.

     A descendant combinator inside :has() is fine — it is :has() inside
     :has() that is invalid. Specificity 0-4-0 (0-1-0 + 0-1-0 + the argument's
     own 0-2-0), against :3388's 0-2-0.

     scripts/audit-ed527-layout.js asserts this directly (A7): it injects a
     stray .ba-zone into .bf-stack at every desktop width and requires the
     rail and the layout not to move. */
  .bf-page .bf-wrap:has(.bf-daily-body .ba-zone) {
    max-width: 1240px;
  }
  .bf-page .bf-daily-body:has(.ba-zone) {
    display: grid;
    /* THE GAP IS FIXED, NOT 3vw. L2 set `clamp(28px, 3vw, 56px)`, which is a
       proportional gutter inside a container that STOPS growing at 1240px —
       so past the cap every extra pixel of viewport came straight out of the
       prose. That is the §ED527 defect in miniature and the layout check's
       monotonic assertion fails on it: 686.8px at 1440 falling to 678.2px at
       1728.

       40px is not a taste value either. The card's content box at the 1240
       rail is 1240 − 2px border − 128px padding = 1110px; with a 380px answer
       rail, 1110 − 40 − 380 = 690px — the SAME measure the single-column
       desktop delivers. So the answer rail is now purely ADDITIVE: turning it
       on cannot cost the reader a single character of line length. That is
       what L2's own comment asked for ("L2 keeps the measure and uses the
       space") and what the 3vw gutter quietly prevented. */
    column-gap: 40px;
  }
  .bf-page .bf-daily-body:has(.ba-zone) > .bf-reaction-wrap {
    position: sticky;
    top: 92px;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   §ED440 PHASE E · PART 5e — THE EMPTY STATE

   "Today's edition is still at the printers." plus a button, set as two grey
   paragraphs in the middle of a blank card. For a reader who arrives before
   the edition lands, THIS IS THE PRODUCT — the only screen they see — and it
   currently looks like an error page for a service that is down.

   NO NEW COPY. Both strings and the retry button are exactly what
   js/briefing.js showState() already renders; this is presentation only,
   which is also why it needs no JS change at all.

   IT BORROWS THE LOCKED STATE'S VOCABULARY DELIBERATELY. A press mark in a
   specular-edged well, then the heading at display size, then rules
   dissolving into the ground — the same grammar as the seal, saying the same
   kind of thing: there IS something here, it is not available to you yet.
   A reader who meets both screens meets one design.
   ══════════════════════════════════════════════════════════════════════════ */
html .bf-page .bf-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 14px;
  padding: var(--c-room-sm) 20px calc(var(--c-room-sm) + 8px);
  max-width: 34ch;
  margin-inline: auto;
}
/* THE MARK — a press/plate glyph in the same pressed well the lock uses.
   ::before so no DOM change is needed. Drawn, not an emoji, and it carries no
   text so it needs no label: the heading beneath it is the accessible name of
   this screen and the mark is decoration. */
html .bf-page .bf-state::before {
  content: '';
  width: 56px;
  height: 56px;
  border-radius: var(--c-r-full);
  background:
    /* the plate: two ruled lines, centred — a page on the press */
    linear-gradient(var(--c-ink-3), var(--c-ink-3)) center 22px / 22px 2px no-repeat,
    linear-gradient(var(--c-ink-3), var(--c-ink-3)) center 30px / 14px 2px no-repeat,
    color-mix(in srgb, var(--c-ink) 5%, transparent);
  box-shadow: inset 0 2px 3px rgba(20, 18, 14, .13),
              inset 0 -1px 0 var(--c-specular);
  opacity: .8;
}
html:not(.theme-light) .bf-page .bf-state::before,
html[data-theme="dark"] .bf-page .bf-state::before {
  background:
    linear-gradient(var(--c-ink-3), var(--c-ink-3)) center 22px / 22px 2px no-repeat,
    linear-gradient(var(--c-ink-3), var(--c-ink-3)) center 30px / 14px 2px no-repeat,
    rgba(0, 0, 0, .28);
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, .45),
              inset 0 -1px 0 rgba(255, 252, 245, .07);
}
html .bf-page .bf-state .bf-state-h {
  font-family: var(--c-f-display);
  font-variation-settings: 'opsz' 72, 'wght' 400;
  font-size: clamp(1.375rem, 3.6vw, 1.75rem);
  line-height: 1.22;
  letter-spacing: -0.01em;
  color: var(--c-ink);
  margin: 0;
}
/* THE DISSOLVE — the same rules as the seal, saying the same thing: an
   edition exists, it is just not here yet. */
html .bf-page .bf-state::after {
  content: '';
  width: 100%;
  max-width: 26ch;
  height: 34px;
  margin-top: 4px;
  background:
    linear-gradient(var(--c-ink-2), var(--c-ink-2)) 0 0    / 100% 9px no-repeat,
    linear-gradient(var(--c-ink-2), var(--c-ink-2)) 0 18px / 62% 9px no-repeat;
  opacity: .22;
  border-radius: 3px;
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, rgba(0,0,0,.5) 45%, transparent 100%);
          mask-image: linear-gradient(to bottom, #000 0%, rgba(0,0,0,.5) 45%, transparent 100%);
}


/* ══════════════════════════════════════════════════════════════════════════
   §ED440 PHASE E · PART 5f — THE CULTURE SELECTOR AS A BRAND MOMENT

   It reads as a form control: a bordered pill with a caret, the same object
   as a sort dropdown. But this is the reader saying WHO THEY ARE — it is the
   single most identity-laden interaction in the product and the one a new
   reader meets first.

   WHAT CHANGES: the trigger gains the flag as a real object (a raised, lit
   token rather than an inline emoji in a text run), the name takes the
   display face, and the open sheet becomes GLASS — it is chrome, it floats
   above content, so it belongs to the glass layer by the Part 3 rule rather
   than by decoration.
   NO COPY CHANGES and no DOM changes.
   ══════════════════════════════════════════════════════════════════════════ */
html .bf-cult-trigger {
  gap: 10px;
  padding: 10px 16px;
  border-radius: var(--c-r-full);
  border: 1px solid var(--c-line);
  box-shadow: var(--c-specular-edge-soft), var(--c-e1-shadow);
}
/* The flag becomes a struck token — round, raised, lit from the one source —
   instead of an emoji sitting in a line of text. */
html .bf-cult-trigger-flag {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: var(--c-r-full);
  background: var(--c-surface-2);
  box-shadow: inset 0 1px 0 var(--c-specular), 0 1px 2px rgba(20, 18, 14, .10);
  font-size: 1rem;
  line-height: 1;
}
html .bf-cult-trigger-name {
  font-family: var(--c-f-display);
  font-variation-settings: 'opsz' 40, 'wght' 400;
  font-size: var(--c-t-micro);
  color: var(--c-ink);
  max-width: 180px;
}
html .bf-cult-trigger-caret { color: var(--c-ink-3); }

/* THE OPEN SHEET IS CHROME, SO IT IS GLASS. Full recipe, specular edge, L3
   elevation — the same material as the masthead and the nav, because it is
   the same KIND of thing: a surface floating above the content.

   §ED528 — THE PANEL WAS SEE-THROUGH. --c-glass-chrome is .66 alpha (light) /
   .72 (dark), and blur alone does not destroy legibility: the wordmark and the
   dateline sit directly behind this panel on /briefing and read straight
   through it. Measured, not eyeballed — scripts/audit-ed528-cult-sheet.js
   composites the panel over a known text colour and compares against the same
   pixel with the panel removed.

   THE TREATMENT IS NOT REMOVED — this is an OPACITY fix, per §ED440's own
   licence for glass on the chrome layer. The tint, the blur, the specular
   edge, the line and the L3 elevation are all untouched. What changes is that
   the tint is now composited over an OPAQUE base instead of over the page:
   --c-surface, an existing §ED440 token (#FFFDFA light / #191714 dark), is
   painted underneath and --c-glass-chrome is layered on top of it as a solid
   gradient. Net alpha is 1 by construction, so nothing behind can be legible.

   WHY NOT RAISE THE TOKEN ITSELF: --c-glass-chrome is shared with the masthead
   and the nav (global.css), and tokens.css documents a coupling — "push this
   to .72 and the specular stops reading". Raising it would silently restyle
   two other surfaces and break that coupling to fix a third. The fix is scoped
   to the one panel that has opaque content behind it.

   backdrop-filter is KEPT deliberately: it composites nothing now, but it is
   the treatment this panel is declared to have, and removing it would also
   drop the containing block/stacking context the panel's positioning sits on.
   The cost is one blur on a small, transient, click-to-open panel. */
html .cult-sheet {
  background:
    linear-gradient(var(--c-glass-chrome), var(--c-glass-chrome)),
    var(--c-surface);
  -webkit-backdrop-filter: var(--c-glass-filter);
          backdrop-filter: var(--c-glass-filter);
  transform: translateZ(0);
  border: 1px solid var(--c-glass-chrome-line);
  border-radius: var(--c-r-lg);
  box-shadow: var(--c-specular-edge), var(--c-e3-shadow);
}
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  html .cult-sheet { background: var(--c-surface); }
}
html .cult-sheet-title {
  font-family: var(--c-f-display);
  font-variation-settings: 'opsz' 40, 'wght' 400;
  font-size: var(--c-t-micro);
  color: var(--c-ink-2);
}
html .cult-sheet-item {
  border-radius: var(--c-r-md);
  color: var(--c-ink);
  font-size: var(--c-t-micro);
  gap: 12px;
  transition: background-color var(--c-m-toggle-out) var(--c-e-standard),
              transform var(--c-m-tap) var(--c-e-standard);
}
html .cult-sheet-item:active { transform: scale(.98); }
html .cult-sheet-item-flag {
  display: grid;
  place-items: center;
  width: 30px; height: 30px;
  border-radius: var(--c-r-full);
  background: var(--c-surface-2);
  box-shadow: inset 0 1px 0 var(--c-specular);
  font-size: 1.05rem; line-height: 1;
}
html .cult-sheet-item.is-current {
  background: var(--c-accent-wash);
  box-shadow: var(--c-specular-edge-soft);
}
html .cult-sheet-item-check { color: var(--c-accent-ink); }

/* §ED528 — A LOCKED CULTURE IS STILL SHOWN, AND STILL TAPPABLE.
   Same reasoning as the archive showing locked titles: the shelf is the
   argument, so the row keeps its flag, its name and its full 44px hit area —
   it recedes, it does not disappear and it is never disabled. The lock is the
   ONLY thing added; tapping still switches culture and lands on the membership
   wall, which is where the price is stated (js/card.js's renderList note).

   Deliberately NOT `cursor: not-allowed` and NOT `pointer-events: none` —
   both would state a refusal the product does not make.
   .bf-cult.is-locked (the older row selector, further up this file) uses
   opacity .6 for its never-tappable "SOON" cultures; this uses ink colour
   instead, because these ARE tappable and must not read as inert. */
html .cult-sheet-item.is-locked .cult-sheet-item-name { color: var(--c-ink-2); }
html .cult-sheet-item.is-locked .cult-sheet-item-flag { opacity: .72; }
html .cult-sheet-item-lock {
  flex-shrink: 0;
  font-size: .95rem;
  line-height: 1;
  color: var(--c-ink-3);
}
html .cult-sheet-item.is-locked:hover .cult-sheet-item-lock { color: var(--c-ink-2); }

/* ── §ED529 · A LOCKED CULTURE READS AS MEMBERS-ONLY, NOT AS BROKEN ────────
   The icon carried the whole message and a 0.95rem glyph is not a message.
   The word goes next to it. Sized and coloured off the SAME two tokens the
   sheet's own title already uses (--c-t-micro, --c-ink-3), so this adds no
   new type step and no new ink tier. It is a state label, not an action:
   no accent, no border, no button affordance — §ED528's ruling that the row
   stays tappable and unrefused is untouched, and the one-lime-action budget
   (§ED240/Part 1d) is not spent on a label. */
html .cult-sheet-item-tag {
  flex-shrink: 0;
  font-family: var(--c-f-body, var(--font-body));
  font-size: .62rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--c-ink-3);
}
html .cult-sheet-item.is-locked:hover .cult-sheet-item-tag { color: var(--c-ink-2); }

/* THE READER'S OWN CULTURE IS OWNED, AND OWNED IS FULL STRENGTH.
   .is-locked dims the flag chip to .72; the current row must never inherit
   that or a lock and a possession start to look alike. Stated explicitly
   rather than relied on: is-current and is-locked are independent classes and
   nothing in renderList() forbids a reader from holding both at once. */
html .cult-sheet-item.is-current .cult-sheet-item-name { color: var(--c-ink); opacity: 1; }
html .cult-sheet-item.is-current .cult-sheet-item-flag { opacity: 1; }
html .cult-sheet-item.is-current { opacity: 1; }

/* ── §ED529 · ADD A CULTURE — the one row that navigates ───────────────────
   Separated from the culture list by a hairline so it reads as a different
   KIND of thing (an action, not a choice), and given the same 30px chip the
   flags wear so the row's left edge still lines up. Neutral by §ED240: the
   Daily already spends its single lime action on the membership wall's own
   CTA, and this sheet can be open while that card is on screen. */
html .cult-sheet-add {
  display: flex; align-items: center; gap: 12px;
  margin-top: 6px; padding: 13px 12px;
  border-top: 1px solid var(--c-glass-chrome-line);
  border-radius: var(--c-r-md);
  color: var(--c-ink-2);
  font-family: var(--font-numeric);
  font-size: var(--c-t-micro);
  font-weight: 600;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
  transition: background-color var(--c-m-toggle-out) var(--c-e-standard),
              color var(--c-m-toggle-out) var(--c-e-standard);
}
html .cult-sheet-add:hover { background: var(--c-surface-2); color: var(--c-ink); }
html .cult-sheet-add:focus-visible { outline: 2px solid var(--c-accent-ink); outline-offset: -2px; }
/* §ED529 — the four dropdown glyphs are inline SVG now (js/card.js's
   cultIcon(), and the note above it explains why the icon FONT was not the
   answer). The old rules sized them with font-size, which an <svg> with
   explicit width/height ignores; these three give the wrappers a box to sit in
   and let `color` keep driving the stroke. Sizes come from cultIcon()'s own
   argument, so there is exactly one place that decides how big a glyph is. */
html .bf-cult-trigger-caret,
html .cult-sheet-item-check,
html .cult-sheet-item-lock {
  display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
html .cult-sheet-add-icon {
  display: grid; place-items: center;
  width: 30px; height: 30px; flex-shrink: 0;
  border-radius: var(--c-r-full);
  background: var(--c-surface-2);
  box-shadow: inset 0 1px 0 var(--c-specular);
  font-size: 1rem; line-height: 1;
}
/* ══════════════════════════════════════════════════════════════════════════
   END §ED440 OVERRIDE LAYER. APPEND NOTHING AFTER THIS LINE.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── §ED545 · THE HONEST QUESTION STATES ──────────────────────────────────
 * When there is no question to ask, the block SAYS SO. It does not collapse,
 * and it does not render an em-dash — js/card.js:2462's dash is what an
 * absent fact looks like, and Jake has ruled that out as a day's answer.
 *
 * .ba-state reuses .ba-zone (both classes are set on the node) so the box,
 * its margins and its typography are the ones the answer block already owns —
 * a reader meets one surface here, whether it is asking or explaining.
 * .ba-slot is the placeholder appended synchronously while the question is
 * fetched: it holds the position and NOTHING ELSE, so a slow round trip shows
 * empty space rather than a frame around nothing. */
.ba-slot { min-height: 0; }

.ba-state-sub {
  margin: 6px 0 0;
  font-size: 0.9375rem;
  line-height: 1.5;
  color: var(--text-muted);
}

/* The vault link on the exhausted state is an <a>, not a <button>, because it
 * navigates. .ba-more carries the affordance's look; these two lines are the
 * whole of what an anchor needs that a button did not. */
a.ba-more { display: inline-block; text-decoration: none; }

/* ═══════════════════════════════════════════════════════════════════════════
   §ED548 · THE DAILY SURFACE IS THE QUESTION
   ═══════════════════════════════════════════════════════════════════════════
   PORTED FROM record-moment.html (branch ed535-record-moment, commits
   b5001aeb / 2b3da7ad / c1d74c12) — the RATIFIED design. Every rule below is
   its .rm-* counterpart with the class renamed to the .ba-* family this
   surface already uses; geometry, tokens, and motion are unchanged from the
   approved file. Where a value differs from the prototype it is because the
   prototype was a full-bleed <main> and this is a block inside #bf-stack.

   ── DESIGN LAW (§ED440), CHECKED CLAUSE BY CLAUSE ────────────────────────
   · HUE 72 LOCKED, WARM NEUTRALS. Every colour here is a --c-* token from
     css/tokens.css. There is not one literal colour value in this block, so
     a cool grey cannot get in by hand.
   · ONE ACCENT PER VIEW, AND IT BELONGS TO THE RECORD AFFORDANCE. This is
     the clause that forced a real change rather than a rename: `.ba-zone >
     .ba-question` puts the question on a FULL-BLEED LIME BAND. On a card
     among cards that band is the licensed answer-zone exception (§ED469).
     On a page whose only content IS the question, the band and the record
     button would be two large accent areas in one view — and the law says
     the accent is the record affordance's. So `.is-bank` drops the band and
     sets the question as the prototype sets it: Fraunces 400 at opsz 72, on
     the page's own ground. The corpus path keeps its band untouched; the
     band's own note (briefing.css, "that tension is Jake's to rule on") is
     answered here only for the surface §ED548 rules on.
     THE LIVE WAVEFORM IS NOT A SECOND ACCENT: 28 two-pixel bars carrying
     real analyser data are a mark, not an area — §ED440 R10/F1 governs large
     lime AREAS and glows at scale, which this is neither.
   · 16PX TYPE FLOOR. --c-t-micro is the smallest size named anywhere below,
     and it is 16px. Nothing here sets a rem/px font-size under it.
   · GLASS ON CHROME, NEVER CONTENT. No backdrop-filter in this block.
   · TRANSFORM AND OPACITY ONLY. Every transition and every keyframe below
     animates transform or opacity and nothing else — no width, no height,
     no top/left, no colour, no box-shadow. The ONE spring is the existing
     .ba-answer.is-just-saved, fired on server-confirmed save; this block
     adds no second one.
   · BOTH THEMES. The tokens re-point under html.theme-light, so every rule
     here follows without a light-only override — with two exceptions that
     are called out where they appear.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── THE SURFACE ─────────────────────────────────────────────────────────
   UN-CARDED. .ba-zone (briefing.css above) is a bordered, padded, shadowed
   well because it sits among cards. Here it is the whole screen, so the
   chrome comes off: no border, no background, no radius, no shadow, and the
   negative-margin band trick below it has nothing left to bleed against.
   Specificity is measured, not hoped for: `.ba-zone.is-bank` is (0,2,0) and
   beats both the base `.ba-zone` (0,1,0) and the token layer's own
   `.ra-card, .ba-zone` (0,1,0) on specificity, and beats
   `html.theme-light .ba-zone` (0,2,1)? — no, it does NOT, so the light
   override is restated explicitly below rather than assumed. */
.ba-zone.is-bank {
  --card-pad-x: 0px;
  --card-pad-y: 0px;
  padding: 0;
  border: 0;
  border-radius: 0;
  overflow: visible;
  background: transparent;
  margin: 0;
}
/* (0,3,1) — matches html.theme-light .ba-zone's own specificity and wins on
   source order. Without this the light theme kept --surface and --shadow-card
   and the question sat on a white card the dark theme did not have. */
html.theme-light .ba-zone.is-bank,
html[data-theme="light"] .ba-zone.is-bank {
  background: transparent;
  box-shadow: none;
}

/* ── THE QUESTION ────────────────────────────────────────────────────────
   .rm-question, verbatim. BOTH VARIATION AXES ARE NAMED: Fraunces defaults
   opsz to 144 and wght to 900, so naming one axis alone renders this in Black
   at the poster cut. 72 rather than 144 because at display size on a phone the
   poster cut's apertures close up, and this audience is the one the 16px floor
   exists for.
   THE BAND IS UNSET, NOT OVERPAINTED. background/color/margin/padding are all
   returned to their unstyled values so nothing of the lime band survives as a
   sliver — an overpaint would leave the negative margins in place and pull the
   text outside the column. (0,2,0), matching `.ba-zone > .ba-question` and
   winning on source order; the `.is-answered-q` variant is listed for the same
   reason the band lists it — it carries its own colour at equal specificity. */
.ba-zone.is-bank > .ba-question,
.ba-zone.is-bank > .ba-question.is-answered-q {
  background: transparent;
  color: var(--c-ink);
  margin-left: 0;
  margin-right: 0;
  padding-left: 0;
  padding-right: 0;
  font-family: var(--c-f-display);
  font-variation-settings: 'opsz' 72, 'wght' 400;
  font-weight: 400;
  font-size: clamp(2rem, 4.3vw, 3.5rem);     /* 32px -> 56px */
  line-height: 1.14;
  letter-spacing: -.012em;
  text-wrap: balance;
  margin-top: 0;
  margin-bottom: 0;
}
/* The answered question steps back to body scale — it is a caption for the
   answer beneath it now, not the thing being asked. */
.ba-zone.is-bank > .ba-question.is-answered-q {
  font-size: clamp(1.25rem, 2.2vw, 1.625rem);
  color: var(--c-ink-2);
  margin-bottom: 14px;
}

/* PINNED, THE is-bank QUESTION MUST BE OPAQUE — §ED440: "GLASS ON CHROME,
   NEVER CONTENT" and this is content. Unpinned it sits `background:
   transparent` on the page's own ground (above); pinned it can overlap
   scrolled content underneath it once stuck, so it takes the page's own
   ground color solid instead of letting that content show through. No
   backdrop-filter, no alpha — a flat --c-bg fill, both themes. */
.ba-zone.is-bank > .ba-question.ba-question-pinned {
  background: var(--c-bg);
}

/* ── READ ALOUD — A FIRST-CLASS CONTROL, NEVER DRAWN DEAD ────────────────
   .rm-aloud, verbatim. The element does not exist at all where the engine has
   no speechSynthesis (js/reader-answer.js buildAloud() returns null), so there
   is no disabled state to style and none is provided. */
.ba-aloud {
  margin-top: var(--c-room-sm);
  align-self: flex-start;
  display: inline-flex; align-items: center; gap: 12px;
  font: inherit; font-size: 1.0625rem;       /* 17px */
  color: var(--c-ink);
  background: var(--c-surface);
  border: 1px solid var(--c-line);
  border-radius: var(--c-r-full);
  padding: 13px 24px 13px 20px;
  min-height: 52px;
  cursor: pointer;
  box-shadow: var(--c-e1-shadow);
  transition: transform var(--c-m-tap) var(--c-e-standard),
              opacity var(--c-m-toggle) var(--c-e-standard);
}
.ba-aloud:hover { opacity: .86; }
.ba-aloud:active { transform: scale(.98); }
.ba-aloud:focus-visible { outline: 2px solid var(--c-focus); outline-offset: 3px; }
.ba-aloud-ic { display: inline-flex; flex: 0 0 auto; }
.ba-aloud-ic svg { display: block; width: 22px; height: 22px; }
/* OPACITY, NOT COLOUR — the transform/opacity-only rule applies to the
   speaking state too, so the icon breathes rather than tinting. */
@keyframes ba-speak { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }
.ba-aloud.is-speaking .ba-aloud-ic svg { animation: ba-speak 1.1s var(--c-e-inout) infinite; }

/* ── THE RECORD AFFORDANCE ───────────────────────────────────────────────
   .rm-cta, verbatim: the largest element on the screen in every state that
   draws it, and the one place the accent lives. */
.ba-rec-host { display: block; margin-top: var(--c-room-sm); }
.ba-rec-cta {
  width: 100%;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 8px;
  min-height: 168px;
  padding: 34px 28px;
  border: 1px solid transparent;
  border-radius: var(--c-r-xl);
  background: var(--c-accent);
  color: var(--c-on-accent);
  font: inherit;
  cursor: pointer;
  box-shadow: var(--c-e2-shadow);
  transition: transform var(--c-m-tap) var(--c-e-standard),
              opacity var(--c-m-toggle) var(--c-e-standard);
}
.ba-rec-cta:hover { transform: translateY(calc(-1 * var(--c-amp-sm))); }
.ba-rec-cta:active { transform: scale(.985); }
.ba-rec-cta:focus-visible { outline: 3px solid var(--c-focus); outline-offset: 4px; }
.ba-rec-cta[disabled] { opacity: .6; cursor: default; }
.ba-rec-ic svg { display: block; width: 44px; height: 44px; }
.ba-rec-label {
  font-family: var(--c-f-display);
  font-variation-settings: 'opsz' 72, 'wght' 400;
  font-weight: 400;
  font-size: clamp(1.5rem, 2.4vw, 1.875rem);  /* 24px -> 30px */
  line-height: 1.2; letter-spacing: -.008em;
}
.ba-rec-sub { font-size: var(--c-t-micro); opacity: .72; line-height: 1.45; text-align: center; }

/* THE BUTTON STAYS LIME WHILE RECORDING — §ED535, Jake's ruling, carried
   across verbatim. An earlier build handed the accent to the waveform when
   recording began and dropped the button to a neutral outline; rendered, that
   made STOP — the control the reader most needs to find — the quietest thing
   on screen at the one moment it matters. No rule below changes the fill. */

/* ── THE EVIDENCE ROW — waveform + clock, and in review the player ───────── */
.ba-rec-evidence {
  display: flex; align-items: center; gap: 16px;
  width: 100%; min-width: 0;
  margin-top: 20px;
  padding: 16px 20px;
  background: var(--c-surface);
  border: var(--c-e1-border);
  border-radius: var(--c-r-md);
}
.ba-rec-evidence .wv { flex: 1 1 auto; min-width: 0; height: 72px; }
.ba-rec-wave-host { display: flex; flex: 1 1 auto; min-width: 0; }
.ba-rec-elapsed {
  flex: 0 0 auto;
  font-family: var(--c-f-mono);
  font-size: var(--c-t-micro);
  font-variant-numeric: tabular-nums;
  color: var(--c-ink-2);
  letter-spacing: .02em;
}
.ba-rec[data-state="recording"] .ba-rec-elapsed { color: var(--c-ink); }

/* ── TRANSPORT ───────────────────────────────────────────────────────────
   Pause / Carry on / Record it again / the confirmation pair. All neutral
   outline — NOT lime, deliberately, even where one of them is the primary
   thing to do: the accent is spent, once, on the record affordance. */
.ba-rec-transport { display: flex; gap: 12px; margin-top: 14px; flex-wrap: wrap; }
.ba-rec-tbtn {
  font: inherit; font-size: var(--c-t-micro);
  color: var(--c-ink); background: var(--c-surface);
  border: 1px solid var(--c-line); border-radius: var(--c-r-full);
  padding: 12px 22px; min-height: 48px; cursor: pointer;
  display: inline-flex; align-items: center; gap: 9px;
  transition: transform var(--c-m-tap) var(--c-e-standard),
              opacity var(--c-m-toggle) var(--c-e-standard);
}
.ba-rec-tbtn:hover { opacity: .84; }
.ba-rec-tbtn:active { transform: scale(.97); }
.ba-rec-tbtn:focus-visible { outline: 2px solid var(--c-focus); outline-offset: 2px; }
.ba-rec-tbtn svg { display: block; width: 17px; height: 17px; }

/* THE STATUS LINE — PRESENT ONLY IN THE STATES THAT SPEAK.
   The first cut reserved its height in every state, reasoning that "Go quiet
   as long as you need to" appearing must not push the writing box down. That
   was the wrong trade and the screenshots showed it: at rest and in review the
   line is EMPTY, so the page carried a 97px hole between the record affordance
   and the writing box (measured: CTA ends 777, label starts 874) that read as
   a missing element rather than as breathing room.
   The reflow it was buying off is not worth buying: the status only ever
   appears at the same instant the evidence row and the transport do, which is
   a change the reader just caused by tapping. Its 39px inside that is not a
   jump. So it is displayed in recording and paused, and collapsed elsewhere. */
.ba-rec-status {
  display: none;
  margin: 14px 0 0;
  font-size: var(--c-t-micro);
  line-height: 1.55;
  color: var(--c-ink-2);
  min-height: 1.55em;
}
.ba-rec[data-state="recording"] .ba-rec-status,
.ba-rec[data-state="paused"] .ba-rec-status { display: block; }

/* ── REVIEW ──────────────────────────────────────────────────────────────── */
.ba-review { margin-top: var(--c-room-sm); }
.ba-review-head {
  font-family: var(--c-f-display);
  font-variation-settings: 'opsz' 72, 'wght' 400;
  font-weight: 400;
  font-size: 1.5rem; line-height: 1.25; margin: 0 0 4px;
  color: var(--c-ink);
}
.ba-review-sub { margin: 0 0 18px; font-size: var(--c-t-micro); color: var(--c-ink-2); }
.ba-rec-play {
  flex: 0 0 auto;
  width: 48px; height: 48px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--c-ink);
  background: var(--c-bg);
  border: 1px solid var(--c-line);
  border-radius: var(--c-r-full);
  cursor: pointer;
  transition: transform var(--c-m-tap) var(--c-e-standard),
              opacity var(--c-m-toggle) var(--c-e-standard);
}
.ba-rec-play:hover { opacity: .84; }
.ba-rec-play:active { transform: scale(.94); }
.ba-rec-play:focus-visible { outline: 2px solid var(--c-focus); outline-offset: 3px; }
.ba-rec-play svg { display: block; width: 20px; height: 20px; }
/* THE WAVEFORM IS THE PLAYER. The strip is the seek target and it is a real
   slider (role/tabindex/aria-value* set in js/reader-answer.js's mountPlayer),
   so it needs a focus ring and a pointer, not just bars. */
.ba-rec-seek {
  display: flex; flex: 1 1 auto; min-width: 0;
  align-items: center;
  min-height: 48px;
  border-radius: var(--c-r-sm);
  cursor: pointer;
  touch-action: manipulation;
}
.ba-rec-seek:focus-visible { outline: 2px solid var(--c-focus); outline-offset: 4px; }
.ba-rec-audio { display: none; }        /* the engine, not a control */
.ba-rec-silent { margin: 14px 0 0; }

/* THE CONFIRMATION. Quiet prose and two neutral buttons — discarding a
   recording is the only destructive act on this surface, and it asks. */
.ba-rec-confirm { margin-top: 16px; }
.ba-rec-confirm-q { margin: 0 0 12px; font-size: var(--c-t-micro); color: var(--c-ink); }
.ba-rec-confirm-row { display: flex; gap: 12px; flex-wrap: wrap; }

/* ── THE WRITING BOX — AN EQUAL CITIZEN ─────────────────────────────────
   Always present, never introduced by a failure, and labelled as an
   alternative rather than as a fallback. */
.ba-write-label {
  display: block;
  margin: var(--c-room-sm) 0 10px;
  font-size: var(--c-t-micro);
  color: var(--c-ink-2);
}
.ba-zone.is-bank .ba-input {
  width: 100%;
  min-height: 132px;
  resize: vertical;
  font-family: var(--c-f-body);
  font-size: var(--c-t-body);              /* 18px reading floor */
  line-height: var(--c-lh-body);
  color: var(--c-ink);
  background: var(--c-surface);
  border: var(--c-e1-border);
  border-radius: var(--c-r-md);
  padding: 18px 20px;
  box-shadow: var(--c-e1-shadow);
}
.ba-zone.is-bank .ba-input::placeholder { color: var(--c-ink-3); opacity: 1; }
.ba-zone.is-bank .ba-input-row { display: block; }

/* KEEP THIS. Ink-filled, not lime — the accent is already spent, and the
   prototype's own [data-state="review"] promotion to lime is deliberately NOT
   ported: on this surface the record CTA is still on screen above it in every
   state except review, and two lime areas in one view is the thing the law
   forbids. Full width, 56px, the landing's pill. */
.ba-zone.is-bank .ba-save {
  margin-top: 22px;
  width: 100%;
  font-family: var(--c-f-body);
  font-size: 1.0625rem;
  color: var(--c-bg);
  background: var(--c-ink);
  border: 1px solid var(--c-ink);
  border-radius: var(--c-r-full);
  padding: 16px 28px; min-height: 56px; cursor: pointer;
  transition: transform var(--c-m-tap) var(--c-e-standard),
              opacity var(--c-m-toggle) var(--c-e-standard);
}
.ba-zone.is-bank .ba-save:hover { opacity: .88; }
.ba-zone.is-bank .ba-save:active { transform: scale(.985); }
.ba-zone.is-bank .ba-save:focus-visible { outline: 2px solid var(--c-focus); outline-offset: 3px; }
.ba-zone.is-bank .ba-actions { display: block; }
.ba-zone.is-bank .ba-cancel {
  margin-top: 12px;
  width: 100%;
  min-height: 48px;
  border-radius: var(--c-r-full);
  font-size: var(--c-t-micro);
}

/* ── THE ATTACHMENT SEAM (§ED548 addendum) ───────────────────────────────
   A PLACE, NOT A FEATURE. `.ba-attachments` is rendered `hidden` and empty by
   js/reader-answer.js's renderAfterSave(); this rule exists so that the day an
   attachment affordance is built, it has a position, a rhythm and a rule
   already waiting for it — a feature, not a redesign of the saved answer.
   Photos and recipes will hang off AN ANSWER: never a gallery, never a recipe
   manager, no ingredient or step fields. The layout below says so — it is a
   row of small items under one answer, not a grid that wants to be browsed.
   It styles nothing today because `[hidden]` keeps it out of the flow. */
.ba-attachments {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 18px;
}

.ba-attachments[hidden] { display: none; }

/* ── §ED569 · THE VOICE DECISION ─────────────────────────────────────────
   Mounted by js/reader-answer.js's mountVoiceConsent(), beneath the saved
   answer and its recording, above the progress ladder.

   IT IS DELIBERATELY QUIET. This is not a banner, not a modal, not a card
   competing with the answer above it — a reader has just finished saying
   something, and a consent prompt that shouts would be the loudest thing on a
   surface whose whole design law puts the reader's own words first. A hairline
   rule and the muted type the .ba-voice-note family already uses; no accent, no
   fill, no shadow, no motion. Nothing on this block animates and nothing
   demands a decision.

   THE TWO BUTTONS ARE EQUALS. Same size, same weight, same border, same
   colour — No is not a text link beside a filled Yes. A visual hierarchy
   between them would be a thumb on the scale of a decision this product has no
   business influencing, and it would quietly contradict the landing's promise
   that a recording is used "only if you say so".

   48px MIN-HEIGHT on both, the tap-target floor the rest of this sheet keeps.
   flex-wrap so a narrow viewport stacks them rather than shrinking them under
   that floor. */
.ba-consent {
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid var(--c-line, rgba(0, 0, 0, 0.12));
}
.ba-consent-body {
  margin: 0;
  font-size: var(--c-t-micro);
  line-height: 1.5;
  color: var(--c-ink-soft, inherit);
  max-width: 44rem;
}
.ba-consent-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 12px;
}

/* `flex: 1 1 0` AND NOT `1 1 auto`, AND THAT IS THE WHOLE OF THE EQUALITY.
   With an `auto` basis the two buttons size to their CONTENT first, so "Yes"
   came out 402.2px against "No" at 397.8px — a 4.4px advantage to Yes, on a
   consent decision, produced by nothing but the width of the word. This probe
   measured it (test-ed569-client.js §3) and the number is why the rule reads
   as it does: a zero basis divides the row evenly and the labels stop voting. */
.ba-consent-yes,
.ba-consent-no {
  flex: 1 1 0;
  min-width: 96px;
  min-height: 48px;
  padding: 0 18px;
  border: 1px solid var(--c-line, rgba(0, 0, 0, 0.24));
  border-radius: var(--c-r-full);
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: var(--c-t-micro);
  cursor: pointer;
}
.ba-consent-yes[disabled],
.ba-consent-no[disabled] { opacity: 0.5; cursor: default; }
.ba-consent-foot {
  margin: 12px 0 0;
  font-size: var(--c-t-micro);
  color: var(--c-ink-soft, inherit);
  opacity: 0.75;
}

/* ── THE QUESTION-SHAPED SKELETON ────────────────────────────────────────
   js/briefing.js's renderSkeleton() draws these three bars when the fact
   surface is off. They model the question (two lines) and the record
   affordance (one tall block) — the shape that actually lands, so the wait
   does not become a layout jump. .bf-skel-line carries the shimmer already. */
.bf-skel-q      { height: 44px; width: 92%; margin-bottom: 10px; }
.bf-skel-q-2    { height: 44px; width: 64%; margin-bottom: 26px; }
.bf-skel-rec    { height: 168px; width: 100%; border-radius: var(--c-r-xl); }

/* The honest-state link (js/briefing.js showState opts.link). */
.bf-state-link { display: inline-block; margin-top: 14px; font-size: var(--c-t-micro); }

/* ── NARROW ──────────────────────────────────────────────────────────────
   The prototype's own 560px breakpoint. Nothing changes shape; the record
   affordance loses some of its height so the writing box stays reachable
   without a scroll on a short phone. */
@media (max-width: 560px) {
  .ba-rec-cta { min-height: 140px; padding: 26px 20px; }
  .ba-rec-ic svg { width: 38px; height: 38px; }
  .ba-rec-evidence { padding: 14px 16px; gap: 12px; }
  .ba-rec-evidence .wv { height: 60px; }
  .bf-skel-rec { height: 140px; }
}
