/* Revline Portal — the founders' stylesheet.
   It used to be a cockpit: 78rem wide, a dense metarail of big numbers, tables
   that needed a `data-label` hack to survive a phone. It worked, and it made
   the internal side feel like a different product from the page we hand to a
   client — different width, different rhythm, different way of showing the
   same funnel. A founder should not have to change gear between the two.

   So this is now the client portal's language, pointed inward: one 62rem
   column, a serif heading over a 2px rule per section, hairline readouts, mono
   for anything countable. Same two worlds (ink masthead, paper body, redline
   hairlines) and the same four faces as revlinehq.com. Plain CSS on purpose —
   a server handing out one stylesheet does not need a build step. */

@font-face {
  font-family: "FiraVar";
  src: url("/fonts/fira-code-var.woff2") format("woff2");
  font-weight: 300 700;
  font-display: swap;
}
@font-face {
  font-family: "Newsreader Display";
  src: url("/fonts/newsreader-display-roman.woff2") format("woff2");
  font-weight: 500 700;
  font-display: swap;
}
@font-face {
  font-family: "Newsreader Text";
  src: url("/fonts/newsreader-text-roman.woff2") format("woff2");
  font-weight: 400 700;
  font-display: swap;
}
@font-face {
  font-family: "Instrument Sans";
  src: url("/fonts/instrument-sans-var.woff2") format("woff2");
  font-weight: 400 700;
  font-display: swap;
}

:root {
  --ink: #141110;
  --ink-lift: #1c1917;
  --ink-lift-2: #26211f;
  --paper: #f2ede3;
  --paper-deep: #eae3d5;
  --paper-sunk: #e0d8c8;
  --cream: #f5f1ed;
  --cream-dim: #a89f94;
  --graphite: #5e574f;
  --redline: #e8342a;
  --redline-deep: #cc2e25;

  /* Hairline density is one decision, not twenty. */
  --rule: rgb(20 17 16 / 0.14);
  --rule-mid: rgb(20 17 16 / 0.24);
  --rule-strong: rgb(20 17 16 / 0.4);
  --rule-dark: rgb(245 241 237 / 0.14);
  --rule-dark-mid: rgb(245 241 237 / 0.28);

  --font-sans: "Instrument Sans", ui-sans-serif, system-ui, sans-serif;
  /* Two optical cuts: display is drawn at opsz 72, text at opsz 16. Picking the
     wrong one is the difference between a display serif and a spindly one. */
  --font-display: "Newsreader Display", Georgia, "Times New Roman", serif;
  --font-text-serif: "Newsreader Text", Georgia, "Times New Roman", serif;
  --font-mono: "FiraVar", ui-monospace, SFMono-Regular, monospace;

  /* 8pt scale. 4 and 12 exist only for inline, text-adjacent gaps. */
  --sp-4: 0.25rem;
  --sp-8: 0.5rem;
  --sp-12: 0.75rem;
  --sp-16: 1rem;
  --sp-24: 1.5rem;
  --sp-32: 2rem;
  --sp-48: 3rem;
  --sp-64: 4rem;
  --sp-96: 6rem;

  /* One measure for every surface Revline renders, internal or client-facing.
     78rem let six columns of numbers spread until they stopped being one
     object; 62rem is the width the client portal reads at. */
  --shell: 62rem;
  /* The founders' rail. A spine, not a second world — wide enough for a client
     name at 12px mono, narrow enough that the canvas keeps its measure. */
  --rail: 15.25rem;
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
  --dur: 160ms;
}

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

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

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

body {
  margin: 0;
  min-height: 100dvh;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 1rem;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

::selection {
  background: var(--redline-deep);
  color: var(--cream);
}

:focus-visible {
  outline: 2px solid var(--redline-deep);
  outline-offset: 3px;
}

[data-surface="dark"] :focus-visible {
  outline-color: var(--redline);
}

a {
  color: inherit;
  text-underline-offset: 0.22em;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--rule-strong);
  transition: text-decoration-color var(--dur) var(--ease);
}

a:hover {
  text-decoration-color: var(--redline-deep);
}

[data-surface="dark"] a {
  text-decoration-color: var(--rule-dark-mid);
}

[data-surface="dark"] a:hover {
  text-decoration-color: var(--redline);
}

/* ------------------------------------------------------------- type scale */

.eyebrow {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  line-height: 1.5;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  font-weight: 400;
  color: var(--graphite);
  margin: 0;
}

[data-surface="dark"] .eyebrow {
  color: var(--cream-dim);
}

/* A record id is case-sensitive and the eyebrow's small caps are not allowed to
   lie about it — same reason `.keytag` exists, one register up. */
.eyebrow .id {
  text-transform: none;
  letter-spacing: 0.06em;
}

.mono {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  line-height: 1.6;
  letter-spacing: 0.01em;
  font-variant-numeric: tabular-nums;
  margin: 0;
}

.serif-h1 {
  font-family: var(--font-display);
  font-weight: 500;
  letter-spacing: -0.018em;
  line-height: 1.06;
  text-wrap: balance;
  margin: 0;
  font-size: clamp(1.75rem, 1.25rem + 2vw, 2.5rem);
}

.serif-h2 {
  font-family: var(--font-text-serif);
  font-weight: 600;
  letter-spacing: -0.006em;
  line-height: 1.25;
  margin: 0;
  font-size: 1.3125rem;
}

.serif-h3 {
  font-family: var(--font-text-serif);
  font-weight: 600;
  letter-spacing: -0.004em;
  line-height: 1.3;
  margin: 0;
  font-size: 1.0625rem;
}

.copy {
  font-size: 0.9375rem;
  line-height: 1.65;
  margin: 0;
  max-width: 68ch;
}

.muted {
  color: var(--graphite);
}

[data-surface="dark"] .muted {
  color: var(--cream-dim);
}

/* ---------------------------------------------------------------- masthead */

.shell {
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: var(--sp-24);
}

@media (min-width: 48rem) {
  .shell {
    padding-inline: var(--sp-32);
  }
}

.masthead {
  background: var(--ink);
  color: var(--cream);
}

.masthead-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--sp-16);
  padding-block: var(--sp-16);
  border-bottom: 1px solid var(--rule-dark);
}

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-12);
  text-decoration: none;
  color: var(--cream);
  padding-block: var(--sp-4);
}

.wordmark-name {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.0625rem;
  letter-spacing: -0.012em;
}

/* The one place redline earns its keep in the chrome: a rule, on dark. */
.wordmark-rule {
  display: block;
  width: 2px;
  height: 0.875rem;
  background: var(--redline);
}

.wordmark-sub {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--cream-dim);
  transition: color var(--dur) var(--ease);
}

/* Where the founders' navigation sits, a client sees who the page is for. It
   occupies the same slot deliberately: the absence of nav is the signal that
   this is somebody's own portal and not the cockpit. */
.for-who {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--cream-dim);
}

.for-who b {
  color: var(--cream);
  font-weight: 500;
}

.wordmark:hover .wordmark-sub {
  color: var(--cream);
}

.signout {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--rule-dark-mid);
  color: var(--cream-dim);
  padding: 0 0 3px;
  min-height: 44px;
  cursor: pointer;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}

.signout:hover {
  color: var(--cream);
  border-bottom-color: var(--redline);
}

.signout:active {
  color: var(--cream-dim);
}

.masthead-body {
  padding-block: var(--sp-32);
}

@media (min-width: 48rem) {
  .masthead-body {
    padding-block: var(--sp-48);
  }
}

.masthead-body .serif-h1 {
  margin-top: var(--sp-12);
  max-width: 24ch;
}

.masthead-sub {
  margin: var(--sp-16) 0 0;
  max-width: 62ch;
  color: var(--cream-dim);
  font-size: 0.9375rem;
}

.masthead-sub b {
  color: var(--cream);
  font-weight: 500;
}

.masthead-sub strong {
  color: var(--cream);
  font-weight: 500;
}

/* The site's signature: a 2px redline where the two worlds meet. */
.boundary {
  height: 2px;
  background: var(--redline);
}

/* ============================================================= the two worlds

   Everything above this line dresses a CLIENT's page: an ink masthead, a 62rem
   column, no navigation, because a client's account is the whole scope and
   there is nothing to switch to.

   Everything below dresses the FOUNDERS' side, which does have somewhere to go
   — five destinations and three clients — and was until now wearing the
   client's clothes. A masthead that carried three links made every internal
   screen a scroll-to-find exercise: the way to the payments list ran through
   the dashboard, and the way to a client ran through a list of every client.

   So the internal side turns the seam ninety degrees. The redline that ran
   horizontally under a client's masthead becomes the vertical edge of a
   persistent rail: same mark, same meaning — ink on one side, paper on the
   other — but now it is a spine you navigate from rather than a lid you scroll
   past. Recognition over recall (Nielsen #6): every destination is named on
   every screen, so nothing has to be remembered or hunted for. */

.frame {
  display: grid;
  grid-template-columns: var(--rail) minmax(0, 1fr);
  min-height: 100dvh;
  /* The rail is sticky, so it is never taller than the viewport; on a long page
     the column beneath it would show paper through. The ink and its redline
     seam are painted onto the grid itself so the spine runs the full height. */
  background: linear-gradient(
    90deg,
    var(--ink) 0 var(--rail),
    var(--redline) var(--rail) calc(var(--rail) + 2px),
    transparent calc(var(--rail) + 2px)
  );
}

.rail {
  color: var(--cream);
  border-right: 2px solid var(--redline);
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100dvh;
  overflow-y: auto;
}

.rail-block {
  padding: var(--sp-16);
  border-bottom: 1px solid var(--rule-dark);
}

/* The last block — who you are, and the way out — is pinned to the floor. */
.rail-block:last-child {
  border-bottom: 0;
  margin-top: auto;
}

.rail .wordmark {
  padding-block: 0;
}

/* ------------------------------------------------------------ scope switcher

   Revline has exactly one kind of tenant, so this is the only switcher on the
   page, and it is a <details>: no JavaScript, so it still works if the bundle
   never loads. Open by default — with three clients, hiding them buys nothing.

   The summary states where you are and the list states where you could go, so
   the current scope appears exactly once. It used to appear twice, adjacent and
   identical, which reads as a rendering fault rather than as a control. */
.scope-eyebrow {
  margin: 0 0 var(--sp-8);
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--cream-dim);
}

.scope-caret {
  color: var(--cream-dim);
  font-size: 0.75rem;
  transition: transform var(--dur) var(--ease), color var(--dur) var(--ease);
}

.scope[open] .scope-caret {
  transform: rotate(180deg);
}

.scope > summary:hover .scope-caret {
  color: var(--cream);
}

.scope > summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-8);
  padding: var(--sp-12);
  min-height: 2.75rem;
  border: 1px solid var(--rule-dark-mid);
  background: var(--ink-lift);
  transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}

.scope > summary::-webkit-details-marker,
.scope > summary::marker {
  display: none;
  content: "";
}

.scope > summary:hover {
  border-color: var(--cream-dim);
  background: var(--ink-lift-2);
}

.scope-name {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  color: var(--cream);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.scope-count {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  color: var(--cream-dim);
  font-variant-numeric: tabular-nums;
  flex: none;
}

.scope-list {
  list-style: none;
  margin: var(--sp-8) 0 0;
  padding: 0;
  border-top: 1px solid var(--rule-dark);
}

.scope-list a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-8);
  padding: var(--sp-8) var(--sp-12);
  min-height: 2.75rem;
  text-decoration: none;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--cream-dim);
  border-left: 2px solid transparent;
  transition: color var(--dur) var(--ease), background-color var(--dur) var(--ease),
    border-color var(--dur) var(--ease);
}

.scope-list a:hover {
  color: var(--cream);
  background: var(--ink-lift);
  border-left-color: var(--rule-dark-mid);
}

.scope-list a[aria-current="true"] {
  color: var(--cream);
  border-left-color: var(--redline);
}

.scope-list .scope-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The way out of a client's space, and the only link in the switcher that
   leaves it. Set apart by a rule rather than a colour: inside an account it is
   the one destination that changes what the whole rail means, and it should
   read as a step back rather than as another client to visit. */
.scope-out {
  border-bottom: 1px solid var(--rule-dark);
}

.scope-arrow {
  display: inline-block;
  margin-right: var(--sp-8);
  color: var(--cream-dim);
  transition: transform var(--dur) var(--ease);
}

.scope-out:hover .scope-arrow {
  transform: translateX(-2px);
}

/* A client with something unresolved carries the mark in the switcher, so it is
   visible from every screen rather than only from the list it appears in. */
.scope-dot {
  width: 5px;
  height: 5px;
  flex: none;
  background: var(--redline);
}

/* ----------------------------------------------------------------- nav rail */

.nav {
  display: flex;
  flex-direction: column;
}

.nav a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-8);
  padding: var(--sp-8) var(--sp-12);
  min-height: 2.75rem;
  text-decoration: none;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--cream-dim);
  border-left: 2px solid transparent;
  transition: color var(--dur) var(--ease), background-color var(--dur) var(--ease),
    border-color var(--dur) var(--ease);
}

.nav a:hover {
  color: var(--cream);
  background: var(--ink-lift);
}

.nav a[aria-current="page"] {
  color: var(--cream);
  border-left-color: var(--redline);
  background: var(--ink-lift);
}

/* Inside a client, the spine belongs to the name above it rather than to
   Revline, and it has to be possible to tell which one you are looking at
   without reading the links. An indent under a hairline is the smallest honest
   way to say "these are that account's" — the same gesture as a nested list,
   because that is exactly what this is. */
.nav[data-scoped="true"] {
  margin-left: var(--sp-12);
  padding-left: var(--sp-8);
  border-left: 1px solid var(--rule-dark);
}

.nav .tally {
  font-size: 0.6875rem;
  letter-spacing: 0.06em;
  color: var(--cream-dim);
  font-variant-numeric: tabular-nums;
}

/* Counts are quiet, except where the count is a queue. */
.nav .tally[data-attention="true"] {
  color: var(--cream);
  background: var(--redline-deep);
  padding: 0 var(--sp-8);
  letter-spacing: 0.08em;
}

/* --------------------------------------------------------------- the estate

   The other five things Revline runs. They are named in the chrome rather than
   remembered from a bookmarks bar — the same recognition-over-recall argument
   as the nav, applied to the systems that live off this host. */
.estate summary {
  list-style: none;
  cursor: pointer;
}

.estate summary::-webkit-details-marker {
  display: none;
}

.estate-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--cream-dim);
  min-height: 2.25rem;
  transition: color var(--dur) var(--ease);
}

.estate summary:hover .estate-head {
  color: var(--cream);
}

.estate ul {
  list-style: none;
  margin: var(--sp-8) 0 0;
  padding: 0;
}

.estate a {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-8);
  padding: var(--sp-4) 0;
  min-height: 2.25rem;
  text-decoration: none;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--cream-dim);
  transition: color var(--dur) var(--ease);
}

.estate a:hover {
  color: var(--cream);
}

.estate .host {
  font-size: 0.625rem;
  letter-spacing: 0.04em;
  opacity: 0.72;
}

/* ------------------------------------------------------------- who is signed in */

.seat {
  margin: 0 0 var(--sp-8);
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  color: var(--cream-dim);
  overflow-wrap: anywhere;
}

.seat b {
  display: block;
  color: var(--cream);
  font-weight: 500;
  text-transform: none;
  letter-spacing: 0.04em;
}

/* ---------------------------------------------------------------- the canvas */

.canvas {
  min-width: 0;
  background: var(--paper);
}

.canvas-inner {
  padding: var(--sp-24) var(--sp-24) var(--sp-64);
  max-width: 76rem;
}

@media (min-width: 48rem) {
  .canvas-inner {
    padding: var(--sp-24) var(--sp-32) var(--sp-64);
  }
}

.pagehead {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--sp-16);
  padding-bottom: var(--sp-16);
  border-bottom: 2px solid var(--ink);
}

.pagehead .serif-h1 {
  margin-top: var(--sp-4);
  max-width: 30ch;
}

.pagehead-sub {
  margin: var(--sp-12) 0 0;
  max-width: 62ch;
  color: var(--graphite);
  font-size: 0.9375rem;
}

.pagehead-sub b {
  color: var(--ink);
  font-weight: 500;
}

