/* SleeperLot — "measurement bench".
 *
 * The design direction comes from the product's own thesis rather than from a website
 * genre: this is an instrument that reports ranges and refuses to guess, so the page is
 * built out of the vocabulary of measurement — ruled edges with tick marks, a bone-paper
 * ground, ink, and one instrument blue used only where a number is being asserted.
 *
 * The signature element is the interval bar (.iv): every price claim is drawn as a band
 * with the observed value plotted on it, so a range reads as a picture instead of a pair
 * of numbers. It is the one loud thing; everything around it stays quiet.
 *
 * Data colours were validated with the dataviz palette checker against the paper surface
 * (originally bone #F5F4EE, now blue-white #F2F5FB): band blue, miss red and gain green
 * clear the lightness, chroma, normal-vision and 3:1 contrast gates. The ground moved to
 * blue at the same lightness, so every one of those ratios rose slightly and none fell. Green vs red sits in the CVD warn band, which is
 * why every good/bad mark on this site also carries a word or a sign — never colour alone.
 */

:root {
  color-scheme: light;

  /* Ground. Blue-white, not bone: the mark is a blue tile and the one asserted colour on
     the page is blue, so a warm ground was quietly fighting both. Lightness is held where
     the bone palette had it (paper L 0.90 -> 0.91, the recessed band 0.77 -> 0.78), which
     is why every contrast ratio below moves up rather than down -- see the note above. */
  --paper: #f2f5fb;
  --card: #ffffff;
  --sunk: #dde4f0;

  /* Ink. Cooled by a few degrees to sit on a blue ground instead of a bone one; the
     lightness is unchanged, so nothing that passed before stops passing. */
  --ink: #14181b;
  --ink-2: #4a5462;
  --muted: #5d6572;

  /* Rules */
  --rule: #d1dbeb;
  --rule-strong: #a8b6d2;

  /* Data */
  --signal: #1b45c4; /* the assertion: a number we stand behind */
  --signal-wash: #d3ddfa;  /* deeper and more saturated than --sunk, which is now also
                              blue: the wash has to keep meaning "asserted" next to a band
                              that means nothing. Separated on chroma, not lightness. */
  --gain: #0f7a4a;
  --miss: #c33a2e;
  --miss-wash: #f3ddda;
  --silence: #8b93a3; /* declined — deliberately mute */

  --display: "Archivo", "Archivo Expanded", ui-sans-serif, system-ui, sans-serif;
  --body: "Public Sans", ui-sans-serif, system-ui, sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, "SFMono-Regular", Consolas, monospace;

  --page: 1180px;
  --prose: 66ch;
}

*, *::before, *::after { box-sizing: border-box; }
[hidden] { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--paper);
  color: var(--ink-2);
  font-family: var(--body);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

body {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

h1, h2, h3 {
  font-family: var(--display);
  font-stretch: 112%;
  color: var(--ink);
  font-weight: 700;
  letter-spacing: -0.015em;
  line-height: 1.1;
  margin: 0;
}

a { color: var(--signal); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { color: var(--ink); }

:focus-visible {
  outline: 2px solid var(--signal);
  outline-offset: 2px;
}

/* The recurring device: a hairline carrying measurement ticks. Used instead of a plain
   border wherever a section ends, so the page reads as ruled stock. */
.ticked {
  border-top: 1px solid var(--rule);
  background-image: repeating-linear-gradient(
    to right, var(--rule) 0 1px, transparent 1px 9px);
  background-repeat: no-repeat;
  background-size: 100% 4px;
  background-position: 0 0;
}

/* ---------- Header ---------- */

.site-header {
  display: flex;
  align-items: baseline;
  gap: 2rem;
  flex-wrap: wrap;
  max-width: var(--page);
  width: 100%;
  margin: 0 auto;
  padding: 1.4rem 1.5rem 1rem;
}

.site-header .brand {
  font-family: var(--display);
  font-stretch: 125%;
  font-weight: 800;
  font-size: 1.05rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink);
  text-decoration: none;
}

/* The mark sits inline, not in a flex box, so the wordmark keeps the baseline the
   header aligns every other item to.

   The geometry is the same bracket-and-dot as icons/favicon.svg and icons/icon-*.png,
   scaled from their 64-unit box to this 24-unit one -- one mark, drawn once, so the tab,
   the home screen, the Discord post and the header cannot drift apart again. */
.site-header .brand-mark {
  width: 1.35em;
  height: 1.35em;
  vertical-align: -0.3em;
  margin-right: 0.5rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linejoin: round;
  stroke-linecap: round;
}
.site-header .brand-mark-dot {
  fill: var(--signal);
  stroke: none;
}
.site-header .brand-lot { color: var(--signal); }
.site-header .brand:hover .brand-lot { color: var(--ink); }

.site-header nav {
  display: flex;
  gap: 1.35rem;
  margin-left: auto;
  font-family: var(--mono);
  font-size: 0.76rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.site-header nav a {
  color: var(--muted);
  text-decoration: none;
  padding-bottom: 0.15rem;
  border-bottom: 2px solid transparent;
}
.site-header nav a:hover { color: var(--ink); }
.site-header nav a[aria-current="page"] {
  color: var(--ink);
  border-bottom-color: var(--signal);
}

.header-rule {
  max-width: var(--page);
  width: 100%;
  margin: 0 auto 2rem;
  padding: 0 1.5rem;
}
.header-rule > div { height: 4px; }

/* ---------- Shell ---------- */

main {
  flex: 1;
  width: 100%;
  max-width: var(--page);
  margin: 0 auto;
  padding: 0 1.5rem 4rem;
}

/* The prose column still starts at the page's left edge, under the wordmark -- the header is
   the page's baseline and text that drifts away from it reads as a different document. What
   changed is the frame around it.
   The column was a 66ch measure inside a 1180px page, which left roughly 55% of a 1280
   viewport empty to its right on /method, /terms, /privacy, /support and the 404. Three rounds
   of testers read that as an unfinished layout rather than as a measure, most recently "every
   interior page is a narrow left column on an empty 55% of screen". The measure was never the
   problem and is not touched; the page around it now stops shortly after the text does, so
   there is no empty half left over to read as missing. */
main.prose {
  max-width: 82ch;
  /* Pins the left edge exactly where the 1180px page's left edge falls, so the first
     character still sits under the wordmark, and lets the box end shortly after the text
     instead of running on to a page width the text never fills. `margin: 0 auto` alone would
     centre the whole column and break that alignment, which is worse than the empty half. */
  margin-left: max(0px, calc(50% - var(--page) / 2));
  margin-right: auto;
}
main.prose > * { max-width: var(--prose); }

.site-footer {
  max-width: var(--page);
  width: 100%;
  margin: 0 auto;
  padding: 1.25rem 1.5rem 2.5rem;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.74rem;
  line-height: 1.7;
  letter-spacing: 0.02em;
}

.eyebrow {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 0.6rem;
}

.page-title { font-size: clamp(1.9rem, 4vw, 2.6rem); margin-bottom: 0.4rem; }

.page-sub {
  color: var(--muted);
  font-size: 0.9rem;
  max-width: 72ch;
  margin: 0 0 2rem;
}

/* ---------- Landing ---------- */

.hero { padding: 1rem 0 2.5rem; }

.hero h1 {
  font-size: clamp(2.1rem, 4.4vw, 3.3rem);
  max-width: 20ch;
  margin-bottom: 1.5rem;
}

.hero .lede {
  font-size: 1.08rem;
  max-width: 56ch;
  color: var(--ink-2);
  margin: 0 0 2rem;
}

.cta-row { display: flex; gap: 0.75rem; flex-wrap: wrap; }

.cta {
  display: inline-block;
  font-family: var(--mono);
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  padding: 0.75rem 1.15rem;
  background: var(--signal);
  color: #fff;
  border: 1px solid var(--signal);
}
.cta:hover { background: var(--ink); border-color: var(--ink); color: #fff; }

.cta.secondary { background: transparent; color: var(--ink); border-color: var(--rule-strong); }
.cta.secondary:hover { background: var(--ink); color: #fff; border-color: var(--ink); }

.cta.tiny { padding: 0.4rem 0.8rem; font-size: 0.72rem; }

/* A <button class="cta"> should match an <a class="cta"> to the pixel: buttons default to
   line-height: normal, which made the Customize card's button sit a hair off the other two. */
button.cta { line-height: inherit; }

/* ---------- Header auth actions ---------- */

.site-header .auth-actions {
  display: flex;
  align-items: center;
  gap: 1rem;
}

/* ---------- Readouts: the day bar and the calibration bar ---------- */

.readouts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 1.5rem 3rem;
  padding: 2rem 0 0.5rem;
}

/* The money tiles. The number is the tile: label above it in small caps, the figure set
   large and flush left directly under it, the sentence of context smaller and narrower
   than either. The label used to sit at the far left of a wide column with the figure
   pushed to the far right, which put a hand-span of empty paper between a number and its
   own name and let the prose below out-weigh both. */
.readout .head {
  display: block;
  margin-bottom: 0.4rem;
}

.readout .head .k {
  display: block;
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.3rem;
}

.readout .head .v {
  display: block;
  font-family: var(--display);
  font-stretch: 112%;
  font-weight: 700;
  font-size: clamp(2.4rem, 5.5vw, 3.4rem);
  line-height: 1;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

/* Proportion bar. Segments are separated by a 2px gap in the paper colour rather than
   by a stroke — white does the separating, per the dataviz mark spec. */
.prop {
  display: flex;
  gap: 2px;
  height: 26px;
  background: var(--paper);
}
.prop .seg { height: 100%; min-width: 2px; }
.prop .seg.silence { background: var(--silence); }
.prop .seg.signal { background: var(--signal); }
.prop .seg.miss { background: var(--miss); }
.prop .seg.priced { background: var(--signal-wash); border: 1px solid var(--signal); box-sizing: border-box; }
/* Matched to a cluster but with no comparable pool to price it against: between silence
   and a published number, so it reads as a hatch rather than a third colour. */
.prop .seg.unpriced { background: repeating-linear-gradient(45deg, var(--silence) 0 3px, transparent 3px 6px); }

.prop-legend {
  display: flex;
  gap: 1.5rem;
  flex-wrap: wrap;
  margin-top: 0.6rem;
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--muted);
}
.prop-legend .key { display: flex; align-items: center; gap: 0.45rem; }
.prop-legend .swatch { width: 10px; height: 10px; flex: none; }
.prop-legend .swatch.silence { background: var(--silence); }
.prop-legend .swatch.signal { background: var(--signal); }
.prop-legend .swatch.miss { background: var(--miss); }
.prop-legend .swatch.priced { background: var(--signal-wash); border: 1px solid var(--signal); box-sizing: border-box; }
.prop-legend .swatch.unpriced { background: repeating-linear-gradient(45deg, var(--silence) 0 3px, transparent 3px 6px); }
.prop-legend strong { color: var(--ink); font-weight: 600; }

/* The 80% target on the calibration bar: a tick, so "81.2%" is read against the thing it
   is supposed to hit rather than in isolation. */
.target-rail {
  position: relative;
  height: 14px;
  margin-bottom: 2px;
  font-family: var(--mono);
  font-size: 0.68rem;
  color: var(--muted);
}
.target-rail .tick {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  white-space: nowrap;
}
.target-rail .tick::after {
  content: "";
  display: block;
  width: 1px;
  height: 6px;
  margin: 2px auto 0;
  background: var(--rule-strong);
}

/* One line of compartments under each figure, not a sentence: each span is a single fact,
   dotted off from its neighbour, and the row wraps before any span breaks internally. */
.readout .note {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.1rem 0.75rem;
  margin: 0.55rem 0 0;
  font-size: 0.82rem;
  line-height: 1.5;
  color: var(--muted);
  max-width: none;
}
.readout .note > span { white-space: nowrap; }
.readout .note > span + span::before {
  content: "·";
  margin-right: 0.75rem;
  color: var(--rule-strong);
}
.readout .note strong { color: var(--ink); font-weight: 600; }

/* The money is the headline, and the money is a gain: the one figure on this page a reader
   is here for, so it carries the gain green the rest of the site reserves for a saving.
   Hue is decoration here, never the meaning -- the label above it says what it is. */
.readout .head .v.is-gain { color: var(--gain); }

/* One tile, not two. The summary is a single measurement of a single pool, so it takes the
   full column and its prose keeps a readable measure rather than running the page width. */
.readout-lede { max-width: 92ch; }

/* This one tile leads the page, so it is set larger than a readout in a row of them: the
   figure a size up, and the facts under it at reading size rather than caption size --
   they are the counts the money divides by, not a footnote to it. Scoped to the lede so
   the day bar and the calibration bar elsewhere keep their own scale. */
.readout-lede .head .k { font-size: 0.78rem; margin-bottom: 0.45rem; }
.readout-lede .head .v { font-size: clamp(3rem, 8vw, 4.8rem); }
.readout-lede .note {
  font-size: 1rem;
  gap: 0.2rem 0.85rem;
  margin-top: 0.8rem;
}
.readout-lede .note > span + span::before { margin-right: 0.85rem; }

/* A caveat, not a stat line: prose, so it does not inherit the dot-separated nowrap runs
   the figure row uses, and marked off by a rule so it reads as a condition on the number
   above rather than as another number. */
.readout .readout-caveat {
  display: block;
  margin-top: 0.6rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--rule);
  font-size: 0.78rem;
  line-height: 1.45;
  color: var(--muted);
  max-width: 54em;
}

/* On a phone a compartment is wider than the column, so let the long ones break rather
   than push the page sideways. */
@media (max-width: 560px) {
  .readout .note > span { white-space: normal; }
}

.stat-note {
  font-size: 0.82rem;
  color: var(--muted);
  max-width: 72ch;
  margin: 1.5rem 0 0;
}
.stat-note code { font-family: var(--mono); font-size: 0.85em; }

/* ---------- Explainer ---------- */

.explainer { max-width: var(--prose); padding-top: 3rem; }
.explainer h2 { font-size: 1.3rem; margin: 2.5rem 0 0.6rem; }
.explainer h2:first-child { margin-top: 0; }
.explainer p { margin: 0 0 1rem; }

/* ---------- Prose / method ---------- */

.prose h2 {
  font-size: 1.25rem;
  margin: 2.75rem 0 0.75rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--rule);
}
.prose p { margin: 0 0 1rem; }
.prose strong { color: var(--ink); font-weight: 600; }
.prose em { color: var(--ink-2); }
.prose ul { margin: 0 0 1.25rem; padding-left: 1.1rem; }
.prose li { margin-bottom: 0.6rem; }
.prose code {
  font-family: var(--mono);
  font-size: 0.85em;
  color: var(--ink);
  background: var(--sunk);
  padding: 0.05rem 0.3rem;
}

.prose table {
  width: 100%;
  border-collapse: collapse;
  margin: 0.5rem 0 1.5rem;
  font-size: 0.92rem;
}
.prose th, .prose td {
  text-align: left;
  padding: 0.55rem 0.8rem 0.55rem 0;
  border-bottom: 1px solid var(--rule);
  vertical-align: top;
}
.prose th {
  font-family: var(--mono);
  font-weight: 500;
  font-size: 0.68rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  border-bottom-color: var(--rule-strong);
}
.prose td:first-child { white-space: nowrap; }

.ladder td:first-child {
  font-family: var(--mono);
  font-weight: 600;
  color: var(--ink);
  width: 3rem;
}
.ladder tr.ok td:first-child {
  box-shadow: inset 3px 0 0 var(--signal);
  padding-left: 0.8rem;
}
.ladder tr.no td { color: var(--muted); }
.ladder tr.no td:first-child {
  box-shadow: inset 3px 0 0 var(--rule-strong);
  padding-left: 0.8rem;
  color: var(--muted);
}

.callout {
  border-left: 3px solid var(--signal);
  background: var(--card);
  padding: 1rem 1.1rem;
  margin: 0 0 1.25rem;
}
.callout strong { color: var(--ink); }

/* ---------- The interval bar: the signature mark ---------- */

/* Capped rather than full-bleed: past about this width the observation tick and its
   caption stop reading as one object. */
.iv, .iv-scale, .iv-caption { max-width: 540px; }

.iv {
  --iv-h: 26px;
  position: relative;
  height: var(--iv-h);
  margin: 0.55rem 0 0.3rem;
}

/* The domain line. Hairline, solid, recessive — it is the ruler, not the data. */
.iv .axis {
  position: absolute;
  left: 0; right: 0;
  top: 50%;
  height: 1px;
  background: var(--rule);
}

/* The claimed range. A wash, not a saturated block. */
.iv .band {
  position: absolute;
  top: calc(50% - 7px);
  height: 14px;
  background: var(--signal-wash);
  border-left: 2px solid var(--signal);
  border-right: 2px solid var(--signal);
}
.iv.is-miss .band { background: var(--miss-wash); border-color: var(--miss); }

/* The observed value. A 2px rule with a dot, ringed in the surface colour so it stays
   legible where it sits on top of the band. */
.iv .obs {
  position: absolute;
  top: calc(50% - 11px);
  width: 2px;
  height: 22px;
  background: var(--ink);
  transform: translateX(-1px);
}
.iv .obs::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--ink);
  box-shadow: 0 0 0 2px var(--paper);
  transform: translate(-50%, -50%);
}
.iv.is-miss .obs, .iv.is-miss .obs::after { background: var(--miss); }
.iv.is-gain .obs, .iv.is-gain .obs::after { background: var(--gain); }

.iv-scale {
  display: flex;
  justify-content: space-between;
  font-family: var(--mono);
  font-size: 0.7rem;
  color: var(--muted);
}

.iv-caption {
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--muted);
  margin-top: 0.35rem;
}
.iv-caption strong { color: var(--ink); font-weight: 500; }

/* ---------- Filters ---------- */

.filter-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem;
  align-items: end;
  padding: 0 0 1rem;
  border-bottom: 1px solid var(--rule-strong);
  margin: 2.5rem 0 1.5rem;
}

.filter-bar .field { display: flex; flex-direction: column; gap: 0.3rem; }

.filter-bar label {
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}

.filter-bar select,
.filter-bar button {
  font-family: var(--body);
  font-size: 0.9rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--rule-strong);
  padding: 0.45rem 0.6rem;
}
.filter-bar button { cursor: pointer; }
.filter-bar button:hover { border-color: var(--ink); }
/* A control that has nothing to act on -- the category select on the Buy It Now record, whose
   rows carry no category. The browser's own 0.7 opacity is too quiet to read as unavailable
   next to three live selects, so it is stated: sunk, muted, and refusing the pointer. The
   label above it dims with it, since the pair is one control. */
.filter-bar select:disabled {
  color: var(--muted);
  background: var(--sunk);
  border-color: var(--rule);
  cursor: not-allowed;
  opacity: 1;
}
.filter-bar .field:has(select:disabled) label { color: var(--silence); }

.filter-count {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--muted);
  align-self: center;
}

/* ---------- Cards ---------- */

.feed { list-style: none; margin: 0; padding: 0; }

.card {
  display: grid;
  grid-template-columns: 148px minmax(0, 1fr) minmax(150px, auto);
  gap: 1.25rem;
  padding: 1.4rem 0;
  border-top: 1px solid var(--rule);
  align-items: start;
}
.card:last-child { border-bottom: 1px solid var(--rule); }

/* Sized to the row rather than to the icon it used to be. A row runs 250-300px tall once
   the price lines are in it, and a 56px picture left a column of dead paper under every
   one of them -- reported as blank space, correctly. */
.card .thumb,
.card .thumb-fallback {
  width: 148px;
  height: 148px;
  object-fit: cover;
  background: var(--sunk);
  border: 1px solid var(--rule);
}
.card .thumb-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--mono);
  font-size: 0.6rem;
  color: var(--muted);
  text-align: center;
}

.card .body { min-width: 0; }

.card .title-row { margin-bottom: 0.5rem; }
.card .title-row a,
.card .title-row .untitled {
  font-family: var(--display);
  font-stretch: 100%;
  font-weight: 600;
  font-size: 1.02rem;
  line-height: 1.3;
  color: var(--ink);
  text-decoration: none;
}
.card .title-row a:hover { color: var(--signal); text-decoration: underline; }

.badges { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 0.4rem; }

.badge {
  font-family: var(--mono);
  font-size: 0.66rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  padding: 0.1rem 0;
}
.badge + .badge { padding-left: 0.5rem; border-left: 1px solid var(--rule); }

.badge-outcome-hit { color: var(--signal); font-weight: 600; }
.badge-outcome-miss { color: var(--miss); font-weight: 600; }
.badge-outcome-priced { color: var(--ink); font-weight: 600; }
.badge-outcome-no_estimate { color: var(--muted); font-weight: 600; }
.badge-outcome-unsold { color: var(--silence); font-weight: 600; }
/* A Buy It Now ask we called underpriced that stopped being listed. The instrument blue, not
   the gain green: green on this page means a measured outcome, and this one is an inference
   from an absence. The word is "went" for the same reason -- see `closes.js:buildBinCard`. */
