/* ==========================================================================
   Dumperator — dumperator.com
   Palette: black base, purple + orange accents, white text
   ========================================================================== */

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('/assets/fonts/inter-var-latin.woff2') format('woff2-variations');
}

:root {
  /* Surfaces, lifted off zero.

   The previous ramp sat between 0.002 and 0.010 relative luminance — every
   surface within 1% of absolute black, which is what made the page read as
   too dark to read comfortably. Lifting the base roughly triples it while
   keeping the theme unambiguously dark and the violet cast intact.

   The text tokens are solved against this ramp, not inherited from the old
   one: raising a surface raises everything on it, so --text-faint would have
   fallen from 5.18:1 to 3.66:1 — below AA — had it been left alone. Each is
   the lightest shade of the original's hue that still clears its target on
   the darkest surface it can land on (--surface-2). */
  --black:       #12101c;   /* page base        */
  --black-2:     #191527;   /* alternating band */

  /* Ink for text sitting ON a --purple fill (skip link, primary button, the
     "One-time" badge). --black-2 used to do this job too, but lifting it for
     the band surface dropped it to 4.24:1 on the logo purple. Darker than the
     band on purpose: it has to clear 4.5:1 against a mid-lightness violet,
     which no surface token can do. */
  --ink-accent:  #110f1b;   /* 4.50:1 on --purple */
  --surface:     #221d33;   /* cards            */
  --surface-2:   #2b2440;   /* raised, table heads */
  --border:      #3b3358;
  --border-lite: #4d4470;

  /* The brand mark's own purple, sampled from logo-alpha.png: #d806dd covers
     33% of the opaque pixels and is the dominant non-white. This is not a
     tint of the old --purple (#8b5cf6) — it is a more saturated magenta, and
     the difference is visible at logo size.

     On the lifted ramp it measures 4.66:1 on --black but 3.86:1 on --surface,
     so it stays the fill for large text and buttons while anything at body
     size goes through --purple-lite (5.02:1 on --surface, 5.19:1 on base). */
  --purple:      #d806dd;
  --purple-lite: #e138e5;  /* 4.57:1 on --surface, 5.28:1 on --black */
  --purple-deep: #a800ad;
  --purple-glow: rgba(216, 6, 221, .35);

  --orange:      #f97316;
  --orange-lite: #fb923c;
  --orange-glow: rgba(249, 115, 22, .35);

  --red:         #ef4444;   /* the app's DUMP button colour */
  --green:       #16a34a;   /* the app's IN DELAY colour  */

  --text:        #ffffff;   /* 18.8:1 on --black */
  --text-dim:    #b7b0cd;   /* 7.07:1 on --surface-2, 9.05:1 on --black */
  /* Solved for 4.5:1 on --surface-2, the darkest surface body text sits on.
     Reaches 5.8:1 on the page base. Hue matches the old --text-faint. */
  --text-faint:  #928ba8;
  --text-faint-aa: #a49dc0;
  /* .plan.featured's background is a linear-gradient tinted 9% toward orange
     rather than a flat fill, and the warm backdrop costs a token more than a
     point of lightness buys — on the lifted ramp --text-faint-aa lands at
     4.02:1 there. Solved again against this ramp: #a49dba, 4.51:1. */
  --text-faint-featured: #a49dba;

  --radius:      14px;
  --radius-sm:   9px;
  /* The content column's hard ceiling, centred at every viewport width.
     Everything above this is background, not content.

     1024px puts the inner content area at 976px, which is where every measure
     on the page now lands. Chosen because it is the widest column the design
     actually benefits from: past this the section bands gain whitespace
     without gaining clarity, because every prose block is already capped in
     `ch` and the grid columns are already sized. */
  --maxw:        1024px;

  --font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
          Helvetica, Arial, sans-serif;
  --mono: "SFMono-Regular", ui-monospace, "Cascadia Mono", Menlo, Consolas, monospace;
}

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

html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--black);
  color: var(--text);
  font-family: var(--font);
  /* A fixed 17px, deliberately.

     This was a clamp(17px, 0.179vw + 14.43px, 19px) that grew the type on wide
     screens, added when the wrap ran to 95% of the viewport and 17px sat
     alone in a 2418px column. The cap removed that condition.

     With the content column fixed at 976px, keying type to viewport width is
     incoherent: a 1440px laptop and a 2560px desktop would now render the
     identical column at 17px and 19px respectively, so the same layout would
     have two different type sizes depending only on the bezel. Bounding the
     column and holding type constant is the coherent version of the same fix. */
  font-size: 17px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

/* Skip link.
   Off-screen until focused, then pinned to the top-left. Without it the
   shortest keyboard path to the download runs brand -> 4 nav links -> hero
   CTAs -> ten full sections -> footer, because #how is the only anchor that
   has an id to land on.

   :focus rather than :focus-visible here on purpose: this is only ever
   reached by keyboard, so there is no click case to suppress a ring for. */
.skip-link {
  position: fixed;
  top: 12px;
  left: 12px;
  z-index: 100;
  padding: 12px 20px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  background: var(--purple);
  color: var(--ink-accent);
  font-weight: 700;
  text-decoration: none;
  border-radius: var(--radius-sm);
  transform: translateY(-200%);
  transition: transform .18s ease-out;
}
.skip-link:focus {
  transform: translateY(0);
}