/* The period is the denominator under every number on the page, so it belongs
   with the page title rather than inside any one card. */
.period {
  display: flex;
  gap: 0;
  border: 1px solid var(--rule-mid);
}

.period a {
  padding: var(--sp-8) var(--sp-12);
  min-height: 2.5rem;
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--graphite);
  border-left: 1px solid var(--rule);
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease);
}

.period a:first-child {
  border-left: 0;
}

.period a:hover {
  background: var(--paper-deep);
  color: var(--ink);
}

.period a[aria-current="true"] {
  background: var(--ink);
  color: var(--cream);
}

/* Inside the frame a section leads with a hairline, not a 2px rule — the 2px
   rule is spent on the page title, and spending it twice flattens the two. */
.canvas .section {
  margin-top: var(--sp-48);
}

.canvas .section + .section {
  margin-top: var(--sp-48);
}

.canvas .section-head {
  border-bottom: 1px solid var(--rule-mid);
  padding-bottom: var(--sp-12);
  margin-bottom: var(--sp-16);
}

/* ------------------------------------------------------------------- queue

   What needs a human, first on the page, because it is the only thing on the
   page that decays. Everything below it is a reading; this is a list of debts.
   Three columns — who, what, how long — so age is scannable down one edge. */
.queue {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--rule-mid);
}

.queue li {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-8);
  padding: var(--sp-16) 0;
  border-bottom: 1px solid var(--rule);
}

@media (min-width: 48rem) {
  .queue li {
    /* Wide enough for two words of a company name on one line: "Northgate
       Roofing" wrapping mid-name made the column read as two entries. */
    grid-template-columns: 13rem minmax(0, 1fr) auto;
    gap: var(--sp-8) var(--sp-24);
    align-items: baseline;
  }
}

.queue .who {
  font-family: var(--font-text-serif);
  font-size: 1.0625rem;
  font-weight: 600;
}

.queue .what {
  margin: 0;
  font-size: 0.9375rem;
}

.queue .what .kind {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--graphite);
  display: block;
  margin-bottom: var(--sp-4);
}

.queue .when {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.06em;
  color: var(--graphite);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ------------------------------------------------------------------- delta

   Movement is set in words and sign, never in colour alone and never as an
   arrow — the brand mark is a pulse, not a swoosh. */
.delta {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
  color: var(--graphite);
  margin: var(--sp-4) 0 0;
}

.delta b {
  font-weight: 500;
  color: var(--ink);
}

/* --------------------------------------------------------------- leak rows

   One row per funnel carrying only the stage losing the most. The overview asks
   "where is it leaking", not "what are all the numbers" — those live one click
   down, on the funnel itself. */
.leaks {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--rule-mid);
}

.leaks li {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-12);
  padding: var(--sp-16) 0;
  border-bottom: 1px solid var(--rule);
}

@media (min-width: 48rem) {
  .leaks li {
    grid-template-columns: 18rem minmax(0, 1fr) 9rem;
    gap: var(--sp-24);
    align-items: center;
  }
}

.leaks .fname {
  font-family: var(--font-text-serif);
  font-size: 1.0625rem;
  font-weight: 600;
  margin: 0;
}

.leaks .fsub {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.02em;
  color: var(--graphite);
  margin: var(--sp-4) 0 0;
  overflow-wrap: anywhere;
}

/* Survivorship as a shape, so the funnel is legible before a figure is read —
   the squint test the numbers alone cannot pass. */
.steps {
  display: flex;
  align-items: flex-end;
  gap: 4px;
  /* The stage labels are positioned above the bars, so the element reserves
     room for them rather than letting them overflow — stacked on a phone they
     were landing on top of the funnel name in the row above. */
  height: 4.5rem;
  padding-top: 1rem;
  /* Constrained on purpose. Stretched across a 1fr column each stage became a
     110px-wide, 36px-tall box, and a bar that much wider than it is tall reads
     as a filled panel rather than as a height — the one thing it is drawn to
     show. Narrow columns make the funnel's shape legible at a glance. */
  max-width: 13rem;
}

.steps span {
  flex: 1;
  height: 100%;
  background: var(--paper-deep);
  position: relative;
}

.steps span i {
  position: absolute;
  inset: auto 0 0 0;
  background: var(--ink);
}

.steps span[data-leak="true"] i {
  background: var(--redline);
  /* A stage converting at 0% still has to be visible, or the one mark on the
     row that says "here" disappears exactly when it matters most. */
  min-height: 2px;
}

.steps span b {
  position: absolute;
  inset: auto 0 calc(100% + 5px) 0;
  font-family: var(--font-mono);
  font-size: 0.5625rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 400;
  color: var(--graphite);
  text-align: center;
}

.leaks .verdict {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
  text-align: left;
  margin: 0;
}

@media (min-width: 48rem) {
  .leaks .verdict {
    text-align: right;
  }
}

.leaks .verdict b {
  display: block;
  font-weight: 500;
  color: var(--redline-deep);
  letter-spacing: 0.02em;
}

.leaks .verdict span {
  display: block;
  color: var(--graphite);
  font-size: 0.6875rem;
  letter-spacing: 0.06em;
  margin-top: var(--sp-4);
}

/* ------------------------------------------------------------ estate cards */

.estate-grid {
  display: grid;
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
}

/* The hairline is the container showing through a 1px gap, which is only
   invisible while the cards fill the last row exactly. Removing Umami left
   five cards in a three-column grid and the sixth cell painted as a solid grey
   block. So the last card stretches across whatever is left of its row: the
   grid stays whole at any count, rather than at counts that happen to divide. */
@media (min-width: 40rem) {
  .estate-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .estate-card:last-child:nth-child(odd) {
    grid-column: span 2;
  }
}

@media (min-width: 64rem) {
  .estate-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
  .estate-card:last-child:nth-child(3n + 1) {
    grid-column: span 3;
  }
  .estate-card:last-child:nth-child(3n + 2) {
    grid-column: span 2;
  }
}

.estate-card {
  background: var(--paper);
  padding: var(--sp-16);
  display: block;
  text-decoration: none;
  transition: background-color var(--dur) var(--ease);
}

.estate-card:hover {
  background: var(--paper-deep);
}

.estate-card .name {
  font-family: var(--font-text-serif);
  font-size: 1.0625rem;
  font-weight: 600;
  margin: 0;
}

.estate-card .host {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.02em;
  color: var(--graphite);
  margin: var(--sp-4) 0 0;
  overflow-wrap: anywhere;
}

.estate-card .job {
  font-size: 0.875rem;
  color: var(--graphite);
  margin: var(--sp-8) 0 0;
}

.stamp {
  margin-top: var(--sp-48);
  padding-top: var(--sp-16);
  border-top: 1px solid var(--rule);
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--graphite);
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-8) var(--sp-24);
  justify-content: space-between;
}

/* ================================================== the rail becomes a lid

   Below 64rem the spine has nowhere to stand, so it lies down: static rather
   than sticky, its seam back to a horizontal redline, and the nav scrolls
   sideways. Nothing hides behind a hamburger — six destinations fit on a
   phone, and a tool used one-handed on a call should not need two taps to
   change screen. */
@media (max-width: 63.9375rem) {
  .frame {
    grid-template-columns: minmax(0, 1fr);
    background: var(--paper);
  }

  .rail {
    position: static;
    height: auto;
    background: var(--ink);
    border-right: 0;
    border-bottom: 2px solid var(--redline);
  }

  .rail-block:last-child {
    margin-top: 0;
  }

  .nav {
    flex-direction: row;
    overflow-x: auto;
    gap: var(--sp-4);
    scrollbar-width: none;
  }

  .nav::-webkit-scrollbar {
    display: none;
  }

  .nav a {
    border-left: 0;
    border-bottom: 2px solid transparent;
    white-space: nowrap;
    padding: var(--sp-8);
  }

  .nav a[aria-current="page"] {
    border-left: 0;
    border-bottom-color: var(--redline);
  }

  .nav .tally {
    margin-left: var(--sp-8);
  }

  /* A scrolling strip that does not say it scrolls is a menu with items nobody
     finds. At 390px the rail cuts "Payments" mid-word and Onboarding and
     Accounts are past the edge entirely, with no scrollbar (hidden above) and
     no other hint. The mask fades the last few pixels so the row visibly
     continues, which is the whole affordance. */
  .nav {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 2rem), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 2rem), transparent);
  }

  /* The rail is chrome, and on a phone it was taking the entire first screen:
     brand, switcher, every client, nav, estate, the signed-in block — roughly
     640px before the page said anything. The client list is the tallest part
     and the least often used, so it gets a ceiling and scrolls inside itself.
     Desktop is untouched: there the rail is a column with room to spare. */
  .scope[open] > ul,
  .scope[open] > div {
    max-height: 8.5rem;
    overflow-y: auto;
  }

  /* The account block is the least-used thing in the rail and was the second
     tallest: "Signed in", the address, the role, Security and Sign out stacked
     down the page on every single load. Laid out as one row on mobile it keeps
     everything reachable and gives roughly 100px back to the content. */
  .rail-block:last-child {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-8) var(--sp-16);
  }

  .rail-block:last-child .seat {
    margin: 0;
  }

  /* "Signed in" and the role are the two things nobody needs on a phone: you
     know you signed in, and a founder knows what they are. The address stays,
     because which account you are looking at is worth being sure of.

     Both are bare text nodes either side of the <b> holding the address, so
     there is no element to hide — zeroing the paragraph and restoring the one
     child is the only way to drop them without changing the markup. That is
     also why the rule this replaces did nothing at all: it was written against
     a `<br>` the markup does not contain, and set a size the paragraph already
     had. */
  .rail-block:last-child .seat {
    font-size: 0;
  }

  .rail-block:last-child .seat b {
    display: inline;
    font-size: 0.6875rem;
  }

  .rail-block:last-child .seat-links,
  .rail-block:last-child form {
    margin: 0;
  }

  /* ------------------------------------------- the lid becomes a header

     Measured before it was changed: on a 390×844 phone the rail stood 541px
     tall, so 64% of the first screen was chrome and the page's own headline
     began below the fold. Five blocks stacked — mark, switcher, nav, estate,
     account — each one a full-width row on a device that has no width to
     spare.

     Three of those five are identity and utility rather than navigation, and
     none of them is read on every page load. Paired off two to a row they
     stop costing a row each, and the nav — the one part that IS used on every
     screen — keeps its full width and its horizontal scroll. Nothing is
     hidden behind a tap that was not already behind one. */
  .rail {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: center;
  }

  /* Only the first row pairs: the mark and the switcher are both "where am I",
     they are both short, and together they read as one header line.

     Everything below spans. The nav scrolls sideways and half a phone is not
     enough to scroll in; the estate and the account block were tried side by
     side and the account lost — at 195px it wrapped into three lines and its
     rule tore across the middle of the estate beside it. A row saved is not
     worth a row that looks broken. */
  .rail-block:nth-child(n + 3) {
    grid-column: 1 / -1;
  }

  .rail-block {
    padding: var(--sp-12) var(--sp-16);
  }

  /* The eyebrow says "Viewing" above a control that already says "All
     clients". On a rail with room that is a label; on a phone it is a line of
     type charging 18px to repeat the line under it. */
  .scope-eyebrow {
    display: none;
  }

  /* "PORTAL" beside the mark answers a question nobody asks on a screen they
     signed into. The mark and the name carry it. */
  .rail .wordmark-sub {
    display: none;
  }

  /* The client list is closed here, which is the opposite of the desktop
     default and deliberately so — the note by the switcher argues that with
     three clients hiding them buys nothing, and on a spine with room to spare
     that is true. On a phone those three rows were 132px of the first screen,
     on every page, to answer a question asked about once a session.

     Set by portal.js, so with the bundle blocked this degrades to exactly the
     behaviour that shipped before: open, tall, and working. */
  .scope:not([open]) + * {
    margin-top: 0;
  }
}

/* ------------------------------------------------------------- gauge terms */

/* The summary figures, in the client portal's gauge-terms treatment. The old
   metarail drew four numbers at 22px across the masthead, which is four things
   shouting, none of which is what the founder came for — the one that IS (an
   action still open) was body text underneath them. Small mono, tabular, label
   quiet and figure lifted: a caption on the page, not a scoreboard. */
.gauge-terms {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4) var(--sp-32);
  margin: var(--sp-24) 0 0;
  padding-top: var(--sp-24);
  border-top: 1px solid var(--rule-dark);
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.06em;
  line-height: 1.9;
  color: var(--cream-dim);
  font-variant-numeric: tabular-nums;
}

.gauge-terms b {
  color: var(--cream);
  font-weight: 500;
}

.chiprow {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-8);
  margin: var(--sp-24) 0 0;
  padding-top: var(--sp-24);
  border-top: 1px solid var(--rule-dark);
}

/* ------------------------------------------------------------------- chips */

/* State encoded in form, not only in words: a neutral chip is a hairline
   outline, an attention chip carries the redline, a settled chip fills. */
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-8);
  padding: var(--sp-4) var(--sp-12);
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  line-height: 1.6;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
  border: 1px solid var(--rule-mid);
  color: var(--graphite);
  background: transparent;
}

.chip[data-state="done"] {
  border-color: var(--ink);
  background: var(--ink);
  color: var(--cream);
}

.chip[data-state="live"] {
  border-color: var(--redline-deep);
  color: var(--redline-deep);
}

.chip[data-state="idle"] {
  border-color: var(--rule);
  color: var(--graphite);
}

[data-surface="dark"] .chip {
  border-color: var(--rule-dark-mid);
  color: var(--cream-dim);
}

[data-surface="dark"] .chip[data-state="done"] {
  border-color: var(--cream);
  background: var(--cream);
  color: var(--ink);
}

/* Red never carries text on ink: the redline becomes the rule instead. */
[data-surface="dark"] .chip[data-state="live"] {
  border-color: var(--redline);
  color: var(--cream);
}

[data-surface="dark"] .chip[data-state="idle"] {
  border-color: var(--rule-dark);
  color: var(--cream-dim);
}

/* ------------------------------------------------------------------ layout */

.body-inner {
  padding-block: var(--sp-48) var(--sp-96);
}

.section + .section {
  margin-top: var(--sp-64);
}

.section-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-8) var(--sp-24);
  padding-bottom: var(--sp-16);
  border-bottom: 2px solid var(--ink);
  margin-bottom: var(--sp-24);
}

.section-head .serif-h2 {
  margin-right: auto;
}

.section-note {
  margin: 0;
  color: var(--graphite);
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
}

/* A section that is a section until you need it.

   For the controls a founder reaches for once a year — adding somebody to
   Revline is the first — where a permanently open form outweighs everything
   the page is actually for. The head reads exactly as any other section head,
   so the page still names what is there; only the body is folded. A <details>
   rather than a toggle, so it works with no JavaScript at all and the browser
   handles the keyboard. */
.fold > summary {
  list-style: none;
  cursor: pointer;
}

.fold > summary::-webkit-details-marker,
.fold > summary::marker {
  display: none;
  content: "";
}

.fold > summary .section-head {
  margin-bottom: 0;
  transition: border-color var(--dur) var(--ease);
}

.fold > summary:hover .section-head {
  border-bottom-color: var(--redline);
}

.fold > summary:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 4px;
}

/* The caret is the affordance, and it turns. Drawn in the mono face so it sits
   on the same baseline as the count beside it. */
.fold > summary .section-head::after {
  content: "+";
  font-family: var(--font-mono);
  font-size: 0.875rem;
  color: var(--graphite);
}

.fold[open] > summary .section-head::after {
  content: "−";
}

.fold[open] > summary {
  margin-bottom: var(--sp-24);
}

/* Site keys are case-sensitive, so they are the one mono string on the page
   that must never be transformed. */
.keytag {
  margin: 0;
  color: var(--graphite);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.02em;
  overflow-wrap: anywhere;
}

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

.table-wrap {
  overflow-x: auto;
}

table {
  width: 100%;
  border-collapse: collapse;
}

thead th {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 400;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--graphite);
  text-align: left;
  white-space: nowrap;
  padding: 0 var(--sp-24) var(--sp-12) 0;
  border-bottom: 1px solid var(--rule-mid);
}