.badge-outcome-gone { color: var(--signal); font-weight: 600; }
.badge-outcome-listed { color: var(--ink); font-weight: 600; }
.badge-outcome-lost { color: var(--muted); font-weight: 600; }
/* A lot whose own title says it is broken. Coloured like a miss because that is what it is
   for the reader: the reference ask beside it is for a working unit. */
.badge-condition { color: var(--miss); font-weight: 600; }

.price-lines {
  font-family: var(--mono);
  font-size: 0.82rem;
  color: var(--ink-2);
  margin-top: 0.5rem;
}
.price-lines .row { display: flex; gap: 0.6rem; flex-wrap: wrap; }
.price-lines .k { color: var(--muted); min-width: 9rem; }
.price-lines .v { color: var(--ink); }
.price-lines .n { color: var(--muted); }

.attention {
  display: flex;
  gap: 1.1rem;
  flex-wrap: wrap;
  margin-top: 0.6rem;
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--muted);
}
.attention .pill strong { color: var(--ink); font-weight: 600; }
.attention .pill.zero-bids strong { color: var(--gain); }
.attention .pill.countdown { color: var(--ink); font-variant-numeric: tabular-nums; }

.headline { text-align: right; }
.headline .savings {
  font-family: var(--display);
  font-stretch: 112%;
  font-weight: 700;
  font-size: 1.45rem;
  line-height: 1.1;
  color: var(--ink);
}
.headline .savings.positive { color: var(--gain); }
.headline .savings.negative { color: var(--miss); }
.headline .savings.unknown { color: var(--muted); }
/* A saving the alert CLAIMED on a lot that then got no bids. Deliberately not `--gain`:
   nobody banked it, and the number carries a "claimed, no bids" label to say so. Muted ink
   rather than the miss colour, because the alert was not wrong -- there was simply no buyer. */
.headline .savings.unrealised {
  color: var(--muted);
  text-decoration: line-through;
  text-decoration-thickness: 1px;
  text-decoration-color: color-mix(in srgb, var(--muted) 45%, transparent);
}

.headline .savings-label,
.headline .savings-sub {
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  margin-top: 0.2rem;
}

.no-estimate-note {
  margin: 0.6rem 0 0;
  font-size: 0.84rem;
  color: var(--muted);
  max-width: 56ch;
}

/* ---------- Totals, empty states, funnel ---------- */

.totals-footer {
  display: flex;
  gap: 2rem;
  flex-wrap: wrap;
  margin-top: 1.25rem;
  padding-top: 1rem;
  font-family: var(--mono);
  font-size: 0.78rem;
  color: var(--muted);
}
.totals-footer strong { color: var(--ink); font-weight: 600; }

.price-lines .row.closed-at .v {
  font-weight: 700;
  color: var(--ink);
}

.empty-state {
  padding: 3rem 0;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  color: var(--muted);
  max-width: 62ch;
}
.empty-state strong {
  display: block;
  font-family: var(--display);
  font-stretch: 112%;
  font-size: 1.3rem;
  color: var(--ink);
  margin-bottom: 0.5rem;
}

.stale-banner {
  border-left: 3px solid var(--miss);
  background: var(--miss-wash);
  color: var(--ink);
  padding: 0.8rem 1rem;
  margin: 0 0 1.5rem;
  font-size: 0.88rem;
}

.funnel { margin: 2.5rem 0; }
.funnel h2 {
  font-family: var(--mono);
  font-stretch: normal;
  font-weight: 500;
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.75rem;
}
.funnel-table { border-collapse: collapse; font-size: 0.85rem; }
.funnel-table td {
  padding: 0.3rem 1rem 0.3rem 0;
  border-bottom: 1px solid var(--rule);
  color: var(--ink-2);
}
.funnel-table td.count {
  font-family: var(--mono);
  color: var(--ink);
  text-align: right;
  min-width: 4rem;
}
.funnel-note { font-size: 0.8rem; color: var(--muted); margin-top: 0.75rem; max-width: 60ch; }

.method-note {
  font-size: 0.8rem;
  line-height: 1.55;
  color: var(--muted);
  max-width: 68ch;
  margin: 1.1rem 0 0;
}
.method-note strong { color: var(--ink); font-weight: 600; }

/* A footnote inside a footnote: the reseller proxy, demoted out of the money tiles. Set on
   its own line and a step down again, so it is available to the reader who wants it without
   competing with the measured figures above. */
.method-note .aside {
  display: block;
  margin-top: 0.5rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--rule);
  font-size: 0.94em;
}
.method-note .aside strong { font-weight: 600; }

/* ---------- Waitlist ---------- */

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.waitlist {
  margin-top: 3.5rem;
  padding-top: 1.75rem;
  border-top: 1px solid var(--rule-strong);
  max-width: var(--prose);
}
.waitlist h2 { font-size: 1.2rem; margin-bottom: 0.6rem; }
.waitlist p { margin: 0 0 1.25rem; }

.waitlist form { display: flex; gap: 0.5rem; flex-wrap: wrap; }

.waitlist input[type="email"] {
  flex: 1 1 18rem;
  font-family: var(--mono);
  font-size: 0.9rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--rule-strong);
  padding: 0.7rem 0.75rem;
}
.waitlist input[type="email"]::placeholder { color: var(--muted); }
.waitlist button {
  font-family: var(--mono);
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  background: var(--signal);
  color: #fff;
  border: 1px solid var(--signal);
  padding: 0.7rem 1.15rem;
  cursor: pointer;
}
.waitlist button:hover:not(:disabled) { background: var(--ink); border-color: var(--ink); }
.waitlist button:disabled { opacity: 0.55; cursor: default; }

.waitlist-status {
  margin: 0.85rem 0 0;
  font-size: 0.86rem;
  min-height: 1.2em;
  color: var(--muted);
}
.waitlist-status.ok { color: var(--gain); }
.waitlist-status.error { color: var(--miss); }

/* The resend offer under a successful signup, and the purchase-confirmation notice on the
   account plan card. Both are quiet on purpose: neither is an error, and neither is asking
   for money -- one is a second copy of an email, the other is a delivery check on a plan
   that is already paid for and already running. */
.signup-resend { margin: 0.5rem 0 0; }
.signup-resend button {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: 0.86rem;
  color: var(--signal);
  text-decoration: underline;
  cursor: pointer;
}
.signup-resend button:disabled { opacity: 0.55; cursor: default; text-decoration: none; }

.account-confirm {
  margin: 1rem 0 0;
  padding: 0.85rem 0 0;
  border-top: 1px solid var(--rule, #d1dbeb);
}
.account-confirm-lede {
  margin: 0 0 0.6rem;
  font-size: 0.86rem;
  color: var(--muted);
  max-width: 44ch;
}

/* Quieter than the status line and narrower than the prose column: this has to be readable
   before someone types their address, but it is not the reason they are on the page. */
.waitlist-consent {
  margin: 0.6rem 0 0;
  max-width: 46ch;
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--muted);
}
.waitlist-consent a { color: var(--muted); text-decoration: underline; }
.waitlist-consent a:hover { color: var(--ink); }

/* ---------- Auth card (sign in / sign up) ---------- */

.auth-wrap { display: flex; justify-content: center; margin: 2.5rem 0 1rem; max-width: none; }

.auth-card {
  width: 100%;
  max-width: 24rem;
  background: var(--card);
  border: 1px solid var(--rule);
  box-shadow: inset 3px 0 0 var(--signal);
  padding: 2.25rem 2rem;
}

.auth-card h2 {
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  font-family: var(--mono);
  font-weight: 500;
  margin: 0 0 1.5rem;
  padding-top: 0;
  border-top: none;
  text-align: center;
}

.auth-card form { display: flex; flex-direction: column; gap: 1.1rem; }

.auth-field { display: flex; flex-direction: column; gap: 0.4rem; }
.auth-field label {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.auth-card input[type="email"],
.auth-card input[type="password"] {
  width: 100%;
  font-family: var(--body);
  font-size: 0.95rem;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--rule-strong);
  padding: 0.75rem 0.85rem;
}
.auth-card input::placeholder { color: var(--muted); }

.auth-card button[type="submit"] {
  margin-top: 0.5rem;
  width: 100%;
  font-family: var(--mono);
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  background: var(--signal);
  color: #fff;
  border: 1px solid var(--signal);
  padding: 0.85rem 1.15rem;
  cursor: pointer;
}
.auth-card button[type="submit"]:hover:not(:disabled) { background: var(--ink); border-color: var(--ink); }
.auth-card button[type="submit"]:disabled { opacity: 0.55; cursor: default; }

.auth-card .waitlist-status { text-align: center; }

/* A consent box, wherever one is taken. Same control as `.config-addon` on the plan
   configurator -- same accent, same 15px box -- with two differences that come from the
   text rather than the styling: it aligns to the FIRST LINE instead of centring, because
   consent text wraps and a checkbox floating beside the middle of a paragraph reads as
   decoration, and the box gets its own top padding so a tick sits on the cap-height of the
   line it belongs to. Left-aligned even inside a centred column: a sentence someone is
   agreeing to should start where their eye already is. */
.consent-check {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  margin: 0 0 1.1rem;
  font-size: 0.83rem;
  line-height: 1.5;
  color: var(--ink-2);
  text-align: left;
  cursor: pointer;
}
.consent-check input {
  accent-color: var(--signal);
  width: 15px;
  height: 15px;
  margin: 0;
  flex: none;
  /* Optical, not arithmetic: nudges the box down onto the first line's cap-height. */
  margin-top: 0.15rem;
}

.auth-fine-print {
  margin-top: 1rem;
  text-align: center;
  font-size: 0.76rem;
  line-height: 1.5;
  color: var(--muted);
}
.auth-fine-print a { color: var(--muted); text-decoration: underline; }
.auth-fine-print a:hover { color: var(--ink); }

.auth-switch {
  margin-top: 1.5rem;
  text-align: center;
  font-size: 0.9rem;
  color: var(--muted);
}

/* ---------- Auth hero (split sign-in/up + graphic, pricing below) ---------- */

/* `align-items: start`, not `center`.
 *
 * Centred, the two columns are different heights and therefore share no edge: the form is tall
 * and the track-record panel beside it is short, so the panel floated ~157px below the form's
 * top with a void above it and a larger one below. Three round-7 testers measured that offset
 * independently and one put it as "nothing on that page shares a baseline". Starting them
 * together gives the row the one alignment it needs, and the shorter column's slack falls at
 * the bottom where it reads as space rather than as a misplacement. */
.auth-hero {
  display: grid;
  grid-template-columns: minmax(0, 25rem) minmax(0, 1fr);
  gap: 3rem;
  align-items: start;
  margin: 1rem 0 3.5rem;
}

.auth-hero .auth-wrap { margin: 0; max-width: none; }
.auth-hero .auth-card { max-width: none; box-shadow: none; }

.auth-tabs {
  display: flex;
  margin-bottom: 1.5rem;
  border-bottom: 1px solid var(--rule);
}
.auth-tabs button {
  flex: 1;
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  font-family: var(--mono);
  font-size: 0.76rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  padding: 0 0.5rem 0.75rem;
  cursor: pointer;
}
.auth-tabs button:hover { color: var(--ink); }
.auth-tabs button[aria-selected="true"] { color: var(--ink); border-bottom-color: var(--signal); }

.auth-graphic {
  background: var(--card);
  border: 1px solid var(--rule);
  box-shadow: inset 3px 0 0 var(--signal);
  padding: 2.5rem 2.25rem;
  min-height: 22rem;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.auth-graphic .eyebrow { margin-bottom: 2rem; }
.auth-graphic .iv-block + .iv-block { margin-top: 2.25rem; }
.auth-graphic .caption {
  margin-top: 2rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--rule);
  font-size: 0.85rem;
  color: var(--ink-2);
}
.auth-graphic .caption strong { color: var(--ink); }

.auth-statement {
  background: var(--card);
  border: 1px solid var(--rule);
  box-shadow: inset 3px 0 0 var(--signal);
  padding: 2.5rem 2.25rem;
  min-height: 22rem;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.auth-statement .eyebrow { margin-bottom: 1.75rem; }
.auth-stats {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.75rem;
}
.auth-stat-value {
  font-family: var(--display);
  font-stretch: 112%;
  font-weight: 700;
  font-size: 2.5rem;
  line-height: 1;
  color: var(--gain);
  margin: 0 0 0.4rem;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}
.auth-stat-label {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0;
}
.auth-stats-reseller {
  margin-top: 1.75rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--rule);
}
.auth-stats-reseller-label {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 0.9rem;
}
.auth-stats-reseller .auth-stat-value { color: var(--ink); font-size: 1.75rem; }
.auth-statement .caption {
  margin-top: 1.75rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--rule);
  font-size: 0.85rem;
  color: var(--ink-2);
}
@media (max-width: 420px) {
  .auth-stats { grid-template-columns: 1fr; gap: 1.25rem; }
}

/* ---------- Onboarding (persona + profile, post-signup) ---------- */

.onboard-main { display: flex; justify-content: center; padding: 3rem 0 4rem; }

.onboard-card {
  width: 100%;
  max-width: 30rem;
  background: var(--card);
  border: 1px solid var(--rule);
  box-shadow: inset 3px 0 0 var(--signal);
  padding: 2.5rem 2.25rem;
}

.onboard-steps {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 1.75rem;
}
.onboard-step {
  width: 1.6rem;
  height: 1.6rem;
  border-radius: 50%;
  border: 1px solid var(--rule-strong);
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.78rem;
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
}
.onboard-step.is-active { border-color: var(--signal); color: var(--signal); }
.onboard-step.is-done { border-color: var(--signal); background: var(--signal); color: #fff; }
.onboard-rule { flex: 1; height: 1px; background: var(--rule); }

.onboard-eyebrow {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 0.6rem;
}

.onboard-panel h1 { font-size: 1.5rem; margin: 0 0 0.5rem; }
.onboard-sub { color: var(--ink-2); font-size: 0.92rem; margin: 0 0 1.75rem; max-width: 42ch; }

.onboard-options { display: flex; flex-direction: column; gap: 0.75rem; margin-bottom: 2rem; }

.onboard-option {
  display: flex;
  align-items: flex-start;
  gap: 0.85rem;
  width: 100%;
  text-align: left;
  background: var(--paper);
  border: 1px solid var(--rule-strong);
  padding: 1.1rem 1.15rem;
  cursor: pointer;
  font: inherit;
  appearance: none;
  border-radius: 0;
}
.onboard-option:hover { border-color: var(--ink); }
.onboard-option.is-selected { border-color: var(--signal); background: var(--signal-wash); box-shadow: inset 3px 0 0 var(--signal); }

.onboard-option-mark {
  flex: none;
  width: 1.05rem;
  height: 1.05rem;
  margin-top: 0.15rem;
  border-radius: 50%;
  border: 1px solid var(--rule-strong);
  background: var(--card);
}
.onboard-option.is-selected .onboard-option-mark {
  border-color: var(--signal);
  background: var(--signal);
  box-shadow: inset 0 0 0 3px var(--card);
}

.onboard-option-body { display: flex; flex-direction: column; gap: 0.3rem; }
.onboard-option-title { font-weight: 600; color: var(--ink); font-size: 0.98rem; }
.onboard-option-desc { color: var(--ink-2); font-size: 0.86rem; line-height: 1.5; }

.onboard-option.dc-option-connected { border-color: var(--gain); }
.onboard-option.dc-option-connected .onboard-option-mark {
  border-color: var(--gain);
  background: var(--gain);
  box-shadow: inset 0 0 0 3px var(--card);
}
.onboard-option.dc-option-pending .onboard-option-mark { border-color: var(--rule-strong); border-style: dashed; }

.onboard-panel form { display: flex; flex-direction: column; gap: 1.1rem; }
.onboard-panel .auth-field input {
  width: 100%;
  font-family: var(--body);
  font-size: 0.95rem;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--rule-strong);
  padding: 0.75rem 0.85rem;
}
.onboard-panel .auth-field input::placeholder { color: var(--muted); }
.onboard-field-note { color: var(--muted); font-size: 0.83rem; line-height: 1.4; margin: -0.15rem 0 0.55rem; }

.onboard-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 0.5rem;
}
.onboard-actions .cta { flex: none; cursor: pointer; border-radius: 0; appearance: none; }
.onboard-actions .cta:disabled { opacity: 0.4; cursor: default; }
.onboard-actions .cta:disabled:hover { background: var(--signal); border-color: var(--signal); }

.onboard-panel .waitlist-status { text-align: center; margin-top: 1rem; }
.onboard-panel .auth-fine-print { margin-top: 1.25rem; }

.onboard-panel select {
  width: 100%;
  font-family: var(--body);
  font-size: 0.95rem;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--rule-strong);
  padding: 0.75rem 0.85rem;
}

.onboard-field-label {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 1.5rem 0 0.6rem;
}

.onboard-windows { display: flex; flex-direction: column; gap: 0.6rem; margin-bottom: 0.75rem; }

.onboard-window-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
/* Half-hour selects, not <input type="time">: the native picker offers every minute and
   its popup covers the row. "7:30 PM" is the widest label, so the pair fits one line in
   the narrow account column without wrapping "to" or the remove button onto its own. */
.onboard-window-row select {
  flex: 1 1 0;
  min-width: 0;
  font-family: var(--body);
  font-size: 0.92rem;
  color: var(--ink);
  background-color: var(--paper);
  border: 1px solid var(--rule-strong);
  border-radius: 0;
  padding: 0.6rem 1.35rem 0.6rem 0.5rem;
  appearance: none;
  -webkit-appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%),
                    linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: right 0.75rem center, right 0.45rem center;
  background-size: 0.3rem 0.3rem, 0.3rem 0.3rem;
  background-repeat: no-repeat;
  cursor: pointer;
}
.onboard-window-row select:focus { outline: 2px solid var(--ink); outline-offset: -1px; }
.onboard-window-row .ow-sep {
  flex: none;
  color: var(--muted);
  font-size: 0.82rem;
}
.onboard-window-row .ow-remove {
  flex: none;
  width: 2.4rem;
  align-self: stretch;
  font-size: 1.1rem;
  line-height: 1;
  background: transparent;
  color: var(--muted);
  border: 1px solid var(--rule-strong);
  cursor: pointer;
  appearance: none;
  border-radius: 0;
}
.onboard-window-row .ow-remove:hover { border-color: var(--miss); color: var(--miss); }

#onboard-add-window { cursor: pointer; appearance: none; border-radius: 0; }
#onboard-add-window:disabled { opacity: 0.4; cursor: default; }

@media (max-width: 860px) {
  .auth-hero { grid-template-columns: minmax(0, 1fr); gap: 2rem; }
  .auth-graphic, .auth-statement { order: -1; min-height: 0; }
}

.pricing-teaser { margin: 0 0 3rem; }
.pricing-teaser .section-label {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 1.25rem;
  padding-bottom: 0.9rem;
  border-bottom: 1px solid var(--rule);
}
.pricing-teaser .more-link { margin: -1.75rem 0 0; font-size: 0.85rem; color: var(--muted); max-width: 72ch; }
.pricing-teaser .more-link a { color: var(--muted); text-decoration: underline; }
.pricing-teaser .more-link a:hover { color: var(--ink); }

/* ---------- Account ---------- */

/* Two tracks, not auto-fit columns. Auto-fit gave four cards of wildly different
   heights one shared row height -- the plan card padded out to the profile form's
   depth -- and then wrapped 4 into 3+1. A narrow rail for what the account IS
   (plan) and a single stacked column for what you CHANGE (settings) has no orphan
   row and no stretch: align-items:start lets every card end where its content does. */
.account-grid {
  display: grid;
  grid-template-columns: minmax(0, 19rem) minmax(0, 1fr);
  align-items: start;
  gap: 1.5rem;
  /* Capped: the settings column only ever holds a form and a note column, so a
     full-bleed page width buys nothing but white space to the right of it. */
  max-width: 62rem;
  margin: 0 0 3rem;
}
.account-rail,
.account-main-col {
  display: grid;
  align-content: start;
  gap: 1.5rem;
  min-width: 0;
}

@media (max-width: 800px) {
  .account-grid { grid-template-columns: 1fr; }
}

/* Fields inside a now-wide card. One long column of inputs across a 40rem card
   reads as a form that lost its container, so short inputs pair up and anything
   carrying an explanatory note takes the full width. */
.account-fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  align-items: start;
  gap: 0 1.25rem;
}
.account-fields .auth-field.span-all { grid-column: 1 / -1; }
/* An alert-window row is two selects, "to" and a remove button; below ~19rem it has to
   clip its own times, so that card stacks its pair a column sooner. */
.account-fields-wide { grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr)); }
.account-card form .auth-field .field-note { max-width: 62ch; }
/* A full-width row is not a licence for a full-width control: a ZIP box the width
   of the card reads as though it wants more than five characters. */