/* main is focusable only as a skip target; it should not draw a ring. */
main:focus { outline: none; }

img { max-width: 100%; display: block; }

a { color: var(--purple-lite); text-decoration: none; }
a:hover { text-decoration: underline; }

/* Focus indicator.
   DESIGN.md:304 specifies `outline: 2px solid var(--orange)` with a 3px
   offset and says never `outline: none` without a replacement — but the rule
   was only ever written for .deck-arrow, leaving every other focusable
   element on the page with the UA default, which on a near-black surface is
   a faint grey ring. 28 focusables, 1 styled.

   Declared before :hover so hover cannot paint over the focus ring, which is
   what makes a focused link look unfocused.

   :focus-visible rather than :focus so a mouse click doesn't leave a ring on
   a button, while keyboard and assistive-tech users always get one. */
:focus-visible {
  outline: 2px solid var(--orange);
  outline-offset: 3px;
  border-radius: 2px;
}

/* The deck arrow carries its own, tighter ring (deck.css); this keeps the two
   from doubling up if that stylesheet is ever loaded alongside. */
.deck-arrow:focus-visible { outline-offset: 3px; }

h1, h2, h3, h4 { line-height: 1.15; margin: 0 0 .5em; font-weight: 800; letter-spacing: -.02em; text-wrap: balance; }
h1 { font-size: clamp(2.3rem, 5.5vw, 4rem); }
h2 { font-size: clamp(1.75rem, 3.6vw, 2.6rem); }
h3 { font-size: 1.2rem; font-weight: 700; letter-spacing: -.01em; }
p  { margin: 0 0 1.1em; text-wrap: pretty; }

/* Measure cap for prose.
   These blocks were spanning their full grid column. A line that long is
   measurably harder to track back to the next one, which is the whole point
   of a cap.

   42rem lands 64-70ch at the 15-17px these blocks actually render at, and
   stays left-aligned inside the 1120px wrap rather than centring, so it does
   not disturb the existing section rhythm. max-width (not width) so narrow
   viewports still fill their column.

   Measure in ch against the loaded font's "0" advance, not by counting
   characters per rendered rect — the two disagree by up to 20%.

   .tl-note is set in timeline.css, which loads after this file, so it is
   capped there instead. */
.callout p,
details .body,
.stat-note { max-width: 42rem; }

.wrap {
  width: 100%;
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 0 24px;
}

/* --maxw is 1024px and it is centred, at every width. An earlier cut ran the
   wrap to 95% of the viewport on the theory that a large monitor has room to
   fill. It does have the room; it just should not be filled. Past ~1000px the
   extra width stopped being useful and became the problem: prose measures ran
   to 131ch in card tracks and 152ch in the table's notes column, and the hero
   had 73% of itself empty because the mark was floating in the gutter.

   The cap makes the content column a constant 976px (1024 less its padding)
   from 1024px upward, so every measure on the page is bounded by the design
   rather than by whichever monitor someone happens to own. */
@media (max-width: 820px) {
  .wrap { margin: 0 auto; }
}

.eyebrow {
  display: inline-block;
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--purple-lite);
  margin-bottom: 14px;
}

.dim { color: var(--text-dim); }
.center { text-align: center; }

/* ---------- Buttons ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 15px 30px;
  border-radius: var(--radius-sm);
  font-weight: 700;
  font-size: 1rem;
  border: 1px solid transparent;
  cursor: pointer;
  transition:
    transform .22s cubic-bezier(.22, 1, .36, 1),
    box-shadow .28s cubic-bezier(.22, 1, .36, 1),
    background .22s cubic-bezier(.22, 1, .36, 1),
    border-color .22s cubic-bezier(.22, 1, .36, 1);
  text-decoration: none !important;
  /* Long labels ("Download Dumperator for Windows") must be allowed to wrap,
     otherwise a nowrap button is wider than a phone and drags the page
     sideways. max-width keeps it inside the wrap on every screen. */
  max-width: 100%;
  text-align: center;
}

/* Below this width, let multi-word button labels wrap onto two lines. */
@media (max-width: 520px) {
  .btn { white-space: normal; hyphens: none; }
}
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0) scale(.985); }

/* The brand mark's purple carries the primary action. --ink-accent reads
   4.50:1 at the gradient's dark end and 5.21:1 at its light end; white only
   reaches 4.21:1 at the dark end. Orange stays as the secondary accent for
   cost figures and the DUMP affordance, where it reads as warning rather than
   invitation. */