tbody td {
  text-align: left;
  vertical-align: top;
  padding: var(--sp-16) var(--sp-24) var(--sp-16) 0;
  border-bottom: 1px solid var(--rule);
}

tbody tr {
  transition: background-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}

tbody tr:hover,
tbody tr:focus-within {
  background: var(--paper-deep);
  box-shadow: inset 2px 0 0 var(--redline);
}

td:last-child,
th:last-child {
  padding-right: 0;
}

.cell-name {
  font-family: var(--font-text-serif);
  font-size: 1.0625rem;
  font-weight: 600;
  letter-spacing: -0.004em;
  text-decoration-color: transparent;
}

tr:hover .cell-name {
  text-decoration-color: var(--redline-deep);
}

.cell-sub {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.02em;
  color: var(--graphite);
  margin: var(--sp-4) 0 0;
  overflow-wrap: anywhere;
}

td.num {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.01em;
  white-space: nowrap;
}

/* Numbers align on their right edge or they are not a column, they are a
   scatter. The header aligns with them, which is the whole point. */
thead th.r {
  text-align: right;
}

tbody td.r {
  text-align: right;
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.01em;
  white-space: nowrap;
}

/* Below 48rem a six-column table is not a table, it is a horizontal scroll
   nobody performs. Each row becomes a record, and each cell carries the header
   it lost — so the phone reads the same data, not less of it. */
@media (max-width: 47.9375rem) {
  .t-stack thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
  }

  .t-stack tbody tr {
    display: block;
    border-top: 1px solid var(--rule-mid);
    padding: var(--sp-16) 0 var(--sp-16) var(--sp-12);
    margin-left: -0.75rem;
  }

  .t-stack tbody tr:last-child {
    border-bottom: 1px solid var(--rule-mid);
  }

  .t-stack tbody td {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-16);
    padding: var(--sp-8) 0 0;
    border-bottom: 0;
    text-align: right;
  }

  .t-stack tbody td:first-child {
    display: block;
    padding-top: 0;
    text-align: left;
  }

  .t-stack tbody td::before {
    content: attr(data-label);
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--graphite);
    flex: 0 0 auto;
  }

  .t-stack tbody td:first-child::before {
    content: none;
  }
}

/* ------------------------------------------------------------------- chain */

/* The funnel as six readings on one line, hairline-separated, read left to
   right. This is the same object the client sees on their own page, drawn from
   the same rules, so "where it leaks" means one thing in a founders' review and
   in a client call.

   The bar under each figure is survivorship rather than decoration: it is the
   share of the previous stage that made it through, so a short bar IS the loss.
   The worst-converting stage carries the redline and says so in words. That is
   arithmetic, not editorial — lowest rate against its own denominator, ties
   going to the earlier stage, because an upstream leak is the one worth fixing
   first. It is also the single most useful mark on the page: the number a
   founder needs is not the biggest one, it is the smallest. */
.chain {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  margin: 0;
}

.reading {
  min-width: 0;
  padding: var(--sp-16);
  border-left: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}

.reading:nth-child(2n + 1) {
  border-left: 0;
  padding-left: 0;
}

@media (min-width: 40rem) {
  .chain {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .reading:nth-child(2n + 1) {
    border-left: 1px solid var(--rule);
    padding-left: var(--sp-16);
  }

  .reading:nth-child(3n + 1) {
    border-left: 0;
    padding-left: 0;
  }
}

@media (min-width: 64rem) {
  .chain {
    grid-template-columns: repeat(6, minmax(0, 1fr));
  }

  .reading {
    border-bottom: 0;
  }

  .reading:nth-child(3n + 1) {
    border-left: 1px solid var(--rule);
    padding-left: var(--sp-16);
  }

  .reading:first-child {
    border-left: 0;
    padding-left: 0;
  }
}

.reading dt {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--graphite);
  margin: 0;
}

.reading dd {
  margin: 0;
  min-width: 0;
}

.figure {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  font-size: 1.5rem;
  line-height: 1.15;
  letter-spacing: -0.03em;
  color: var(--ink);
  margin: var(--sp-8) 0 0;
  overflow-wrap: anywhere;
}

.rate {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
  color: var(--graphite);
  margin: var(--sp-4) 0 0;
}

.surv {
  height: 3px;
  background: var(--paper-sunk);
  margin-top: var(--sp-12);
}

.surv i {
  display: block;
  height: 100%;
  background: var(--ink);
}

.reading[data-leak="true"] .surv i {
  background: var(--redline);
}

.reading[data-leak="true"] .rate {
  color: var(--redline-deep);
}

.leak-tag {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--redline-deep);
  margin: var(--sp-8) 0 0;
}

/* The mono label above a readout that is not a chain cell — the head of a code
   block, a figure standing on its own. */
.stat-label {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--graphite);
  margin: 0;
}

/* ----------------------------------------------------------------- funnels */

/* A funnel is a name and a chain. It used to be a filled card, which set it
   apart from everything else on the page for no reason other than that it
   contained numbers. It is now a `files` row that happens to carry a readout:
   same hairline, same serif name left, same mono meta right. */
.funnels {
  border-top: 1px solid var(--rule-mid);
}

.funnel {
  padding-block: var(--sp-24);
  border-bottom: 1px solid var(--rule);
}

.funnel-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-8) var(--sp-24);
  margin-bottom: var(--sp-8);
}

.funnel-head .name {
  font-family: var(--font-text-serif);
  font-size: 1.0625rem;
  font-weight: 600;
  margin: 0;
}

/* -------------------------------------------------------------------- asks */

/* Every open loop is drawn the way the client's "what we need from you" is:
   the statement and its consequence on the left, mono meta right-aligned on
   the right, one hairline between rows. The client roster is the same object —
   a client is an open loop with a name on it.

   This is what replaced the tables. A five-column table needed a `data-label`
   mobile hack to survive a phone, and the hack put the column heading in front
   of every value like a form label. Two grid columns collapsing to one needs
   nothing, and the row that is blocking can carry the redline in its own meta
   instead of spending a whole column on a status. */
.asks {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--rule-mid);
}

.asks li {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-8);
  padding: var(--sp-16) 0;
  border-bottom: 1px solid var(--rule);
}

@media (min-width: 48rem) {
  .asks li {
    grid-template-columns: minmax(0, 1fr) 12rem;
    gap: var(--sp-24);
    align-items: baseline;
  }
}

.asks .what {
  margin: 0;
  font-size: 0.9375rem;
}

.asks .what b {
  font-weight: 500;
}

/* The name is the target, so it is set in the reading face at reading size and
   only shows its underline on approach. */
.asks .what a {
  font-family: var(--font-text-serif);
  font-size: 1.0625rem;
  font-weight: 600;
  text-decoration-color: transparent;
}

.asks li:hover .what a,
.asks li:focus-within .what a {
  text-decoration-color: var(--redline-deep);
}

.asks .why {
  margin: var(--sp-4) 0 0;
  font-size: 0.875rem;
  color: var(--graphite);
}

.asks .meta {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-8);
  min-width: 0;
}

@media (min-width: 48rem) {
  .asks .meta {
    align-items: flex-end;
  }
}

.asks .since {
  display: block;
  margin: 0;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--graphite);
  font-variant-numeric: tabular-nums;
}

@media (min-width: 48rem) {
  .asks .since {
    text-align: right;
  }
}

.asks .since b {
  color: var(--ink);
  font-weight: 500;
}

/* Deliberately NOT reddening the whole row.
   This rule used to paint every `.since` on a flagged row, from a time when
   flagged meant "link expired" and the `.since` beside it WAS the expiry. It
   now also means "paid and stalled", and the two `.since` values on a record
   row are the price and the date it was created — a client owing us their
   details does not make £80/mo a problem, and Quantum Leap's row rendered as
   four separate red things of which one was a claim.

   The alarm lives on `.leak-tag`, which carries its own red and says what is
   wrong in words. Red that means "look here" stops working the moment it also
   means "this row contains something to look at". */

/* ------------------------------------------------------------------- files */

/* A flat list of things with a date on them. What happened on the left, when
   it happened on the right in mono. Stacks natively; no hack. */
.files {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--rule-mid);
}

.files li {
  border-bottom: 1px solid var(--rule);
  padding-block: var(--sp-16);
}

.files .row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-4) var(--sp-24);
}

.files .name {
  font-family: var(--font-text-serif);
  font-size: 1.0625rem;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.files .meta {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--graphite);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ------------------------------------------------------------------- links */

/* Two to six destinations, one line of job description each, a 1px gap doing
   the dividing rather than six competing borders. Anywhere the founder is
   being offered somewhere else to be. */
/* The client portal draws the dividers as a 1px gap over a ruled background,
   which is elegant and only works on an even count — an odd one leaves a solid
   block of rule showing through where the missing cell would be. Internally the
   count is whatever the record has, so the rules are borders on the cells and
   the frame belongs to the grid. Same hairline, no phantom cell. */
.links {
  display: grid;
  background: var(--paper);
  border: 1px solid var(--rule);
}

.link-row + .link-row {
  border-top: 1px solid var(--rule);
}

@media (min-width: 48rem) {
  .links {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .link-row + .link-row {
    border-top: 0;
  }

  .link-row:nth-child(2n) {
    border-left: 1px solid var(--rule);
  }

  .link-row:nth-child(n + 3) {
    border-top: 1px solid var(--rule);
  }
}

.link-row {
  background: var(--paper);
  padding: var(--sp-16);
  display: block;
  min-height: 44px;
  text-decoration: none;
  transition: background-color var(--dur) var(--ease);
}

.link-row:hover {
  background: var(--paper-deep);
}

/* A destination that does not exist yet. Sunk rather than lifted, and it does
   not react to the pointer, because there is nowhere to go. */
.link-row[data-state="empty"],
.link-row[data-state="empty"]:hover {
  background: var(--paper-deep);
  color: var(--graphite);
}

.link-row .name {
  font-family: var(--font-text-serif);
  font-size: 1.0625rem;
  font-weight: 600;
  margin: 0;
}

.link-row .job {
  font-size: 0.875rem;
  color: var(--graphite);
  margin: var(--sp-4) 0 0;
}

/* -------------------------------------------------------------------- foot */

.foot {
  margin-top: var(--sp-64);
  padding-top: var(--sp-24);
  border-top: 1px solid var(--rule);
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--graphite);
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-8) var(--sp-24);
  justify-content: space-between;
}

/* ------------------------------------------------------------------- cards */

.card {
  background: var(--paper-deep);
  padding: var(--sp-24);
}

.card + .card {
  margin-top: var(--sp-16);
}

@media (min-width: 48rem) {
  .card {
    padding: var(--sp-32);
  }
}

.card-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-8) var(--sp-24);
  margin-bottom: var(--sp-8);
}

.card .chain {
  margin-top: var(--sp-16);
}

.card .reading {
  border-color: var(--rule-mid);
}

/* ------------------------------------------------------------- definitions */

.deflist {
  display: grid;
  gap: 0;
  margin: 0;
  border-top: 1px solid var(--rule-mid);
}

.deflist > div {
  display: grid;
  gap: var(--sp-4) var(--sp-24);
  padding-block: var(--sp-16);
  border-bottom: 1px solid var(--rule);
  min-width: 0;
}

@media (min-width: 40rem) {
  .deflist > div {
    grid-template-columns: 12rem minmax(0, 1fr);
    align-items: baseline;
  }
}

.deflist dt {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--graphite);
}

.deflist dd {
  margin: 0;
  font-size: 0.9375rem;
  overflow-wrap: anywhere;
}

/* -------------------------------------------------------------------- code */

.code {
  margin: var(--sp-16) 0 0;
  border: 1px solid var(--rule-mid);
  background: var(--paper);
}

.code-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-16);
  padding: var(--sp-8) var(--sp-8) var(--sp-8) var(--sp-16);
  border-bottom: 1px solid var(--rule);
}

.code pre {
  margin: 0;
  padding: var(--sp-16);
  overflow-x: auto;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  line-height: 1.7;
  letter-spacing: 0.01em;
  color: var(--ink);
  tab-size: 2;
}

.code code {
  font: inherit;
  white-space: pre;
}

/* On a phone a horizontally-scrolling snippet just looks truncated, and this
   is the one string on the page you are here to read in full. */
@media (max-width: 47.9375rem) {
  .code pre {
    overflow-x: visible;
  }

  .code code {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
  }
}

/* ----------------------------------------------------------------- buttons */

.btn {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  min-height: 3rem;
  padding: var(--sp-12) var(--sp-32);
  background: var(--redline-deep);
  color: var(--cream);
  border: 1px solid var(--redline-deep);
  border-radius: 0;
  cursor: pointer;
  text-decoration: none;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease),
    transform var(--dur) var(--ease);
}

.btn:hover {
  background: #b52820;
  border-color: #b52820;
}

.btn:active {
  transform: translateY(1px);
}

.btn[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

.btn-quiet {
  background: transparent;
  color: var(--ink);
  border: 1px solid var(--rule-mid);
  min-height: 2.75rem;
  padding: var(--sp-8) var(--sp-16);
}

.btn-quiet:hover {
  background: transparent;
  border-color: var(--ink);
}

.btn-quiet[data-copied="true"] {
  border-color: var(--redline-deep);
  color: var(--redline-deep);
}

.actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-16);
  margin-top: var(--sp-24);
}

/* ------------------------------------------------------------------- forms */

form {
  margin: 0;
}

.field {
  display: block;
}

.field > .label {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--graphite);
  margin-bottom: var(--sp-8);
}

.field .hint {
  display: block;
  font-size: 0.8125rem;
  color: var(--graphite);
  margin-top: var(--sp-8);
}

/* 1rem is the iOS auto-zoom floor. Drop to 15px and Safari zooms the viewport
   on focus, and the founder spends the rest of the form pinching back out. */
input[type="text"],
input[type="password"],
input:not([type]) {
  width: 100%;
  font-family: var(--font-sans);
  font-size: 1rem;
  line-height: 1.5;
  color: var(--ink);
  background: var(--cream);
  border: 1px solid var(--rule-mid);
  border-radius: 0;
  padding: var(--sp-12) var(--sp-16);
  min-height: 3rem;
  appearance: none;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}

input:hover:not(:disabled) {
  border-color: var(--rule-strong);
}

input:focus {
  border-color: var(--redline-deep);
}

/* Flush against the border rather than offset, so the focus ring reads as one
   thicker rule instead of two concentric red boxes (which look like an error). */
input:focus-visible {
  outline-offset: 0;
}

input::placeholder {
  color: var(--graphite);
  opacity: 0.7;
}

/* ------------------------------------------------------------------ states */

.note {
  padding: var(--sp-24);
  background: var(--paper-deep);
  border-left: 2px solid var(--rule-strong);
  color: var(--graphite);
  font-size: 0.9375rem;
}

.note strong {
  color: var(--ink);
  font-weight: 500;
}

/* ------------------------------------------------------------------- login */

/* The door is the building, seen from outside.

   It used to be a dark card floating in the middle of a paper page — fine, and
   from nowhere: nothing about it told you what was behind it, and the geometry
   it taught you was wrong the instant you got in. So the door is now the app's
   own plan, at full height: ink panel on the left, redline seam, paper on the
   right. Sign in and the panel you were reading turns into the rail you
   navigate from. The layout does not change; it just fills with your work. */
.login {
  min-height: 100dvh;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  /* On a phone the two panels stack, and the rows have to be told which one
     absorbs the slack. Without this, `min-height: 100dvh` and the default
     stretch split the spare height between them, so the emptier the page the
     TALLER the black band grew: the sign-in door opened with 112px of empty
     ink under the wordmark and the forgot-password door with 247px — nearly a
     third of the screen, holding nothing, pushing the one field down. The lid
     is now the height of the mark it carries. */
  grid-template-rows: auto minmax(0, 1fr);
}

@media (min-width: 56rem) {
  .login {
    grid-template-columns: 26rem minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr);
  }
}

/* Left: the ink panel. On a phone it is a lid — enough to set the two worlds
   up, never enough to push the password field below the fold. */
.login-aside {
  background: var(--ink);
  color: var(--cream);
  border-bottom: 2px solid var(--redline);
  padding: var(--sp-24);
  display: flex;
  flex-direction: column;
  gap: var(--sp-24);
}