.account-card form input,
.account-card form select { max-width: 24rem; }
.account-card form input#acct-zip { max-width: 10rem; }
.account-card form .form-save { margin-top: 0.35rem; }
/* The windows column has to line up with the zone column beside it: same label
   metrics, same spacing off the flex gap rather than its own margins, and a button
   that sizes to its text instead of stretching the whole column. */
.account-card .onboard-field-label {
  margin: 0;
  font-size: 0.7rem;
}
.account-card .onboard-windows { margin-bottom: 0; }
.account-card .onboard-windows:empty { display: none; }
.account-card form .auth-field > button { align-self: flex-start; }

.account-card {
  background: var(--card);
  border: 1px solid var(--rule);
  padding: 1.5rem 1.5rem 1.75rem;
}
.account-card h2 {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 500;
  margin: 0 0 1rem;
}
.account-card form { margin-top: 1rem; }
.account-card form .auth-field { margin-bottom: 0.85rem; }
.account-card form .auth-field label { font-size: 0.7rem; }
.account-card form input,
.account-card form select {
  width: 100%;
  font-family: var(--body);
  font-size: 0.95rem;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--rule-strong);
  padding: 0.65rem 0.75rem;
}
.account-card .waitlist-status { margin-top: 0.6rem; text-align: left; }

.account-plan-card { border-color: var(--rule-strong); }

.account-plan-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.5rem 0.75rem;
}

.account-plan-tier {
  font-family: var(--display);
  font-stretch: 112%;
  font-weight: 700;
  font-size: 1.6rem;
  color: var(--ink);
  line-height: 1.1;
}
/* Instrument blue is reserved for an asserted number, and a paid tier is the closest thing
   the account page has to one: it is the thing being charged for. Free stays ink -- colour
   here would be selling a plan back to the person already on it. */
.account-plan-tier.is-paid { color: var(--signal); }

/* The plan's state, as a mark rather than a sentence. It replaced "Status: active", which
   printed the database's own word in running prose -- correct, and the ugliest line on the
   page. A dot plus a word, because colour alone never carries a good/bad reading here. */
.plan-state {
  font-family: var(--mono);
  font-size: 0.66rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-2);
  background: var(--sunk);
  border: 1px solid var(--rule);
  padding: 0.2rem 0.5rem;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  white-space: nowrap;
}
.plan-state::before {
  content: "";
  width: 0.42rem;
  height: 0.42rem;
  border-radius: 50%;
  background: var(--muted);
}
.plan-state--ok { color: var(--ink); background: #e4efe8; border-color: #bcd8c7; }
.plan-state--ok::before { background: var(--gain); }
.plan-state--bad { color: var(--ink); background: var(--miss-wash); border-color: #e0b9b3; }
.plan-state--bad::before { background: var(--miss); }
.plan-state--warn::before { background: var(--rule-strong); }

/* Sits under the input it explains, not above the card. A field that asks for something
   non-obvious -- which of two prices you want to see, why we want a ZIP -- answers that where
   the question is being asked. */
.field-note {
  font-size: 0.8rem;
  line-height: 1.45;
  color: var(--muted);
  margin: 0.15rem 0 0;
}
.field-note strong { color: var(--ink-2); font-weight: 600; }

.account-status { font-size: 0.85rem; color: var(--muted); margin: 0.5rem 0 0; }
.account-status a { color: var(--muted); text-decoration: underline; }
.account-status a:hover { color: var(--ink); }

.account-upsell {
  margin-top: 1.25rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--rule);
}
.account-upsell-lede { font-size: 0.86rem; color: var(--ink-2); line-height: 1.5; margin: 0 0 0.85rem; }
.account-upsell-list {
  list-style: none;
  margin: 0 0 1.1rem;
  padding: 0;
  font-size: 0.85rem;
  color: var(--ink-2);
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
.account-upsell-list li { padding-left: 1.1rem; position: relative; }
.account-upsell-list li::before {
  content: '+';
  position: absolute;
  left: 0;
  color: var(--signal);
  font-weight: 700;
}
.account-upsell .cta { display: inline-block; }

.account-categories {
  list-style: none;
  margin: 0.85rem 0 0;
  padding: 0;
  font-size: 0.88rem;
}
.account-categories li {
  padding: 0.5rem 0;
  border-top: 1px solid var(--rule);
  display: flex;
  justify-content: space-between;
  gap: 0.75rem;
  color: var(--ink-2);
}
.account-categories li .k { color: var(--muted); text-transform: capitalize; }
.account-categories li .v { font-family: var(--mono); font-size: 0.82rem; }
.account-categories li.account-empty { color: var(--muted); border-top: 1px solid var(--rule); }

/* Two single-use invites, shown as two literal rows. A spent one keeps its row and greys
   out rather than disappearing -- a vanishing row reads as a miscount, not as a success. */
.account-invites { list-style: none; margin: 1rem 0 0; padding: 0; }
.account-invites .invite-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  padding: 0.7rem 0;
  border-top: 1px solid var(--rule);
}
.invite-link {
  font-family: var(--mono);
  font-size: 0.8rem;
  color: var(--ink-2);
  overflow-wrap: anywhere;
  flex: 1 1 12rem;
}
.invite-code {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  color: var(--muted);
  border: 1px solid var(--rule);
  padding: 0.15rem 0.4rem;
}
.invite-state {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.account-invites .invite-row.is-spent .invite-link { color: var(--muted); text-decoration: line-through; }

/* ---------- Delivery channels (priority handoffs) ---------- */

.dc-row-detail { font-family: var(--mono); font-size: 0.78rem; color: var(--muted); overflow-wrap: anywhere; }

.dc-modal-existing {
  list-style: none;
  margin: 0 0 1.25rem;
  padding: 0 0 1.1rem;
  border-bottom: 1px solid var(--rule);
  font-size: 0.88rem;
}
.dc-modal-existing-row {
  padding: 0.5rem 0;
  display: flex;
  align-items: center;
  gap: 0.6rem;
}
.dc-modal-existing-row .dc-row-detail { flex: 1; }
.dc-modal-add-heading {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 0.85rem;
}

.dc-modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.25rem;
}
body.dc-modal-open { overflow: hidden; }

.dc-modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(20, 20, 18, 0.5);
}

.dc-modal-panel {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 26rem;
  max-height: calc(100vh - 2.5rem);
  overflow-y: auto;
  background: var(--card);
  border: 1px solid var(--rule-strong);
  box-shadow: 0 1.25rem 3rem rgba(20, 20, 18, 0.25);
  padding: 1.75rem 1.75rem 2rem;
}

.dc-modal-close {
  position: absolute;
  top: 0.9rem;
  right: 0.9rem;
  width: 2rem;
  height: 2rem;
  border: 1px solid var(--rule-strong);
  background: var(--paper);
  color: var(--muted);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}
.dc-modal-close:hover { color: var(--ink); border-color: var(--ink); }

.dc-modal-title {
  font-family: var(--display);
  font-stretch: 112%;
  font-weight: 700;
  font-size: 1.2rem;
  color: var(--ink);
  margin: 0 2.25rem 1.25rem 0;
}

.dc-confirm-panel { max-width: 22rem; text-align: center; padding: 2.25rem 1.75rem 2rem; }
.dc-confirm-panel .dc-modal-title { margin: 0 0 0.6rem; }
.dc-confirm-icon {
  width: 2.75rem;
  height: 2.75rem;
  margin: 0 auto 1rem;
  border-radius: 50%;
  border: 2px solid var(--gain);
  color: var(--gain);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.3rem;
  font-weight: 700;
}
.dc-confirm-error .dc-confirm-icon { border-color: var(--miss); color: var(--miss); }
.dc-confirm-message { font-size: 0.9rem; color: var(--ink-2); line-height: 1.5; margin: 0 0 1.4rem; }
.dc-confirm-panel .cta { width: 100%; text-align: center; cursor: pointer; appearance: none; border-radius: 0; }

.dc-confirm-delete-actions { display: flex; gap: 0.75rem; }
.dc-confirm-delete-actions .cta { flex: 1; cursor: pointer; appearance: none; border-radius: 0; }
.dc-delete-btn {
  background: var(--miss);
  border-color: var(--miss);
  color: var(--paper);
}
.dc-delete-btn:hover { background: var(--ink); border-color: var(--ink); }
.dc-delete-btn:disabled { opacity: 0.6; cursor: default; }

.dc-badge {
  flex: none;
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 0.2rem 0.5rem;
}
.dc-badge-on { color: var(--gain); border: 1px solid var(--gain); }
.dc-badge-pending { color: var(--muted); border: 1px solid var(--rule-strong); }
.dc-badge-off { color: var(--muted); border: 1px solid var(--rule); }

.dc-remove {
  flex: none;
  width: 1.8rem;
  height: 1.8rem;
  line-height: 1;
  background: transparent;
  color: var(--muted);
  border: 1px solid var(--rule-strong);
  cursor: pointer;
  appearance: none;
  border-radius: 0;
}
.dc-remove:hover { border-color: var(--miss); color: var(--miss); }

/* ---------- Watchlists ----------
   Borrows .dc-badge and .dc-remove rather than drawing a second pair: a paused watchlist
   and a disabled channel are the same statement and must not be two different shapes.

   The rule itself is a spec, not a sentence -- a mono label column against a value column,
   which is what gives a list of rules two edges to align on. The row gap is deliberately
   bigger than the gap inside a row (roughly 2:1), so several rules read as several things
   rather than as one undifferentiated column. */

.wl-list { list-style: none; margin: 1rem 0 0; padding: 0; }
.wl-list:empty { display: none; }
.wl-row { padding: 0.9rem 0 1rem; border-top: 1px solid var(--rule); }
.wl-row.is-paused .wl-name,
.wl-row.is-paused .wl-spec-v { color: var(--muted); }

.wl-row-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  margin-bottom: 0.55rem;
}
.wl-name {
  font-family: var(--display);
  font-stretch: 112%;
  font-weight: 700;
  font-size: 1rem;
  color: var(--ink);
  flex: 1 1 8rem;
  overflow-wrap: anywhere;
}
.wl-row-head .cta { cursor: pointer; appearance: none; border-radius: 0; flex: none; }

.wl-spec { display: grid; grid-template-columns: 7.5rem 1fr; gap: 0.2rem 0.75rem; }
.wl-spec-row { display: contents; }
.wl-spec-k {
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  padding-top: 0.12rem;
}
.wl-spec-v { font-size: 0.86rem; color: var(--ink-2); overflow-wrap: anywhere; }

/* The form opens under the rules, so it takes the same hairline the rules take -- without
   it the first field reads as part of the last rule rather than as a new one. */
#acct-wl-form { border-top: 1px solid var(--rule); padding-top: 1.1rem; margin-top: 0.9rem; }
#acct-wl-add { margin-top: 1rem; cursor: pointer; appearance: none; border-radius: 0; }
.wl-list:empty + #acct-wl-form { border-top: 0; padding-top: 0; }

/* A percentage is two digits. Given the full column it reads as a field expecting a
   sentence, the same way #acct-zip did before it was cut down. */
.account-card form input#wl-min-savings { max-width: 7rem; }

/* The band is one field made of two inputs, so it wraps as a pair the way an alert window
   does and reuses that row's .ow-sep for the word between them. */
.wl-band { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
.account-card form .wl-band input { flex: 1 1 7rem; min-width: 7rem; width: auto; }

/* Narrow keeps the two columns rather than stacking them: stacking doubles a rule's height
   for no gain, and the label column is exactly what makes a list of rules scannable. The
   gutter shrinks to the widest label instead. */
@media (max-width: 30rem) {
  .wl-spec { grid-template-columns: 5.5rem 1fr; gap: 0.3rem 0.6rem; }
  .wl-spec-k { font-size: 0.64rem; letter-spacing: 0.06em; }
  /* The page renders in quirks mode (no DOCTYPE anywhere in web/), so these widths are
     content-box: padding and border sit OUTSIDE the basis. At 375 a 7rem basis puts the
     pair over the card and wraps the second input under the word "to". */
  .account-card form .wl-band input { flex: 1 1 4.5rem; min-width: 4.5rem; }
}

.dc-actions { display: flex; align-items: center; gap: 0.75rem; margin-top: 0.85rem; }
.dc-actions .cta { cursor: pointer; appearance: none; border-radius: 0; }
.dc-bot-actions { flex-direction: column; align-items: flex-start; gap: 0.6rem; }
.dc-bot-manual-join { font-size: 0.78rem; color: var(--muted); text-decoration: underline; }
.dc-bot-manual-join:hover { color: var(--ink); }
.dc-bot-manual-join[aria-disabled="true"] { pointer-events: none; opacity: 0.5; }

.dc-push-qr { margin-top: 0.85rem; display: flex; gap: 1rem; align-items: flex-start; }
.dl-qr { flex: none; width: 132px; height: 132px; background: #fff; padding: 8px; box-shadow: inset 0 0 0 1px var(--rule); }
.dl-qr svg { display: block; width: 100%; height: 100%; }
.dl-note { color: var(--muted); font-size: 0.82rem; line-height: 1.5; max-width: 34ch; margin: 0; }

/* ---------- Responsive ---------- */

@media (max-width: 720px) {
  /* Narrower than the desktop picture on purpose: at 375px the text column is the scarce
     thing, and every pixel the picture takes comes straight out of the measure. */
  .card { grid-template-columns: 64px minmax(0, 1fr); }
  .card .thumb, .card .thumb-fallback { width: 64px; height: 64px; }
  .headline {
    grid-column: 1 / -1;
    text-align: left;
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
  }
  .headline .savings-label, .headline .savings-sub { margin-top: 0; }
  .site-header nav { margin-left: 0; width: 100%; gap: 1rem; }
  .filter-count { margin-left: 0; }
}

@media (prefers-reduced-motion: no-preference) {
  .card { transition: background-color 120ms ease; }
  .card:hover { background: var(--card); }
}

.site-footer .footer-links { display: block; margin-top: 0.5rem; }
.site-footer .footer-links a { color: var(--muted); text-decoration: underline; text-underline-offset: 0.2em; }
.site-footer .footer-links a:hover { color: var(--ink); }

/* ---------- Pricing ---------- */

.founding-banner {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  flex-wrap: wrap;
  border-left: 3px solid var(--signal);
  background: var(--card);
  padding: 0.9rem 1.1rem;
  margin: 0 0 2.5rem;
}
.founding-banner .msg { font-size: 0.9rem; color: var(--ink-2); }
.founding-banner .msg strong { color: var(--ink); }
.founding-banner .seats {
  font-family: var(--mono);
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  color: var(--muted);
  white-space: nowrap;
}
.founding-banner .seats strong { color: var(--ink); font-weight: 700; }

.pricing-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1.5rem;
  margin: 0 0 3rem;
}

.price-card {
  display: flex;
  flex-direction: column;
  background: var(--card);
  border: 1px solid var(--rule);
  padding: 1.5rem 1.5rem 1.75rem;
}
.price-card { cursor: pointer; }
.price-card.is-selected { border-color: var(--signal); box-shadow: inset 3px 0 0 var(--signal); }

.price-card .tier-name {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.5rem;
}

.price-card .tier-price {
  font-family: var(--display);
  font-stretch: 112%;
  font-weight: 700;
  font-size: 2rem;
  color: var(--ink);
  line-height: 1;
  margin-bottom: 0.15rem;
}
.price-card .tier-price span {
  font-family: var(--mono);
  font-size: 0.72rem;
  font-weight: 400;
  color: var(--muted);
  text-transform: none;
  letter-spacing: normal;
}

/* The launch price: the regular price struck through, the promotional one large and in the
   gain green. Green is the colour this site reserves for a saving, which is what this is --
   and per the palette note at the top of the file it is never carrying the meaning alone:
   the struck number beside it, and the "then $9/mo" line under it, both say so in words. */
.price-card .tier-price .tier-was {
  font-family: var(--mono);
  font-size: 1.05rem;
  font-weight: 400;
  color: var(--muted);
  text-decoration: line-through;
  text-decoration-thickness: 2px;
  margin-right: 0.45rem;
  vertical-align: 0.3rem;
}
.price-card .tier-price .tier-now {
  font-family: var(--display);
  font-stretch: 112%;
  /* Clamped rather than fixed: this is the largest type on the card and a phone is 400px. */
  font-size: clamp(2.2rem, 9vw, 2.9rem);
  font-weight: 700;
  color: var(--gain);
  letter-spacing: -0.01em;
}

.price-card .tier-standard {
  font-size: 0.78rem;
  color: var(--muted);
  margin-bottom: 0.35rem;
}

/* What the seat actually gets, on its own line and louder than the price footnote above it.
   Volume is the thing people compare between tiers, and it read as an afterthought while it
   sat behind a middot on the "then $9/mo" line. Every card carries one, including Customize,
   so the three heads keep the same number of lines and the subgrid rows stay level. */
.price-card .tier-alerts {
  font-family: var(--display);
  font-stretch: 112%;
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--ink);
  line-height: 1.25;
  margin-bottom: 1.1rem;
}

.price-card ul {
  list-style: none;
  margin: 0 0 1.4rem;
  padding: 0;
  font-size: 0.88rem;
  color: var(--ink-2);
  flex: 1;
}
.price-card li {
  padding: 0.38rem 0;
  border-top: 1px solid var(--rule);
}

/* No tick. Removed with the landing page's, for the same reason: a check mark confirms a
   line against an unticked alternative, and there is none on this row. See the note on
   `.landing .plan li`. */
.price-card .cta { text-align: center; }

.price-card .tier-head {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}

/* Wide enough for all three columns: the header block, every feature row and the button
   share their track heights across the cards, so nothing drifts a few pixels out of line. */
/* The subgrid that used to live here is gone.
 *
 * It gave the row seven feature tracks so all three cards stated each bullet at the same
 * height, and pinned every card to `grid-row: 1 / -1`. Pro has four bullets against the other
 * two cards' seven, so Pro spanned three empty tracks: a 200-300px hole between its last
 * feature and its button. Five round-8 testers reported it and three more in round 9, and one
 * put it exactly right -- the cheapest plan, the one most people should buy, was drawn as an
 * absence. `align-items: start` on the grid could not win against a card told to span every
 * row, which is why removing that rule in round 8 changed nothing.
 *
 * What is lost is bullet-by-bullet alignment between cards, which nobody has ever mentioned.
 * What is kept is the thing that actually reads as alignment: a shared top edge, one width,
 * one border, and heads that align because their content is the same shape. */

/* ---------- Plan builder (Customize tier, step-through like onboarding) ---------- */

.onboard-card-wide { max-width: 42rem; }

.plan-categories { display: flex; flex-direction: column; gap: 0.75rem; margin-bottom: 2rem; }

.plan-cat {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  width: 100%;
  text-align: left;
  background: var(--paper);
  border: 1px solid var(--rule-strong);
  padding: 1rem 1.15rem;
  cursor: pointer;
  font: inherit;
  appearance: none;
  border-radius: 0;
}
.plan-cat:hover { border-color: var(--ink); }
.plan-cat.is-selected { border-color: var(--signal); background: var(--signal-wash); box-shadow: inset 3px 0 0 var(--signal); }

.plan-cat-mark {
  flex: none;
  width: 1.05rem;
  height: 1.05rem;
  border-radius: 3px;
  border: 1px solid var(--rule-strong);
  background: var(--card);
}
.plan-cat.is-selected .plan-cat-mark {
  border-color: var(--signal);
  background: var(--signal);
  box-shadow: inset 0 0 0 3px var(--card);
}

.plan-cat .onboard-option-body { flex: 1; }
.plan-cat-seats {
  flex: none;
  font-family: var(--mono);
  font-size: 0.7rem;
  color: var(--muted);
  white-space: nowrap;
}
.plan-cat-seats strong { color: var(--ink); }

#plan-bands { display: flex; flex-direction: column; gap: 0.75rem; margin-bottom: 1.5rem; }
#plan-bands .config-cat { background: var(--paper); }

.onboard-panel .config-total { margin: 1.5rem 0; }
.onboard-panel .config-addon { margin: 0 0 1.5rem; }

.config-cat {
  border: 1px solid var(--rule-strong);
  background: var(--paper);
  padding: 0.85rem 1rem;
}
.config-cat .row-1 {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.6rem;
  margin-bottom: 0.5rem;
}
.config-cat .cat-name { font-family: var(--body); font-size: 0.92rem; color: var(--ink); }
.config-cat .volume {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--mono);
  font-size: 0.72rem;
  color: var(--muted);
}
.config-cat .volume-input {
  width: 5.5rem;
  border: 1px solid var(--rule-strong);
  background: var(--paper);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 0.86rem;
  padding: 0.3rem 0.45rem;
}
.config-cat .volume-input:focus { outline: none; border-color: var(--signal); }
.config-cat .volume-price { margin-left: auto; color: var(--ink); }