.btn-primary {
  background: linear-gradient(135deg, var(--purple) 0%, #e03ce4 100%);
  color: var(--ink-accent);
  box-shadow: 0 10px 30px -8px var(--purple-glow);
}
.btn-primary:hover { box-shadow: 0 16px 40px -8px var(--purple-glow); }

.btn-purple {
  background: linear-gradient(135deg, var(--purple) 0%, var(--purple-deep) 100%);
  color: #fff;
  box-shadow: 0 10px 30px -8px var(--purple-glow);
}
.btn-purple:hover { box-shadow: 0 16px 40px -8px var(--purple-glow); }

.btn-ghost {
  background: transparent;
  color: var(--text);
  border-color: var(--border-lite);
}
.btn-ghost:hover { background: var(--surface-2); border-color: var(--purple); }

.btn-sm { padding: 11px 22px; font-size: .92rem; }

/* ---------- Hero ---------- */

.hero {
  position: relative;
  padding: 92px 0 84px;
  /* Visible so the tilted / extruded mark can paint outside its track. */
  overflow: visible;
}
.hero .wrap { position: relative; z-index: 3; }

.hero::before {
  content: "";
  position: absolute;
  top: -320px; left: 50%;
  width: 1000px; height: 700px;
  transform: translateX(-50%);
  background: radial-gradient(ellipse at center, var(--purple-glow) 0%, transparent 62%);
  pointer-events: none;
}
.hero::after {
  content: "";
  position: absolute;
  bottom: -280px; right: -180px;
  width: 720px; height: 620px;
  background: radial-gradient(ellipse at center, var(--orange-glow) 0%, transparent 65%);
  pointer-events: none;
}

/* The hero copy is the only column now that the app mock is gone, so the
   grid is gone with it. 560px is the measure this text was already capped
   at via .hero-sub. */
.hero-copy { max-width: 640px; }

/* Wide screens: the hero becomes a real two-column grid instead of a 640px
      column stranded beside the rest of the width.

      The breakpoint is 1024px, not a viewport width but a content width: the
      wrap caps at 1024px, which is 976px of content after its 24px padding
      either side, and the columns below need 600 + 48 + 328 to fill exactly
      that. The wrap only reaches its cap at a 1024px viewport and holds there
      at every width above it, so the hero switches over once and then never
      changes again.

      Both tracks were resized when the cap came down from 1200px. At the old
      1152px column, 640 + 56 + 456 fitted; at 976px those numbers overflow the
      grid's own track. The copy lost 40px and the gap 8px; the mark lost the
      most, since it is the one element here that is not text and can absorb a
      smaller box without losing anything.

      Below 1024px the mark stays absolutely positioned in the gutter, which is
      what it did before and costs nothing at those sizes.

      Note the cap does the work here. There is no max-width on this grid: the
      .wrap above already bounds it to 976px, so the columns cannot spread no
      matter how wide the monitor is. An earlier version needed a 1720px cap
      here to stop space-between opening a 1298px gap; with the content column
      fixed, that constraint is gone rather than merely re-tuned. */
@media (min-width: 1024px) {
  .hero-inner {
    display: grid;
    /* Mark left, copy right — brand leads the composition.
       Track sized for the larger mark; rotateY rest/tilt may paint outside it. */
    grid-template-columns: minmax(260px, 400px) minmax(0, 1fr);
    gap: 40px;
    align-items: center;
  }
  .hero-inner .brand {
    position: static;
    grid-column: 1;
    grid-row: 1;
    justify-self: start;
    align-self: center;
    width: 100%;
  }
  /* Explicit columns: mark precedes copy in the markup for mobile stacking,
     and also takes column 1 on desktop after the swap. */
  .hero-inner .hero-copy {
    grid-column: 2;
    max-width: 520px;
  }
}

.hero h1 { margin-bottom: 20px; }
/* Solid orange — the hue DESIGN.md assigns to action / the DUMP affordance.
   Purple stays on the brand mark and primary CTA; the warning in the sentence
   needs the other accent, and colour alone is not carrying meaning (the words
   still do). */
.hero h1 .accent {
  color: var(--orange-lite);
}

.hero-sub {
  font-size: 1.16rem;
  color: var(--text-dim);
  max-width: 560px;
  margin-bottom: 30px;
}

.hero-cta { display: flex; flex-wrap: wrap; gap: 14px; margin-bottom: 18px; }

.hero-note { font-size: .88rem; color: var(--text-faint); }

/* ---------- Sections ---------- */

section { padding: 88px 0 100px; }
section.tight { padding: 68px 0; }

/* Merged denser sections need more vertical room between blocks than the old
   one-topic-per-slide rhythm; hero/download stay tighter. */
.section-why { padding: 96px 0 108px; }
.section-how { padding: 100px 0 112px; }
.section-specs { padding: 96px 0 108px; }
.section-buy { padding: 96px 0 100px; }
.section-download { padding: 72px 0 96px; }

/* Left-aligned: instrument / manual, not campaign-centred SaaS. */
.section-head {
  max-width: 42rem;
  margin: 0 0 40px;
  text-align: left;
}
.section-head h2 { margin-bottom: .45em; }
.section-head p {
  color: var(--text-dim);
  font-size: 1.08rem;
  margin-bottom: 0;
  max-width: 58ch;
}

.alt {
  background: var(--black-2);
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}

/* ---------- Why: prose + facts ---------- */

.why-body {
  max-width: 58ch;
  margin: 0 0 36px;
}
.why-body p {
  color: var(--text-dim);
  font-size: 1.02rem;
  margin-bottom: 1em;
}
.why-body p:last-child { margin-bottom: 0; }

.facts {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0;
  margin: 0 0 14px;
  padding: 0;
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}
@media (max-width: 760px) {
  .facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.fact {
  padding: 18px 16px 18px 0;
  border-right: 1px solid var(--border);
  margin: 0;
}
.fact:last-child { border-right: none; padding-right: 0; }
@media (max-width: 760px) {
  .fact:nth-child(2n) { border-right: none; padding-right: 0; }
  .fact:nth-child(-n+2) {
    border-bottom: 1px solid var(--border);
  }
}

.fact dt {
  margin: 0 0 6px;
  font-family: var(--mono);
  /* .75rem = 12px floor; check.py bans reading text under ~12px. */
  font-size: .75rem;
  font-weight: 400;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-faint-aa);
}
.fact dd {
  margin: 0;
  font-size: clamp(1.35rem, 4vw, 1.75rem);
  font-weight: 800;
  letter-spacing: -.03em;
  font-variant-numeric: tabular-nums;
  color: var(--orange-lite);
  line-height: 1.1;
}

.facts-note {
  max-width: 58ch;
  margin: 0 0 40px;
  font-size: .95rem;
  line-height: 1.6;
  color: var(--text-faint-aa);
}

/* ---------- How: numbered steps (no cards) ---------- */

.steps {
  list-style: none;
  margin: 0 0 46px;
  padding: 0;
  display: grid;
  gap: 0;
  border-top: 1px solid var(--border);
}
.steps > li {
  display: grid;
  grid-template-columns: 52px minmax(0, 1fr);
  gap: 18px;
  padding: 22px 0;
  border-bottom: 1px solid var(--border);
  align-items: start;
}
.step-num {
  font-family: var(--mono);
  font-size: .85rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--purple-lite);
  line-height: 1.6;
  padding-top: 2px;
}
.step-body h3 {
  margin: 0 0 6px;
  font-size: 1.08rem;
}
.step-body p {
  margin: 0;
  color: var(--text-dim);
  font-size: .96rem;
  max-width: 62ch;
}