@media (min-width: 56rem) {
  .login-aside {
    border-bottom: 0;
    border-right: 2px solid var(--redline);
    padding: var(--sp-48) var(--sp-32);
    /* Three bands: mark at the top, the claim in the optical centre, the chain
       on the floor. Space-between alone left the claim stranded against the
       ceiling with a screen of nothing under it. */
    display: grid;
    grid-template-rows: auto 1fr auto;
    gap: var(--sp-32);
  }
}

.login-pitch-block {
  align-self: center;
}

/* Both of its children are hidden on a phone, but an empty flex item still
   claims the gap either side of it. Hiding the wrapper too is what makes the
   lid actually collapse to the wordmark. */
@media (max-width: 55.9375rem) {
  .login-pitch-block {
    display: none;
  }
}

.login-pitch {
  margin: var(--sp-24) 0 0;
  font-family: var(--font-display);
  font-weight: 500;
  letter-spacing: -0.018em;
  line-height: 1.12;
  font-size: clamp(1.75rem, 1.1rem + 2.2vw, 2.75rem);
  max-width: 14ch;
}

.login-blurb {
  margin: var(--sp-24) 0 0;
  max-width: 34ch;
  color: var(--cream-dim);
  font-size: 0.9375rem;
  line-height: 1.6;
}

@media (max-width: 55.9375rem) {
  .login-blurb {
    display: none;
  }
}

.login-pitch em {
  font-style: normal;
  color: var(--cream-dim);
}

@media (max-width: 55.9375rem) {
  .login-pitch {
    display: none;
  }
}

/* The chain, named on the door. It is what the portal measures, and saying so
   here means the first screen a new seat ever sees has already taught them the
   vocabulary the rest of the product uses. */
.login-chain {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-8) var(--sp-16);
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--cream-dim);
}

.login-chain li {
  display: flex;
  align-items: center;
  gap: var(--sp-16);
}

.login-chain li + li::before {
  content: "";
  width: 10px;
  height: 1px;
  background: var(--rule-dark-mid);
}

.login-chain li:last-child {
  color: var(--cream);
}

@media (max-width: 55.9375rem) {
  .login-chain {
    display: none;
  }
}

/* Right: the form, on paper, held to a reading width and vertically centred. */
.login-main {
  display: grid;
  place-items: center;
  padding: var(--sp-32) var(--sp-24) var(--sp-64);
}

@media (min-width: 56rem) {
  .login-main {
    padding: var(--sp-48) var(--sp-48) var(--sp-64);
  }
}

.login-card {
  width: 100%;
  max-width: 24rem;
}

.login-card .serif-h1 {
  margin: var(--sp-8) 0 0;
  font-size: clamp(1.75rem, 1.4rem + 1.2vw, 2.125rem);
}

.login-card .login-note {
  margin: var(--sp-12) 0 var(--sp-32);
  color: var(--graphite);
  font-size: 0.9375rem;
  max-width: 34ch;
}

.login-card .btn {
  width: 100%;
  margin-top: var(--sp-24);
}

.login-error {
  margin: 0 0 var(--sp-24);
  padding: var(--sp-12) var(--sp-16);
  border-left: 2px solid var(--redline);
  background: var(--paper-deep);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.02em;
}

/* Same shape as the error, in the rule colour rather than the redline: a
   notice is the door telling you something, not the door refusing you. */
.login-notice {
  margin: 0 0 var(--sp-24);
  padding: var(--sp-12) var(--sp-16);
  border-left: 2px solid var(--rule-strong);
  background: var(--paper-deep);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.02em;
}

.login-foot {
  margin: var(--sp-24) 0 0;
  color: var(--graphite);
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.login-foot + .login-foot {
  margin-top: var(--sp-8);
  padding-top: var(--sp-24);
  border-top: 1px solid var(--rule);
}

/* -------------------------------------------------------------------- misc */

.skip-link {
  position: absolute;
  left: var(--sp-16);
  top: var(--sp-16);
  padding: var(--sp-8) var(--sp-16);
  background: var(--cream);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-decoration: none;
  transform: translateY(-200%);
  z-index: 10;
}

.skip-link:focus-visible {
  transform: translateY(0);
}

/* ------------------------------------------------------------ tap targets */

/* The portal's navigation is set in 11px mono, which makes a 13px-tall tap
   target — fine for a mouse, a coin toss for a thumb. The ink stays exactly
   where it is and the hit area grows underneath it: padding would push the
   underline half a line away from the word it belongs to, and that rule IS the
   current-page marker. The inset is smaller than the gap between any two of
   them, so no two targets overlap. */
.navrail a,
.eyebrow a,
.section-note a,
.deflist dd > a,
.asks .what a {
  position: relative;
}

.navrail a::after,
.eyebrow a::after,
.section-note a::after,
.deflist dd > a::after,
.asks .what a::after {
  content: "";
  position: absolute;
  inset: -0.6875rem -0.375rem;
}

/* ============================================================================
   PORTAL ADDITIONS
   ----------------------------------------------------------------------------
   Everything above is the HQ cockpit, unchanged. Everything below is what the
   onboarding admin needs on top of it — the surfaces that used to live on a
   second stylesheet with a second set of opinions.

   The rule for this block: no new tokens, no new type sizes, no new spacing
   values. Every addition is composed from what the cockpit already declares, so
   there is one system to keep honest rather than two that agree by luck.
   ========================================================================= */

/* --------------------------------------------------------------- masthead nav */

/* Two areas now share one masthead. The current one is marked with the redline
   rather than a fill, so the nav reads as a position rather than a button bar. */
.navrail {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-16);
  margin-left: auto;
  margin-right: var(--sp-24);
}

.navrail a {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--cream-dim);
  padding-bottom: var(--sp-4);
  border-bottom: 1px solid transparent;
}

.navrail a:hover {
  color: var(--cream);
  border-bottom-color: var(--rule-dark-mid);
}

.navrail a[aria-current="page"] {
  color: var(--cream);
  border-bottom-color: var(--redline);
}

@media (max-width: 39.9375rem) {
  .navrail {
    order: 3;
    width: 100%;
    margin: var(--sp-16) 0 0;
  }
}

/* --------------------------------------------------------------------- forms */

/* The cockpit styled `input:not([type])` and text/password, because its only
   form was a password box. The admin has email, url, tel and number fields, and
   an unstyled one next to a styled one is the single loudest way to make a form
   look unfinished. */
input[type="email"],
input[type="url"],
input[type="tel"],
input[type="number"],
input[type="search"],
input[type="date"],
textarea,
select {
  width: 100%;
  font-family: var(--font-sans);
  font-size: 1rem;
  line-height: 1.5;
  color: var(--ink);
  background: var(--cream);
  border: 1px solid var(--rule-mid);
  border-radius: 0;
  padding: var(--sp-12) var(--sp-16);
  min-height: 3rem;
  appearance: none;
  transition: border-color var(--dur) var(--ease);
}

textarea {
  min-height: 6rem;
  resize: vertical;
  line-height: 1.6;
}

/* The chevron is drawn rather than fetched: an inline data URI keeps the strict
   CSP intact (img-src 'self' data:) and costs no request. */
select {
  padding-right: var(--sp-48);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%235e574f' stroke-width='1.4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--sp-16) center;
  background-size: 10px 6px;
}

textarea:hover:not(:disabled),
select:hover:not(:disabled) {
  border-color: var(--rule-strong);
}

textarea:focus,
select:focus {
  border-color: var(--redline-deep);
}

input[aria-invalid="true"],
textarea[aria-invalid="true"],
select[aria-invalid="true"] {
  border-color: var(--redline-deep);
  border-left-width: 2px;
}

textarea[readonly] {
  background: var(--paper-deep);
  color: var(--ink);
}

/* Money and ids are read as digits, not as words. */
.mono-input {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* One field was enough for a login card. A form of twelve needs rhythm. */
.field + .field,
.field + .actions,
form > .field:not(:first-child) {
  margin-top: var(--sp-24);
}

fieldset {
  border: 0;
  border-top: 1px solid var(--rule);
  margin: var(--sp-32) 0 0;
  padding: var(--sp-24) 0 0;
  min-width: 0;
}

fieldset:first-of-type {
  margin-top: var(--sp-24);
}

legend {
  padding: 0 var(--sp-12) 0 0;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink);
}

.grid-2 {
  display: grid;
  gap: var(--sp-24);
  grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 48rem) {
  .grid-2 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* Inside a grid the fields are already separated by the gap, so the stacking
   margin would double it. */
.grid-2 > .field + .field,
.grid-2 > .field {
  margin-top: 0;
}

.field-error {
  display: block;
  margin-top: var(--sp-8);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.02em;
  color: var(--redline-deep);
}

/* A checkbox is the one control on the client's page that carries legal weight,
   so it is drawn at a size a thumb cannot miss rather than at the browser's. */
.check {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-12);
  cursor: pointer;
}

.check input[type="checkbox"] {
  flex: none;
  width: 1.5rem;
  height: 1.5rem;
  margin: 0.125rem 0 0;
  accent-color: var(--redline-deep);
}

/* ------------------------------------------------------------------ surfaces */

/* The panel is a card that is making a point rather than holding a table: the
   redline down its left edge is the same mark the section rules use, turned. */
.panel {
  background: var(--paper-deep);
  padding: var(--sp-24);
  border-left: 2px solid var(--rule-strong);
}

.panel.rule-left {
  border-left-color: var(--redline);
}

.panel > :first-child {
  margin-top: 0;
}

.panel > :last-child {
  margin-bottom: 0;
}

/* The one block on the founders' side whose contents are addressed OUTWARD:
   the message about to be pasted into an email. Drawing it in the client's own
   dark keeps the founder honest about which side of the wall it lands on. */
.panel-dark {
  background: var(--ink);
  color: var(--cream);
  padding: var(--sp-32) var(--sp-24);
  border-top: 2px solid var(--redline);
}

@media (min-width: 48rem) {
  .panel-dark {
    padding: var(--sp-32);
  }
}

.panel-dark .label {
  color: var(--cream-dim);
}

/* Two names and what each is for. The role has to be its own line: run inline
   after the name it reads as one long sentence and the client cannot tell who
   they are supposed to email. */
.who {
  display: grid;
  gap: var(--sp-24);
  margin: var(--sp-24) 0 0;
}

@media (min-width: 40rem) {
  .who {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.who dt {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--cream-dim);
  margin: 0 0 var(--sp-4);
}

.who dd {
  margin: 0;
  font-size: 0.9375rem;
}

.who dd .role {
  display: block;
  color: var(--cream-dim);
  font-size: 0.875rem;
  margin-top: var(--sp-4);
}

.panel-dark textarea,
.panel-dark textarea[readonly] {
  background: var(--ink-lift);
  color: var(--cream);
  border-color: var(--rule-dark-mid);
}

.panel-dark textarea:hover:not(:disabled) {
  border-color: var(--cream-dim);
}

.panel-dark textarea:focus {
  border-color: var(--redline);
}

.panel-dark .btn-quiet {
  color: var(--cream);
  border-color: var(--rule-dark-mid);
}

.panel-dark .btn-quiet:hover {
  border-color: var(--cream);
}

.banner {
  margin-bottom: var(--sp-24);
  padding: var(--sp-16);
  border-left: 2px solid var(--redline);
  background: var(--paper-deep);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.02em;
  color: var(--redline-deep);
}

/* The rail's own links about your seat, under the "Signed in" line. Quiet by
   default; the dot appears only while two-step sign-in is off, because a
   permanent badge is one everybody stops seeing within a week. */
.seat-links {
  margin: var(--sp-8) 0 var(--sp-12);
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.seat-links a {
  color: var(--cream-dim);
  text-decoration: none;
  border-bottom: 1px solid transparent;
}

.seat-links a:hover,
.seat-links a:focus-visible {
  color: var(--cream);
  border-bottom-color: var(--rule-dark-mid);
}

.seat-nudge {
  margin-left: var(--sp-4);
  color: var(--redline);
  font-size: 1.25rem;
  line-height: 0;
  vertical-align: middle;
}

/* ------------------------------------------------------ the second factor

   The security page and the sign-in challenge. Everything here is sized for
   transcription: a key typed into a desktop password manager, a code read off
   a phone, ten recovery codes copied onto paper. Monospace and generous
   tracking are the whole design brief. */

/* Six digits, and nothing else.

   Sized to what it holds. A field's width is a promise about its input, and a
   six-digit box drawn 1100px wide reads as "paste an essay" — it was the one
   thing on the security page that looked like a mistake. 11ch fits six
   characters at this tracking with room either side.

   Wide tracking so a mistyped digit is visible before the form is submitted,
   and 1.25rem so iOS never zooms the page on focus — the 16px floor with room
   to spare, on a field whose whole purpose is to be filled from a
   notification. */
.code-input {
  font-family: var(--font-mono);
  font-size: 1.25rem;
  letter-spacing: 0.35em;
  /* Not `width`, so it still shrinks on a narrow phone rather than overflowing. */
  max-width: 11ch;
  text-align: center;
  /* The tracking adds a trailing gap after the last digit, which reads as an
     off-centre value; half of it back as left padding recentres the run. */
  padding-left: calc(var(--sp-16) + 0.35em);
}

/* On the challenge page the field is the whole task, so it gets the card's
   width the way the password field above it does — nothing else is competing
   for the eye there, and a small box centred in a large card reads as timid. */
.login-card .code-input {
  max-width: 100%;
  text-align: left;
}

/* The enrolment pair: the square, and the fallback for anyone who cannot scan
   it. Side by side where there is room, stacked on a phone — where scanning is
   impossible anyway, because the code is ON the phone doing the scanning, so
   the typed key is the primary path and belongs first. */
.qr-block {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-32);
  align-items: flex-start;
  margin-top: var(--sp-24);
}

.qr {
  flex: 0 0 auto;
  border: 1px solid var(--rule);
  background: #ffffff;
}

@media (max-width: 40rem) {
  .qr-block {
    flex-direction: column-reverse;
  }
}

.qr-aside {
  flex: 1 1 16rem;
  min-width: 0;
}

/* Grouped in fours by the server; this keeps the groups from being split
   across a line break, which is exactly where a transcription error happens. */
.secret {
  margin: var(--sp-16) 0;
  padding: var(--sp-12) var(--sp-16);
  background: var(--paper-deep);
  border-left: 2px solid var(--rule-strong);
  font-family: var(--font-mono);
  font-size: 0.9375rem;
  letter-spacing: 0.08em;
  word-spacing: 0.3em;
  overflow-wrap: break-word;
}

/* Shown once and never again, so they are set to be read and copied rather
   than skimmed: two columns on anything wider than a phone, tabular figures so
   the codes line up, and numbered so somebody ticking them off on paper knows
   which one they are on. */
.recovery-codes {
  margin: var(--sp-24) 0;
  padding: 0 0 0 var(--sp-32);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: var(--sp-8) var(--sp-24);
  font-family: var(--font-mono);
  font-size: 0.9375rem;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.06em;
  color: var(--ink);
}

.recovery-codes li {
  padding: var(--sp-4) 0;
}

/* A section that has to be read before the page is closed. The redline edge is
   the portal's own "this needs a person", used here at section scale. */
.section[data-tone="loud"] {
  border-left: 2px solid var(--redline);
  padding-left: var(--sp-24);
  background: var(--paper-deep);
}

.copy[data-tone="warn"] {
  color: var(--redline-deep);
}

/* The quiet banner: same shape as the alert, in the rule colour. A confirmation
   is the portal telling you something worked, not warning you. */
.banner[data-tone="quiet"] {
  border-left-color: var(--rule-strong);
  color: var(--ink);
}

.section-gap {
  margin-top: var(--sp-64);
}

.stack > * + * {
  margin-top: var(--sp-16);
}

/* ---------------------------------------------------------------- lifecycle */

/* The onboarding lifecycle is five ordered stages plus one interrupt. It maps
   onto the cockpit's three chip forms rather than inventing five colours:
   outline = not there yet, redline = ours to move, filled = settled. */
.chip[data-state="attention"] {
  border-color: var(--redline-deep);
  background: var(--redline-deep);
  color: var(--cream);
}

[data-surface="dark"] .chip[data-state="attention"] {
  border-color: var(--redline);
  background: var(--redline);
  color: var(--cream);
}

/* ----------------------------------------------------------------- timeline */

/* A `files` list whose names happen to be event types. It used to be a
   two-column grid with the timestamp in the FIRST column, which put the least
   interesting fact in the position the eye lands on; what happened now leads
   and when it happened sits right, in mono, where every other date on the
   portal sits. */
.timeline {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--rule-mid);
}