.config-addon {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 0.9rem;
  color: var(--ink-2);
  margin-bottom: 1.5rem;
}
.config-addon input { accent-color: var(--signal); width: 15px; height: 15px; }

.config-total {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  border-top: 1px solid var(--rule-strong);
  padding-top: 1.1rem;
}
.config-total .label {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}
.config-total .amount {
  font-family: var(--display);
  font-stretch: 112%;
  font-weight: 700;
  font-size: 1.9rem;
  color: var(--ink);
}
.config-total .amount span {
  font-family: var(--mono);
  font-size: 0.7rem;
  font-weight: 400;
  color: var(--muted);
}
.config-total .breakdown {
  flex-basis: 100%;
  font-family: var(--mono);
  font-size: 0.76rem;
  color: var(--muted);
  margin-top: 0.4rem;
}

.pricing-faq { margin: 0 0 2rem; }
.pricing-faq summary {
  cursor: pointer;
  font-size: 0.85rem;
  color: var(--muted);
  list-style: none;
}
.pricing-faq summary::-webkit-details-marker { display: none; }
.pricing-faq summary::before { content: '+ '; }
/* Literal glyphs, and a non-breaking space after each.
   The open marker was a CSS escape with a trailing space, and CSS eats that space as the
   escape's terminator -- so it shipped as one character hard against the question text:
   round 10 read it back as "-What is SleeperLot". The rule in .claude/rules/frontend.md
   is to paste literal glyphs rather than escapes, and this is the case it was written for. */
.pricing-faq[open] summary::before { content: '− '; }
.pricing-faq p { margin: 0.9rem 0 0; font-size: 0.88rem; }

/* ---------- Landing: scroll narrative ----------
 *
 * The home page is read, not scanned, so it is built as a sequence of beats rather than a
 * wall: one claim per screen, arriving as the reader reaches it. The motion is the only
 * animation on the site and it is deliberately small — a 14px rise and a fade, no scale,
 * no slide-in from the side. The page is an instrument; instruments do not swoosh.
 *
 * The hidden state is scoped to .js-reveal (set inline in <head>, cleared by reveal.js for
 * reduced-motion visitors) so that a reader without JavaScript, or with the script blocked,
 * gets the full page rather than an empty one.
 */

.js-reveal [data-reveal] {
  opacity: 0;
  transform: translate3d(0, 14px, 0);
  transition: opacity 620ms cubic-bezier(0.22, 0.61, 0.36, 1),
              transform 620ms cubic-bezier(0.22, 0.61, 0.36, 1);
  will-change: opacity, transform;
}
.js-reveal [data-reveal].is-in {
  opacity: 1;
  transform: none;
}

/* The hero is exempt. Everything below the fold can afford to wait for reveal.js, which
   runs from the bottom of the document behind six other scripts; the first screen cannot.
   Three cold readers in a row described the landing page as "blank for two or three
   seconds" -- which is exactly how long it took the observer to be wired up. The headline
   is the one thing on this site that must be painted on the first frame, so it is never
   gated on a script running at all. It animates in on its own clock instead. */
.js-reveal .hero [data-reveal] {
  opacity: 1;
  transform: none;
  animation: hero-in 620ms cubic-bezier(0.22, 0.61, 0.36, 1) both;
}
.js-reveal .hero .eyebrow[data-reveal] { animation-delay: 0ms; }
.js-reveal .hero h1[data-reveal] { animation-delay: 70ms; }
.js-reveal .hero .lede[data-reveal] { animation-delay: 140ms; }
.js-reveal .hero .cta-row[data-reveal] { animation-delay: 210ms; }

@keyframes hero-in {
  from { opacity: 0; transform: translate3d(0, 14px, 0); }
  to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .js-reveal [data-reveal] { opacity: 1; transform: none; transition: none; }
  .js-reveal .hero [data-reveal] { animation: none; }
}

/* Beats are sized off the viewport so one claim occupies the screen, but with a min-height
   rather than a fixed height: the text must be able to grow past the fold on a short
   window instead of being clipped by it. */
.landing .beat {
  min-height: 74vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* The bottom value clears .beat-rule, which is 56px tall sitting 2.25rem off the bottom --
     92px in total. Anything less and the closing link of the copy is painted over the ticks. */
  padding: 4rem 0 7.5rem;
  border-top: 1px solid var(--rule);
}

.landing .beat h2 {
  font-size: clamp(2rem, 5vw, 3.1rem);
  /* ch on the display face, so the measure holds as the clamp resizes the type — a fixed
     px width would break the line in a different place at every viewport. */
  max-width: 15ch;
  margin: 0;
}
.landing .beat h2 em {
  font-style: normal;
  border-bottom: 3px solid var(--signal);
}

/* The lead beat: an extrapolated yearly figure over the hard counts it was derived from.
   The big number is the one place on the page where the instrument blue is used at size,
   because it is the page's single largest assertion — and it sits directly above its own
   working, so the reader can see what it was annualised from without leaving the section. */
.landing .beat-lead h2 { max-width: 20ch; }

.landing .beat-sub {
  font-size: 1.15rem;
  color: var(--ink-2);
  max-width: 40ch;
  margin: 1.25rem 0 0;
}

.landing .huge {
  display: block;
  font-size: clamp(3.2rem, 9vw, 6rem);
  line-height: 0.92;
  letter-spacing: -0.03em;
  color: var(--signal);
  margin-bottom: 0.4rem;
}

.landing .beat-projection .huge {
  font-size: clamp(2.6rem, 6vw, 4rem);
  color: var(--ink);
}

/* When the projection opens the page it carries the page's largest assertion and takes the
   full headline size — but never the instrument blue, which stays the mark of a measured
   number. The measured window sits in the next screen down, in blue, at the same size. */
.landing .beat-projection.is-lead .huge {
  font-size: clamp(3.2rem, 9vw, 6rem);
}

.landing .beat-stats {
  list-style: none;
  display: grid;
  /* Capped tracks, not 1fr: at two stats a fractional track spreads a short pair to
     the far corners of the band and reads as a table with the middle deleted. */
  grid-template-columns: repeat(auto-fit, minmax(160px, 15rem));
  justify-content: start;
  gap: 1.75rem 2.5rem;
  margin: 2.5rem 0 0;
  padding: 1.5rem 0 0;
  border-top: 1px solid var(--rule);
}

.landing .beat-stats li { display: flex; flex-direction: column; gap: 0.35rem; }

.landing .beat-stats .s-v {
  font-family: var(--display);
  font-stretch: 112%;
  font-weight: 700;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--ink);
}

.landing .beat-stats .s-k {
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

.landing .beat-note {
  margin: 1.5rem 0 0;
  max-width: 46ch;
  color: var(--muted);
  font-size: 0.84rem;
}

/* The one beat that carries a picture: the day bar, in the middle of the claim it
   supports rather than parked in a stats strip away from it. */
.landing .beat-viz { margin-top: 2rem; max-width: 540px; }
.landing .beat-viz .head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 0.75rem;
}
.landing .beat-viz .head .k {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}
.landing .beat-viz .head .v {
  font-family: var(--display);
  font-stretch: 112%;
  font-weight: 700;
  font-size: 1.5rem;
  color: var(--ink);
}

/* The hero is the foreground plane, pinned to the top of the viewport while the stack of
   sections slides up over it. Two consequences worth stating:
     - It must be no taller than the viewport, or it would scroll before it ever pinned.
     - It must be BELOW the stack, and the stack must be opaque, or the hero would show
       through the gaps between bands as it is covered. */
/* The hero stopped being pinned.
 *
 * It used to be `position: sticky` with the stack sliding up over it, dimmed to 0.12 by
 * js/hero.js as it was covered. At any scroll position in between, the headline was a
 * light-grey ghost cut in half by the top edge of the next section: round 8 reported it twice
 * unprompted, once as "it reads as a z-index bug, not a transition" and once as the hero being
 * "sliced". The effect also predates this column having anything in it -- there was nothing to
 * lose behind the stack then, and now there are three live rows. */
.landing .hero {
  position: static;
  min-height: calc(100vh - 8rem);
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 3rem 0 4rem;
  /* Dimmed by js/hero.js as it is covered; this is the value before any script runs, so a
     visitor without JavaScript gets a hero at full strength rather than a blank one. */
  will-change: opacity, transform;
}

.landing .stack {
  position: relative;
  z-index: 1;
  background: var(--paper);
}
.landing .hero h1 { max-width: 17ch; }
.landing .hero .lede { max-width: 44ch; }

/* The figures strip is the only place on the landing page where numbers stand alone, so
   each one carries its unit as a label — a bare $10,098.96 asserts nothing on its own. */
.landing .figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 2.5rem 2rem;
  padding: 5rem 0;
  border-top: 1px solid var(--rule);
}

.landing .figure { display: flex; flex-direction: column; gap: 0.5rem; }

.landing .fig-v {
  font-family: var(--display);
  font-stretch: 112%;
  font-weight: 700;
  font-size: clamp(1.8rem, 3.4vw, 2.5rem);
  line-height: 1;
  color: var(--ink);
}

.landing .fig-k {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  max-width: 26ch;
}

.landing .figures .eyebrow { grid-column: 1 / -1; margin: 0; }
.landing .figures .stat-note { grid-column: 1 / -1; margin-top: 0.5rem; }

@media (max-width: 720px) {
  .landing .beat { min-height: 0; padding: 3rem 0; }
  .landing .hero { padding: 1.5rem 0 3rem; }
  .landing .figures { padding: 3rem 0; }
}

/* ---------- Landing: bands, tiers, closer ----------
 *
 * Alternating grounds break the page out of one continuous column without adding a single
 * word. The band bleeds to the viewport edge from inside the centred column via a pseudo
 * element rather than a wrapper div, so the markup stays flat and the text keeps its
 * measure. It sits behind content (z-index: -1) and is inert to the pointer. */

/* Full-bleed band. Three earlier attempts failed, all recorded so none is tried again:
 *
 *   ::before at z-index -1   disappears behind the root canvas background.
 *   box-shadow offsets       an offset shadow is the box moved, not the box extended, so a
 *                            -100vmax offset lands the fill entirely off-screen.
 *   negative 50vw margins    reach the edges, but 50vw counts the vertical scrollbar and the
 *                            content box does not, so the section overhangs and becomes
 *                            horizontally scrollable. Suppressing that needed overflow-x on
 *                            html, which -- because the page then rendered in quirks mode --
 *                            broke scroll event dispatch and silently killed every scroll
 *                            effect. Every page now carries a doctype, so that specific trap
 *                            is gone; the scrollbar-width correction below is kept anyway,
 *                            because it is the fix that does not depend on overflow at all.
 *
 * So: negative margins, corrected by the real scrollbar width, which only script can measure
 * (--sbw, set in the head before first paint). It falls back to 0px, which is exactly right
 * on any platform with overlay scrollbars. */
.landing .band {
  background: var(--sunk);
  border-top: 0;
  margin-left: calc(50% - 50vw + var(--sbw, 0px) / 2);
  margin-right: calc(50% - 50vw + var(--sbw, 0px) / 2);
  padding-left: calc(50vw - 50% - var(--sbw, 0px) / 2);
  padding-right: calc(50vw - 50% - var(--sbw, 0px) / 2);
}

/* Scroll position, drawn as the ruled edge the rest of the page is built from. */
.scroll-rail {
  position: fixed;
  top: 0;
  left: 0;
  height: 2px;
  width: 100%;
  transform: scaleX(0);
  transform-origin: 0 50%;
  background: var(--signal);
  z-index: 50;
  pointer-events: none;
}

/* ---------- Landing: closer ---------- */

.landing .closer .cta-row { margin-top: 2rem; }

@media (max-width: 720px) {
  .landing .tier-row { margin-top: 1.75rem; }
}

/* ---------- Landing: depth ----------
 *
 * Three planes moving at different rates (see js/parallax.js). Everything back here is
 * texture: aria-hidden, unselectable, and inert to the pointer, so the page reads and
 * tabs exactly as it would without it.
 *
 * Both layers are drawn from the ink colour at very low alpha rather than from a new
 * grey, so they stay in the palette and cannot drift out of contrast with the two
 * grounds they cross. */

.backdrop {
  position: fixed;
  inset: -20vh 0 -20vh 0;
  z-index: -2;
  pointer-events: none;
  /* The measurement bench, at page scale: a field of hairline ticks. */
  background-image:
    repeating-linear-gradient(to right, rgba(20, 24, 27, 0.045) 0 1px, transparent 1px 96px),
    repeating-linear-gradient(to bottom, rgba(20, 24, 27, 0.028) 0 1px, transparent 1px 96px);
  will-change: transform;
}

.landing .beat {
  position: relative;
  /* No clipping here, deliberately. The clip existed to contain a background word wider
     than its column, and it was also what let that layer vanish -- drift walked it out of
     its own clip box. The ruler spans left:0/right:0, so it can never overflow, which means
     nothing has to be clipped and there is no box for it to fall out of. */
}

/* The middle plane is a ruler, not a word.
 *
 * An oversized word was the first attempt and it is fragile by construction: it has to be
 * wider than the column to read as a background, which means it is always clipped, and how
 * it clips depends on how long the word happens to be. MEASURED lost its M and read
 * "IEASURED"; SEATS was short enough to hide behind the pricing cards with two letters
 * poking out of the left edge.
 *
 * A ruler has no such failure mode. It is a pattern, so it is correct at every width, it
 * cannot be half-there, and it is the site's own vocabulary rather than decoration bolted
 * on: ticks rising off a baseline, the same mark the interval bars are built from. */
.beat-rule {
  position: absolute;
  left: 0;
  right: 0;
  /* Parked in the section's own bottom padding, where there is no content to collide with.
     Sitting it mid-section put ticks straight through the figures. */
  bottom: 2.25rem;
  height: 56px;
  pointer-events: none;
  will-change: transform;
  background-image:
    /* the baseline the ticks stand on */
    linear-gradient(to right, var(--rule-strong) 0 100%),
    /* majors, every 120px */
    repeating-linear-gradient(to right, rgba(20, 24, 27, 0.30) 0 1px, transparent 1px 120px),
    /* minors, every 24px */
    repeating-linear-gradient(to right, rgba(20, 24, 27, 0.18) 0 1px, transparent 1px 24px);
  background-repeat: no-repeat, repeat-x, repeat-x;
  /* Each layer's height is how far that tick rises off the baseline. */
  background-size: 100% 1px, 100% 52px, 100% 22px;
  background-position: 0 100%, 0 100%, 0 100%;
  opacity: 0.5;
}

/* The sunk band is darker, so the same ink reads fainter on it. */
.landing .band .beat-rule { opacity: 0.62; }

@media (max-width: 720px) {
  .beat-rule { height: 44px; background-size: 100% 1px, 100% 34px, 100% 16px; }
  .backdrop { background-size: 64px 64px, 64px 64px; }
}

/* ---------- Landing: plans ----------
 *
 * The full cards, not the three-line summary that was here before: someone reading this far
 * down the page is deciding, and a decision needs the whole of what is on offer rather than
 * a link to go and find it. */

.landing .plan-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1.25rem;
  margin: 2.5rem 0 0;
  align-items: stretch;
}

.landing .plan {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 1.5rem 1.4rem 1.6rem;
  background: var(--paper);
  border: 1px solid var(--rule);
}

/* One card carries the recommendation, and it is marked with a rule and a label rather
   than with a colour fill -- the instrument blue is reserved for asserted numbers. */
.landing .plan.is-featured {
  border-color: var(--rule-strong);
  border-top: 3px solid var(--signal);
}

.landing .p-flag {
  font-family: var(--mono);
  font-size: 0.62rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--signal);
}

.landing .p-name {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

.landing .p-price {
  font-family: var(--display);
  font-stretch: 112%;
  font-weight: 700;
  font-size: 2.1rem;
  line-height: 1;
  color: var(--ink);
}
.landing .p-price span { font-size: 0.85rem; font-weight: 400; color: var(--muted); }

/* Same launch-price treatment as the pricing cards -- see the note beside `.tier-was`. */
.landing .p-price .p-was {
  font-family: var(--mono);
  font-size: 1rem;
  font-weight: 400;
  color: var(--muted);
  text-decoration: line-through;
  text-decoration-thickness: 2px;
  margin-right: 0.45rem;
  vertical-align: 0.3rem;
}
.landing .p-price .p-now {
  font-family: var(--display);
  font-stretch: 112%;
  font-size: clamp(2.3rem, 9vw, 3rem);
  font-weight: 700;
  color: var(--gain);
  letter-spacing: -0.01em;
}

.landing .p-standard {
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.04em;
  color: var(--muted);
}

/* The volume line on the home page's plan cards -- see `.tier-alerts` on the pricing cards. */
.landing .p-alerts {
  font-family: var(--display);
  font-stretch: 112%;
  font-size: 1rem;
  font-weight: 700;
  color: var(--ink);
  line-height: 1.25;
}

/* The CTA is the last thing in the card and it belongs on one line across all three, however
   many features each lists. */
.landing .plan .cta { margin-top: auto; }
.landing .plan ul {
  list-style: none;
  margin: 0.9rem 0 1.4rem;
  padding: 0;
  font-size: 0.88rem;
  color: var(--ink-2);
}

/* The tick is gone.
 *
 * There was a blue check mark in front of every bullet in all three cards -- between 13 and 23
 * of them depending on who counted -- and five of ten round-7 testers made the same point
 * independently: a tick confirms something against an unticked alternative, and there is no
 * unticked state anywhere on this row, so every one of them carried zero information. One
 * tester found the case that settles it: a blue confirming tick in front of the words
 * "Limited watchlists", which is a limit, not a feature.
 *
 * What replaces it is nothing. The hairline rule between rows already separates them, it is
 * the rule this whole site is drawn with, and a list does not need a glyph to be a list. */
.landing .plan li {
  padding: 0.38rem 0;
  border-top: 1px solid var(--rule);
}

.landing .plan .cta { margin-top: auto; text-align: center; }

.landing .p-head {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  justify-content: flex-end;
}

@media (min-width: 62rem) {
  @supports (grid-template-rows: subgrid) {
    .landing .plan-grid {
      grid-template-columns: repeat(3, 1fr);
      grid-template-rows: auto repeat(7, auto) auto;
    }
    .landing .plan {
      display: grid;
      grid-row: 1 / -1;
      grid-template-rows: subgrid;
      gap: 0;
    }
    .landing .plan ul {
      display: grid;
      grid-row: span 7;
      grid-template-rows: subgrid;
      margin: 0.9rem 0 0;
      align-content: start;
    }
    .landing .plan .cta { align-self: end; margin-top: 1.1rem; }
  }
}

@media (max-width: 720px) {
  .landing .hero { min-height: 0; padding: 1.5rem 0 3rem; }
  .landing .plan-grid { margin-top: 1.75rem; }
}

/* The alert gets a screen to itself: it is the one place the page shows the product rather
   than argues about it, and it was previously read past on the way to the next claim. */
.landing .beat-alert {
  min-height: 100vh;
  justify-content: center;
}

/* ---------- Landing: the example alert ----------
 *
 * Showing one is worth more than describing one, and it costs less text. It is built to be
 * unmistakably an example: the label is the first thing in the figure, in the reject red
 * used nowhere else on this page, and the card is drawn with a dashed edge so it does not
 * read as one of the real cards on the board. */

.landing .alert-demo {
  margin: 2.25rem 0 0;
  max-width: 460px;
  padding: 1.1rem 1.2rem 1.2rem;
  background: var(--paper);
  border: 1px dashed var(--rule-strong);
}

.landing .ad-head {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  margin-bottom: 0.9rem;
  padding-bottom: 0.9rem;
  border-bottom: 1px solid var(--rule);
}

.landing .ad-thumb { width: 56px; height: 56px; flex: 0 0 56px; }

.landing .ad-flag {
  display: block;
  font-family: var(--mono);
  font-size: 0.56rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--miss);
  margin-bottom: 0.6rem;
}

.landing .ad-title {
  font-family: var(--display);
  font-stretch: 112%;
  font-weight: 700;
  font-size: 1rem;
  line-height: 1.3;
  color: var(--ink);
  margin: 0;
}