.section-how .chain { margin-top: 8px; }
.section-how .tl { margin-top: 46px; }

/* ---------- Specs aside + budget ---------- */

.spec-aside {
  max-width: 62ch;
  margin: 28px 0 0;
  padding-top: 22px;
  border-top: 1px solid var(--border);
}
.spec-aside p {
  margin: 0;
  color: var(--text-dim);
  font-size: .96rem;
  line-height: 1.65;
}

.budget-block { margin-top: 48px; }
.budget-head {
  font-size: clamp(1.2rem, 2.4vw, 1.45rem);
  font-weight: 800;
  letter-spacing: -.02em;
  margin: 0 0 12px;
}
.budget-lead {
  max-width: 58ch;
  margin: 0 0 22px;
  color: var(--text-dim);
  font-size: 1.02rem;
}

/* ---------- Buy / FAQ ---------- */

.faq-head {
  max-width: 42rem;
  margin: 52px 0 22px;
  font-size: clamp(1.35rem, 2.8vw, 1.75rem);
  font-weight: 800;
  letter-spacing: -.02em;
}

/* ---------- Cards (plans only; no feature grids) ---------- */

.grid { display: grid; gap: 22px; }
.grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 760px) { .grid-2 { grid-template-columns: minmax(0, 1fr); } }

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 28px 26px;
}
.card h3 { margin-bottom: 8px; }
.card p {
  color: var(--text-dim);
  font-size: .96rem;
  margin-bottom: 0;
  max-width: 68ch;
}

/* ---------- Comparison table ---------- */

.table-scroll { overflow-x: auto; border-radius: var(--radius); border: 1px solid var(--border); }

table { width: 100%; border-collapse: collapse; background: var(--surface); }

/* Only the multi-column reference table needs a floor to stay legible.
   The two-column comparison is short text in two cells, and inheriting the
   620px floor forced a horizontal scroll on a phone for no reason. */
.budget-table { min-width: 620px; }

/* The notes column used to reach 1455px — 152 characters per line, double the
   75ch ceiling — and needed a 1700px cap of its own to hold back. With the
   content column fixed at 976px the widest cell is ~340px and the cap has
   nothing left to do, so it is gone rather than left in place to mislead the
   next person into thinking the table is special-cased. */

th, td { padding: 16px 20px; text-align: left; border-bottom: 1px solid var(--border); font-size: .95rem; }

thead th {
  background: var(--surface-2);
  font-size: .76rem;
  text-transform: uppercase;
  letter-spacing: .1em;
  /* -aa, not -faint: 12.2px column labels are body-sized text and need 4.5:1.
     --text-faint measured 4.08:1 on --surface-2; this reaches 5.18:1. */
  color: var(--text-faint-aa);
  font-weight: 700;
}

tbody tr:last-child td { border-bottom: none; }
tbody td:first-child { color: var(--text-dim); }

.hl { background: color-mix(in oklab, var(--purple) 8%, transparent); }
/* `td.hl`, not `.hl td`. The class sits ON the cell in the markup
   (<td class="hl">), so a descendant selector never matched anything and the
   whole emphasised value column was rendering as plain body text. */