.timeline li {
  padding-block: var(--sp-16);
  border-bottom: 1px solid var(--rule);
}

.timeline .row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-4) var(--sp-24);
}

.timeline time {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--graphite);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* An event type is an identifier, not a sentence, so it stays in mono at
   reading weight rather than being dressed up as a serif headline. */
.timeline .event-type {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  color: var(--ink);
  overflow-wrap: anywhere;
}

.timeline pre,
.action-card pre {
  margin: var(--sp-8) 0 0;
  padding: var(--sp-12) var(--sp-16);
  background: var(--paper-deep);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  line-height: 1.7;
  color: var(--graphite);
  white-space: pre-wrap;
  word-break: break-word;
  overflow-x: auto;
}

/* --------------------------------------------------------------- action cards */

/* An open action is work a human still owes a paying client, so it carries the
   redline. A resolved one steps back to a hairline and keeps its receipt. */
.action-card {
  padding: var(--sp-24);
  background: var(--paper-deep);
  border-left: 2px solid var(--redline);
}

.action-card + .action-card {
  margin-top: var(--sp-16);
}

.action-card[data-resolved="1"] {
  border-left-color: var(--rule);
  opacity: 0.7;
}

.action-card .action-head {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--graphite);
}

.action-card .actions {
  margin-top: var(--sp-16);
}

/* ------------------------------------------------------------------ summary */

/* A definition list that reads as a record rather than as a table. Shares the
   cockpit's .deflist rules; this only widens the term column where the admin's
   terms are longer than HQ's. */
.deflist.wide > div {
  grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 40rem) {
  .deflist.wide > div {
    grid-template-columns: 12rem minmax(0, 1fr);
  }
}

/* A readonly mono readout is still a focusable field, and iOS zooms the whole
   viewport on any field under 1rem — readonly included. This one holds the
   message a founder is about to paste into an email, so it is also the last
   thing that should be read at 13px. */
.readout {
  font-family: var(--font-mono);
  font-size: 1rem;
  line-height: 1.7;
}

/* A record id, a token, a site key: monospace, quiet, selectable. */
.mono-data {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.02em;
  color: var(--graphite);
  margin: var(--sp-4) 0 0;
  word-break: break-all;
}

.mono-figure {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

/* Space BETWEEN, not around: `.copy` carries no margin of its own because HQ
   only ever set it inside a card, where the padding did the work. In the admin
   a paragraph explains the form directly beneath it, and with no rule here the
   explanation and the first field label touch. */
.copy + form,
.copy + .field,
.copy + .grid-2,
.copy + .deflist,
.copy + .table-wrap,
.copy + .banner,
.copy + .card,
.copy + .panel,
.copy + .asks,
.copy + .files,
.copy + .links,
.copy + .funnels {
  margin-top: var(--sp-24);
}

/* Inside a fieldset the paragraph is the legend's elaboration, so it needs air
   under it before the controls start. */
fieldset > .copy {
  margin-bottom: var(--sp-24);
}

/* ---------------------------------------------------- bar sizes, as classes

   The Content-Security-Policy forbids inline styles, so a survivorship bar
   cannot carry `style="width:27%"` — it was being blocked and every bar in the
   portal drew at zero. Widths are named as classes instead.

   Two resolutions, because a funnel's interesting end is the narrow one. Above
   10% the step is 5%, where nobody can see the difference on a 3px rule. Below
   it the step is 1%, because rounding to 5 collapsed the whole bottom of every
   cascade into one identical stub: bookings at 2.4% of visitors, shows at 1.4%
   and closes at 0.5% all drew the same 3px, so the three stages where the money
   actually is were the three the chart could not tell apart. */
.w-0 { width: 0%; }
.w-1 { width: 1%; }
.w-2 { width: 2%; }
.w-3 { width: 3%; }
.w-4 { width: 4%; }
.w-5 { width: 5%; }
.w-6 { width: 6%; }
.w-7 { width: 7%; }
.w-8 { width: 8%; }
.w-9 { width: 9%; }
.w-10 { width: 10%; }
.w-15 { width: 15%; }
.w-20 { width: 20%; }
.w-25 { width: 25%; }
.w-30 { width: 30%; }
.w-35 { width: 35%; }
.w-40 { width: 40%; }
.w-45 { width: 45%; }
.w-50 { width: 50%; }
.w-55 { width: 55%; }
.w-60 { width: 60%; }
.w-65 { width: 65%; }
.w-70 { width: 70%; }
.w-75 { width: 75%; }
.w-80 { width: 80%; }
.w-85 { width: 85%; }
.w-90 { width: 90%; }
.w-95 { width: 95%; }
.w-100 { width: 100%; }

.h-0 { height: 0%; }
.h-1 { height: 1%; }
.h-2 { height: 2%; }
.h-3 { height: 3%; }
.h-4 { height: 4%; }
.h-5 { height: 5%; }
.h-6 { height: 6%; }
.h-7 { height: 7%; }
.h-8 { height: 8%; }
.h-9 { height: 9%; }
.h-10 { height: 10%; }
.h-15 { height: 15%; }
.h-20 { height: 20%; }
.h-25 { height: 25%; }
.h-30 { height: 30%; }
.h-35 { height: 35%; }
.h-40 { height: 40%; }
.h-45 { height: 45%; }
.h-50 { height: 50%; }
.h-55 { height: 55%; }
.h-60 { height: 60%; }
.h-65 { height: 65%; }
.h-70 { height: 70%; }
.h-75 { height: 75%; }
.h-80 { height: 80%; }
.h-85 { height: 85%; }
.h-90 { height: 90%; }
.h-95 { height: 95%; }
.h-100 { height: 100%; }

/* ------------------------------------------------------------- utilities

   A small, closed set on the 8pt scale, for the handful of one-off spacing
   decisions that genuinely belong to a single element rather than to its
   component. These exist because the CSP blocks the `style` attribute; they
   are not an invitation to lay pages out in class names. */
.m-0 { margin: 0; }
.mt-0 { margin-top: 0; }
.mt-8 { margin: var(--sp-8) 0 0; }
.mt-12 { margin: var(--sp-12) 0 0; }
.mt-16 { margin: var(--sp-16) 0 0; }
.mt-20 { margin: 1.25rem 0 0; }
.mt-24 { margin: var(--sp-24) 0 0; }
.mt-32 { margin: var(--sp-32) 0 0; }
.mb-24 { margin: 0 0 var(--sp-24); }

/* A measure, where the element's own component does not already set one. */
.narrow { max-width: 34rem; }
.measure-38 { max-width: 38rem; }

/* Two things on one line until they cannot be, which is most toolbars. */
.split-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-24);
  align-items: center;
  justify-content: space-between;
}

/* A section-note used as a caption under something, rather than in a head. */
.note-plain {
  border: 0;
  padding: 0;
  margin-top: var(--sp-12);
}

/* ------------------------------------- gauge terms and chips, on paper

   Both were written for the ink masthead a client's page still uses, so both
   paint themselves cream. Used on the founders' canvas that is cream on cream:
   the summary line under every page title was rendering invisible. The canvas
   restates them in ink rather than the components guessing which ground they
   are standing on. */
.canvas .gauge-terms {
  color: var(--graphite);
  border-top-color: var(--rule);
}

.canvas .gauge-terms b {
  color: var(--ink);
}

.canvas .chiprow {
  border-top-color: var(--rule);
  padding-top: var(--sp-16);
  margin-top: var(--sp-16);
}

/* ============================================================== the mark

   A pulse trace with the baseline broken either side of the spike. The trace
   takes currentColor so one piece of markup is ink on paper and cream on ink;
   the two baseline segments stay redline on both grounds, because that break
   in the line IS the brand — it is the gap a leak makes. */
.mark {
  width: 1.375rem;
  height: 1.375rem;
  flex: none;
  overflow: visible;
}

.mark-rule {
  stroke: var(--redline);
  stroke-width: 4;
}

.mark-trace {
  fill: none;
  stroke: currentColor;
  stroke-width: 7;
  stroke-linecap: square;
  stroke-linejoin: miter;
  stroke-miterlimit: 14;
}

/* The wordmark drops its separator rule now the mark carries the red. */
.wordmark {
  gap: var(--sp-8);
}

.wordmark-sub {
  padding-left: var(--sp-4);
}

/* ------------------------------------------------------------- the spinner

   The same trace, running. Every form here posts and redirects, and
   provisioning a client calls ClickUp and GoHighLevel — seconds, sometimes.
   A button that does not answer gets pressed twice, and on "mark as paid and
   provision" the second press is a second provisioning run. */
[data-busy] .btn-label {
  visibility: hidden;
}

.btn[data-busy],
button[data-busy] {
  position: relative;
  pointer-events: none;
}

.spinner {
  position: absolute;
  inset: 50% auto auto 50%;
  translate: -50% -50%;
  width: 1.25rem;
  height: 1.25rem;
  overflow: visible;
  color: inherit;
}

.spinner polyline {
  fill: none;
  stroke: currentColor;
  stroke-width: 10;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 38 189;
  stroke-dashoffset: 189;
  animation: pulse-run 1.25s linear infinite;
}

@keyframes pulse-run {
  to {
    stroke-dashoffset: -38;
  }
}

@media (prefers-reduced-motion: reduce) {
  .spinner polyline {
    animation: none;
    stroke-dasharray: none;
    stroke-dashoffset: 0;
    opacity: 0.5;
  }
}

/* A form mid-submit stops inviting a second attempt anywhere in it. */
form[data-submitting] {
  cursor: progress;
}

form[data-submitting] input,
form[data-submitting] select,
form[data-submitting] textarea {
  pointer-events: none;
  opacity: 0.72;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ============================================================== the cascade

   The funnel for the person paying for it. Every bar is a share of the same
   denominator — the visitors at the top — so the bars narrow down the page and
   the narrowing IS the loss. The count that fell out sits between the rows, in
   words, because "929 did not click" makes someone act and "27.6% click rate"
   gets nodded at. */
.cascade {
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: none;
}

.cascade-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--sp-4) var(--sp-16);
  align-items: baseline;
  padding: var(--sp-8) 0 var(--sp-12);
}

.cascade-label {
  margin: 0;
  font-family: var(--font-text-serif);
  font-size: 1.0625rem;
  font-weight: 600;
}

.cascade-figure {
  margin: 0;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  font-size: 1.375rem;
  letter-spacing: -0.02em;
  text-align: right;
  white-space: nowrap;
}

.cascade-bar {
  grid-column: 1 / -1;
  height: 10px;
  background: var(--paper-deep);
  margin-top: var(--sp-4);
}

/* Seven customers out of 1,160 visitors is 0.6%, which quantises to a bar of no
   width — and a real number beside an empty track reads as a rendering fault
   rather than as a small number. Anything above zero draws a stub; zero draws
   nothing, because nothing is what happened. */
.cascade-bar i[data-any="yes"] {
  min-width: 3px;
}

.cascade-bar i {
  display: block;
  height: 100%;
  background: var(--ink);

  /* Grows on load rather than appearing at length: the eye follows the motion
     down the page and reads the narrowing as one gesture instead of five
     unrelated bars. Off entirely under reduced-motion. */
  transform-origin: left center;
}

[data-ready="true"] .cascade-bar i {
  animation: cascade-grow 620ms var(--ease) both;
}

@keyframes cascade-grow {
  from {
    transform: scaleX(0);
  }
  to {
    transform: scaleX(1);
  }
}

.cascade-row:nth-child(3) .cascade-bar i { animation-delay: 60ms; }
.cascade-row:nth-child(5) .cascade-bar i { animation-delay: 120ms; }
.cascade-row:nth-child(7) .cascade-bar i { animation-delay: 180ms; }
.cascade-row:nth-child(9) .cascade-bar i { animation-delay: 240ms; }

@media (prefers-reduced-motion: reduce) {
  [data-ready="true"] .cascade-bar i {
    animation: none;
  }
}

.cascade-rate {
  grid-column: 1 / -1;
  margin: var(--sp-4) 0 0;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.06em;
  color: var(--graphite);
  font-variant-numeric: tabular-nums;
}

/* The gap between two stages, which is the only place the loss is named. */
.cascade-drop {
  display: flex;
  align-items: baseline;
  gap: var(--sp-8);
  padding: var(--sp-4) 0 var(--sp-4) var(--sp-24);
  border-left: 1px solid var(--rule-mid);
  margin-left: var(--sp-8);
  font-size: 0.875rem;
  color: var(--graphite);
}

.cascade-drop-figure {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  color: var(--ink);
}

/* One mark per funnel: the stage converting worst against the one before it. */
.cascade-row[data-leak="true"] .cascade-bar i,
.cascade-drop[data-leak="true"] {
  background: var(--redline);
}

.cascade-drop[data-leak="true"] {
  background: none;
  border-left-color: var(--redline);
  color: var(--redline-deep);
}

.cascade-drop[data-leak="true"] .cascade-drop-figure {
  color: var(--redline-deep);
}

.cascade-revenue .cascade-bar {
  display: none;
}

.cascade-revenue {
  border-top: 2px solid var(--ink);
  margin-top: var(--sp-8);
}

/* ================================================================ the tour

   The walk-through explains the page by moving through it, not by covering it:
   the card sits on the floor of the viewport and the section being described
   stays fully visible and fully usable above it. A tour that dims the page is
   a tour that teaches you about a page you cannot see. */
.tour-welcome {
  padding: var(--sp-24);
  background: var(--paper-deep);
  border-left: 2px solid var(--redline);
  margin-bottom: var(--sp-32);
}

.tour-welcome .serif-h2 {
  margin: var(--sp-8) 0 var(--sp-12);
}

.tour-welcome .copy {
  margin: 0 0 var(--sp-24);
}

.tour {
  position: fixed;
  inset: auto 0 0 0;
  z-index: 40;
  padding: var(--sp-16);
  display: flex;
  justify-content: center;
  pointer-events: none;
}

.tour[hidden] {
  display: none;
}

.tour-card {
  pointer-events: auto;
  width: min(34rem, 100%);
  background: var(--ink);
  color: var(--cream);
  border-top: 2px solid var(--redline);
  padding: var(--sp-16) var(--sp-24) var(--sp-24);
  box-shadow: 0 1px 2px rgb(20 17 16 / 0.14), 0 18px 48px rgb(20 17 16 / 0.28);
  animation: tour-rise 260ms var(--ease) both;
}

@keyframes tour-rise {
  from {
    opacity: 0;
    translate: 0 12px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .tour-card {
    animation: none;
  }
}

.tour-count {
  margin: 0 0 var(--sp-8);
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--cream-dim);
  font-variant-numeric: tabular-nums;
}

.tour-card .serif-h3 {
  margin: 0;
  color: var(--cream);
}

.tour-body {
  margin: var(--sp-8) 0 var(--sp-16);
  color: var(--cream-dim);
  font-size: 0.9375rem;
  max-width: 58ch;
}

.tour-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-8);
}

/* Skip is pushed away from Next so the two are never mis-tapped for each
   other on a phone — the destructive-ish one is not adjacent to the one
   pressed five times in a row. */
.tour-skip {
  margin-right: auto;
}

.tour-card .btn-quiet {
  color: var(--cream-dim);
  border-color: var(--rule-dark-mid);
  background: transparent;
}

.tour-card .btn-quiet:hover {
  color: var(--cream);
  border-color: var(--cream-dim);
}

/* The section under discussion, marked rather than spotlit. */
[data-tour-active] {
  position: relative;
  animation: tour-mark 420ms var(--ease) both;
}

[data-tour-active]::before {
  content: "";
  position: absolute;
  inset: calc(var(--sp-12) * -1) calc(var(--sp-16) * -1);
  border: 1px solid var(--redline);
  pointer-events: none;
}

@keyframes tour-mark {
  from {
    opacity: 0.4;
  }
}