.landing .ad-rows { margin: 0; }
.landing .ad-rows > div {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.32rem 0;
}
.landing .ad-rows dt {
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.landing .ad-rows dd {
  margin: 0;
  font-family: var(--display);
  font-stretch: 112%;
  font-weight: 700;
  font-size: 1rem;
  color: var(--ink);
}

/* The gap is the number the whole alert exists to deliver, so it is the one that carries
   colour -- and it carries a word too, never colour alone. Not a minus sign: "-$580" reads
   as a negative amount, when what it means is $580 UNDER the ask. */
.landing .ad-rows dd.ad-gain { color: var(--gain); }

/* The clock. Set in mono because it is a reading off an instrument rather than a headline
   figure, and because a countdown wants fixed-width digits. The value in the markup is
   inside the board's real 5-minute window (board.py DEFAULT_WINDOW_MINUTES) -- an example
   showing 14 minutes advertised a product that does not exist. */
.landing .ad-rows dd.ad-clock {
  font-family: var(--mono);
  font-size: 0.86rem;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
}

/* The exclusivity row. Set in the same ink as the other values rather than in a colour --
   it is a fact about delivery, not a number being asserted, and the instrument blue on this
   page belongs to the figures. */
.landing .ad-rows dd.ad-solo {
  font-family: var(--mono);
  font-size: 0.82rem;
  letter-spacing: 0.04em;
}
.landing .ad-rows dd.ad-gain span {
  font-family: var(--mono);
  font-size: 0.66rem;
  font-weight: 400;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin-left: 0.3em;
}

@media (max-width: 720px) {
  .landing .alert-demo { max-width: none; }
  .landing .beat-alert { min-height: 0; }
}

/* ---------- Order summary and form fields (checkout.html, support.html, cancel.html) ----
   Borrows the onboarding card so these read as part of the same flow, and states the tier as
   a receipt-shaped block: what you picked and what it costs. */

.reserve-summary {
  border: 1px solid var(--rule-strong);
  background: var(--sunk);
  padding: 1.1rem 1.25rem;
  margin-bottom: 1.75rem;
}
.reserve-summary-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}
.reserve-tier {
  font-family: var(--display);
  font-stretch: 112%;
  font-weight: 700;
  font-size: 1.15rem;
  color: var(--ink);
}
.reserve-price {
  font-family: var(--display);
  font-stretch: 112%;
  font-weight: 700;
  font-size: 1.5rem;
  color: var(--ink);
}
.reserve-price span {
  font-family: var(--mono);
  font-size: 0.7rem;
  font-weight: 400;
  color: var(--muted);
}
.reserve-lines {
  list-style: none;
  margin: 0.9rem 0 0;
  padding: 0;
  font-size: 0.86rem;
  color: var(--ink-2);
}
.reserve-lines li {
  padding-left: 1rem;
  position: relative;
  margin-bottom: 0.3rem;
}
.reserve-lines li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.62em;
  width: 5px;
  height: 1px;
  background: var(--rule-strong);
}
.reserve-note {
  margin: 0.9rem 0 0;
  padding-top: 0.75rem;
  border-top: 1px solid var(--rule);
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--muted);
}

.reserve-form { margin-bottom: 2rem; }
.reserve-field { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 0.4rem; }
.reserve-field input[type="email"],
.reserve-field input[type="text"] {
  flex: 1 1 16rem;
  font-family: var(--mono);
  font-size: 0.9rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--rule-strong);
  padding: 0.7rem 0.75rem;
}
.reserve-field input[type="email"]::placeholder,
.reserve-field input[type="text"]::placeholder { color: var(--muted); }
.reserve-field .cta { flex: none; }

/* ---------- Support form (web/support.html) ----------
   Rides on .reserve-field rather than inventing a second form language: a support page that
   looks like a different site is a tell that it was bolted on afterwards. Only the controls
   the reserve form never needed -- a select and a textarea -- are added here. */
.support-form select,
.support-form textarea {
  flex: 1 1 16rem;
  font-family: var(--mono);
  font-size: 0.9rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--rule-strong);
  padding: 0.7rem 0.75rem;
}
/* The dropdown list itself is drawn by the OS, not by us: the only lever that reaches it
   is the font, and monospace option rows read as code rather than as choices. */
/* Elsewhere .onboard-field-label sits on a <p>; here it is a real <label>, which is
   inline, so vertical margins on it are ignored and every field ran into the one above.
   The form is a column with one gap between groups instead, the way a stacked form is
   normally built -- the label's own tiny margin then binds it to the field beneath it. */
.support-form { display: flex; flex-direction: column; gap: 1.5rem; }
.support-form .onboard-field-label {
  display: block;
  font-weight: 700;
  font-size: 0.75rem;
  color: var(--ink);
  margin: 0 0 0.5rem;
}
/* A label and the field it names travel together in a .support-group; the form's gap
   falls between groups, never between a label and its own box. */
.support-form .support-group .reserve-field { margin-top: 0; }
.support-form #support-score-fields { display: flex; flex-direction: column; gap: 1.5rem; }
.support-form .opt {
  font-weight: 400;
  color: var(--muted);
  text-transform: none;
  letter-spacing: 0.02em;
}
.support-form select { font-family: inherit; font-size: 0.95rem; }
.support-form optgroup { font-style: normal; font-weight: 600; }
.support-form option { font-weight: 400; }
.support-form textarea { line-height: 1.5; resize: vertical; min-height: 8rem; }
.support-form textarea::placeholder { color: var(--muted); }
.support-form input:focus-visible,
.support-form select:focus-visible,
.support-form textarea:focus-visible {
  outline: 2px solid var(--signal);
  outline-offset: 1px;
  border-color: var(--signal);
}
.support-form .waitlist-status:empty { display: none; }
.support-hint {
  font-size: 0.78rem;
  color: var(--muted);
  margin: 0.6rem 0 0;
}
.support-fallback {
  margin: 0;
  padding-top: 1.25rem;
  border-top: 1px solid var(--rule);
  font-size: 0.84rem;
  color: var(--muted);
}
/* Off-screen rather than display:none: a bot that skips hidden inputs still fills this one,
   which is the entire point of it. Never focusable, never announced. */
.support-hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* Numbered because the order is the reassurance: nothing happens now, one email later,
   and the rate is held in between. */
.reserve-steps {
  margin: 0 0 1.75rem;
  padding: 1.25rem 0 0 1.15rem;
  border-top: 1px solid var(--rule);
  font-size: 0.84rem;
  color: var(--muted);
}
.reserve-steps li { margin-bottom: 0.5rem; padding-left: 0.25rem; }
.reserve-steps strong { color: var(--ink-2); }
.reserve-breakdown {
  font-family: var(--mono);
  font-size: 0.74rem;
  line-height: 1.5;
  color: var(--muted);
  margin: 0.75rem 0 0;
}

/* ---------- Board card: details panel and the eBay handoff ----------
   The card opens in place rather than into a modal: the feed re-renders itself every minute,
   and a dialog that survives a re-render is machinery this page does not need. */

.card-expand {
  margin-top: 0.7rem;
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  background: none;
  border: 0;
  border-bottom: 1px solid var(--rule-strong);
  padding: 0 0 0.15rem;
  cursor: pointer;
}
.card-expand:hover { color: var(--signal); border-bottom-color: var(--signal); }
.card.is-open { background: var(--card); }

.card-detail {
  margin-top: 0.9rem;
  padding: 1rem 1.1rem;
  background: var(--sunk);
  border-left: 2px solid var(--signal);
}
.card-detail .price-lines { margin-top: 0; }
.card-detail .price-lines .row { padding: 0.15rem 0; }
.card-detail .price-lines .k { min-width: 12rem; }

.card-actions {
  margin-top: 1rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--rule);
}
.card-ebay { display: inline-block; }
.card-no-link { font-family: var(--mono); font-size: 0.78rem; color: var(--muted); }

/* The last thing read before leaving for a page where money is spent: the price we showed is
   a snapshot, and nobody bids on this reader's behalf. */
.card-handoff-note {
  margin: 0.75rem 0 0;
  max-width: 52ch;
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--muted);
}

@media (max-width: 720px) {
  .card-detail .price-lines .k { min-width: 0; flex-basis: 100%; }
  .card-ebay { display: block; text-align: center; }
}

/* ---------- The BIN record: settled underpriced asks, under the closes ----------
   Its own band so it can never be read as a continuation of the list above it: those rows
   are closes with a hammer, these are asks that stopped being listed, and the difference is
   the difference between a measurement and an inference. */
.bin-record { margin-top: 3rem; }
.bin-record .section-title {
  font-size: clamp(1.2rem, 2.4vw, 1.5rem);
  margin-bottom: 0.4rem;
}
.bin-record .page-sub { max-width: 62ch; }

/* ---------- Confidence chip ----------
   How surely the item behind a number was identified, ranked three ways at once: the word,
   the filled pips, and the colour. The palette note at the top of this file is the reason —
   green against red sits in the CVD warn band, so no good/bad mark here may rest on hue
   alone, and this one is legible with colour removed entirely.

   Only the two colours already validated against this ground are used. High is the gain
   green; medium is the instrument blue, the colour this site reserves for a number it
   stands behind; low and none take the "declined" grey, because a low-confidence match is
   not a warning about the lot, it is us saying nothing about it. */

.conf-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-family: var(--mono);
  font-size: 0.66rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--silence);
  white-space: nowrap;
}
.conf-chip .pips { display: inline-flex; gap: 2px; }
.conf-chip .pip {
  width: 5px;
  height: 5px;
  border: 1px solid currentColor;
  opacity: 0.45;
}
.conf-chip .pip.is-on { background: currentColor; opacity: 1; }

.conf-high { color: var(--gain); }
.conf-medium { color: var(--signal); }
.conf-low, .conf-none { color: var(--silence); }

/* The chip leads the badge row, so the divider that separates badges has to start after it. */
.conf-chip + .badge { padding-left: 0.5rem; border-left: 1px solid var(--rule); }

/* In a price-lines row the chip sits in the value column, where the row's own type is larger. */
.price-lines .conf-chip { font-size: 0.7rem; }

/* On a board card the same chip sits in a row of bordered ones, so it takes the border too --
   the mark has to read as one of the card's chips, while staying the SAME mark the track
   record uses. A live ask and a settled one are graded on one scale or the reader has to
   learn two. */
.bc-meta .conf-chip {
  font-size: 0.62rem;
  letter-spacing: 0.06em;
  padding: 0.1rem 0.35rem;
  border: 1px solid currentColor;
  border-radius: 2px;
}

/* ---------- The board: a dense card grid, modelled on Coflnet's flipper ----------
   Deliberately not built on `.card` / `.feed`, which the track record also uses. That layout is
   one wide row per item, sized for reading a paragraph of justification; this one is a grid of
   small fixed-shape tiles sized for scanning twenty at once. Sharing a class would have meant
   every future change to one silently reshaping the other. */
.board-main { max-width: var(--page); margin: 0 auto; padding: 1.25rem 1.25rem 3rem; }

.board-bar {
  position: sticky;
  top: 0;
  z-index: 5;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
  align-items: center;
  justify-content: space-between;
  padding: 0.6rem 0.1rem;
  margin-bottom: 0.9rem;
  background: var(--paper);
  border-bottom: 1px solid var(--rule-strong);
  font-family: var(--mono);
  font-size: 0.74rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}
.board-bar-left, .board-bar-right { display: flex; align-items: center; gap: 0.7rem; }
/* The right group is filters-then-clock and needs a little more air between its items than
   the left's slash-separated counts, which have a divider glyph doing that work already. */
.board-bar-right { gap: 0.9rem; }
.board-bar strong { color: var(--ink); font-weight: 600; font-size: 0.95rem; }
/* Each count and each filter is one unit; the strip wraps BETWEEN them, never through one.
   Without this a narrow screen breaks "buy it now" across two lines and the numbers stop
   lining up with the words they belong to. */
.board-count, .board-stamp { white-space: nowrap; }
.board-bar-left, .board-bar-right { flex-wrap: wrap; }
/* A count's second number -- the denominator in `0 of 1 auctions`, the badge inside a
   toggle. Inked so it reads as a figure, but not at `strong`'s size: it qualifies the
   number beside it rather than competing with it. */
.board-bar b { color: var(--ink); font-weight: 600; }
.board-sep { color: var(--rule-strong); }
.board-stamp { color: var(--muted); text-transform: none; letter-spacing: 0; }

/* Unlit unless something actually cleared the bar. An always-green lamp on a board that is
   empty most days would be the page's one dishonest pixel. */
.lamp {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--silence);
  box-shadow: 0 0 0 3px var(--sunk);
}
.lamp.is-live { background: var(--gain); box-shadow: 0 0 0 3px var(--signal-wash); }


/* Cards stretch to the tallest in their row, and the button sits on the bottom edge.
 *
 * `align-items: start` let every card be its own height, so within one row the photos, the
 * savings figures, the chip strips and the blue buttons each sat at four different heights --
 * the ragged-baseline complaint, reported by five of ten round-9 testers and by three in
 * round 8. One called the one screen built for fast scanning the slowest on the site, which is
 * the right way to put it: a grid you scan sideways as well as down needs its rows to line up,
 * and a card that grew a second title line dragged everything under it out of step.
 *
 * `stretch` plus a button pushed to the bottom by `margin-top: auto` gives every row one top
 * edge and one bottom edge. The slack lands above the button, inside the card, where it reads
 * as padding rather than as a step. */
.board-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(232px, 1fr));
  gap: 0.6rem;
  align-items: stretch;
}
.bcard .bc-go,
.bcard .bc-go.is-dead { margin-top: auto; }
/* The title is the element that changes height between cards, so it is given both its lines
   whether or not it needs them. Two short-titled cards beside a long one no longer sit a line
   higher than their neighbours. */
.bc-title { min-height: 2.6em; }

.bcard {
  display: flex;
  flex-direction: column;
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: 3px;
  overflow: hidden;
}

.bc-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.42rem 0.6rem;
  background: var(--sunk);
  border-bottom: 1px solid var(--rule);
}
/* Two lines before the ellipsis, not one.
 *
 * A single clamped line cut "Canon EF 16-35mm f/2.8L III USM Lens - 0573C002" and a different
 * Canon at exactly the same point, so two distinct listings rendered as the same string and
 * three round-8 testers read the board as padded with duplicates. The distinguishing part of a
 * listing title is almost always at the end -- the mount, the model suffix, the grade -- which
 * is precisely what one line throws away. */
.bc-title {
  min-width: 0;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--ink);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  line-height: 1.3;
}
.bc-title a { color: var(--ink); text-decoration: none; }
.bc-title a:hover { color: var(--signal); text-decoration: underline; }
/* The right end of a card header: what surface it came from, then how far under reference it
   sits. Grouped so the two never wrap apart from each other on a narrow card. */
.bc-marks {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}
.bc-tag {
  flex: none;
  font-family: var(--mono);
  font-size: 0.68rem;
  color: var(--muted);
}

/* Coflnet's BIN chip: a filled badge in the card header, on the Buy It Now cards only. The
   auction cards are left unbadged deliberately -- they carry a running clock, which says the
   same thing and says it in the unit the reader is acting on. Filled rather than outlined so
   it reads at a glance in a grid of twenty, which is the whole job a one-word badge has. */
.bc-kind {
  flex: none;
  font-family: var(--mono);
  font-size: 0.6rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  line-height: 1;
  padding: 0.2rem 0.32rem;
  border-radius: 2px;
  background: var(--gain);
  color: var(--paper);
}

/* The accent is the whole signal that this one cleared: it is the only warm edge on the page. */
.bcard.is-pass { border-color: var(--rule-strong); }
.bcard.is-pass .bc-head { background: var(--signal-wash); border-bottom-color: var(--signal); }
.bcard.is-pass .bc-tag { color: var(--signal); font-weight: 600; }

/* The lot's photo, full-bleed between the header and the numbers. `contain`, not `cover`:
   the picture is here to answer "is this the thing I think it is", and a crop is exactly what
   removes the mount, the grade label or the connector that answers it. Fixed height so cards
   with and without a picture keep one baseline across the grid -- a BIN row observed before
   the column existed has no image until it is re-observed. */
/* `cover`, not `contain`.
 *
 * Contain letterboxes: a wide photo sat as a narrow strip in the middle of the box with large
 * dead margins either side, while a square one filled it, so the image mass jumped row to row
 * and three round-7 testers said the grid never settled. Cover crops instead, which is the
 * right trade for a thumbnail whose job is to let you recognise the thing -- eBay's own
 * s-l225 renditions are already padded to near-square, so there is very little to lose. */
.bc-shot {
  display: block;
  width: 100%;
  height: 116px;
  object-fit: cover;
  background: var(--sunk);
  border-bottom: 1px solid var(--rule);
}
.bc-shot.is-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--mono);
  font-size: 0.63rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.bc-body { padding: 0.55rem 0.6rem 0.4rem; }

.bc-headline {
  font-family: var(--display);
  font-size: 1.5rem;
  line-height: 1.05;
  font-weight: 700;
  color: var(--gain);
  font-variant-numeric: tabular-nums;
}
/* A card with no reseller line still has an ask, a standing and a link, so it stays on the
   grid -- but its headline is an absence, not a gain, and painting an em-dash in the gain
   colour would read as a number the reader could not parse. */
.bc-headline.is-none {
  color: var(--muted);
}
.bc-headline-k {
  font-family: var(--mono);
  font-size: 0.63rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
  margin-bottom: 0.5rem;
}

/* Occupies the headline's slot on a card that has words where a number would be -- an alert
   still awaiting its result, or one with no reference to score against. Keeps one baseline
   across the grid. */
.bc-reason {
  font-family: var(--mono);
  font-size: 0.78rem;
  color: var(--ink-2);
  padding: 0.3rem 0 0.62rem;
}

.bc-bar {
  height: 4px;
  background: var(--sunk);
  border-radius: 2px;
  overflow: hidden;
  margin-bottom: 0.55rem;
}
.bc-bar span { display: block; height: 100%; background: var(--signal); }

.bc-stat {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.15rem 0;
  font-family: var(--mono);
  font-size: 0.73rem;
}
.bc-k { color: var(--muted); }
.bc-v { color: var(--ink); font-variant-numeric: tabular-nums; }
.bc-v i { font-style: normal; color: var(--muted); font-size: 0.9em; }

.bc-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  padding: 0.45rem 0.6rem;
  border-top: 1px solid var(--rule);
  margin-top: auto;
}
.bc-chip {
  font-family: var(--mono);
  font-size: 0.66rem;
  padding: 0.1rem 0.35rem;
  border: 1px solid var(--rule);
  border-radius: 2px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
/* Zero bids is the thesis in one number, so it is the only chip that gets colour. */
.bc-chip.is-quiet { color: var(--gain); border-color: var(--gain); }
/* The slab tier the comparison was made in. Uppercased and inked because it qualifies the
   headline number rather than annotating it -- a PSA 9 saving and a raw saving are different
   claims, and the reader must not have to infer which one they are looking at. */
.bc-chip.is-tier { color: var(--ink); text-transform: uppercase; letter-spacing: 0.03em; }
.bc-clock { margin-left: auto; color: var(--ink); }
/* The clock runs off the reader's own machine against the listing's absolute end time, so it
   keeps counting between the file's five-minute regenerations. Its last minute is the only
   moment on this page that is genuinely urgent -- a human has to place the bid, and there is
   no second chance -- so it stops looking like the chips beside it. `closed` is muted instead:
   a lot that has already gone is the one chip that must not shout. */
.bc-clock.is-soon { color: var(--miss); border-color: var(--miss); font-weight: 600; }
.bc-clock.is-closed { color: var(--silence); }

.bc-go {
  display: block;
  padding: 0.42rem 0.6rem;
  background: var(--signal);
  color: #fff;
  font-family: var(--mono);
  font-size: 0.7rem;
  text-align: center;
  text-decoration: none;
  letter-spacing: 0.03em;
}
.bc-go:hover { background: var(--ink); color: #fff; }
.bc-go.is-dead {
  background: var(--sunk);
  color: var(--muted);
  border-top: 1px solid var(--rule);
}

/* An alert that did not go under: same footprint, no colour, no call to action. */
.bcard.is-miss { background: var(--paper); border-style: dashed; }
.bcard.is-miss .bc-title { font-weight: 500; color: var(--ink-2); }
.bcard.is-miss .bc-v { color: var(--ink-2); }

.bcard.is-empty { grid-column: 1 / -1; padding: 1.5rem 1.1rem; background: var(--sunk); }
.bc-empty-k {
  font-family: var(--mono);
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink);
  margin-bottom: 0.4rem;
}
.bcard.is-empty p { margin: 0; max-width: 52ch; font-size: 0.88rem; color: var(--ink-2); }

/* Pager under the board grid. Hidden entirely when everything fits on one page, so a short
   window looks exactly as it did before the board started paginating. */
.board-pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.9rem;
  margin-top: 1.4rem;
  padding-top: 1rem;
  border-top: 1px solid var(--rule);
}
.board-pager button {
  font-family: var(--body);
  font-size: 0.9rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--rule-strong);
  padding: 0.45rem 0.8rem;
  cursor: pointer;
}
.board-pager button:hover:not(:disabled) { border-color: var(--ink); }
.board-pager button:disabled { color: var(--muted); cursor: default; opacity: 0.55; }
.board-pager-at {
  font-family: var(--mono);
  font-size: 0.8rem;
  color: var(--muted);
  text-align: center;
  min-width: 22ch;
}