td.hl { color: #fff; font-weight: 600; }
td.hl:first-child { color: var(--purple); }

.price-cell { font-weight: 800; font-size: 1.05rem; color: var(--orange); }
/* Matches `td.hl` above (0,1,1) rather than losing to it, so the price keeps
   its accent instead of turning white like every other value cell. */
td.hl.price-cell { font-weight: 800; color: var(--orange); }

/* Below 760px the comparison table's two columns are each too narrow for the
   sentences in them, so a scroll just hides content behind a swipe. Drop the
   header row — the "Dumperator" column is already marked inline — and render
   each pair as a labelled block: the attribute as the row heading, the
   Dumperator value beneath it. Order and pairing survive; the sideways
   scroll does not. */
@media (max-width: 760px) {
  .table-scroll:has(table:not(.budget-table)) {
    overflow-x: visible;
    border: none;
    background: none;
  }

  .table-scroll table:not(.budget-table) thead {
    display: none;
  }

  .table-scroll table:not(.budget-table),
  .table-scroll table:not(.budget-table) tbody,
  .table-scroll table:not(.budget-table) tr,
  .table-scroll table:not(.budget-table) td {
    display: block;
    width: auto;
  }

  .table-scroll table:not(.budget-table) tr {
    padding: 14px 16px;
    border-bottom: 1px solid var(--border);
  }

  .table-scroll table:not(.budget-table) tr:last-child {
    border-bottom: none;
  }

  /* The first cell is the row's label; the second is the value, kept on its
     own line so a long sentence does not run under the label. */
  .table-scroll table:not(.budget-table) td {
    padding: 0;
    border-bottom: none;
  }

  .table-scroll table:not(.budget-table) td:first-child {
    font-size: .76rem;
    text-transform: uppercase;
    letter-spacing: .1em;
    /* -aa, not -faint: this sits on the section background at 4.33:1 as
       --text-faint, under the 4.5:1 floor for a 12.2px label. */
    color: var(--text-faint-aa);
    font-weight: 700;
    margin-bottom: 4px;
  }

  .table-scroll table:not(.budget-table) .hl {
    background: transparent;
  }

  /* Restore the purple/orange accent that the .hl tint was carrying. */
  .table-scroll table:not(.budget-table) td.hl {
    color: #fff;
  }

  .table-scroll table:not(.budget-table) td.hl::before {
    content: "Dumperator — ";
    color: var(--purple-lite, #a78bfa);
    font-weight: 700;
  }
}

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

/* minmax(0, 1fr), not 1fr: a 1fr track floors at its content's min-content
   width, so long list items push the cards wider than the container and the
   page gains a horizontal scrollbar on phones. */
.price-wrap { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 24px; max-width: 880px; margin: 0 auto; }
@media (max-width: 780px) { .price-wrap { grid-template-columns: minmax(0, 1fr); } }

.plan {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 34px 30px;
  display: flex;
  flex-direction: column;
}

.plan.featured {
  border-color: var(--orange);
  background: linear-gradient(170deg, rgba(249,115,22,.09) 0%, var(--surface) 55%);
  /* Border carries the emphasis; a soft tinted halo only — not a ghost-card stack. */
  box-shadow: 0 0 0 1px color-mix(in oklab, var(--orange) 28%, transparent);
  position: relative;
}

.plan .tag {
  position: absolute;
  top: -13px; left: 30px;
  /* Purple, matching .btn-primary. White on the logo purple is only 4.21:1;
     --ink-accent is 4.50:1, and the badge and the CTA are now the same colour
     rather than the same trick applied to two different hues. */
  background: var(--purple);
  color: var(--ink-accent);
  /* .72rem rendered at 11.5px; raised to a 12px floor. */
  font-size: .75rem;
  font-weight: 800;
  letter-spacing: .1em;
  text-transform: uppercase;
  padding: 5px 13px;
  border-radius: 99px;
}

.plan h3 { font-size: 1.05rem; text-transform: uppercase; letter-spacing: .1em; color: var(--text-faint-aa); margin-bottom: 14px; }

/* Inside .plan.featured the backdrop is warm-tinted, so every faint tone here
   needs the warmer-lighter variant rather than --text-faint/-aa. Measured:
   --text-faint 3.07:1, --text-faint-aa 3.67:1, --text-faint-featured 4.53:1. */
.plan.featured h3,
.plan.featured .amount small,
.plan.featured .per,
.plan.featured li.no,
.plan.featured li.no::before { color: var(--text-faint-featured); }

.plan .amount {
  font-size: 3.1rem;
  font-weight: 800;
  letter-spacing: -.04em;
  line-height: 1;
  margin-bottom: 6px;
  font-variant-numeric: tabular-nums;
}
.plan .amount small { font-size: 1rem; font-weight: 600; color: var(--text-faint); letter-spacing: 0; }
/* faint-aa: 14.4px text needs 4.5:1 */
.plan .per { font-size: .9rem; color: var(--text-faint-aa); margin-bottom: 24px; }

.plan ul { list-style: none; padding: 0; margin: 0 0 26px; flex: 1; }
.plan li {
  padding-left: 27px;
  position: relative;
  margin-bottom: 12px;
  font-size: .95rem;
  color: var(--text-dim);
}
.plan li::before {
  content: "";
  position: absolute;
  left: 0; top: 7px;
  width: 15px; height: 9px;
  border-left: 2px solid var(--orange); /* check: allow-sidestripe - draws the tick glyph, not an accent */
  border-bottom: 2px solid var(--orange);
  transform: rotate(-45deg);
}
.plan li.yes { color: #fff; }
.plan li.no::before {
  content: "—";
  border: none;
  transform: none;
  width: auto; height: auto;
  color: var(--text-faint);
  font-weight: 700;
  top: 0; font-size: .95rem;
}
.plan li.no { color: var(--text-faint-aa); }

/* ---------- FAQ ---------- */

.faq { max-width: 800px; margin: 0; }

details {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  margin-bottom: 12px;
  overflow: hidden;
}
details[open] { border-color: var(--border-lite); }

summary {
  padding: 19px 24px;
  font-weight: 700;
  font-size: 1.01rem;
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  transition: background .22s cubic-bezier(.22, 1, .36, 1);
}
summary:hover { background: color-mix(in oklab, var(--surface-2) 55%, transparent); }
summary::-webkit-details-marker { display: none; }
summary::after {
  content: "+";
  font-size: 1.5rem;
  font-weight: 400;
  color: var(--orange);
  line-height: 1;
  flex-shrink: 0;
  transition: transform .28s cubic-bezier(.22, 1, .36, 1);
}
details[open] summary::after { transform: rotate(45deg); }

details .body { padding: 0 24px 22px; color: var(--text-dim); font-size: .96rem; }
details .body p:last-child { margin-bottom: 0; }
details .body ul { padding-left: 20px; margin: 0 0 1em; }
details .body li { margin-bottom: 7px; }

/* An email address is the whole point of that answer, so give it a real tap
   target rather than the ~19px line box an inline link gets by default. */
details .body a {
  display: inline-block;
  padding: 6px 0;
  min-height: 44px;
  line-height: 32px;
}

/* ---------- Download / CTA ---------- */

.dl-box {
  background: linear-gradient(150deg, var(--surface-2) 0%, var(--surface) 100%);
  border: 1px solid var(--border-lite);
  border-radius: var(--radius);
  padding: 54px 40px;
  text-align: center;
  position: relative;
  overflow: hidden;
}
.dl-box::before {
  content: "";
  position: absolute;
  top: -180px; left: 50%;
  width: 700px; height: 420px;
  transform: translateX(-50%);
  background: radial-gradient(ellipse at center, var(--purple-glow) 0%, transparent 68%);
  pointer-events: none;
}
.dl-box > * { position: relative; z-index: 2; }

.dl-box h2 { margin-bottom: 14px; }
.dl-box p { color: var(--text-dim); max-width: 560px; margin: 0 auto 30px; }

.dl-meta {
  margin-top: 22px;
  font-size: .85rem;
  color: var(--text-faint);
  font-family: var(--mono);
}
.dl-meta code {
  background: var(--black);
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 2px 8px;
  font-size: .84rem;
  color: var(--text-dim);
}

/* ---------- Callout ---------- */

/* A tinted full border rather than the side-stripe the register bans.
   color-mix is declared twice: the first is the fallback for browsers without
   it, the second is the real value.

   55% is the smallest mix that clears 3:1 against --black, which is the bar
   for a meaningful UI boundary. Measured, not guessed: 30% gave 2.22:1 and 45%
   gave 2.88:1, both under. */
.callout {
  background: #1a1424;
  background: color-mix(in oklab, var(--orange) 6%, var(--surface));
  border: 1px solid #6b4141;
  border-color: color-mix(in oklab, var(--orange) 55%, var(--border));
  border-radius: var(--radius-sm);
  padding: 22px 26px;
  max-width: 42rem;
  margin: 0 auto;
}
.callout-follow { margin-top: 34px; }
.callout p { color: var(--text-dim); margin: 0; font-size: .96rem; }
.callout strong { color: #fff; }

/* ---------- Footer ---------- */

footer {
  border-top: 1px solid var(--border);
  padding: 44px 0 38px;
  background: var(--black-2);
}
/* The footer row used to hold the mark and the links side by side. With the
   mark gone the list is the only content, so it centres rather than starting
   hard against the left gutter — the copyright rule below it is already
   centred, and a left-aligned list above a centred rule read as unbalanced. */
.foot-links { display: flex; justify-content: center; gap: 22px; flex-wrap: wrap; }
.foot-links a { color: var(--text-faint); font-size: .9rem; }
.foot-links a:hover { color: var(--text-dim); text-decoration: none; }

/* Footer links were 24px tall — comfortably under the 44px touch target
   floor, and packed on one line with 22px gaps, so a thumb has little to
   aim at.

   820px rather than the site's usual 760px: a portrait iPad is 768-834px
   wide, which is squarely touch territory, and the layout viewport there is
   ~15px narrower than the screen once the scrollbar is counted, so a 760px
   query would not match at a 768px screen. */
@media (max-width: 820px) {
  .foot-links { gap: 2px 20px; }
  .foot-links a { display: inline-flex; align-items: center; min-height: 44px; }
}

.copyright {
  margin: 28px auto 0;
  padding-top: 22px;
  border-top: 1px solid var(--border);
  font-size: .84rem;
  color: var(--text-faint);
  text-align: center;
  max-width: 40rem;
}
/* Deploy stamp. deploy.sh replaces the "dev" placeholder with the short
   commit hash so a hard refresh is enough to know which build the domain is
   serving — including when a cached stylesheet still looks like the old one. */
.build {
  margin: 10px 0 0;
  font-size: .75rem;
  color: var(--text-faint);
  text-align: center;
  font-family: var(--mono);
}
.build code {
  font: inherit;
  color: var(--text-faint-aa);
}
/* The credit keeps the footer link's colour rather than the global anchor
   purple: at 13.4px next to faint body text, a saturated violet link reads as
   a call to action rather than an attribution. It keeps the underline the
   global rule would give it anyway, since it leaves the site. */
.credit-link { color: var(--text-faint); text-decoration: underline; }
.credit-link:hover { color: var(--text-dim); }


/* Brand mark.
   It used to sit inside a .hero-right column above a stacked nav; with the
   menu gone the wrapper went too, and the copy-clearance rules that existed
   only to dodge it are removed with it — see below. */
/* Brand mark.

   It was pinned to the hero's top-right with position:absolute, which is what
   produced the void on a wide monitor: a 336px mark floating over 2418px of
   wrap while the copy sat alone in a 640px column on the left, leaving 73% of
   the hero empty. It is now a real grid child, so the hero lays out as two
   columns and the space between them is content space rather than a gap.

   Stays absolutely positioned below 1100px, where two columns would squeeze
   the measure past readability. */
.brand {
  position: absolute;
  top: 22px;
  left: 24px;
  right: auto;
  z-index: 4;
  display: block;
  /* Perspective lives on the link so the pivot child's 3D transforms resolve
     against the mark's own centre, not the viewport. */
  perspective: 1100px;
  perspective-origin: 50% 50%;
  /* Rotated mark paints outside its track; don't clip the tilt. */
  overflow: visible;
}
.brand-pivot {
  display: block;
  position: relative;
  width: fit-content;
  max-width: 100%;
  margin: 0 auto;
  transform-origin: 50% 50%;
  transform-style: preserve-3d;
  /* Right edge further away than the left (yaw around the vertical axis).
     Idle orbit + stretch are driven from logo-3d.js. */
  transform: rotateY(20deg);
  /* Transform is driven every frame from logo-3d.js — no CSS transition. */
  will-change: transform;
}

/* Extruded stack: Z-stepped clones reveal thickness as the mark yaws.
   The stack is positioned over the face's box; body slices are absolute. */
.brand-stack {
  display: block;
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  pointer-events: none;
}
.brand-slice {
  display: block;
  transform-style: preserve-3d;
}
.brand-stack .brand-slice {
  position: absolute;
  left: 0;
  top: 0;
}
.brand-slice--face {
  position: relative;
  display: block;
  filter: drop-shadow(0 0 22px rgba(216, 6, 221, .45));
}
.brand picture { display: block; }
.brand img {
  /* Larger mark. Ceiling 380px fits the 400px desktop track before rotateY;
     the rest pose and pointer tilt may paint outside the track — intentional. */
  height: clamp(240px, 34vh, 380px);
  width: auto;
  max-width: 100%;
  margin-left: 0;
  display: block;
}

@media (max-width: 1100px) {
  .brand { top: 18px; left: 18px; right: auto; }
}
@media (max-width: 820px) {
  /* In flow on small screens: absolutely positioned over the copy would
     collide with the headline once the two columns collapse. */
  .brand {
    position: static;
    margin: 0 auto 32px;
    text-align: center;
  }
  .brand img { height: clamp(220px, 38vw, 300px); }
}

/* Keep the hero copy clear of the logo while the mark is still absolutely
   positioned. This block is scoped to the range where that is true: 821px to
   1023px. Above 1024px the mark is a grid child with its own column; below
   821px the mark is in normal flow above the copy. */
@media (min-width: 821px) and (max-width: 1023px) {
  .hero .wrap {
    padding-left: 360px;
    padding-right: 24px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .brand-pivot {
    transition: none;
    /* Keep the resting yaw; drop interactive tilt (JS no-ops). */
    transform: rotateY(20deg) !important;
  }
}

.hero-tagline {
  font-size: clamp(1.05rem, 2.4vw, 1.5rem);
  font-weight: 700;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin: 0 0 26px;
}

/* The headline keeps the global h1 scale. It previously carried its own
   clamp(1.4rem, 3.1vw, 2.1rem), which at 1440px rendered it at 33.6px against
   41.6px for every h2 — the page's most important sentence was typographically
   subordinate to all nine section headers, at every breakpoint. Only the
   leading stays tighter than the default 1.15, because this is display copy. */
.hero-lead {
  line-height: 1.12;
  font-weight: 800;
  letter-spacing: -.03em;
  margin-bottom: 18px;
  text-wrap: balance;
}
.hero-lead .accent {
  color: var(--orange-lite);
}

/* ==========================================================================
   Signal-path diagram
   A labelled flow: source -> the unit -> output, with the ring buffer drawn
   as a segmented track beneath. Every part of it is described in text too,
   so the figure is never the only carrier of the information.
   ========================================================================== */

.chain {
  margin: 46px 0 0;
  padding: 30px 28px 24px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.chain-stage {
  display: flex;
  align-items: stretch;
  gap: 0;
}

.chain-node {
  flex: 1 1 0;
  min-width: 0;
  padding: 16px 18px;
  background: var(--surface-2);
  border: 1px solid var(--border-lite);
  border-radius: var(--radius-sm);
  text-align: center;
}

.chain-node b {
  display: block;
  font-size: 1rem;
  font-weight: 700;
  color: #fff;
  margin-bottom: 4px;
}

.chain-node span {
  display: block;
  font-size: .84rem;
  line-height: 1.45;
  color: #a79fbf;
}

/* The unit itself gets the accent so the eye lands on the part that matters. */
.chain-unit {
  background: linear-gradient(165deg, #241b3d 0%, #1b1727 100%);
  border-color: var(--purple);
  box-shadow: 0 0 0 1px rgba(139, 92, 246, .18);
}

.chain-unit b { color: var(--orange-lite); }

/* Wire between nodes, with a dot that reads as signal travelling along it. */
.chain-wire {
  flex: 0 0 46px;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}

.chain-wire::before {
  content: "";
  position: absolute;
  left: 4px;
  right: 4px;
  height: 1px;
  background: var(--border-lite);
}

.chain-wire i {
  position: relative;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--purple);
  box-shadow: 0 0 10px var(--purple-glow);
}

.chain-wire:last-of-type i { background: var(--orange); box-shadow: 0 0 10px var(--orange-glow); }

/* Ring-buffer track. Blocks are ordered oldest -> newest, left to right. */
.buffer { margin-top: 26px; }

.buffer-track {
  display: flex;
  gap: 4px;
  height: 34px;
  padding: 4px;
  background: var(--black-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

.bs { flex: 1 1 0; border-radius: 3px; min-width: 0; }

.bs-air  { background: #2a2440; }
.bs-slip { background: var(--red); box-shadow: 0 0 14px rgba(239, 68, 68, .5); }
.bs-wait { background: #3a3358; }

.buffer-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 22px;
  margin-top: 12px;
  font-size: .84rem;
  color: #a79fbf;
}

.buffer-legend span { display: flex; align-items: center; gap: 8px; }

.buffer-legend i {
  width: 11px;
  height: 11px;
  border-radius: 3px;
  flex: 0 0 auto;
}

.k-air  { background: #2a2440; border: 1px solid var(--border-lite); }
.k-slip { background: var(--red); }
.k-wait { background: #3a3358; }

.chain-dump {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  margin-top: 22px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
}

.chain-key {
  flex: 0 0 auto;
  padding: 6px 14px 7px;
  border-radius: var(--radius-sm);
  background: linear-gradient(180deg, #ff3030 0%, #dc2626 55%, #b91c1c 100%);
  border: 1px solid #fff;
  color: #fff;
  font-weight: 800;
  font-size: .95rem;
  letter-spacing: .05em;
}

.chain-dump p {
  margin: 0;
  color: #a79fbf;
  font-size: .95rem;
  line-height: 1.6;
  /* Capped with the other prose blocks; this spans the full column. */
  max-width: 42rem;
}

/* ==========================================================================
   Spec tables — three labelled columns of term/definition pairs.
   Replaces a grid of near-identical cards with something that carries more
   information in less vertical space.
   ========================================================================== */

.spec {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 26px;
  margin-bottom: 26px;
}

.spec-col {
  padding: 22px 24px 8px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.spec-col h3 {
  font-size: 1.02rem;
  font-weight: 700;
  color: #fff;
  margin: 0 0 6px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border);
}

.spec-col dl { margin: 0; }

.spec-col dt {
  font-size: .78rem;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--orange-lite);
  margin-top: 16px;
}

.spec-col dd {
  margin: 3px 0 0;
  font-size: .95rem;
  line-height: 1.5;
  color: #a79fbf;
}

/* ==========================================================================
   Budget table
   ========================================================================== */

.budget-table th[scope="row"] {
  font-weight: 650;
  color: #fff;
  white-space: nowrap;
}

.budget-table td { color: #a79fbf; }
.budget-table td.hl { color: #fff; font-weight: 700; }

.table-foot {
  margin: 18px 0 0;
  /* 78ch measured 95ch in the browser (the ch unit assumes a "0" glyph, which
     is wider than this text's average), so tighten to land near the cap. */
  max-width: 62ch;
  font-size: .88rem;
  line-height: 1.6;
  color: #a79fbf;
}

.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;
}

.tight-grid { margin-top: 0; }

/* ==========================================================================
   Responsive
   ========================================================================== */

@media (max-width: 900px) {
  .spec { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 760px) {
  .chain-stage { flex-direction: column; gap: 0; }

  .chain-wire {
    flex: 0 0 34px;
    width: 100%;
  }

  /* rotate the horizontal wire into a vertical one */
  .chain-wire::before {
    left: 50%;
    right: auto;
    top: 4px;
    bottom: 4px;
    width: 1px;
    height: auto;
  }

  .spec { grid-template-columns: minmax(0, 1fr); gap: 18px; }
}

@media (max-width: 520px) {
  .chain { padding: 22px 18px 20px; }
  .chain-dump { flex-direction: column; gap: 10px; }
  .buffer-legend { font-size: .8rem; gap: 6px 16px; }
  .bs-slip { box-shadow: 0 0 10px rgba(239, 68, 68, .45); }
}

@media (prefers-reduced-motion: reduce) {
  .chain-wire i { box-shadow: none; }
  .bs-slip { box-shadow: none; }
  .btn,
  .btn:hover,
  .btn:active,
  summary,
  summary::after,
  details[open] summary::after {
    transition: none;
    transform: none;
  }
}