@media (prefers-reduced-motion: reduce) {
  [data-tour-active] {
    animation: none;
  }
}

/* The card owns the bottom of the screen while it is open, so nothing lands
   underneath it and stays unreadable. */
.tour:not([hidden]) ~ * {
  scroll-margin-bottom: 14rem;
}

body:has(.tour:not([hidden])) {
  padding-bottom: 12rem;
}

/* ============================================ moving between pages

   Cross-document view transitions. One declaration: the browser cross-fades
   the old page into the new one instead of blanking to white between them, so
   a server-rendered portal navigates like an application without becoming one.
   Browsers without it simply navigate, which is what they did before. */
@view-transition {
  navigation: auto;
}

/* The rail is the one thing that genuinely does not move between pages, so it
   is named and held still while the canvas changes underneath it. Without this
   the spine cross-fades against itself and appears to flicker. */
.rail {
  view-transition-name: rail;
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

/* An outline offset outwards is clipped by any scroll container it sits in, and
   the rail is one. Keeping focus inside its own box means the ring is never
   half-drawn on the destination you are about to choose. */
.rail :focus-visible {
  outline-offset: -2px;
}

/* --------------------------------------------------- since you were last here

   The register of a timestamp, not an alert. The loud thing on the Overview is
   the queue; a second loud thing would make both quiet. */
.since-line {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4) var(--sp-12);
  align-items: baseline;
  margin: var(--sp-16) 0 0;
  padding-top: var(--sp-12);
  border-top: 1px solid var(--rule);
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.06em;
  font-variant-numeric: tabular-nums;
  color: var(--graphite);
}

.since-what {
  color: var(--ink);
}

.since-quiet {
  color: var(--graphite);
  font-style: italic;
}

/* ==================================================================== print

   A client will put their funnel in front of somebody else — a partner, an
   accountant, a board. Making them screenshot it is making them present our
   work badly, so the page prints as a document: ink on white, chrome gone,
   nothing cut in half across a page break.

   The bars are backgrounds, and browsers drop backgrounds when printing unless
   told otherwise, so they are told. */
@media print {
  :root {
    --paper: #fff;
    --paper-deep: #ececec;
    --paper-sunk: #e0e0e0;
    --ink: #000;
    --graphite: #444;
  }

  * {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }

  body {
    background: #fff;
  }

  /* Navigation is not information. Neither is a tour, a sign-out button, or a
     form nobody can fill in on paper. */
  .rail,
  .tour,
  .tour-welcome,
  .period,
  .signout,
  .skip-link,
  form,
  .btn {
    display: none !important;
  }

  .frame {
    display: block;
    background: none;
  }

  .canvas-inner {
    padding: 0;
    max-width: none;
  }

  .masthead {
    background: none;
    color: #000;
    border-bottom: 2px solid #000;
  }

  .masthead-sub,
  .gauge-terms,
  .wordmark-sub,
  .for-who {
    color: #333;
  }

  .wordmark,
  .wordmark-name,
  .gauge-terms b,
  .masthead-sub b {
    color: #000;
  }

  .mark-trace {
    stroke: #000;
  }

  .panel-dark {
    background: none;
    color: #000;
    border: 1px solid #999;
  }

  /* A funnel split across two sheets is a funnel nobody can read. */
  .section,
  .funnel,
  .cascade-row,
  .leaks li,
  .queue li,
  tr {
    break-inside: avoid;
  }

  .section {
    break-before: auto;
  }

  a {
    text-decoration: none;
  }

  /* On paper a link is a dead end unless it says where it went. Internal
     hrefs are noise on a client's own page, so only external ones are shown. */
  .files a[href^="http"]::after,
  .estate-card::after {
    content: " (" attr(href) ")";
    font-family: var(--font-mono);
    font-size: 0.625rem;
    color: #555;
  }

  /* Growing a bar over 620ms is meaningless on paper and risks printing it
     mid-animation. */
  .cascade-bar i {
    animation: none !important;
  }
}

/* ---------------------------------------------------- building a client's CRM */

.crm-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 14px;
}

/* One row per thing the build touched. Preview and result share the styling
   deliberately — what would happen and what did happen read the same way, so
   nobody has to learn two layouts to check the same account twice. */
.crm-steps {
  list-style: none;
  margin: 8px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.crm-steps li {
  display: flex;
  gap: 10px;
  align-items: baseline;
  padding: 7px 0;
  border-bottom: 1px solid var(--rule, #ddd5c6);
  font-size: 14px;
}

.crm-steps li:last-child { border-bottom: 0; }

.crm-steps .cm {
  flex: 0 0 14px;
  font-family: var(--font-mono);
  font-size: 12px;
  text-align: center;
}

.crm-steps li.created .cm { color: var(--redline, #e8342a); }
.crm-steps li.present { color: var(--graphite); }
.crm-steps li.failed .cm { color: var(--redline, #e8342a); font-weight: 700; }
.crm-steps li.failed { color: var(--ink, #141110); }

/* Outstanding, not broken. Full-strength text so it does not sink into the
   list of things that are already fine, but not the failure red — a step
   waiting on a person is a different fact from one that went wrong, and
   colouring them the same is how the real failures stop being read. */
.crm-steps li.manual { color: var(--ink, #141110); }
.crm-steps li.manual .cm { color: var(--ink, #141110); font-weight: 700; }
.crm-steps i { font-style: normal; color: var(--graphite); }

/* ------------------------------------------------------- ad-hoc pay links */

/* Amount, currency, what it is for, and whether it repeats — one row on a
   desktop and a stack on a phone. Engagements are not structured, so this is
   the normal way a link gets made rather than an exception to the fixed ones. */
.paylink-form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--sp-12);
  margin-top: var(--sp-16);
  padding-top: var(--sp-16);
  border-top: 1px solid var(--rule);
}

.paylink-form label {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}

.paylink-form input[type="text"],
.paylink-form select {
  /* 16px floor: anything smaller and iOS zooms the page on focus, which on a
     form inside a scrolled page loses the founder's place entirely. */
  font-size: 1rem;
  padding: var(--sp-8) var(--sp-12);
  min-height: 2.75rem;
  border: 1px solid var(--rule);
  background: var(--paper);
  color: inherit;
  font-family: var(--font-mono);
}

.paylink-amount input {
  width: 7rem;
}

.paylink-reason {
  flex: 1 1 12rem;
}

.paylink-reason input {
  width: 100%;
}

.paylink-repeat {
  flex-direction: row;
  align-items: center;
  gap: var(--sp-8);
  min-height: 2.75rem;
  /* 16px floor like the fields beside it: iOS zooms on focusing anything
     smaller, and this row sits inside the same form. */
  font-size: 1rem;
}

.paylink-repeat input {
  width: 1.1rem;
  height: 1.1rem;
  /* A checkbox cannot be typed into, so iOS never zooms for it — but a blanket
     "no field under 16px" check is only useful if nothing is exempt from it by
     argument. Sized explicitly above, so this changes nothing visually. */
  font-size: 1rem;
}

@media (max-width: 47.9375rem) {
  .paylink-form > * {
    flex: 1 1 100%;
  }

  .paylink-amount input,
  .paylink-form select {
    width: 100%;
  }
}

/* ------------------------------------------------------------------ trends */

/* The one thing the client page could not answer before: is this getting
   better. Deliberately quiet — a line, a label, a current figure. The cascade
   below it carries the detail; this carries the direction. */
.trend {
  display: flex;
  flex-direction: column;
  gap: var(--sp-16);
  margin-top: var(--sp-24);
}

.trend-row {
  display: grid;
  grid-template-columns: minmax(7rem, 10rem) minmax(0, 1fr);
  align-items: center;
  gap: var(--sp-16);
}

.trend-label {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-8);
  margin: 0;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: inherit;
}

.trend-label b {
  font-size: 0.9375rem;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* Height in rem rather than the viewBox's own units: the viewBox stretches to
   whatever width it is given, and a fixed height is what keeps a line legible
   on a phone instead of collapsing to a hairline.

   Tall enough to be honest. At 2.25rem against a ~500px box the horizontal
   stretch was about 5:1 and every slope flattened — a line that climbed 3x over
   twelve weeks looked level, which understates the client's own results. */
.trend-line {
  width: 100%;
  height: 4rem;
  display: block;
  overflow: visible;
}

.trend-line path {
  stroke: var(--ink);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.4;
}

.trend-line.is-accent path {
  stroke: var(--redline);
  stroke-width: 2;
  opacity: 1;
}

/* A caption, not a panel. The bordered note this replaced gave a footnote the
   same weight as the chart above it, and a page that explains itself at that
   volume reads as a page that expects to be confusing. */
.trend-caption {
  margin: var(--sp-16) 0 0;
  max-width: 56ch;
  font-size: 0.8125rem;
  line-height: 1.5;
  opacity: 0.65;
}

.trend-axis {
  display: flex;
  justify-content: space-between;
  margin: 0;
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.04em;
  color: inherit;
  opacity: 0.7;
}

@media (max-width: 47.9375rem) {
  /* Label above the line on a phone: side by side leaves the line about 40% of
     a 390px screen, which is too little to read a direction off. */
  .trend-row {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--sp-4);
  }

  .trend-line {
    height: 4.5rem;
  }
}

/* ------------------------------------------------------------- the answer */

/* The first thing on a client's page, and the reason they log in: what
   happened, in a sentence, before any chart. Agency reporting research is
   blunt about the alternative — clients do not complain about a confusing
   report, they go quiet and then do not renew.

   Set large in the display face because it is prose, not a readout. The
   figures inside it are the only thing lifted, so the eye lands on the numbers
   without the sentence turning into a scoreboard. */
/* Which offer the figures below cover.
   Deliberately NOT the redline: the accent on this page marks the leak, which
   is the one thing meant to worry somebody. A filter that shouts competes with
   it, so the selected state is an ink fill and everything else is a hairline. */
.offer-switch {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-8);
  margin: var(--sp-24) 0 var(--sp-24);
}

.offer-switch-label {
  font-family: var(--font-mono, monospace);
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--graphite);
  margin-right: var(--sp-4);
}

.offer-pill {
  /* 44px min, so this is usable with a thumb — most of this traffic reads the
     portal on a phone, and a filter you cannot hit is a filter nobody uses. */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 var(--sp-16);
  border: 1px solid var(--rule-mid);
  border-radius: 999px;
  font-size: 0.875rem;
  line-height: 1;
  color: var(--ink);
  text-decoration: none;
  background: transparent;
  transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
}

.offer-pill:hover {
  border-color: var(--rule-strong);
  background: rgb(20 17 16 / 0.04);
}

.offer-pill.on {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--cream);
}

@media (prefers-reduced-motion: reduce) {
  .offer-pill {
    transition: none;
  }
}

.answer {
  margin: 0 0 var(--sp-32);
  padding-bottom: var(--sp-32);
  border-bottom: 1px solid var(--rule);
}

.answer-line {
  margin: 0;
  font-family: var(--font-display, inherit);
  /* Fluid rather than stepped: this is one or two lines of prose and it should
     fill the measure at any width without a breakpoint deciding for it. */
  font-size: clamp(1.375rem, 1.05rem + 1.6vw, 2.125rem);
  line-height: 1.25;
  text-wrap: balance;
  max-width: 34ch;
}

.answer-line b {
  font-variant-numeric: tabular-nums;
}

.answer-leak {
  margin: var(--sp-16) 0 0;
  font-size: 0.9375rem;
  line-height: 1.5;
  max-width: 52ch;
}

/* The leak is the one thing on this page that should worry somebody, so it is
   the one thing that gets the accent — colour marking an anomaly rather than
   decorating the page. */
.answer-leak b {
  color: var(--redline);
  font-weight: 600;
}

.answer-leak.quiet {
  opacity: 0.7;
}

.answer-leak.quiet b {
  color: inherit;
}

/* ---------------------------------------------------------------- timeline */

/* What has happened on an account, for the founder mid-call. Rules rather than
   cards: this is a list to scan, and a dozen bordered boxes would give each
   line the weight of a section. */
.timeline {
  list-style: none;
  margin: var(--sp-16) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.timeline li {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-16);
  padding: var(--sp-8) 0;
  border-bottom: 1px solid var(--rule);
}

.timeline li:last-child {
  border-bottom: 0;
}

.timeline-what {
  font-size: 0.9375rem;
}

.timeline-when {
  flex: none;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  opacity: 0.6;
  font-variant-numeric: tabular-nums;
}

/* Viewing a client's portal as the client.

   A bar rather than a tint or a badge, because the page it sits on is designed
   to look like it belongs to whoever opened it — that is the whole point of the
   client portal. A founder who forgets which page they are on reads a client's
   figures as Revline's, and nothing else on the screen would correct them. */
.as-client {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-8) var(--sp-24);
  margin: 0 0 var(--sp-32);
  padding: var(--sp-12) var(--sp-16);
  background: var(--ink);
  color: var(--cream);
  border-left: 3px solid var(--redline);
  border-radius: 3px;
  font-size: 0.9375rem;
}

.as-client b {
  font-weight: 400;
}

.as-client a {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--cream-dim);
}

.as-client a:hover {
  color: var(--cream);
}

/* ------------------------------------------------------------- heat map */

/* Density in ink, dead clicks in redline. Both the one-red rule and the right
   call on its own terms: on a page of neutral marks the eye lands on the red
   one, and the red one is the thing somebody can go and fix this afternoon. A
   map where everything glows says only that people used the page. */

.heat-controls {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-16) var(--sp-24);
  align-items: center;
  margin-bottom: var(--sp-24);
}

.heat-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
}

.heat-tab {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--graphite);
  text-decoration: none;
  padding: var(--sp-8) var(--sp-12);
  border: 1px solid var(--rule);
  border-radius: 2px;
  /* 44px of target on a control that sits next to five siblings. */
  min-height: 2.75rem;
  display: inline-flex;
  align-items: center;
}

.heat-tab:hover {
  border-color: var(--rule-strong);
  color: var(--ink);
}

.heat-tab.is-on {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--cream);
}

.heat-tab:focus-visible {
  outline: 2px solid var(--redline);
  outline-offset: 2px;
}

.heat {
  margin: 0;
}

/* A long sales page maps to a strip several thousand pixels tall. Framed and
   scrolled, because a panel that pushes the rest of the screen away is not one
   anybody scrolls back up from. */
.heat-frame {
  width: 100%;
  max-width: 26rem;
  max-height: 32rem;
  overflow-y: auto;
  overscroll-behavior: contain;
  border: 1px solid var(--rule-mid);
  background: var(--paper-deep);
}

.heat-frame:focus-visible {
  outline: 2px solid var(--redline);
  outline-offset: 2px;
}

/* The map is an SVG because the CSP forbids inline styles: a cell positioned
   with style="left:45%" is dropped by the browser and every mark stacks at the
   origin. Geometry lives in attributes; only colour is styled here. */
.heat-page {
  display: block;
  width: 100%;
  height: auto;
}

/* Two ramps, never mixed. Ordinary traffic reads as ink and dead clicks as
   redline, and because they are separate gradients rather than one recoloured
   layer they can never blend into a third colour that means nothing. */
.heat-stop-core.is-live {
  stop-color: var(--ink);
  stop-opacity: 0.9;
}

.heat-stop-mid.is-live {
  stop-color: var(--ink);
  stop-opacity: 0.42;
}

.heat-stop-core.is-dead {
  stop-color: var(--redline);
  stop-opacity: 0.95;
}

.heat-stop-mid.is-dead {
  stop-color: var(--redline);
  stop-opacity: 0.5;
}

/* Both ramps fall to nothing at the edge. Without a transparent final stop the
   marks are discs rather than falloffs, and the contour filter turns a disc
   into a flat plate with one hard ring. */
.heat-stop-edge.is-live {
  stop-color: var(--ink);
  stop-opacity: 0;
}

.heat-stop-edge.is-dead {
  stop-color: var(--redline);
  stop-opacity: 0;
}

.heat-fold {
  stroke: var(--rule-strong);
  stroke-width: 2;
  stroke-dasharray: 8 6;
  vector-effect: non-scaling-stroke;
}

.shotbar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--sp-12);
  margin-top: var(--sp-16);
  padding-top: var(--sp-16);
  border-top: 1px solid var(--rule);
}