@media (max-width: 520px) {
  .board-pager { gap: 0.5rem; }
  .board-pager-at { min-width: 0; font-size: 0.74rem; }
}

.board-foot {
  margin-top: 1.6rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--rule);
  font-size: 0.78rem;
  color: var(--muted);
  max-width: 72ch;
}

@media (max-width: 520px) {
  .board-grid { grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); }
  .bc-headline { font-size: 1.25rem; }
  /* Two cards to a phone screen, so the picture takes proportionally less of one -- the
     saving is still what the reader is scanning for. */
  .bc-shot { height: 88px; }
}

/* Small on purpose. The page's job is the grid; a display-sized title would push the first row
   of cards below the fold on a laptop, which is the one thing this layout exists to prevent. */
.board-h1 {
  font-size: 1.05rem;
  letter-spacing: -0.01em;
  margin: 0 0 0.1rem;
}


/* Track record: the standing caveats, set as a bordered aside rather than as prose in the
   footer, because they were things cold readers discovered on their own three minutes in and
   read as concealment. They sit BELOW the rows: stated in one line each so the reader meets
   them, expandable for the reader who wants the whole statement, and never a screen of grey
   prose between the summary and the record it describes. */
.record-caveats {
  /* The box, not just its text, carries the measure. Left full-width it stretched to the
     page while the prose stopped at 68ch, so two thirds of it was empty panel. */
  max-width: 74ch;
  margin: 2rem 0 1.5rem;
  padding: 0.9rem 1.1rem;
  border: 1px solid var(--rule);
  border-left: 3px solid var(--rule-strong);
  background: var(--sunk);
}
.record-caveats p {
  margin: 0;
  max-width: 68ch;
  font-size: 0.86rem;
  line-height: 1.55;
  color: var(--muted);
}
.record-caveats p + p { margin-top: 0.6rem; }
.record-caveats strong { color: var(--ink); font-weight: 600; }
.record-caveats .badge { border: 0; padding: 0; }

/* The two caveats in one line, with the full statement of each folded behind a disclosure.
   Both facts still meet the reader before the numbers do; what they no longer do is spend
   half a screen on it, which is what pushed readers past the record entirely. */
.record-caveats .caveat-lede { color: var(--ink); }
.record-notes { margin-top: 0.55rem; }
.record-notes > summary {
  cursor: pointer;
  font-size: 0.82rem;
  color: var(--muted);
  list-style: none;
}
.record-notes > summary::-webkit-details-marker { display: none; }
.record-notes > summary::before { content: '+ '; }
.record-notes[open] > summary::before { content: '− '; }
.record-notes > summary:hover { color: var(--ink); }
.record-notes p { margin-top: 0.6rem; }

/* Reserve: the requested tier did not exist, so a different one is on screen. Said in the
   miss colour because it is a correction to what the reader asked for. */
.reserve-fellback {
  margin: 0.4rem 0 0;
  font-size: 0.82rem;
  line-height: 1.45;
  color: var(--miss);
  max-width: 46ch;
}

/* Shown only when scripts are off, on the two pages that are rendered from JSON. */
.noscript-note {
  margin: 1rem 0;
  padding: 0.9rem 1.1rem;
  border: 1px solid var(--rule);
  border-left: 3px solid var(--rule-strong);
  background: var(--sunk);
  max-width: 68ch;
  font-size: 0.86rem;
  line-height: 1.55;
  color: var(--muted);
}
/* Status lines reserve their first line of space whether or not they have anything to say.
   Without this the form jumps every time a message appears or clears, which reads as the
   page moving under the cursor mid-click. */
.waitlist-status { min-height: 1.35em; }

/* The hero's standing disclosure. Deliberately quiet typographically and deliberately
   unavoidable positionally: it is the fact that most changes how the rest of the page reads,
   and readers were finding it four pages deep, under a price table. */
.hero-status {
  margin: 1.5rem 0 0;
  padding-top: 0.9rem;
  border-top: 1px solid var(--rule);
  max-width: 46ch;
  font-size: 0.84rem;
  line-height: 1.55;
  color: var(--muted);
}
.hero-status strong { color: var(--ink); font-weight: 600; }


/* The board's empty state is the state the board is in most of the time, so it is the state
   that has to be designed. It was one small card wedged into a grid of rejection stubs, which
   ten readers read as "the page failed to load" rather than as "the bar held". Give it the
   full row and enough presence to read as an answer. */
.board-grid .bcard.is-empty {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.5rem;
  min-height: 190px;
  padding: 2rem 2.25rem;
  text-align: left;
  background: var(--sunk);
  border: 1px dashed var(--rule-strong);
}
.board-grid .bcard.is-empty .bc-empty-k {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}
.board-grid .bcard.is-empty p {
  margin: 0;
  max-width: 62ch;
  font-size: 1.02rem;
  line-height: 1.55;
  color: var(--ink-2);
}
.board-grid .bcard.is-empty strong { color: var(--ink); }


/* The filter selects were the browser's, on the page readers rank best-looking. Same ground,
   same rule, same mono as every other control here; the chevron is drawn rather than an
   emoji or icon font so it inherits the ink. */
.filter-bar select {
  appearance: none;
  -webkit-appearance: none;
  font-family: var(--mono);
  font-size: 0.82rem;
  color: var(--ink);
  background-color: var(--card);
  background-image:
    linear-gradient(45deg, transparent 50%, var(--ink-2) 50%),
    linear-gradient(135deg, var(--ink-2) 50%, transparent 50%);
  background-position: right 1.05rem center, right 0.8rem center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  border: 1px solid var(--rule-strong);
  border-radius: 0;
  padding: 0.5rem 2.1rem 0.5rem 0.7rem;
  /* Category names come from the data and can run long; cap the control so one of them
     cannot stretch the filter bar past the rows it sits above. */
  max-width: 15rem;
  text-overflow: ellipsis;
}
.filter-bar select:focus-visible { outline: 2px solid var(--signal); outline-offset: 1px; }

/* "from" is a qualifier, not part of the figure — set like the "/mo" beside it so the
   Customize tier stops reading as the largest price in the row. */
.landing .p-price .from,
.price-card .tier-price .from {
  font-family: var(--mono);
  font-size: 0.72rem;
  font-weight: 400;
  color: var(--muted);
}

/* Shown only to a visitor who arrived on an invite link. Quiet on purpose: it is a note
   about how they got here, not the page's argument. */
.referral-banner {
  margin: 0 0 1.5rem;
  padding: 0.7rem 0.9rem;
  border-left: 2px solid var(--signal);
  background: var(--card);
  font-size: 0.88rem;
  line-height: 1.5;
  color: var(--ink-2);
  max-width: 44rem;
}

/* ---------------------------------------------------------------------------------------
   alert.html -- one lot, given the whole page.

   It used to draw a `.bcard`, the component the board packs sixty of into a grid at 232px.
   On a page with a single subject that reads as a thumbnail someone forgot to finish, in a
   column beside an empty one. Same parts, same order, at the size a decision deserves: the
   picture, the name, the three prices, what they rest on, then the two things you can do.
   --------------------------------------------------------------------------------------- */
.lot {
  display: grid;
  grid-template-columns: minmax(0, 19rem) minmax(0, 1fr);
  gap: 1.6rem;
  align-items: start;
  background: var(--card);
  border: 1px solid var(--rule-strong);
  padding: 1.2rem;
  max-width: 54rem;
}

/* Recessed, like every other well on the site: the photograph is the seller's, not ours, and
   the frame says so. `contain`, never `cover` -- a cropped lot is a lot you cannot check. */
.lot-shot {
  margin: 0;
  background: var(--sunk);
  border: 1px solid var(--rule);
  aspect-ratio: 1 / 1;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.lot-shot img { width: 100%; height: 100%; object-fit: contain; }
.lot-shot.is-empty {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.lot-facts { min-width: 0; }
.lot-name {
  font-family: var(--display);
  font-size: 1.25rem;
  line-height: 1.25;
  margin: 0 0 1rem;
  color: var(--ink);
}

/* Three columns, hairline-separated, because they are one comparison and not three facts.
   Tabular numerals so the digits line up under each other down the row. */
.lot-prices {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin: 0;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}
.lot-price { padding: 0.7rem 0.9rem 0.75rem 0; }
.lot-price + .lot-price { padding-left: 0.9rem; border-left: 1px solid var(--rule); }
.lot-price dt {
  font-family: var(--mono);
  font-size: 0.64rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--muted);
  margin-bottom: 0.3rem;
}
.lot-price dd {
  margin: 0;
  font-family: var(--display);
  font-size: 1.45rem;
  font-weight: 700;
  line-height: 1;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
/* The saving is the only asserted number of the three -- the other two are just prices --
   so it is the only one that takes the gain colour, and it carries a sign as well, because
   colour alone never carries meaning here. */
.lot-price.is-save dd { color: var(--gain); }

.lot-basis {
  font-family: var(--mono);
  font-size: 0.72rem;
  color: var(--muted);
  margin: 0.6rem 0 0;
}

/* The evidence rows. Label left, figure right, hairline between -- the same shape the board's
   card uses for the same job, at reading size instead of card size. */
.lot-evidence {
  margin: 1.1rem 0 0;
  border-top: 1px solid var(--rule);
}
.lot-ev {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.42rem 0;
  border-bottom: 1px solid var(--rule);
  font-family: var(--mono);
  font-size: 0.78rem;
}
.lot-ev dt { color: var(--muted); }
.lot-ev dd { margin: 0; color: var(--ink); font-variant-numeric: tabular-nums; text-align: right; }
.lot-ev dd i { font-style: normal; color: var(--muted); }
/* One word, coloured, and the word is what carries it -- the colour is a second reading of
   the same fact, never the only one (green and red both sit in the CVD warn band). */
.lot-conf { font-weight: 600; text-transform: capitalize; }
.lot-conf.is-high { color: var(--gain); }
.lot-conf.is-medium { color: var(--ink); }
.lot-conf.is-low { color: var(--miss); }

/* The dated history. A three-column strip rather than more label/value rows: they are one
   series read down a column, and the date and the price have to line up to be read that way. */
.lot-recent { margin-top: 0.9rem; }
.lot-recent-k {
  font-family: var(--mono);
  font-size: 0.63rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--muted);
  margin-bottom: 0.25rem;
}
.lot-recent-row {
  display: grid;
  grid-template-columns: 4.5rem minmax(0, 1fr) auto;
  gap: 0.6rem;
  align-items: baseline;
  padding: 0.3rem 0;
  border-bottom: 1px solid var(--rule);
  font-family: var(--mono);
  font-size: 0.78rem;
}
.lot-recent-at { color: var(--muted); }
.lot-recent-v { color: var(--ink); font-variant-numeric: tabular-nums; }
/* A close is a price somebody paid; a delisting is an ask that vanished. The words carry the
   difference, never the colour alone. */
.lot-recent-kind { color: var(--muted); font-size: 0.72rem; }
.lot-recent-kind.is-sold { color: var(--gain); }

/* Links out to the places a reader can check the numbers themselves. Quiet by design: they
   are an offer, not the page's business. */
.lot-checks {
  margin: 0.85rem 0 0;
  font-family: var(--mono);
  font-size: 0.72rem;
  color: var(--muted);
  line-height: 1.7;
}
.lot-checks a { color: var(--ink-2); text-decoration: underline; text-underline-offset: 2px; }
.lot-checks a:hover { color: var(--signal); }

.lot-actions { display: flex; flex-wrap: wrap; gap: 0.7rem; margin-top: 1.1rem; }
.lot-actions .cta { cursor: pointer; appearance: none; border-radius: 0; }

@media (max-width: 680px) {
  .lot { grid-template-columns: minmax(0, 1fr); gap: 1.1rem; padding: 1rem; }
  /* Landscape on a phone: a square well would push every number below the fold. */
  .lot-shot { aspect-ratio: 4 / 3; }
  .lot-price dd { font-size: 1.2rem; }
  .lot-price { padding-right: 0.55rem; }
  .lot-price + .lot-price { padding-left: 0.55rem; }
  /* The three stay in one row at every width -- they are a comparison, and a comparison that
     wraps is three unrelated numbers -- so it is the type that gives way, not the layout. */
  .lot-actions .cta { flex: 1 1 100%; text-align: center; }
}
@media (max-width: 400px) {
  .lot-price dd { font-size: 1.02rem; }
  .lot-price dt { font-size: 0.58rem; letter-spacing: 0.04em; }
}

/* The invite ask, under the claim action on an alert. Separated by a rule rather than a card
   of its own: it is a footnote to the alert, not a second thing competing with it. */
.alert-invite {
  margin-top: 1.25rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--rule);
}
.alert-invite-lede { font-size: 0.86rem; color: var(--ink-2); line-height: 1.5; margin: 0 0 0.8rem; }

/* Collapsed by default: an always-visible referral field tells every organic visitor they
   are missing a discount someone else got. */
.signup-ref { margin: 0 0 1rem; }
.signup-ref summary {
  cursor: pointer;
  font-size: 0.82rem;
  color: var(--muted);
  text-decoration: underline;
  list-style: none;
}
.signup-ref summary::-webkit-details-marker { display: none; }
.signup-ref summary:hover { color: var(--ink-2); }
.signup-ref .auth-field { margin-top: 0.7rem; }

/* ---------- Checkout (web/checkout.html) ----------
   Two columns, the shape a checkout has to have: the order on the left, one path to paying
   on the right, one button at the end of it. Everything in the pay column is inert until
   Stripe is wired up — the controls are real markup so the layout that ships is the one
   being looked at, but every one of them is disabled.

   The billing rules did not leave the page, they compressed: three one-line facts stay
   visible (cancel / non-refundable / founding rate) and the wording folds into a <details>.
   Each fact still carries a glyph AND a word, never colour alone. */

.checkout-main { padding-top: 2.5rem; }

.checkout-grid {
  display: grid;
  /* Not 1fr 1fr. The order column has less to say than the form does, so an equal split
     leaves it spread thin next to a dense bordered card — the page reads empty even though
     nothing is missing. Narrower column, tighter gap, smaller page. */
  grid-template-columns: 25rem 1fr;
  gap: 3rem;
  align-items: start;
  max-width: 56rem;
  margin: 0 auto;
}
@media (max-width: 860px) {
  .checkout-grid { grid-template-columns: 1fr; gap: 2.25rem; max-width: 32rem; }
}

/* --- The order --- */

.checkout-order h1 { font-size: 1.5rem; margin: 0 0 1.2rem; }
.checkout-order h1 span { color: var(--signal); }

.checkout-amount { display: flex; align-items: baseline; gap: 0.6rem; margin: 0 0 1.3rem; }
.checkout-amount-value {
  font-family: var(--display);
  font-stretch: 112%;
  font-weight: 700;
  font-size: 2.7rem;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--ink);
}
.checkout-amount-per {
  font-family: var(--mono);
  font-size: 0.76rem;
  letter-spacing: 0.04em;
  color: var(--muted);
}

.checkout-order .reserve-breakdown { margin: -0.9rem 0 1.2rem; }

/* The spec sheet. Label in mono because it names a setting, value in body because it is a
   sentence fragment; a hairline under every row so the column has structure of its own and
   does not need a border around it to look like a finished object. */
.checkout-spec {
  display: grid;
  grid-template-columns: 9rem 1fr;
  margin: 0;
  border-top: 1px solid var(--rule);
}
.checkout-spec dt,
.checkout-spec dd {
  margin: 0;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--rule);
  font-size: 0.92rem;
  line-height: 1.4;
}
.checkout-spec dt {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  padding-top: 0.72rem;
}
.checkout-spec dd { color: var(--ink); padding-left: 0.5rem; }
@media (max-width: 380px) {
  .checkout-spec { grid-template-columns: 7.5rem 1fr; }
}

.checkout-facts {
  list-style: none;
  margin: 1.5rem 0 0;
  padding: 0;
  font-size: 0.9rem;
  line-height: 1.5;
  color: var(--ink-2);
}
.checkout-facts li { position: relative; padding-left: 1.35rem; margin-bottom: 0.55rem; }
.checkout-facts li:last-child { margin-bottom: 0; }
.checkout-facts li::before {
  content: "–";
  position: absolute;
  left: 0;
  top: 0;
  font-family: var(--mono);
  color: var(--rule-strong);
}
.checkout-facts li.good::before { content: "✓"; color: var(--gain); }
.checkout-facts li.warn::before { content: "!"; color: var(--miss); font-weight: 700; }
.checkout-facts li.warn strong { color: var(--miss); }
.checkout-facts strong { color: var(--ink); }

.checkout-terms { margin-top: 1.25rem; }
.checkout-terms summary {
  cursor: pointer;
  list-style: none;
  font-family: var(--mono);
  font-size: 0.76rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.checkout-terms summary::-webkit-details-marker { display: none; }
.checkout-terms summary::before {
  content: "+ ";
  color: var(--rule-strong);
}
.checkout-terms[open] summary::before { content: "− "; }
.checkout-terms summary:hover { color: var(--ink-2); }

.checkout-terms-list {
  list-style: none;
  margin: 0.9rem 0 0;
  padding: 0 0 0 0.1rem;
  font-size: 0.86rem;
  line-height: 1.55;
  color: var(--ink-2);
}
.checkout-terms-list li {
  position: relative;
  padding-left: 1.1rem;
  margin-bottom: 0.6rem;
}
.checkout-terms-list li:last-child { margin-bottom: 0; }
.checkout-terms-list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.72em;
  width: 5px;
  height: 1px;
  background: var(--rule-strong);
}
.checkout-terms-list strong { color: var(--ink); }

/* What one alert has been worth. The figure is an asserted number, so it gets --signal and
   display type, the same treatment every other measured claim on the site gets; the sentence
   under it is deliberately quiet, because the caveat is doing as much work as the number. */
.alert-value {
  margin-top: 1.5rem;
  padding: 0.9rem 1rem;
  background: var(--sunk);
  border-left: 3px solid var(--signal);
}
.alert-value-head {
  margin: 0;
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
.alert-value-figure {
  margin: 0.35rem 0 0.3rem;
  font-family: var(--display);
  font-stretch: 112%;
  font-weight: 700;
  font-size: 2.3rem;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--signal);
}
.alert-value-note {
  margin: 0;
  font-size: 0.8rem;
  line-height: 1.5;
  color: var(--ink-2);
}

/* The pricing cards get one line of the same fact, sized to sit under a feature list without
   competing with the price at the top of the card. */
.card-value {
  margin: 0.9rem 0 0;
  padding-top: 0.8rem;
  border-top: 1px solid var(--rule);
  font-size: 0.79rem;
  line-height: 1.5;
  color: var(--muted);
}
.card-value strong {
  color: var(--signal);
  font-family: var(--display);
  font-stretch: 112%;
  font-weight: 700;
  font-size: 1.15rem;
}

.checkout-back { margin: 1.6rem 0 0; font-size: 0.86rem; }
.checkout-back a { color: var(--muted); text-decoration: underline; }
.checkout-back a:hover { color: var(--ink); }

/* --- The pay column --- */

.checkout-pay {
  background: var(--card);
  border: 1px solid var(--rule-strong);
  padding: 1.35rem 1.35rem 1.5rem;
}
@media (max-width: 460px) {
  .checkout-pay { padding: 1.25rem 1.1rem 1.4rem; }
}

/* Wallet row: the Express Checkout Element mounts here. A block, not the old two-column
   grid of hand-drawn buttons -- the Element lays out its own buttons and decides how many
   there are, which is why js/checkout.js reveals this row on its ready event instead of
   assuming a wallet exists. */
.pay-wallets { display: block; }

.pay-or {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 1.35rem 0;
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.pay-or::before,
.pay-or::after { content: ""; flex: 1; height: 1px; background: var(--rule); }

/* The recurring-subscription tick, above both ways of paying because it gates both. Boxed
   and set apart from the options under it: it is the one control on this page that is an
   agreement rather than a step. */
.pay-consent {
  border: 1px solid var(--rule);
  background: var(--paper);
  padding: 0.85rem 0.9rem;
  margin: 0 0 1.25rem;
}
.pay-consent .consent-check { margin: 0; }
.pay-consent .pay-error { margin-top: 0.6rem; }

/* Pro/Pro+ category pick, ahead of the pay steps -- same box treatment as the consent
   tick below it, since it is likewise a decision that has to be made before anything is
   payable, not a step in HOW to pay. */
.pay-categories {
  border: 1px solid var(--rule);
  background: var(--paper);
  padding: 0.85rem 0.9rem;
  margin: 0 0 1.25rem;
}
.pay-categories .plan-categories { margin-bottom: 0.85rem; }
.pay-categories .pay-error { margin-bottom: 0.6rem; }

/* Step one: how you are paying. The wallet row, a hairline "or", and the card button --
   in that order, because a one-tap wallet the device already holds beats a form every time
   and has to be the first thing read, not the thing above the form nobody scrolled back to. */
.pay-methods { display: block; }

/* The numbered label on each step. Mono and quiet: it orders the box, it is not a heading
   competing with the price in the column beside it. */
.pay-step {
  margin: 0 0 0.85rem;
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.pay-form .pay-step { margin: 1.1rem 0 -0.25rem; }

/* The card choice. Sized like the wallet buttons above it so the two read as one set of
   options rather than a wallet and then some other kind of control -- but drawn in the
   page's own frame, never a mimic of an Apple Pay or Google Pay button. */
.pay-method {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  width: 100%;
  text-align: left;
  background: var(--paper);
  border: 1px solid var(--rule-strong);
  border-radius: 0;
  appearance: none;
  padding: 0.85rem 0.95rem;
  cursor: pointer;
}
.pay-method:hover { border-color: var(--ink); }
.pay-method-icon {
  flex: none;
  width: 1.5rem;
  height: 1rem;
  color: var(--muted);
}
.pay-method:hover .pay-method-icon { color: var(--ink); }
.pay-method-text { display: block; }
.pay-method-label {
  display: block;
  font-family: var(--mono);
  font-size: 0.8rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink);
}
.pay-method-note {
  display: block;
  margin-top: 0.25rem;
  font-size: 0.78rem;
  color: var(--muted);
}

/* The queue, shown only when nothing on this page can take money: checkout not open, the
   category full, the week's supply spent. It replaces the payment choice rather than
   sitting under it, so the box never offers a way to pay and a way to wait at once. */
.pay-waitlist {
  border: 1px solid var(--rule-strong);
  background: var(--paper);
  padding: 1rem 0.95rem 1.1rem;
  margin: 0 0 1.2rem;
}
.pay-waitlist-head {
  margin: 0 0 0.4rem;
  font-family: var(--mono);
  font-size: 0.76rem;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ink);
}
.pay-waitlist-note {
  margin: 0 0 0.9rem;
  font-size: 0.86rem;
  line-height: 1.5;
  color: var(--muted);
}
.pay-waitlist-btn {
  width: 100%;
  font-family: var(--mono);
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  background: var(--ink);
  color: var(--card);
  border: 1px solid var(--ink);
  border-radius: 0;
  appearance: none;
  padding: 0.8rem 1rem;
  cursor: pointer;
}
.pay-waitlist-btn:hover:not(:disabled) { background: var(--signal); border-color: var(--signal); }
.pay-waitlist-btn:disabled { cursor: default; opacity: 0.6; }
.pay-waitlist .waitlist-status { margin: 0.7rem 0 0; }

.pay-form { display: flex; flex-direction: column; gap: 1.1rem; }
.pay-form .auth-field input {
  width: 100%;
  font-family: var(--body);
  font-size: 0.95rem;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--rule-strong);
  border-radius: 0;
  padding: 0.72rem 0.8rem;
}
.pay-form .auth-field input::placeholder { color: var(--muted); }
/* The session's address, shown and not editable -- checkout requires an account, so there
   is nothing to type. Muted so it reads as a fact about the order, not a field left broken. */
.pay-form .auth-field input:read-only { color: var(--muted); cursor: default; }

/* Why there are no card-field styles here any more: there are no card fields. Everything a
   buyer types lives inside a Stripe-hosted iframe mounted into #checkout-payment-element,
   which is what keeps this origin out of PCI scope. The rules that used to draw a card
   number on top of expiry and CVC went with the inputs they were drawing. */

/* Why you cannot pay right now: checkout closed, not signed in, or the category filled
   while this page was open. Reads as information, not as an error -- on most days the true
   state of this page is "not open yet", which is not a failure. */
.pay-notice {
  margin: 0 0 1.2rem;
  padding: 0.85rem 0.9rem;
  border: 1px solid var(--rule-strong);
  background: var(--paper);
  font-size: 0.88rem;
  line-height: 1.5;
  color: var(--ink);
}
.pay-notice a { color: inherit; }

/* A refused payment. This one IS an error and takes the site's own miss colour -- the
   same red the track record uses for a call that did not land, not a new one. */
.pay-error {
  margin: 0;
  font-size: 0.85rem;
  line-height: 1.45;
  color: var(--miss);
}

.pay-submit {
  margin-top: 0.3rem;
  width: 100%;
  font-family: var(--mono);
  font-size: 0.8rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  background: var(--signal);
  color: #fff;
  border: 1px solid var(--signal);
  border-radius: 0;
  appearance: none;
  padding: 0.95rem 1.15rem;
  cursor: pointer;
}
.pay-submit:hover:not(:disabled) { background: var(--ink); border-color: var(--ink); }
.pay-submit:disabled { cursor: default; }

.pay-secure {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  margin: 1rem 0 0;
  font-size: 0.74rem;
  line-height: 1.4;
  color: var(--muted);
}
.pay-secure svg { flex: none; width: 0.85rem; height: 0.85rem; }

.checkout-pay .auth-fine-print { margin-top: 0.7rem; text-align: left; }

/* ------------------------------------------------------------------------------------
   The exit flow (web/cancel.html) and the plan card's foot that opens it.

   Deliberately quiet. A cancel page dressed in warning colours is telling someone they are
   doing something wrong, which is both untrue and the fastest way to turn a churn into a
   complaint. The only emphasis on the page is on the choices, not on the consequence.
   ------------------------------------------------------------------------------------ */
.cancel-card { max-width: 40rem; }

/* What actually stops. Stated once, plainly, before any reason is asked -- the honest
   version of friction is information, not an obstacle. */
.cancel-losses {
  list-style: none;
  margin: 1.4rem 0 1.8rem;
  padding: 0 0 0 1rem;
  border-left: 2px solid var(--rule-strong);
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  font-size: 0.88rem;
  line-height: 1.55;
  color: var(--ink-2);
}

.cancel-reasons { border: 0; margin: 0; padding: 0; }
.cancel-reasons legend {
  display: block;
  font-weight: 700;
  font-size: 0.75rem;
  color: var(--ink);
  margin: 0 0 0.7rem;
  padding: 0;
}

/* Full-width hit areas: on a phone the label text is the target, not the 14px circle. */
.cancel-reason {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 0.65rem 0.75rem;
  border: 1px solid var(--rule);
  background: var(--card);
  font-size: 0.92rem;
  line-height: 1.45;
  color: var(--ink);
  cursor: pointer;
}
.cancel-reason + .cancel-reason { border-top: 0; }
.cancel-reason:hover { background: var(--sunk); }
.cancel-reason input { margin-top: 0.2rem; accent-color: var(--signal); }
.cancel-reason:has(input:checked) {
  background: var(--signal-wash);
  border-color: var(--signal);
}
.cancel-reason:has(input:checked) + .cancel-reason { border-top-color: var(--signal); }
.cancel-reason:focus-within { outline: 2px solid var(--signal); outline-offset: 1px; }

/* Keep-the-plan is the filled button and cancel is the secondary one, because that IS our
   preference and pretending otherwise would be the dishonest kind of design. Both are the
   same size, in the same row, neither is hidden. */
.cancel-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  align-items: center;
  margin-top: 1.6rem;
}

/* The way out of the offer screen. A link, not a button, and always visible. */
.cancel-continue { margin: 1rem 0 0; }
.linklike {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: 0.85rem;
  color: var(--muted);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.linklike:hover { color: var(--ink); }

.cancel-optional {
  font-weight: 400;
  color: var(--muted);
  text-transform: none;
  letter-spacing: 0.02em;
}
.cancel-card textarea {
  flex: 1 1 100%;
  font-family: var(--body);
  font-size: 0.95rem;
  line-height: 1.55;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--rule-strong);
  padding: 0.7rem 0.75rem;
  resize: vertical;
  min-height: 7rem;
}
.cancel-card textarea::placeholder { color: var(--muted); }
.cancel-card textarea:focus-visible { outline: 2px solid var(--signal); outline-offset: 1px; }

/* Plan card foot. Below the upsell, above nothing -- see the comment in account.html. */
.account-plan-foot {
  margin-top: 1.2rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--rule);
}
.account-ending {
  margin: 0 0 0.7rem;
  font-size: 0.82rem;
  line-height: 1.5;
  color: var(--ink-2);
}
.account-cancel-link {
  font-size: 0.78rem;
  color: var(--muted);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.account-cancel-link:hover { color: var(--ink); }
/* `#acct-categories-change` reuses this class on a <button>, which needs its own reset --
   the class above only ever styled an <a> before. */
button.account-cancel-link {
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  font-size: 0.78rem;
  cursor: pointer;
}

.account-categories-edit {
  margin-top: 0.6rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--rule);
}
.account-categories-edit .plan-categories { margin-bottom: 0.6rem; }
.account-categories-edit .pay-error { margin-bottom: 0.5rem; }

/* The session card's only content is one note and one control, so the note's own top margin
   is all the space above it and the button needs its own below. Matches the gap between the
   confirm lede and its resend button in the plan card. */
.account-session-card .cta { margin-top: 0.9rem; }

/* ------------------------------------------------------------------------------------
   The post-payment report (web/welcome.html).

   Same card as the exit flow, for the same reason: these are the two screens that tell
   someone what just happened to their money, and a confirmation drawn differently from a
   cancellation reads as a different product. The only thing added here is the gap above the
   outstanding step -- the spec sheet reports, the list below it asks, and a uniform rhythm
   between the two leaves the one action on the page indistinguishable from the receipt.
   ------------------------------------------------------------------------------------ */
.welcome-card { max-width: 40rem; }
.welcome-card h1 { font-size: 1.5rem; margin: 0 0 0.5rem; }
/* Green only once Stripe has said paid: it is the colour this site reserves for a measured
   outcome, and a confirmed charge is one. The checking and failed states keep the blue. */
.welcome-card--paid { box-shadow: inset 3px 0 0 var(--gain); border-color: #bcd8c7; }
.welcome-card--paid .onboard-eyebrow { color: var(--gain); }

/* The spec sheet's last row already draws a hairline, so this is space, not another edge. */
.welcome-next { margin-top: 1.5rem; }

.welcome-aside {
  margin: 1rem 0 0;
  font-size: 0.82rem;
  line-height: 1.5;
  color: var(--muted);
}
.welcome-card .waitlist-status { margin-top: 0.8rem; text-align: left; }
.welcome-card .waitlist-status:empty { display: none; }

/* ---------- Landing hero: two columns, and real rows in the second one ----------
 *
 * The right 55-60% of this page was empty on every screen. Ten of ten testers reported it in
 * each of rounds 5, 6 and 7, in the same words each time: not restraint, a template with the
 * image slot unfilled. It was escalated twice as a decision rather than a defect, on the
 * grounds that centring, two-column and filling-the-right-side are three different products.
 * This is the two-column answer, and it is chosen because the thing that fills the column is
 * not decoration -- it is the live board, which was already public, already one nav click
 * away, and which six of ten testers named the most convincing thing on the site while it sat
 * unseen behind a mock-up.
 *
 * The left column keeps its own measure (17ch on the headline, 44ch on the lede), so the copy
 * breaks exactly where it did before. Below 960px the panel drops under the copy rather than
 * squeezing it, and the hero stops being sticky at 720px as it already did. */
.landing .hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 2.5rem;
  align-content: center;
}
.landing .hero-copy { min-width: 0; }

@media (min-width: 960px) {
  .landing .hero {
    grid-template-columns: minmax(0, 1fr) minmax(360px, 27rem);
    gap: 3.5rem;
    align-items: center;
  }
}

.hero-live {
  min-width: 0;
  border: 1px solid var(--rule);
  border-top: 3px solid var(--rule-strong);
  background: var(--card);
}

.hl-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.6rem 0.9rem;
  border-bottom: 1px solid var(--rule);
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.hl-live { display: inline-flex; align-items: center; gap: 0.45rem; color: var(--ink-2); }
.hl-stamp { white-space: nowrap; }

.hl-rows { list-style: none; margin: 0; padding: 0; }
.hl-row + .hl-row { border-top: 1px solid var(--rule); }

.hl-link {
  display: grid;
  grid-template-columns: 56px minmax(0, 1fr);
  gap: 0.8rem;
  align-items: start;
  padding: 0.75rem 0.9rem;
  text-decoration: none;
  color: inherit;
}
.hl-link:hover { background: var(--sunk); }

/* Fixed box, image cropped to fill it. Testers reported the board's own grid never settling
   because some photos fill their frame and others sit as a narrow strip in a wide box; a
   square box with object-fit: cover is the same decision made once. */
.hl-shot {
  width: 56px;
  height: 56px;
  object-fit: cover;
  background: var(--sunk);
  border: 1px solid var(--rule);
}
.hl-shot.is-none {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--mono);
  font-size: 0.56rem;
  text-align: center;
  line-height: 1.2;
  color: var(--muted);
}