.shotbar-field {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  flex: 1 1 20rem;
  min-width: 0;
}

.shotbar-field input {
  font-family: var(--font-mono);
  /* 16px is the floor. Below it iOS zooms the whole viewport on focus, and the
     founder checking a funnel on a phone lands on a page that has jumped. */
  font-size: 1rem;
  padding: var(--sp-12);
  border: 1px solid var(--rule-mid);
  background: var(--paper);
  color: var(--ink);
  border-radius: 2px;
  min-height: 2.75rem;
  width: 100%;
}

.shotbar-field input:focus-visible {
  outline: 2px solid var(--redline);
  outline-offset: 1px;
}

.shotbar-when {
  flex: 1 1 100%;
  margin: 0;
}

.heat-notice {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--redline-deep);
  border-left: 2px solid var(--redline);
  padding: var(--sp-8) var(--sp-12);
  margin: 0 0 var(--sp-16);
  background: var(--paper-deep);
}

.heat-key {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--graphite);
  margin: var(--sp-8) 0 0;
}

.deadlist {
  list-style: none;
  margin: var(--sp-16) 0 0;
  padding: 0;
  border-top: 1px solid var(--rule-mid);
}

.deadlist li {
  display: flex;
  gap: var(--sp-16);
  align-items: center;
  justify-content: space-between;
  padding: var(--sp-12) 0;
  border-bottom: 1px solid var(--rule);
}

.deadlist .fname {
  font-family: var(--font-text-serif);
  font-size: 1rem;
  font-weight: 600;
  margin: 0;
}

.deadlist .fsub {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  color: var(--graphite);
  margin: var(--sp-4) 0 0;
  overflow-wrap: anywhere;
}

.deadcount {
  margin: 0;
  text-align: right;
  white-space: nowrap;
}

.deadcount b {
  font-family: var(--font-mono);
  font-size: 1.125rem;
  color: var(--redline-deep);
  display: block;
}

/* For counts that are facts rather than faults. Red on these pages means
   something to fix; a figure that is red whatever it says is decoration. */
.deadcount.is-plain b {
  color: var(--ink);
}

.deadcount span {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--graphite);
}

.scrollcurve {
  list-style: none;
  margin: var(--sp-16) 0 0;
  padding: 0;
}

.scrollcurve li {
  display: grid;
  grid-template-columns: 3rem minmax(0, 1fr) 3rem;
  gap: var(--sp-12);
  align-items: center;
  padding: var(--sp-4) 0;
}

.sc-depth,
.sc-reached {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  color: var(--graphite);
}

.sc-reached {
  text-align: right;
  color: var(--ink);
}

.sc-track {
  display: block;
  height: 0.5rem;
  background: var(--paper-sunk);
}

.sc-track i {
  display: block;
  height: 100%;
  background: var(--ink);
}

/* ---------------------------------------------------------------- the ledger

   The money, before any list of it.

   This page opened with counts — four pay links, two paid, one awaiting —
   which answers none of the questions somebody opens a money page to ask.
   Four figures, in a row, in the reading face, so the answer is available
   before any scrolling happens.

   Only "failed" is allowed the redline. It is the one that costs a
   relationship rather than a number: the client believes they have bought
   something, nobody is chasing them, and neither side knows. */
.ledger {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 1px;
  margin: 0 0 14px;
  background: var(--rule);
  border: 1px solid var(--rule);
  border-radius: 3px;
  overflow: hidden;
}

.ledger > div {
  background: var(--paper);
  padding: 14px 16px;
}

.ledger dt {
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--graphite);
}

.ledger dd {
  margin: 5px 0 0;
  font-size: 26px;
  line-height: 1.1;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.ledger .lsub {
  margin: 4px 0 0;
  font-size: 12px;
  color: var(--graphite);
}

.ledger > div.leak dd {
  color: var(--redline-deep);
}

.ledger > div.leak {
  box-shadow: inset 2px 0 0 var(--redline);
}

@media (max-width: 640px) {
  .ledger dd {
    font-size: 22px;
  }
}

/* The two sentences above the attribution table.

   Deliberately not `.answer-leak`, which paints every bold word in the accent.
   Here one line is good news and one is a problem, and colouring them the same
   would tell somebody their best campaign is something to worry about. The
   accent stays on the one thing that should worry them, which is what makes an
   accent mean anything on a page. */
.verdict-line {
  margin: var(--sp-16) 0 0;
  font-size: 1rem;
  line-height: 1.55;
  max-width: 56ch;
}

.verdict-line b {
  font-weight: 600;
}

.verdict-line.warn b {
  color: var(--redline);
}

/* What a client's own staff said.

   Set as quotes rather than as rows in a table: these are somebody's words,
   often written carefully and at some personal risk, and a data grid reads them
   as records. The measure is held short so they stay readable. */
.voices { list-style: none; margin: 0; padding: 0; }

.voices li {
  padding: var(--sp-16) 0;
  border-bottom: 1px solid var(--rule, rgb(20 17 16 / 0.12));
}

.voices li:last-child { border-bottom: 0; }

.vbody {
  margin: 0;
  font-size: 1rem;
  line-height: 1.6;
  max-width: 60ch;
  white-space: pre-line;
}

.vfix {
  margin: var(--sp-8, 0.5rem) 0 0;
  font-size: 0.9375rem;
  line-height: 1.55;
  max-width: 60ch;
  color: var(--graphite, #5e574f);
}

.vmeta {
  margin: var(--sp-8, 0.5rem) 0 0;
  font-family: var(--font-mono, monospace);
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--graphite);
}

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

.themes li {
  display: flex;
  align-items: baseline;
  gap: var(--sp-16);
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--rule, rgb(20 17 16 / 0.08));
}

.themes .tname { flex: 1 1 auto; font-size: 0.9375rem; }
.themes .tcount { font-variant-numeric: tabular-nums; font-size: 0.9375rem; }

.themes .tdaily {
  flex: 0 0 11rem;
  text-align: right;
  font-family: var(--font-mono, monospace);
  font-size: 0.6875rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--redline);
}

/* A note that is a warning.

   `.warn` used to style one thing — a `<b>` inside it — and three of the four
   places that reach for it write `<strong>`, so on those the class did nothing
   at all. A modifier that silently does nothing is worse than no modifier: it
   reads as handled in the markup and is invisible on the page.
   
   The rule is the redline the rest of the portal already uses for a leak, and
   the emphasis inside it is bolder rather than red. Red text on paper shouts;
   a red rule down the side is read before the words are and stops shouting the
   moment you have read it. */
.note.warn {
  border-left-color: var(--redline);
}

.note.warn b,
.note.warn strong {
  color: var(--ink);
  font-weight: 600;
}

/* --------------------------------------------------- what a client has been given

   The four legs of setting a client up, drawn as the same two-column row grid
   the rest of the record page uses. The state label is not decoration: the
   sunk background on a missing leg is the only other signal, and background
   colour alone cannot carry meaning for somebody who cannot see it. So each
   row says "In place" or "Not there" in words, and the colour agrees with the
   words rather than replacing them. */

.link-row .stat-label {
  margin-bottom: var(--sp-4);
}

.link-row .stat-label[data-mark="done"] {
  color: var(--ink);
}

.link-row .stat-label[data-mark="missing"] {
  color: var(--redline-deep);
}

/* ==================================================================== playbook

   Our own strategy, as a screen rather than a document.

   The unit is a play, not a paragraph: a claim, a grade saying how good the
   evidence for it is, what a client's team does, and — where words exist — the
   exact words, copyable. A strategy nobody can copy out of the page gets
   retyped from memory, and what gets retyped is whatever somebody
   half-remembers.

   The grade badge is doing real work. Some of this is a randomised trial and
   some is four operators agreeing on a podcast; rendering both at the same
   weight would be lying by layout. Trial is ink, Field is graphite, Ours is
   outlined — present but visibly unfilled, because that is what it is. */

.principle {
  margin: 0 0 var(--sp-32);
  padding: var(--sp-24);
  background: var(--ink);
  color: var(--cream);
  border-left: 3px solid var(--redline);
}

.principle p {
  margin: 0;
  max-width: 46ch;
  font-family: var(--font-display);
  font-size: clamp(1.125rem, 1rem + 0.6vw, 1.4375rem);
  line-height: 1.35;
  text-wrap: balance;
}

/* ------------------------------------------------------------- the grade key */

.playbook-key {
  margin: 0 0 var(--sp-32);
}

.playbook-key h2 {
  margin: 0 0 var(--sp-12);
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--graphite);
  font-weight: 500;
}

.grade-key {
  display: grid;
  gap: var(--sp-8);
  margin: 0;
  padding: 0;
  list-style: none;
}

.grade-key li {
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr);
  align-items: baseline;
  gap: var(--sp-12);
  padding: var(--sp-8) 0;
  border-top: 1px solid var(--rule);
  font-size: 0.875rem;
  color: var(--graphite);
}

.grade-key b {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

.play-grade {
  display: inline-block;
  padding: 0.1rem var(--sp-8);
  font-family: var(--font-mono);
  font-size: 0.5625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
  border: 1px solid transparent;
}

.play-grade[data-grade="proven"] {
  background: var(--ink);
  color: var(--cream);
}

.play-grade[data-grade="field"] {
  background: var(--paper-sunk);
  color: var(--ink);
  border-color: var(--rule-mid);
}

/* Outlined, not filled. It should look like what it is: a claim with nothing
   behind it yet. */
.play-grade[data-grade="ours"] {
  background: transparent;
  color: var(--graphite);
  border-color: var(--rule-strong);
  border-style: dashed;
}

/* ------------------------------------------------------------------- stages */

.stage-nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4) var(--sp-16);
  margin: 0 0 var(--sp-48);
  padding: var(--sp-12) 0;
  border-block: 1px solid var(--rule);
}

.stage-nav a {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--graphite);
  text-decoration: none;
  border-bottom: 1px solid transparent;
}

.stage-nav a:hover,
.stage-nav a:focus-visible {
  color: var(--ink);
  border-bottom-color: var(--redline);
}

.stage {
  margin: 0 0 var(--sp-64);
  scroll-margin-top: var(--sp-24);
}

.stage-head {
  margin: 0 0 var(--sp-24);
  padding-bottom: var(--sp-12);
  border-bottom: 2px solid var(--ink);
}

.stage-head h2 {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.375rem, 1.2rem + 0.8vw, 1.75rem);
  line-height: 1.15;
}

.stage-head p {
  margin: var(--sp-8) 0 0;
  max-width: 62ch;
  color: var(--graphite);
  font-size: 0.9375rem;
}

/* -------------------------------------------------------------------- plays */

.play {
  margin: 0 0 var(--sp-32);
  padding: var(--sp-24);
  background: var(--paper-deep);
  scroll-margin-top: var(--sp-24);
}

.play-head {
  display: flex;
  align-items: baseline;
  gap: var(--sp-12);
  margin: 0 0 var(--sp-8);
}

.play-head h3 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1.1875rem;
  line-height: 1.25;
}

.play-claim {
  margin: 0 0 var(--sp-24);
  max-width: 60ch;
  font-family: var(--font-text-serif);
  font-size: 1.0625rem;
  line-height: 1.5;
}

/* Two columns where there is room: what they do beside where it came from.
   Side by side because the second is how you judge the first, and a reader who
   has to scroll between them judges neither. */
.play-detail {
  display: grid;
  gap: var(--sp-24);
}

@media (min-width: 48rem) {
  .play-detail {
    grid-template-columns: 1fr 1fr;
  }
}

.play-detail h4 {
  margin: 0 0 var(--sp-8);
  font-family: var(--font-mono);
  font-size: 0.5625rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--graphite);
  font-weight: 500;
}

.play-how ul {
  margin: 0;
  padding-left: var(--sp-16);
  display: grid;
  gap: var(--sp-8);
}

.play-how li {
  font-size: 0.9375rem;
  line-height: 1.45;
}

.play-evidence p {
  margin: 0;
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--graphite);
}

/* ----------------------------------------------------------------- messages */

.play-messages {
  display: grid;
  gap: var(--sp-16);
  margin-top: var(--sp-24);
}

.msg {
  margin: 0;
  background: var(--ink);
  color: var(--cream);
}

.msg-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-8) var(--sp-12);
  padding: var(--sp-12) var(--sp-16);
  border-bottom: 1px solid var(--rule-dark);
}

.msg-channel {
  padding: 0.1rem var(--sp-8);
  background: var(--redline);
  color: var(--cream);
  font-family: var(--font-mono);
  font-size: 0.5625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* Spoken is not a message you send, so it does not wear the send colour. */
.msg-channel[data-channel="call"] {
  background: transparent;
  color: var(--cream-dim);
  border: 1px solid var(--rule-dark-mid);
}

.msg-label {
  font-size: 0.875rem;
  font-weight: 500;
}

.msg-when {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  color: var(--cream-dim);
}

.msg-copy {
  margin-left: auto;
  padding: var(--sp-4) var(--sp-12);
  min-height: 2rem;
  background: transparent;
  color: var(--cream);
  border: 1px solid var(--rule-dark-mid);
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background var(--dur) var(--ease);
}

.msg-copy:hover,
.msg-copy:focus-visible {
  background: var(--ink-lift-2);
}

.msg-copy[data-copied="true"] {
  background: var(--redline);
  border-color: var(--redline);
}

/* The message as it will actually arrive: same line breaks, same length.
   Wrapped rather than scrolled — a text you have to scroll sideways to read is
   a text you cannot judge the length of, and length is most of what makes an
   SMS land or not. */
.play-body {
  margin: 0;
  padding: var(--sp-16);
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  line-height: 1.6;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.merge {
  padding: 0 0.15em;
  background: rgb(232 52 42 / 0.18);
  color: var(--cream);
  border-radius: 2px;
}

.msg-why {
  margin: 0;
  padding: var(--sp-12) var(--sp-16);
  border-top: 1px solid var(--rule-dark);
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--cream-dim);
}

.msg-why b {
  color: var(--cream);
  font-weight: 500;
}

/* ------------------------------------------------------------------ refused */

.refused ul {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--sp-16);
}

.refused li {
  display: grid;
  gap: var(--sp-4);
  padding-left: var(--sp-16);
  border-left: 2px solid var(--rule-strong);
}

.refused b {
  font-size: 0.9375rem;
}

.refused span {
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--graphite);
}

/* ============================================================== build sheet
 *
 * /playbook/build. The playbook argues; this is worked from with a CRM open in
 * the next tab, so the shape is a sequence rather than an essay: numbered
 * workflows, and inside each one a timeline of steps with the wait stated on
 * the left rail.
 *
 * The preconditions block leads and is deliberately the loudest thing on the
 * page. Every one of those five settings fails silently — nothing errors and
 * the CRM keeps reporting success — so the page has to do the shouting the
 * software does not.
 */

.prechecks {
  margin: 0 0 var(--sp-48);
}

.pre-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--sp-16);
}

.pre {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0 var(--sp-16);
  padding: var(--sp-16) var(--sp-24);
  background: var(--paper-deep);
  border-left: 3px solid var(--redline);
  scroll-margin-top: var(--sp-24);
}

.pre-n {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  color: var(--redline);
  font-variant-numeric: tabular-nums;
  padding-top: 0.2rem;
}

.pre-body h3 {
  margin: 0 0 var(--sp-8);
  font-family: var(--font-display);
  font-size: 1.0625rem;
  line-height: 1.25;
}

.pre-where {
  margin: 0 0 var(--sp-12);
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.04em;
  color: var(--graphite);
}

.pre-wrong,
.pre-why {
  margin: 0 0 var(--sp-8);
  max-width: 68ch;
  font-size: 0.875rem;
  line-height: 1.5;
}

.pre-why {
  margin-bottom: 0;
  color: var(--graphite);
}

/* ------------------------------------------------------------------ flows */

.flow {
  margin: 0 0 var(--sp-32);
  padding: var(--sp-24);
  background: var(--paper-deep);
  scroll-margin-top: var(--sp-24);
}

.flow-head {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: 0 var(--sp-16);
}

.flow-n {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--cream);
  background: var(--ink);
  width: 1.75rem;
  height: 1.75rem;
  display: grid;
  place-items: center;
  font-variant-numeric: tabular-nums;
}