.hl-body { min-width: 0; }
.hl-title {
  display: block;
  font-size: 0.83rem;
  line-height: 1.35;
  color: var(--ink);
  /* Two lines, then ellipsis. A single-line clamp was cutting two different lenses to the
     same string and making distinct listings read as duplicates. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.hl-nums {
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem 1.1rem;
  margin-top: 0.4rem;
}
.hl-num { display: flex; flex-direction: column; gap: 0.05rem; }
.hl-k {
  font-family: var(--mono);
  font-size: 0.6rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.hl-v { font-size: 0.82rem; font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; }
.hl-num.is-gap .hl-v { color: var(--gain); }

.hl-foot {
  margin: 0;
  padding: 0.7rem 0.9rem;
  border-top: 1px solid var(--rule);
  background: var(--sunk);
  font-size: 0.74rem;
  line-height: 1.5;
  color: var(--muted);
}
.hl-foot strong { color: var(--ink); }

/* The anatomy figure, now rendered on a real lot. `is-real` replaces the red
   "EXAMPLE - NOT A REAL CALL" flag: same slot, opposite claim, so it reads in the site's
   ordinary label colour rather than the warning one. */
.landing .ad-flag.is-real { color: var(--muted); }
.landing .alert-demo img.ad-thumb {
  width: 56px;
  height: 56px;
  flex: 0 0 56px;
  object-fit: cover;
  border: 1px solid var(--rule);
  background: var(--sunk);
}
.landing .ad-note {
  margin: 0.9rem 0 0;
  padding-top: 0.8rem;
  border-top: 1px solid var(--rule);
  font-size: 0.78rem;
  line-height: 1.55;
  color: var(--muted);
}

/* Refund policy on the pricing page. Same class as the page subhead so it shares its measure
   and colour, separated only by the rule that separates everything else here. */
.pricing-refunds {
  margin-top: 0.8rem;
  padding-top: 0.8rem;
  border-top: 1px solid var(--rule);
  font-size: 0.82rem;
}

/* The bar's caption. Same mono label vocabulary as .bc-k, sitting directly under the track it
   describes, so the one graphic on the card stops being a shape that looks like data. */
.bc-bar { margin-bottom: 0.25rem; }
.bc-bar-k {
  font-family: var(--mono);
  font-size: 0.66rem;
  line-height: 1.35;
  color: var(--muted);
  margin-bottom: 0.5rem;
}
.bc-bar-k strong { color: var(--ink-2); font-weight: 600; }

/* The ruler strip, drawn one way instead of two.
 *
 * `.beat-rule` is absolutely positioned to left:0/right:0 of its section. In a plain beat that
 * is the content column; in a `.band`, whose negative margins bleed it to the viewport edge,
 * it is the whole viewport. Same motif, two widths, alternating down one page -- and at the
 * seam where a full-bleed strip sat over a band that stopped at the content margin, it left a
 * visible step at both edges. Three round-7 testers described that step as a half-painted
 * background and one said it reads as a rendering fault the first time you hit it.
 *
 * Inside a band the rule is now inset by exactly the padding the band uses to put its content
 * back on the page's measure, so every strip on the page is the same width as the text above
 * it. The count is down too, from six strips to four, because the page is four sections now. */
/* The first attempt at this used the band's own padding expression, and it computed to zero.
 * Recorded so it is not tried again: a percentage in `padding` resolves against the
 * CONTAINING BLOCK (the .stack, 1132px), while a percentage in `left` on an absolutely
 * positioned child resolves against its containing block's PADDING BOX -- which for a band is
 * the full bleed, 1521px. The same expression therefore means two different things in the two
 * properties, and `50vw - 50% - sbw/2` came out as exactly 0.
 *
 * What works instead: leave left/right at 0, cap the width at the page's real measure, and let
 * auto margins centre it. An absolutely positioned box with both left and right set, a
 * constrained width and auto margins is centred in its containing block, so one rule covers
 * band and non-band alike and both land on 1132px. The measure is `main`'s own: max-width
 * 1180px minus its 1.5rem side padding. */
.landing .beat-rule {
  max-width: calc(min(var(--page), 100vw - var(--sbw, 0px)) - 3rem);
  margin-left: auto;
  margin-right: auto;
}

/* ---------- The header travels with the reader ----------
 *
 * It did not, and on a landing page of full-height sections that is a one-way trip: four
 * screens down there was no route back to the board without scrolling to the top. One round-7
 * tester reported exactly that, and a second wrote that the nav being "fixed at the top" was
 * the only reason they had not already left -- they were mistaken about the fact and right
 * about the consequence, and measuring it settled which. `position` was `static`.
 *
 * The header and its ruled edge travel as one unit, so the rule's bottom margin is moved to a
 * spacer on the sticky wrapper instead: a sticky element cannot leave its own margin behind.
 * `--header-h` is published here because the board's own sticky strip has to clear it. */
:root { --header-h: 5.4rem; }

/* The background has to reach the window edge, not the content edge.
 *
 * Both elements are `max-width: var(--page); margin: 0 auto`, so a background on them is a
 * 1180px rectangle with transparent gutters either side -- and once the header started
 * travelling, page content scrolled through those gutters and the strip read as a hard-edged
 * semi-opaque box dragged across the headlines. Four round-8 testers described it that way and
 * one called it the ugliest thing on the site. It was mine, introduced with the stickiness.
 *
 * Spread shadow rather than a wrapper element, because a wrapper means editing 23 files. The
 * note further up this file is right that spread also grows the box vertically; `clip-path`
 * takes the vertical growth back off and leaves the horizontal bleed. */
/* The header does not travel any more.
 *
 * It was made sticky in round 7 to answer one tester who had no way back from four screens
 * down. It then drew complaints in rounds 8, 9 and 10 from seven testers between them: an
 * opaque strip that "slices section headings mid-scroll on every band transition", "slicing a
 * card title in half", read as a rendering bug by people who had not been told it was a
 * feature. The ledger is one complaint answered against seven created, which settles it.
 *
 * The original problem is answered a different way: the landing page is four short sections
 * now instead of six full-height ones, so the top of the page is a short scroll away rather
 * than four screens. */
.site-header,
.header-rule { position: static; }

/* The board's own strip stops travelling.
 *
 * It was sticky before the header was, back when it was the only thing that stayed on screen.
 * With a sticky header above it there are two travelling strips, and the lower one spent every
 * scroll position cutting across card photographs, prices and chips -- reported by four
 * round-8 testers, one of whom said it sliced the savings figures in half. One travelling
 * strip is a convenience and two is a hazard, and the header is the one carrying the nav. */
.board-bar { position: static; }



/* Reserve the scrollbar's width on every page, long or short.
 *
 * Two testers reported the wordmark and the whole nav visibly shifting right when they landed
 * on the 404, and read it as two pages built to different widths. They are built to the same
 * width: the 404 is short enough not to scroll, so it has no scrollbar, so `100vw` is ~15px
 * wider and everything centred in it moves by half that. Reserving the gutter everywhere
 * makes the centre line the same on a one-screen page and a ten-screen one. */
html { scrollbar-gutter: stable; }

/* ---------- Covered categories, as one card ----------
 *
 * Was a bare index: a hairline under every row, three more ruled note rows beneath it, and a
 * third column that ran short and left a hole. Five rule widths down one column with nothing
 * saying where one thing ended and the next began. It is a card now, in the same vocabulary as
 * the board's cards and the accuracy scoreboard: one border, one head, rows inside, caveats in
 * the foot. */
.covered { margin: 0 0 2rem; }

.cov-card {
  border: 1px solid var(--rule);
  border-top: 3px solid var(--rule-strong);
  background: var(--card);
}
.cov-h {
  margin: 0;
  padding: 0.7rem 1rem;
  border-bottom: 1px solid var(--rule);
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
.cov-list {
  list-style: none;
  margin: 0;
  padding: 0.35rem 1rem 0.6rem;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 0 2rem;
}
.cov-list a {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.32rem 0;
  text-decoration: none;
  color: var(--ink-2);
  font-size: 0.88rem;
}
.cov-list a:hover { color: var(--ink); }
.cov-list a:hover .cov-name { text-decoration: underline; }
.cov-n {
  font-family: var(--mono);
  font-size: 0.74rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.cov-foot {
  margin: 0;
  padding: 0.75rem 1rem 0.85rem;
  border-top: 1px solid var(--rule);
  background: var(--sunk);
  font-size: 0.78rem;
  line-height: 1.55;
  color: var(--muted);
}

/* ---------- Free against a seat, as two cards ---------- */
.landing .seat-diff {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1.25rem;
  margin: 2rem 0 0;
}
.seat-card {
  border: 1px solid var(--rule);
  border-top: 3px solid var(--rule-strong);
  background: var(--card);
  padding: 0.95rem 1.1rem 1.15rem;
}
/* The paid side takes the instrument blue on its edge, which is this site's mark for the
   thing it stands behind. */
.seat-card.is-paid { border-top-color: var(--signal); }
.landing .seat-diff dt {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.5rem;
}
.landing .seat-diff dd {
  margin: 0;
  font-size: 0.95rem;
  line-height: 1.55;
  color: var(--ink-2);
}



/* ---------- Landing: the second column, and the end of the full-height slide ----------
 *
 * Two things, and they are the same finding reported four rounds running by 10/10 testers.
 *
 * Every section was `min-height: 74vh` with its content vertically centred in a column about
 * 45% of the viewport, so the page was six screenfuls each holding one heading, one grey line
 * and a lot of ground. Testers did not read that as restraint; the recurring phrase across
 * rounds 5 to 8 is "a two-column layout whose second column failed to load", and round 8
 * added "it drags", "my eye had nowhere to go", and one who described scrolling a narrow
 * gutter with a desert beside it.
 *
 * So: sections are the height of what is in them, and the two that have a second thing to say
 * say it in the second column. Nothing decorative went into the gap -- the scoreboard is the
 * accuracy record and the figure is a real lot.
 *
 * The decorative tick strips are gone entirely, elements and all. Eight of ten round-8 testers
 * called them a chart that failed to render and three zoomed in to check whether it had. A
 * motif that has to be inspected to be understood as a motif is not working. */
/* No rule where the ground already changes. A `.beat` carries a hairline top border, and a
   `.band` changes the background at exactly the same line, so every band boundary was drawn
   twice -- reported in round 10 as "a doubled hairline/background seam at every section
   boundary that reads as a rendering bug". The border survives only between two sections that
   share a ground, where it is the only thing separating them. */
.landing .band + .beat,
.landing .beat + .band { border-top: 0; }

.landing .beat {
  min-height: 0;
  /* `padding-block`, never the `padding` shorthand. A full-bleed `.band` restores the page's
     measure with padding-left/right, and a shorthand here resets those to 0 -- which bled the
     headline to the window edge and left the second column standing outside the band's own
     background. */
  padding-block: 3.5rem 4rem;
}

.landing .beat-copy { min-width: 0; }

@media (min-width: 960px) {
  .landing .beat-lead {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(340px, 26rem);
    gap: 3.5rem;
    align-items: start;
  }

  /* The alert beat runs the other way: the card first, and with the larger share.
   *
   * This section's job is to show what an alert is. The words beside it are the argument for
   * why that lot and not another one, which is worth reading second -- so the example leads
   * and the reasoning supports it, rather than the other way round. It also breaks the
   * left-heading/right-panel silhouette that three round-13 testers said every band shared. */
  .landing .beat-alert {
    display: grid;
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
    gap: 3.5rem;
    align-items: start;
  }
  .landing .beat-alert .alert-demo { grid-column: 1; grid-row: 1; }
  .landing .beat-alert .beat-copy { grid-column: 2; grid-row: 1; }
  /* The seats beat runs the other way round: its heading is short and its second element --
     the list of categories actually on the board -- is a wide index that wants the room. So
     the copy takes the narrow column and the list takes the wide one. */
  .landing .beat-seats {
    display: grid;
    grid-template-columns: minmax(0, 20rem) minmax(0, 1fr);
    gap: 3.5rem;
    align-items: start;
  }
  .landing .beat-seats .beat-copy h2 { max-width: none; font-size: clamp(1.8rem, 3vw, 2.4rem); }
  .landing .beat-seats .price-line,
  .landing .beat-seats .cta-row { grid-column: 1 / -1; }
  .landing .beat-seats .covered { margin-bottom: 0; border-top: 0; padding-top: 0; }
  /* The beat's own headline measure is set for a full-width column; beside a second column it
     has to give the width back or the two fight. */
  .landing .beat-lead .beat-copy h2 { max-width: 14ch; }
  .landing .beat-alert .beat-copy h2 { max-width: 15ch; }
}

/* ---------- The scoreboard ---------- */
.proof {
  border: 1px solid var(--rule);
  border-top: 3px solid var(--rule-strong);
  background: var(--card);
  padding: 1.1rem 1.2rem 1.2rem;
}
.pf-h {
  margin: 0 0 0.9rem;
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-weight: 500;
  color: var(--muted);
}
.pf-rows { margin: 0; }
.pf-row {
  padding: 0.65rem 0;
  border-top: 1px solid var(--rule);
}
.pf-row:first-child { border-top: 0; padding-top: 0; }
.pf-row dt {
  font-size: 0.78rem;
  line-height: 1.4;
  color: var(--muted);
}
.pf-row dd {
  margin: 0.2rem 0 0;
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0 0.5rem;
}
.pf-row dd strong {
  font-family: var(--display);
  font-stretch: 112%;
  font-weight: 700;
  font-size: 1.5rem;
  line-height: 1.1;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
/* Under our own target. Marked in the miss colour rather than quietly in the ink one: the
   whole value of publishing it is that it is visibly a miss. */
.pf-row.is-miss dd strong { color: var(--miss); }
.pf-against {
  font-size: 0.74rem;
  line-height: 1.4;
  color: var(--muted);
}
.pf-note {
  margin: 1rem 0 0;
  padding-top: 0.8rem;
  border-top: 1px solid var(--rule);
  font-size: 0.76rem;
  line-height: 1.55;
  color: var(--muted);
}

/* The figure is the alert beat's second column now, so it stops being a narrow box floating in
   a wide one. */
@media (min-width: 960px) {
  .landing .alert-demo { max-width: none; margin-top: 0; }
}

/* The checkout notice's actions. A notice that says "you cannot pay yet" has to carry the way
   forward as something you press, or the page reads as a payment form that failed to load. */
.pay-notice-acts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin-top: 0.9rem;
}
.pay-notice-acts .cta { flex: 0 0 auto; }

/* The signed-out state of a page reachable from the nav. Drawn as the site draws any other
   panel -- hairline box, one heavier top edge -- so the page ends rather than stops. */
.signedout-card {
  max-width: 46rem;
  margin: 1.5rem 0 3rem;
  border: 1px solid var(--rule);
  border-top: 3px solid var(--rule-strong);
  background: var(--card);
  padding: 1.6rem 1.6rem 1.75rem;
}
.signedout-lead {
  margin: 0;
  font-size: 1.05rem;
  line-height: 1.55;
  color: var(--ink-2);
  max-width: 52ch;
}
.signedout-note {
  margin: 0.9rem 0 0;
  padding-top: 0.9rem;
  border-top: 1px solid var(--rule);
  font-size: 0.84rem;
  line-height: 1.55;
  color: var(--muted);
  max-width: 60ch;
}
.signedout-card .cta-row { margin-top: 1.4rem; }


/* ---------- Plan cards stop being height-matched ----------
 *
 * Pro has four feature rows and the other two have seven, and the grid stretched all three to
 * the tallest, so Pro carried 200-300px of empty white above its button. Five round-8 testers
 * reported it and one said the cheapest plan is "literally drawn as an absence" -- which is
 * exactly backwards, because Pro is the plan most people should buy.
 *
 * `align-items: start` lets each card be its own height. The row still reads as a row: the
 * cards share a top edge, a width and a border, and the heads are already aligned to each
 * other by their own rules. What is gone is a void that only existed to make three different
 * amounts of content pretend to be the same amount. */
.landing .plan-grid,
.pricing-grid {
  /* `stretch`, not `start`.
     Round 9 took the subgrid out because it forced seven feature tracks and left the Pro card
     with 200-300px of void between its last bullet and its button. `start` fixed that and
     replaced it with three cards of three different heights and three unaligned buttons,
     reported by five testers across rounds 11 and 12. Stretching the cards and pushing the
     button to the bottom edge is the board card's answer to the same shape: the slack lands
     below the content, inside the card, where it reads as padding rather than as a hole. */
  align-items: stretch;
  /* `repeat(3, 1fr)` came off with the subgrid block, leaving `auto-fit minmax(280px, 1fr)`
     and grid items at their default `min-width: auto` -- so the Customize card's long mono
     line ("$15 base + $5/category + weekly volume") pushed the row past the viewport and
     clipped that card mid-sentence, on the one screen where someone decides to pay. */
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
}
.price-card { min-width: 0; }
.price-card .tier-standard { overflow-wrap: anywhere; }
.landing .plan .cta,
.price-card .cta { margin-top: auto; padding-top: 1.1rem; }
.price-card, .landing .plan { display: flex; flex-direction: column; }

/* The separator in a category path, so the path reads as a path and not as a delimiter that
   escaped. Muted, because it is punctuation rather than a level of the hierarchy. */
.cat-sep { color: var(--rule-strong); padding: 0 0.15rem; }

/* The landing page's price summary, in place of a second copy of the pricing table. Three
   readings on one rule, the same shape as the figures strip above it. */
.landing .price-line {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 2.25rem;
  margin: 1.75rem 0 0;
  padding-top: 1rem;
  border-top: 1px solid var(--rule);
  font-size: 0.95rem;
  color: var(--ink-2);
  max-width: 72ch;
}
.landing .price-line strong {
  font-family: var(--display);
  font-stretch: 112%;
  font-weight: 700;
  font-size: 1.15rem;
  color: var(--ink);
}
.landing .price-line + .cta-row { margin-top: 1.5rem; }

/* "Show the next N" on the track record. A row in the list rather than a control beside it,
   because that is where the reader's eye already is when they run out of rows. */
.feed-more { list-style: none; margin: 1rem 0 0; }
.feed-more button {
  width: 100%;
  padding: 0.8rem 1rem;
  background: var(--sunk);
  border: 1px solid var(--rule);
  border-top: 2px solid var(--rule-strong);
  font-family: var(--mono);
  font-size: 0.76rem;
  letter-spacing: 0.06em;
  color: var(--ink-2);
  cursor: pointer;
}
.feed-more button:hover { background: var(--card); color: var(--ink); }
.feed-more button:disabled { opacity: 0.5; cursor: default; }

/* ---------- Contents rail on the long prose pages ----------
 *
 * The measure stays exactly where it was, under the wordmark. The rail takes the space to its
 * right, which has been reported empty in every design round since round 5, and fills it with
 * the document's own shape rather than with decoration. Below 1080px it folds above the text
 * as an ordinary list, which is what a contents block is when there is no column for it. */
main.prose.has-toc { max-width: var(--page); }

.toc {
  margin: 0 0 2rem;
  padding: 0.9rem 0 0;
  border-top: 1px solid var(--rule-strong);
}
.toc-h {
  margin: 0 0 0.6rem;
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
.toc-list {
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: toc;
}
.toc-list li { counter-increment: toc; }
.toc-list a {
  display: block;
  padding: 0.3rem 0 0.3rem 1.9rem;
  position: relative;
  font-size: 0.82rem;
  line-height: 1.4;
  color: var(--ink-2);
  text-decoration: none;
  border-top: 1px solid var(--rule);
}
.toc-list li:first-child a { border-top: 0; }
.toc-list a::before {
  content: counter(toc);
  position: absolute;
  left: 0;
  font-family: var(--mono);
  font-size: 0.7rem;
  color: var(--muted);
}
.toc-list a:hover { color: var(--ink); text-decoration: underline; }
.toc-list a.is-here { color: var(--signal); }
.toc-list a.is-here::before { color: var(--signal); }

@media (min-width: 1080px) {
  main.prose.has-toc {
    display: grid;
    grid-template-columns: minmax(0, var(--prose)) minmax(200px, 17rem);
    column-gap: 3.5rem;
    align-items: start;
  }
  /* Everything the page already had stays in the first column and keeps its own measure.
     The rail's own rule has to out-specify this one, or `> *` claims it too -- which is
     exactly what happened on the first deploy: the grid was correct and the rail sat in
     column 1 above the title. */
  main.prose.has-toc > * { grid-column: 1; }
  main.prose.has-toc > .toc {
    grid-column: 2;
    grid-row: 1 / 200;
    position: sticky;
    top: 1.5rem;
    margin: 0;
    max-height: calc(100vh - 3rem);
    overflow-y: auto;
  }
}

/* ---------- The closing section's second column ----------
 *
 * The last three sections of this page were reported in round 12 by five of ten testers as the
 * place it falls apart: "an orphaned lone button", "a final CTA whose blank right half reads as
 * a rendering failure", "that's where I'd have closed the tab". The section's claim is that
 * there is a lot here you can read without paying, and the claim is more convincing itemised
 * than asserted -- so the empty half becomes the list of what is actually open. Four real
 * destinations with a line each, which is the shape of the 404 page testers have praised in
 * every round since the fifth. */
@media (min-width: 960px) {
  .landing .closer {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(320px, 24rem);
    gap: 3.5rem;
    align-items: start;
  }
  .landing .closer .beat-copy h2 { max-width: 16ch; }
}

.closer-index { min-width: 0; }
.ci-h {
  margin: 0 0 0.7rem;
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
.closer-index ul { list-style: none; margin: 0; padding: 0; }
.closer-index li {
  padding: 0.7rem 0;
  border-top: 1px solid var(--rule);
}
.closer-index li:first-child { border-top: 2px solid var(--rule-strong); }
.closer-index a {
  display: block;
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--ink);
  text-decoration: none;
}
.closer-index a:hover { color: var(--signal); text-decoration: underline; }
.closer-index span {
  display: block;
  margin-top: 0.2rem;
  font-size: 0.8rem;
  line-height: 1.5;
  color: var(--muted);
}

/* The price line and its button close the seats section rather than floating under it. One
   strip, one rule above it, so the button is attached to the figures it belongs to instead of
   sitting alone on a field of beige. */
/* The price and its button share one row, under one rule that runs the section's full width.
   Apart, the button was "an orphaned lone button" on "a field of beige" (three round-12
   testers); together they read as the section's closing line: what it costs on the left, the
   way to the detail on the right. */
.landing .price-row {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1.25rem 2.5rem;
  margin-top: 2rem;
  padding-top: 1.1rem;
  border-top: 2px solid var(--rule-strong);
}
.landing .price-row .price-line {
  margin: 0;
  padding: 0;
  border: 0;
  max-width: none;
  flex: 1 1 26rem;
}
.landing .price-row .cta { flex: 0 0 auto; }

/* ---------- The closing section's second column ----------
 *
 * The last three sections of this page were reported in round 12 by five of ten testers as the
 * place it falls apart: "an orphaned lone button", "a final CTA whose blank right half reads as
 * a rendering failure", "that's where I'd have closed the tab". The section's claim is that
 * there is a lot here you can read without paying, and the claim is more convincing itemised
 * than asserted -- so the empty half becomes the list of what is actually open. Four real
 * destinations with a line each, which is the shape of the 404 page testers have praised in
 * every round since the fifth. */


.closer-index { min-width: 0; }
.ci-h {
  margin: 0 0 0.7rem;
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
.closer-index ul { list-style: none; margin: 0; padding: 0; }
.closer-index li {
  padding: 0.7rem 0;
  border-top: 1px solid var(--rule);
}
.closer-index li:first-child { border-top: 2px solid var(--rule-strong); }
.closer-index a {
  display: block;
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--ink);
  text-decoration: none;
}
.closer-index a:hover { color: var(--signal); text-decoration: underline; }
.closer-index span {
  display: block;
  margin-top: 0.2rem;
  font-size: 0.8rem;
  line-height: 1.5;
  color: var(--muted);
}

/* The price line and its button close the seats section rather than floating under it. One
   strip, one rule above it, so the button is attached to the figures it belongs to instead of
   sitting alone on a field of beige. */
/* The price and its button share one row, under one rule that runs the section's full width.
   Apart, the button was "an orphaned lone button" on "a field of beige" (three round-12
   testers); together they read as the section's closing line: what it costs on the left, the
   way to the detail on the right. */
.landing .beat-seats .price-line {
  border-top: 2px solid var(--rule-strong);
  padding-top: 1.1rem;
  max-width: none;
}
.landing .beat-seats .cta-row { margin-top: 1.1rem; }

@media (min-width: 960px) {
  .landing .beat-seats .price-line {
    grid-column: 1;
    grid-row: 2;
  }
  .landing .beat-seats .cta-row {
    grid-column: 2;
    grid-row: 2;
    margin-top: 0;
    align-self: center;
    padding-top: 1.1rem;
    border-top: 2px solid var(--rule-strong);
    justify-content: flex-start;
  }
}

/* The median gap, raised. It is the figure a reader decides on, and it was the smallest type
   in the section while the six-figure total nobody received was the largest. */
.landing .beat-stats li.is-lead .s-v { font-size: 2.1rem; color: var(--signal); }
.landing .beat-stats li.is-lead .s-k { color: var(--ink-2); }

/* A plan row that is NOT included. Same list, same rule, no glyph: the word carries it, and a
   cross would be the tick this site removed in round 7 wearing a different hat. */
.price-card li.is-out,
.landing .plan li.is-out { color: var(--muted); }

/* The filler that completes the board grid's last row. Deliberately not a card: no border, no
   ground, so it reads as the end of the list rather than as a 46th lot. */
.bc-fill {
  /* The span is set inline by board.js from the live column count; see the note there. */
  align-self: stretch;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 0.9rem;
  padding: 0.6rem 0.2rem;
}
.bc-fill p { margin: 0; padding-top: 0.7rem; border-top: 1px solid var(--rule); }
.bc-fill a {
  display: block;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--ink);
  text-decoration: none;
}
.bc-fill a:hover { color: var(--signal); text-decoration: underline; }
.bc-fill span {
  display: block;
  margin-top: 0.15rem;
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--muted);
}
/* The links are in the grid now; the paragraph that used to carry them would be a second copy. */
.board-main > .board-foot { display: none; }

/* The live panel sits under the signup page's stats rather than beside them, so the column
   fills top to bottom instead of stopping a third of the way down. */
.auth-statement .hero-live { margin-top: 1.5rem; }

/* ---------- Landing points ----------
 *
 * The prose blocks on this page became lists: the owner's call, and the right one — a landing
 * page is scanned, and every paragraph here was carrying two or three separable facts that a
 * reader had to unpack from a sentence before they could weigh them.
 *
 * No glyph. The site removed its decorative ticks in round 7 for a reason that applies here
 * too — a mark in front of every row confirms nothing when there is no unmarked row to
 * contrast it with. What separates the items is the hairline the rest of the site is drawn
 * with, which is also how `.plan li` and the covered-categories index already do it.
 *
 * The bold lead-in is the scannable part: it carries the claim, and the sentence after it
 * carries the working. A reader who stops at the bold text still gets the argument. */
.landing .beat-points {
  list-style: none;
  margin: 1.25rem 0 0;
  padding: 0;
  max-width: 46ch;
}
.landing .beat-points li {
  padding: 0.6rem 0;
  border-top: 1px solid var(--rule);
  font-size: 1rem;
  line-height: 1.5;
  color: var(--ink-2);
}
.landing .beat-points li:first-child { border-top: 0; padding-top: 0; }
.landing .beat-points strong { color: var(--ink); font-weight: 600; }

/* The hero's pair is the page's opening claim, so it is set at the lede's size and keeps the
   lede's measure. */
.landing .hero .beat-points {
  font-size: 1.05rem;
  max-width: 44ch;
  margin-top: 1.4rem;
}
.landing .hero .beat-points li { font-size: inherit; }

@media (max-width: 720px) {
  .landing .beat-points { max-width: none; }
}


/* The plan cards on the home page span the seats section's full width and behave exactly as
   the ones on /pricing do -- equal height, button on the bottom edge -- so the two pages
   cannot drift into two different shapes of the same component. */
.landing .beat-seats .plan-grid {
  grid-column: 1 / -1;
  margin-top: 2.25rem;
  padding-top: 1.6rem;
  border-top: 2px solid var(--rule-strong);
  align-items: stretch;
}
.landing .beat-seats .beat-note { grid-column: 1 / -1; }

/* The example card's photograph, as a band rather than a 56px square.
 *
 * It was cropped into a thumbnail the size of a favicon, so a 1600x900 product shot showed
 * you the middle of its background and nothing else -- the card meant to make a lot
 * recognisable was the one place you could not see it. Same treatment the board cards use,
 * which is where this card's credibility comes from in the first place. */
.landing .alert-demo .ad-shot {
  display: block;
  width: 100%;
  height: 190px;
  object-fit: cover;
  background: var(--sunk);
  border: 1px solid var(--rule);
  margin-bottom: 0.75rem;
}
.landing .alert-demo .ad-title { margin: 0 0 0.9rem; }