.flow-head h3 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1.1875rem;
  line-height: 1.25;
}

.flow-trigger {
  margin: var(--sp-8) 0 0;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.03em;
  color: var(--graphite);
}

.flow-trigger span,
.fstep-subject span {
  color: var(--redline);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: 0.5625rem;
  margin-right: var(--sp-8);
}

.flow-purpose {
  margin: var(--sp-16) 0 var(--sp-24);
  max-width: 64ch;
  font-family: var(--font-text-serif);
  font-size: 1rem;
  line-height: 1.5;
}

/* The steps read as one run down the page, so the rule is continuous and the
   wait sits against it — the wait is the thing being scanned for when
   somebody is checking their build against this. */
.fsteps {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--sp-24);
  border-left: 1px solid var(--rule-mid);
  padding-left: var(--sp-24);
}

.fstep {
  display: grid;
  gap: var(--sp-8);
  scroll-margin-top: var(--sp-24);
}

.fstep-when {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--redline);
  position: relative;
}

.fstep-when::before {
  content: "";
  position: absolute;
  left: calc(var(--sp-24) * -1 - 0.5px - 3px);
  top: 0.3rem;
  width: 7px;
  height: 7px;
  background: var(--redline);
}

.fstep-action {
  margin: 0;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-8) var(--sp-12);
  font-size: 0.9375rem;
}

.fstep-subject {
  margin: 0;
  font-size: 0.875rem;
  font-weight: 500;
}

/* A task and a CRM note are not messages sent to a prospect, so they do not
   wear the send colour — same reasoning as a spoken step in the playbook. */
.msg-channel[data-channel="task"],
.msg-channel[data-channel="internal"] {
  background: transparent;
  color: var(--graphite);
  border: 1px solid var(--rule-mid);
}

.fstep .play-body {
  margin: 0;
}

.fstep .msg-why {
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  max-width: 66ch;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--graphite);
}

.flow-stop {
  margin: var(--sp-24) 0 0;
  padding: var(--sp-12) var(--sp-16);
  background: var(--paper-sunk);
  font-size: 0.875rem;
  line-height: 1.5;
}

.flow-plays {
  margin: var(--sp-12) 0 0;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  color: var(--graphite);
}

/* ------------------------------------------------------- naming convention */

.naming {
  margin: 0 0 var(--sp-48);
}

.folders {
  margin: 0 0 var(--sp-24);
  padding: 0;
  list-style: none;
  display: grid;
  gap: 1px;
  background: var(--rule-mid);
  border: 1px solid var(--rule-mid);
}

.folders li {
  display: grid;
  grid-template-columns: minmax(0, 12rem) minmax(0, 1fr);
  gap: 0 var(--sp-16);
  padding: var(--sp-12) var(--sp-16);
  background: var(--paper);
  font-size: 0.875rem;
  line-height: 1.5;
}

@media (max-width: 40rem) {
  .folders li {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--sp-4);
  }
}

.folders b {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  font-weight: 400;
}

.folders span {
  color: var(--graphite);
}

/* The pattern itself, given the weight of a specimen rather than a code
   sample — it is the one line on the page somebody has to hold in their head
   while typing into another window. */
.naming-pattern {
  margin: 0 0 var(--sp-24);
  padding: var(--sp-16);
  background: var(--ink);
  color: var(--cream);
  display: grid;
  gap: var(--sp-8);
  overflow-x: auto;
}

.naming-pattern span {
  font-family: var(--font-mono);
  font-size: 0.5625rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--cream-dim);
}

.naming-pattern code {
  font-family: var(--font-mono);
  font-size: 1rem;
  white-space: nowrap;
}

.naming-rules {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--sp-12);
}

.naming-rules li {
  display: grid;
  gap: var(--sp-4);
  padding-left: var(--sp-16);
  border-left: 2px solid var(--rule-mid);
}

.naming-rules b {
  font-size: 0.9375rem;
}

.naming-rules span {
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--graphite);
  max-width: 70ch;
}

/* ------------------------------------------------------------ setup run */

.setup {
  margin: 0 0 var(--sp-48);
}

.ssteps {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 1px;
  background: var(--rule-mid);
  border: 1px solid var(--rule-mid);
}

.sstep {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0 var(--sp-16);
  padding: var(--sp-16);
  background: var(--paper);
}

/* A hand step is the one that gets skipped, so it is the one that is marked.
   The automated ones recede — they take care of themselves by definition. */
.sstep[data-doer="hand"] {
  background: var(--paper-deep);
}

.sstep-n {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  color: var(--graphite);
  font-variant-numeric: tabular-nums;
  padding-top: 0.25rem;
}

.sstep[data-doer="hand"] .sstep-n {
  color: var(--redline);
}

.sstep-what {
  margin: 0 0 var(--sp-8);
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--sp-8) var(--sp-12);
  font-size: 0.9375rem;
  line-height: 1.4;
}

.sstep-doer {
  font-family: var(--font-mono);
  font-size: 0.5625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 0.1rem var(--sp-8);
  border: 1px solid var(--rule-mid);
  color: var(--graphite);
  white-space: nowrap;
}

.sstep[data-doer="hand"] .sstep-doer {
  background: var(--redline);
  border-color: var(--redline);
  color: var(--cream);
}

.sstep-how,
.sstep-proof {
  margin: 0;
  max-width: 72ch;
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--graphite);
}

.sstep-proof {
  margin-top: var(--sp-8);
  color: var(--ink);
}

.sstep-proof span {
  font-family: var(--font-mono);
  font-size: 0.5625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--redline);
  margin-right: var(--sp-8);
}

/* ------------------------------------------------ add to home screen prompt */

/*
 * Bottom-anchored, above the thumb, and never covering the page it interrupts.
 * A prompt that hides content is a prompt people dismiss without reading, and
 * then the next one gets dismissed the same way.
 *
 * `env(safe-area-inset-bottom)` because this only ever appears on a phone, and
 * on an iPhone the home indicator sits exactly where a Not-now button wants to
 * be.
 */
.installer {
  position: fixed;
  left: 0.75rem;
  right: 0.75rem;
  bottom: calc(0.75rem + env(safe-area-inset-bottom, 0px));
  z-index: 60;
  padding: 0.9rem 1rem 0.95rem;
  background: var(--ink);
  color: var(--cream);
  border: 1px solid var(--rule-dark-mid);
  border-radius: 0.35rem;
  box-shadow: 0 1px 2px rgb(20 17 16 / 0.2), 0 12px 32px rgb(20 17 16 / 0.28);
  animation: ins-rise 0.32s cubic-bezier(0.22, 0.61, 0.36, 1) both;
}

@keyframes ins-rise {
  from { transform: translateY(0.65rem); opacity: 0; }
  to   { transform: none; opacity: 1; }
}

/* It arrives unbidden. Somebody who has asked for less motion is exactly the
   person who should not have an uninvited panel slide up at them. */
@media (prefers-reduced-motion: reduce) {
  .installer { animation: none; }
}

.installer .ins-b {
  margin: 0 0 0.8rem;
  font-size: 0.9375rem;
  line-height: 1.5;
  color: var(--cream-dim);
}

.installer .ins-b b { color: var(--cream); font-weight: 550; }

.installer .ins-a { display: flex; gap: 0.5rem; }

/* 44px floor on both. These are pressed one-handed, often walking. */
.installer button {
  min-height: 2.75rem;
  padding: 0 1rem;
  font: inherit;
  font-size: 0.875rem;
  border-radius: 0.25rem;
  cursor: pointer;
}

.installer .ins-go {
  flex: 1 1 auto;
  background: var(--redline);
  color: #fff;
  border: 1px solid var(--redline);
  font-weight: 550;
}

.installer .ins-go:active { background: var(--redline-deep); }

.installer .ins-no {
  flex: 0 0 auto;
  background: transparent;
  color: var(--cream-dim);
  border: 1px solid var(--rule-dark-mid);
}

.installer button:focus-visible {
  outline: 2px solid var(--cream);
  outline-offset: 2px;
}

/* Installed already: belt and braces behind the JS check, so a cached copy of
   this stylesheet can never paint a prompt inside the app it is advertising. */
@media (display-mode: standalone) {
  .installer { display: none; }
}

/* ------------------------------------------------------------- documents */

/*
 * The company's own documents. Read far more often than written, so the
 * reading measure decides the layout and the editor borrows it.
 */
.doc-shell { padding-block: 1.75rem 4rem; }

.doc-search { display: flex; gap: 0.5rem; align-items: center; margin-bottom: 2rem; }
.doc-search input {
  flex: 1 1 auto; min-width: 0; min-height: 2.75rem; padding: 0 0.85rem;
  font: inherit; font-size: 1rem; background: var(--cream); color: var(--ink);
  border: 1px solid var(--rule-mid); border-radius: 0.25rem;
}
.doc-search button {
  min-height: 2.75rem; padding: 0 1.1rem; font: inherit; font-size: 0.875rem;
  background: var(--ink); color: var(--cream); border: 1px solid var(--ink);
  border-radius: 0.25rem; cursor: pointer;
}
.doc-clear { font-size: 0.875rem; color: var(--graphite); }

.doc-group { margin-bottom: 2.25rem; }
.doc-group > h2 {
  margin: 0 0 0.9rem; font-family: var(--mono, ui-monospace, monospace);
  font-size: 0.6875rem; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--graphite);
}
.doc-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 1.1rem; }
.doc-list > li { display: grid; gap: 0.2rem; }
.doc-list a { font-size: 1.0625rem; font-weight: 550; }
.doc-list p { margin: 0; font-size: 0.9375rem; line-height: 1.5; color: var(--graphite); max-width: 68ch; }
.doc-when {
  font-family: var(--mono, ui-monospace, monospace); font-size: 0.6875rem;
  letter-spacing: 0.06em; color: var(--cream-dim);
}

/* Reading. 68ch because these are dense reference documents rather than
   prose — a shorter measure turns a three-column table into a scrollbar. */
.doc-body { max-width: 68ch; }
.doc-body h1, .doc-body h2, .doc-body h3 { margin: 2rem 0 0.75rem; line-height: 1.2; }
.doc-body h1:first-child { margin-top: 0; }
.doc-body p, .doc-body li { line-height: 1.65; }
.doc-body ul, .doc-body ol { padding-left: 1.25rem; }
.doc-body code {
  font-family: var(--mono, ui-monospace, monospace); font-size: 0.875em;
  padding: 0.1em 0.3em; background: var(--paper-deep); border-radius: 0.2rem;
}
.doc-body pre {
  padding: 0.9rem 1rem; overflow-x: auto; background: var(--ink); color: var(--cream);
  border-radius: 0.25rem;
}
.doc-body pre code { padding: 0; background: none; color: inherit; }
.doc-body blockquote {
  margin: 1.25rem 0; padding: 0.9rem 1.1rem; background: var(--cream);
  border-left: 2px solid var(--rule-strong); border-radius: 0 0.25rem 0.25rem 0;
}
/* A callout is always the thing the writer most wanted read. */
.doc-body blockquote.c-warning { border-left-color: var(--redline); }
.doc-body blockquote p:last-child { margin-bottom: 0; }

/* Wide content scrolls inside its own box. A table that pushes the page
   sideways on a phone is worse than no table. */
.doc-table { overflow-x: auto; margin: 1.25rem 0; }
.doc-table table { border-collapse: collapse; min-width: 100%; font-size: 0.9375rem; }
.doc-table th, .doc-table td {
  padding: 0.5rem 0.75rem; text-align: left; vertical-align: top;
  border-bottom: 1px solid var(--rule);
}
.doc-table th {
  font-family: var(--mono, ui-monospace, monospace); font-size: 0.6875rem;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--graphite); white-space: nowrap;
}

.doc-actions { margin: 0 0 1.5rem; }
.doc-edit {
  display: inline-block; min-height: 2.5rem; padding: 0.6rem 1.1rem;
  font-size: 0.875rem; background: var(--cream); color: var(--ink);
  border: 1px solid var(--rule-mid); border-radius: 0.25rem;
}

.doc-history { margin-top: 3rem; padding-top: 1.5rem; border-top: 1px solid var(--rule); max-width: 68ch; }
.doc-history h2 {
  margin: 0 0 1rem; font-family: var(--mono, ui-monospace, monospace);
  font-size: 0.6875rem; letter-spacing: 0.16em; text-transform: uppercase; color: var(--graphite);
}
.doc-history ul { list-style: none; margin: 0 0 1rem; padding: 0; display: grid; gap: 0.75rem; }
.doc-history li { display: flex; flex-wrap: wrap; gap: 0.6rem; align-items: center; }
.doc-history button {
  min-height: 2.25rem; padding: 0 0.75rem; font: inherit; font-size: 0.8125rem;
  background: transparent; color: var(--graphite);
  border: 1px solid var(--rule-mid); border-radius: 0.25rem; cursor: pointer;
}
.doc-note { font-size: 0.875rem; color: var(--graphite); }
.doc-empty { color: var(--graphite); max-width: 62ch; }
.doc-problem {
  padding: 0.8rem 1rem; background: var(--cream); border-left: 2px solid var(--redline);
  border-radius: 0 0.25rem 0.25rem 0;
}

.doc-form { display: grid; gap: 1.1rem; max-width: 68ch; }
/* Monospace, because most of what goes in here is tables and code fences and
   both are unreadable in a proportional face while you are aligning them. */
.doc-form textarea {
  width: 100%; font-family: var(--mono, ui-monospace, monospace); font-size: 0.9375rem;
  line-height: 1.6; padding: 0.85rem; background: var(--cream); color: var(--ink);
  border: 1px solid var(--rule-mid); border-radius: 0.25rem; resize: vertical;
}
.doc-submit { display: flex; gap: 0.75rem; align-items: center; }
.doc-submit button {
  min-height: 2.75rem; padding: 0 1.4rem; font: inherit; font-size: 0.9375rem; font-weight: 550;
  background: var(--ink); color: var(--cream); border: 1px solid var(--ink);
  border-radius: 0.25rem; cursor: pointer;
}

/* Cold versus ads. A comparison table, so the channels are columns and the
   metrics are rows — see the note above channelSection. It scrolls sideways
   inside its own container like every other table here, because a third
   channel on a phone must not push the page. */
.chan { width: 100%; border-collapse: collapse; margin-top: var(--sp-16); }
.chan th, .chan td { padding: var(--sp-12) var(--sp-8); border-bottom: 1px solid var(--rule); text-align: right; }
.chan thead th { text-align: right; font-size: 13px; letter-spacing: .04em; text-transform: uppercase; }
.chan th[scope="row"] { text-align: left; font-weight: 650; white-space: nowrap; }
.chan-note { display: block; font-weight: 400; font-size: 13px; color: var(--graphite); }

/* ── The client page's section nav (#186) ─────────────────────────────────
   The client's portal is one page on purpose; this is the map across it.
   Anchors, not routes, and pills in the offer-switch idiom so the page has
   one vocabulary for "small tappable chips". Quieter than the offer pills —
   navigation is furniture, the offer filter is a decision. */
.section-nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-8);
  margin: 0 0 var(--sp-24);
}

.section-nav a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 var(--sp-16);
  border: 1px solid var(--rule-mid);
  border-radius: 999px;
  font-size: 0.8125rem;
  line-height: 1;
  color: var(--graphite);
  text-decoration: none;
  transition: color 120ms ease, border-color 120ms ease;
}

.section-nav a:hover {
  color: var(--ink);
  border-color: var(--rule-strong);
}

/* ── Guides on the client page (#186) ───────────────────────────────────── */
.guide-group {
  margin-top: var(--sp-24);
}

.guide-group-title {
  font-family: var(--font-mono, monospace);
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--graphite);
  margin: 0 0 var(--sp-8);
}

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

.guide-links li {
  padding: var(--sp-12) 0;
  border-bottom: 1px solid var(--rule-mid);
}

.guide-links li:last-child {
  border-bottom: 0;
}

.guide-links a {
  color: var(--ink);
  font-weight: 600;
  text-decoration: none;
  border-bottom: 1px solid var(--rule-strong);
}

.guide-links a:hover {
  border-bottom-color: var(--ink);
}

.guide-links .meta {
  display: block;
  margin-top: 2px;
  font-size: 0.8125rem;
  color: var(--graphite);
}
