/* 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;
  /* The red that still clears AA at label sizes on the SUNK grounds.
     --redline-deep is 4.52:1 on --paper and only 4.13:1 on --paper-deep, which
     is where the small uppercase labels on the reading page sit. Measured, not
     guessed: the first attempt at a contrast fix in app.css made one worse. */
  --redline-press: #b3241c;
  /* And the other direction: the red lifted for small text on the rail's ink,
     where --redline is 4.44:1. Same two values as app.css — the rep portal and
     this one are one estate and a red that is legible on one should be legible
     on the other. */
  --redline-lift: #f26a61;

  /* 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. (`.keytag` made the same point one register up; it was removed
   on 28.08.2026 having never been used by any page.) */
.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-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;
  /* The SCROLL moved inside, to `.rail-scroll`. It was here, and it meant a
     door pinned to the bottom of the rail was pinned to the bottom of a list
     that is taller than the screen. */
  overflow: hidden;
}

/* The navigation and the account block, which scroll. Everything outside this
   box stays where it is put. */
.rail-scroll {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
}

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

/* The floor group. `.rail-seat` is pushed down by the auto margin and the
   assistant sits under it, so the bottom-left corner of the portal is the chat
   and the navigation stays at the top where it is read.

   Named rather than positional. This was `.rail-block:last-child` and six
   rules reached the account block that way; the moment anything was added
   below it, every one of them would have started styling the new thing
   instead, silently and only on a phone. */
.rail-seat {
  margin-top: auto;
}

.rail-block:last-child {
  border-bottom: 0;
}

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

/* An action needed often IS a link — an unsent invite carries the URL to send
   by hand, and a tracking key is 22 characters with nothing to break on. At
   327px those have no break opportunity and expand the document rather than
   their own box, which scrolled the founders' home page sideways on a phone
   while every element's right edge sat inside the viewport. */
.queue .what {
  margin: 0;
  font-size: 0.9375rem;
  overflow-wrap: anywhere;
}

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

/* ── THE PREVIEW ──────────────────────────────────────────────────────────
   Austin, 28.08.2026: "the Revline website should have a preview." A name and
   a leak percentage do not tell you which page you are looking at, and this
   list ranks five surfaces that are all called something reasonable.

   Small, cropped from the top, and absent rather than placeheld when no
   photograph has been taken. A grey box shaped like a screenshot is a picture
   of a page that does not look like that.
   ───────────────────────────────────────────────────────────────────────── */

.leak-head { display: flex; align-items: flex-start; gap: var(--sp-12); min-width: 0; }
.leak-head > div { min-width: 0; }

.leak-shot {
  flex: 0 0 auto;
  display: block;
  width: 4.5rem;
  height: 3.25rem;
  overflow: hidden;
  border: 1px solid var(--rule-mid);
  background: var(--paper-deep);
  line-height: 0;
}
.leak-shot img {
  width: 100%;
  height: auto;
  display: block;
  /* From the top: the fold is the part of a page worth recognising, and
     letting it centre would show whatever happens to be halfway down. */
  object-fit: cover;
  object-position: top 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-seat {
    margin-top: 0;
  }

  /* WRAPS, rather than scrolling sideways.
     It scrolled — overflow-x: auto, scrollbar hidden — which meant the tail of
     the rail was reachable only by somebody who guessed there was more and
     swiped for it. That was survivable while the tail was "Documents". It is
     not now: the rail is seven, and the last of the seven is Settings, which
     is the way to Onboarding, Journeys, Accounts, Setup guides, Playbook and
     Documents. A hidden tail holding six pages is six pages nobody finds.
     Seven items are 570px at 390px wide, so two rows fit with room. */
  .nav {
    flex-direction: row;
    flex-wrap: wrap;
    gap: var(--sp-4);
  }

  .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-seat {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-8) var(--sp-16);
  }

  .rail-seat .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-seat .seat {
    font-size: 0;
  }

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

  .rail-seat .seat-links,
  .rail-seat 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: block;
  }

  /* The blocks are children of this now, so the pairing grid and the
     `nth-child` rule below it both still count the same four things. */
  .rail-scroll {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: center;
    overflow-y: visible;
  }

  /* 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);
}

/* The Contrast traffic light, worn as a chip. Semantic colour, not the accent:
   green is EARNED by a check that ran (see the interface framework), amber is
   a human's attention, and the red chip is the roster's one red — the row the
   whole page exists to surface. Grey stays the default chip. */
.chip[data-state="well"] {
  border-color: #2fa84f;
  color: #1e7a38;
}

.chip[data-state="warn"] {
  border-color: #e8a33d;
  color: #9a6414;
}

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

[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);
}

[data-surface="dark"] .chip[data-state="well"] {
  border-color: #2fa84f;
  color: #7fd39a;
}

[data-surface="dark"] .chip[data-state="warn"] {
  border-color: #e8a33d;
  color: #f0c27f;
}

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

/* ------------------------------------------------------------------ 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. */
/* The fold under the funnel. `.fold` hides the disclosure marker and expects a
   section head inside the summary to carry the affordance; this one is a quiet
   note, so it draws its own chevron or it reads as a dead line of text. */
.pod-more { display: inline-flex; align-items: center; gap: var(--sp-8); min-height: 44px; }
.pod-more::before {
  content: "";
  flex: none;
  width: 0.4rem;
  height: 0.4rem;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(-45deg);
  transition: transform 160ms var(--ease);
}
.fold[open] .pod-more::before { transform: rotate(45deg); }
@media (prefers-reduced-motion: reduce) { .pod-more::before { transition: none; } }

.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. */

/* ------------------------------------------------------------------- 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);
}

/* A row header is a label for its row, not a heading for a column, so it takes
   the body cell's alignment and padding rather than the browser default of
   centred and bold. Six portal pages use `scope="row"` and every one of them
   was drawing it centred; the marketing stylesheet had solved this in
   `.mkt-table tbody th` and portal.css had never been given the same rule. */
tbody th {
  text-align: left;
  vertical-align: top;
  font-weight: 600;
  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) {
  /* Visually hidden, and it has to be hidden from LAYOUT as well as from
     sight. `overflow` and `clip-path` do not apply to a `table-header-group`,
     so the header row inside went on being laid out at its full 436px and
     pushed the document's scrollable width to 488 on a 375 screen — the
     founders' home page scrolled sideways on a phone and nothing on it looked
     wrong. `display: block` is what makes the clipping real; the header stays
     readable to a screen reader either way. */
  /* Visually hidden, and it has to be hidden from LAYOUT as well as from
     sight. `overflow` and `clip-path` do not apply to a `table-header-group`,
     so the header row went on being laid out at its full 436px and pushed the
     document's scrollable width to 488 on a 375 screen — the founders' home
     page scrolled sideways on a phone and nothing on it looked wrong.
     `display: block` does not fix it either, because the row inside still
     builds an anonymous table that sizes to its content. Off-canvas does:
     overflow to the LEFT is not scrollable in a left-to-right document, so
     the header can be as wide as it likes and reach a screen reader intact. */
  .t-stack thead {
    position: absolute;
    left: -10000px;
    width: 1px;
    height: 1px;
    overflow: hidden;
  }

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

/* ---- The team picker on a rep row ---- */
/* A control among chips, so it wears chip proportions: the global select is a
   form field with form-field padding, and a 3rem-tall full-width box inside a
   .meta column reads as a broken layout rather than a picker. Font size stays
   at 1rem — the iOS zoom floor — only the box around it shrinks. */
.team-form {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-8);
}

.team-form select {
  width: auto;
  min-height: 0;
  padding: var(--sp-4) var(--sp-32) var(--sp-4) var(--sp-12);
  background-position: right var(--sp-12) center;
}

.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);
  /* These cells hold raw URLs, and one of them is a Google Calendar share link
     with a 120-character base64 id in it — a single unbreakable word. Without
     this the grid column cannot shrink, the whole frame widens past the
     viewport, and the founders' Reps page scrolls sideways on a phone while
     every other page behaves. Measured at 390px: 726px of overflow. */
  overflow-wrap: anywhere;
  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);
}

/* The preview button sits in a page head's meta row, beside chips, so it is a
   notch smaller than a form's submit and never the tallest thing in the line. */
.btn-preview {
  min-height: 2.5rem;
  padding: var(--sp-8) var(--sp-16);
  font-size: 0.6875rem;
}

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

/* The place picker (/places): one button per place a person's account opens,
   stacked, full width, so a phone gets a thumb-sized target for each. */
.places {
  display: grid;
  gap: var(--sp-12);
  margin: 0;
  padding: 0;
  list-style: none;
}

.places .btn {
  width: 100%;
  justify-content: flex-start;
}

/* -------------------------------------------------------------------- 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. */
.eyebrow a,
.section-note a,
.deflist dd > a,
.asks .what a {
  position: relative;
}

.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. */

@media (max-width: 39.9375rem) {
}

/* --------------------------------------------------------------------- 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"],
/* And the one that was left out, which is the one the guests screen uses to
   date a recording. It rendered at the browser's own 13.3px, which is under
   the 16px iOS floor the rest of this block exists to hold. Found by the
   render checker on 28.08.2026, on a form a founder fills in on a phone. */
input[type="datetime-local"],
input[type="file"],
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);
  /* The "two-step is off" dot. --redline is 4.44:1 on the rail's ink; this
     clears AA, and it is the only marker on the spine that means something. */
  color: var(--redline-lift);
  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 links cell that carries an action as well as a link.
 *
 * `.links` is a two-column grid and every rule that draws its dividers targets
 * `.link-row` as the grid CHILD: `.link-row + .link-row`, `:nth-child(2n)`,
 * `:nth-child(n + 3)`. The guests list wrapped each row in a `.split-row` so it
 * could hold an invite button, which made the wrapper the child and the anchor
 * a lone first-child inside it. Every one of those selectors then matched
 * nothing: no dividers anywhere, no second-column rule, and each row sized to
 * its own content so the names did not line up. Austin, 02.09.2026: "they're
 * not aligned. It looks weird."
 *
 * So the cell is the grid child and carries the borders, and the anchor inside
 * it gives up its own padding and background.
 */
/* A row and the control that edits it, stacked, without the breathing room a
   .stack puts between unrelated sections. */
.stack-tight {
  display: grid;
  gap: var(--sp-8);
}

.link-cell {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-16);
  padding: var(--sp-16);
  background: var(--paper);
}

.link-cell > .link-row {
  flex: 1 1 16rem;
  padding: 0;
  background: none;
}

.link-cell > .link-row:hover {
  background: none;
}

.link-cell > .link-row:hover .name {
  text-decoration: underline;
}

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

@media (min-width: 48rem) {
  .link-cell + .link-cell {
    border-top: 0;
  }

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

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

/* 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);
}

/* The one figure on a summary line that is worse than a small number: money a
   client believes they have already paid. --redline-deep rather than --redline
   because the bright one is 3.63:1 on paper and this is body-sized text. */
.canvas .gauge-terms .burn,
.canvas .gauge-terms .burn b {
  color: var(--redline-deep);
}

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

/* Not a loss, and it must not read as one.
   A drop line says people were lost between two stages. This says nobody has
   looked yet, which is a different kind of fact and belongs in a different
   voice: dashed rather than solid, and no figure in ink. Styled as an aside so
   that a client scanning the shape does not read 34 unmarked meetings as 34
   people who walked away, which is the mistake the whole change exists to
   stop. */
.cascade-unknown {
  display: flex;
  align-items: baseline;
  gap: var(--sp-8);
  padding: var(--sp-4) 0 var(--sp-4) var(--sp-24);
  border-left: 1px dashed var(--rule-mid);
  margin-left: var(--sp-8);
  font-size: 0.8125rem;
  color: var(--graphite);
}

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

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

/* The period the figures cover, said once over the column they are in. Without
   it a reader dates them from the heading — which covers every week drawn, and
   not the single one these numbers are. */
.answer-since {
  margin: 0 0 var(--sp-8);
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-dim, #6b6257);
}

.trend-head {
  /* The same two columns as a row, so the period sits directly over the figures
     it dates rather than out at the edge of the chart. */
  display: grid;
  grid-template-columns: minmax(7rem, 10rem) minmax(0, 1fr);
  gap: var(--sp-16);
  margin: calc(var(--sp-8) * -1) 0 0;
  font-family: var(--font-mono);
  font-size: 0.5625rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-dim, #6b6257);
}

.trend-head span:first-child {
  text-align: right;
}

/* 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;
}

/* The account's usual week, as a dashed rule the line is read against, and
   the newest point emphasised — geometry doing the diagnosis. Both quieter
   than the line itself: they are the yardstick and the cursor, not the data. */
.trend-ref {
  stroke: var(--graphite);
  stroke-width: 1;
  stroke-dasharray: 3 4;
  opacity: 0.45;
}

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

/* LAST in the trend block on purpose: `.trend-line path` and
   `.trend-line.is-accent path` both match the dot at equal or greater
   specificity, and each in turn restyled it to a 1.5px 40%-opacity speck.
   Order is the tiebreak, so the dot's own rules must sit below both. */
.trend-line path.trend-dot {
  stroke: var(--ink);
  stroke-width: 5;
  stroke-linecap: round;
  opacity: 1;
}

.trend-line.is-accent path.trend-dot {
  stroke: var(--redline-deep);
}

/* 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 > span { flex: 1 1 22rem; }
.as-client a,
.as-client > b {
  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.

   Wider and taller than it was. Once a photograph sits under the marks the
   thing on screen is a PAGE, and the question being asked of it — "what did
   they press" — needs the page legible enough to recognise the button. At
   26rem a phone screenshot rendered at about two-thirds size in a panel four
   times its width. */
.heat-frame {
  width: 100%;
  max-width: 34rem;
  max-height: 44rem;
  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.

   Weaker than they were, and that is the whole point of the change. With no
   photograph underneath, a near-opaque mark cost nothing — it sat on a blank
   panel. Over a real page the same mark is a hole: the busiest patch on
   STRUCTURE's quiz blacked out the two answer buttons people were pressing,
   so the map hid the one thing it was drawn to show. A heat map you cannot
   read the page through is a picture of its own colour ramp. */

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

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

/* 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-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.

   1rem is the FLOOR and not a preference: iOS Safari zooms the whole viewport
   to focus any form control under 16px, which breaks the layout and has to be
   pinched back out on every field, every time. Austin works this portal on his
   phone. 0.9375rem here was 15px and did exactly that. Bug 100. */
.doc-form textarea {
  width: 100%; font-family: var(--mono, ui-monospace, monospace); font-size: 1rem;
  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);
}

/* ------------------------------------------------- the curricula, read here */
/*
 * Austin, 21 Aug 2026: "a way that I can scroll through it and see it".
 *
 * A reading column, not a dashboard: 68 characters, serif body, generous
 * leading. It is also the print master for the PDFs that go into Drive — the
 * @media block at the bottom is what makes Ctrl+P here and the generator in
 * revline-ops produce the same document.
 */
.read {
  max-width: 42rem;
}

.read-module {
  margin-top: var(--sp-64);
  padding-top: var(--sp-32);
  border-top: 2px solid var(--ink);
}

.read-module:first-child {
  margin-top: var(--sp-32);
}

.read-module-eyebrow {
  margin: 0;
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--graphite);
}

.read-module-title {
  font-family: var(--font-display);
  font-size: clamp(1.75rem, 1.3rem + 1.6vw, 2.5rem);
  line-height: 1.15;
  margin: var(--sp-8) 0 0;
  text-wrap: balance;
}

.read-why {
  margin: var(--sp-12) 0 0;
  font-family: var(--font-text-serif);
  font-size: 1.0625rem;
  line-height: 1.6;
  color: var(--graphite);
}

.read-note {
  margin: var(--sp-16) 0 0;
  padding: var(--sp-12) var(--sp-16);
  border-left: 2px solid var(--redline);
  background: var(--paper-deep);
  font-size: 0.9375rem;
  line-height: 1.55;
}

.read-lesson {
  margin-top: var(--sp-32);
}

.read-lesson-title {
  display: flex;
  gap: var(--sp-12);
  align-items: baseline;
  margin: 0 0 var(--sp-12);
  font-family: var(--font-text-serif);
  font-size: 1.25rem;
  line-height: 1.3;
}

.read-lesson-num {
  color: var(--redline-press);
  font-size: 0.8125rem;
}

.read-body {
  margin: 0 0 var(--sp-12);
  font-family: var(--font-text-serif);
  font-size: 1.0625rem;
  line-height: 1.65;
}

.read-check {
  margin: var(--sp-16) 0 0;
  padding: var(--sp-16);
  background: var(--paper-deep);
  border-radius: 4px;
}

.read-check p {
  margin: 0;
  line-height: 1.6;
}

.read-check-a {
  margin-top: var(--sp-8) !important;
  color: var(--graphite);
}

.read-aside {
  margin: var(--sp-12) 0 0;
  line-height: 1.6;
  color: var(--graphite);
}

/* The label that says which kind of sentence follows. Mono, small, redline —
   the same treatment the rep's own page gives it, so the two read as one
   document seen from two sides. */
.read-tag {
  display: inline-block;
  margin-right: var(--sp-8);
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--redline-press);
}

.read-rules {
  display: grid;
  gap: var(--sp-24);
  margin-top: var(--sp-32);
  padding-top: var(--sp-16);
  border-top: 1px solid var(--rule);
}

@media (min-width: 40rem) {
  .read-rules {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--sp-32);
  }
}

.read-rules-head {
  margin: 0 0 var(--sp-8);
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--graphite);
}

.read-rules ul {
  margin: 0;
  padding-left: 1.1rem;
  display: grid;
  gap: var(--sp-8);
  line-height: 1.55;
}

.read-foot {
  margin-top: var(--sp-64);
  padding-top: var(--sp-16);
  border-top: 2px solid var(--ink);
  color: var(--graphite);
  line-height: 1.6;
}

.read-toc {
  margin: var(--sp-16) 0 0;
  padding-left: 1.4rem;
  display: grid;
  gap: var(--sp-4);
}

/* Seventeen links in a column, read on a phone. An inline anchor is 17px tall,
   which is a poor target for the one control this page has. */
.read-toc a {
  display: inline-block;
  padding: var(--sp-4) 0;
}

.read-toc-sub {
  margin-left: var(--sp-8);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--graphite);
}

/*
 * PRINT: this page is the master for the PDFs that live in Drive.
 *
 * The rail, the period nav and every control disappear; the reading column
 * widens to the page. Modules start on a new sheet so a printed copy has the
 * same shape as the screen one — and `break-inside: avoid` keeps a lesson's
 * question and its answer on one page, which is the only pagination rule that
 * actually matters here.
 */
@media print {
  .rail,
  .period,
  .scope,
  .btn,
  .skip-link,
  /* Navigation that reads as a dangling sentence fragment once the link is
     gone. On screen "Both curricula" is the way back; on paper it is two words
     with nowhere to go. */
  .screen-only {
    display: none !important;
  }

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

  .canvas,
  body {
    background: #fff;
  }

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

  .read {
    max-width: none;
  }

  .read-module {
    break-before: page;
    border-top: 2px solid #000;
  }

  .read-module:first-child {
    break-before: auto;
  }

  .read-lesson,
  .read-check,
  .read-rules {
    break-inside: avoid;
  }

  a {
    text-decoration: none;
    color: inherit;
  }
}

/* ---- To do ---- */
/* One list, so the row has to carry three different weights without becoming
   three different components: the thing to do, when, and whose account. */
.todo-add { display: grid; gap: 0.35rem; }
.todo-add input, .todo-add select, .todo-add textarea { min-height: 44px; padding: 0.5rem 0.7rem;
  font-size: 16px; font-family: inherit; border-radius: 6px; }
.todo-add textarea { min-height: 6rem; resize: vertical; }
.todo-add-row { display: grid; gap: 0.75rem; margin-top: 0.5rem; }
@media (min-width: 40rem) { .todo-add-row { grid-template-columns: 1fr 1fr; } }
.todo-submit { min-height: 44px; margin-top: 0.75rem; padding: 0.5rem 1.1rem; border-radius: 6px;
  cursor: pointer; font-family: var(--font-mono); font-size: 0.76rem; letter-spacing: 0.06em;
  text-transform: uppercase; }
.todo-note { margin: 0 0 0.6rem; font-size: 0.8rem; opacity: 0.72; }
.todo-empty { margin: 0; font-size: 0.9rem; opacity: 0.72; }

.todo-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.4rem; }
.todo { display: flex; align-items: flex-start; gap: 0.65rem; padding: 0.6rem 0.75rem;
  border-radius: 6px; border: 1px solid currentColor; border-color: color-mix(in srgb, currentColor 16%, transparent); }
.todo-tick { margin: 0; }
/* A 22px box inside a 44px hit area: the target is thumb-sized without the
   control looking like a button. */
.todo-box { width: 44px; height: 44px; margin: -0.35rem -0.6rem; padding: 0; cursor: pointer;
  background: none; border: 0; display: grid; place-items: center; }
.todo-box::before { content: ""; width: 22px; height: 22px; border-radius: 4px;
  border: 2px solid color-mix(in srgb, currentColor 45%, transparent); }
.todo-box:hover::before { border-color: var(--redline, currentColor); }
.todo-box:focus-visible { outline: 2px solid var(--redline, currentColor); outline-offset: -6px; }
.todo-box-done::before { display: none; }
.todo-body { flex: 1 1 auto; min-width: 0; }
.todo-title { margin: 0; overflow-wrap: anywhere; }
.todo-detail { margin: 0.2rem 0 0; font-size: 0.85rem; opacity: 0.75; overflow-wrap: anywhere; }
.todo-meta { margin: 0.25rem 0 0; font-size: 0.76rem; opacity: 0.68;
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.2rem 0.5rem; }
/* The account this is about, marked rather than bolded: bold at this size on
   this ground reads as a second title competing with the actual one. */
.todo-for::before { content: "·"; margin-right: 0.5rem; opacity: 0.6; }
.todo-meta > .todo-for:first-child::before { content: none; margin: 0; }
.todo-edit { flex: 0 0 auto; min-height: 44px; display: inline-flex; align-items: center;
  padding: 0 0.5rem; font-family: var(--font-mono); font-size: 0.7rem; letter-spacing: 0.06em;
  text-transform: uppercase; opacity: 0.7; }
.todo-edit:hover { opacity: 1; }

/* Generated work reads as a warning, not a task: it cannot be ticked here, and
   looking tickable is the whole confusion to avoid. */
.todo-generated { border-color: color-mix(in srgb, var(--redline, currentColor) 55%, transparent); }
.todo-mark { flex: 0 0 auto; width: 22px; height: 22px; margin-top: 0.1rem; border-radius: 4px;
  display: grid; place-items: center; font-family: var(--font-mono); font-size: 0.8rem;
  color: var(--redline, currentColor);
  border: 2px solid color-mix(in srgb, var(--redline, currentColor) 55%, transparent); }

.todo-group[data-group="overdue"] .todo { border-color: color-mix(in srgb, var(--redline, currentColor) 40%, transparent); }
.todo-is-done .todo-title { opacity: 0.55; text-decoration: line-through; }
.todo-done { margin-top: 1.5rem; }
.todo-done summary { cursor: pointer; font-family: var(--font-mono); font-size: 0.74rem;
  letter-spacing: 0.06em; text-transform: uppercase; opacity: 0.7; min-height: 44px;
  display: flex; align-items: center; }
.todo-delete { min-height: 44px; padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer;
  background: none; border: 1px solid color-mix(in srgb, var(--redline, currentColor) 55%, transparent);
  color: var(--redline, currentColor); font-family: var(--font-mono); font-size: 0.74rem;
  letter-spacing: 0.06em; text-transform: uppercase; }

/* ---- Ledger lanes ---- */
/* Three tabs, not three rail entries: they are the same thought at different
   stages, and separating them in the spine would make moving between them feel
   like filing rather than thinking. */
/* Set apart from the masthead above it: pressed straight underneath, the tabs
   read as a third line of the description rather than as a control. */
.lanes { display: flex; flex-wrap: wrap; gap: 0.25rem; margin: 1.75rem 0 2rem;
  border-bottom: 1px solid color-mix(in srgb, currentColor 14%, transparent); }
.lane { display: inline-flex; align-items: baseline; gap: 0.45rem; min-height: 44px;
  padding: 0 0.85rem; border-bottom: 2px solid transparent; margin-bottom: -1px;
  text-decoration: none; opacity: 0.62; }
.lane:hover { opacity: 1; }
.lane[aria-current="page"] { opacity: 1; border-bottom-color: var(--redline, currentColor); }
.lane-label { font-family: var(--font-mono); font-size: 0.76rem; letter-spacing: 0.06em;
  text-transform: uppercase; }
.lane-count { font-size: 0.72rem; opacity: 0.6; }

/* A build and an idea sit the same way: the row is two stacked blocks with an
   action column beside them, not one line of text. */
.todo-idea, .todo-build { align-items: flex-start; }
/* How much of it there is, said in words rather than points. Quieter than the
   priority tag beside it: what matters is read first, what fits second. */
.todo-size { font-family: var(--font-mono); font-size: 0.66rem; letter-spacing: 0.06em;
  text-transform: uppercase; opacity: 0.8; }
.todo-idea-actions { flex: 0 0 auto; display: flex; align-items: center; gap: 0.3rem; }
/* `nowrap` and no shrink: at 390px the date input was taking the space and
   squeezing "MAKE IT A TASK" onto three lines inside a 44px box, which read as
   broken rather than as tight. The input gives way instead — it is the optional
   half of this pair. */
.todo-promote { min-height: 44px; padding: 0 0.7rem; border-radius: 6px; cursor: pointer;
  background: none; border: 1px solid color-mix(in srgb, currentColor 22%, transparent);
  color: inherit; font-family: var(--font-mono); font-size: 0.7rem; letter-spacing: 0.06em;
  text-transform: uppercase; opacity: 0.75; white-space: nowrap; flex: 0 0 auto; }
.todo-promote:hover { border-color: var(--redline, currentColor); opacity: 1; }

/* ---- Lessons ---- */
/* Cost is carried by a rail on the left rather than a badge: the list is read
   top-to-bottom looking for one thing, and a badge per row is a second column
   of noise to scan past. */
.lesson-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.4rem; }
.lesson { border-left: 3px solid color-mix(in srgb, currentColor 18%, transparent); }
.lesson[data-cost="high"] { border-left-color: var(--redline, currentColor); }
.lesson[data-cost="medium"] { border-left-color: color-mix(in srgb, currentColor 38%, transparent); }
.lesson-link { display: grid; gap: 0.2rem; padding: 0.6rem 0.9rem; text-decoration: none; }
.lesson-link:hover { background: color-mix(in srgb, currentColor 5%, transparent); }
.lesson-title { overflow-wrap: anywhere; }
.lesson-tags { font-size: 0.7rem; opacity: 0.6; }
.lesson-back { margin: 0 0 1rem; font-family: var(--font-mono); font-size: 0.72rem;
  letter-spacing: 0.06em; text-transform: uppercase; opacity: 0.7; }

/* ---- Home: quick links ---- */
/* Tiles rather than a list: this is the block somebody hits with a thumb or a
   glance on arrival, and a row of underlined text is neither. */
.home-summary { margin: 0 0 0.35rem; }
.quick { display: grid; gap: 0.5rem; grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); }
.quick-tile { position: relative; display: grid; gap: 0.15rem; align-content: start;
  min-height: 5rem; padding: 0.85rem 1rem; border-radius: 6px; text-decoration: none;
  border: 1px solid color-mix(in srgb, currentColor 16%, transparent); }
.quick-tile:hover { border-color: var(--redline, currentColor); }
.quick-tile:focus-visible { outline: 2px solid var(--redline, currentColor); outline-offset: 2px; }
.quick-label { font-family: var(--font-mono); font-size: 0.78rem; letter-spacing: 0.06em;
  text-transform: uppercase; }
.quick-note { font-size: 0.82rem; opacity: 0.65; }
/* Top-right, out of the reading line: the count qualifies the tile, it is not
   the thing being read. */
.quick-count { position: absolute; top: 0.85rem; right: 1rem; font-size: 0.78rem; opacity: 0.7; }

/* ---- Long things inside written prose ---- */
/* A lesson or a document is full of identifiers — `formSubmitRedirectUrl`,
   `RoundRobin_OptimizeForAvailability` — and an inline code span does not wrap
   by default. On a phone one of them is wider than the column, and because the
   article does not scroll it pushes the whole PAGE sideways: the rail moves,
   the body moves, and nothing on screen looks like the culprit. Break the long
   token instead, and let genuine blocks scroll on their own. */
.doc-body code { overflow-wrap: anywhere; }
.doc-body p, .doc-body li { overflow-wrap: break-word; }
.doc-body pre { overflow-x: auto; max-width: 100%; }
.doc-body pre code { overflow-wrap: normal; white-space: pre; }

/* A row that is late says so itself. The count in the heading tells you there
   is one; only the row tells you which, and reading a list to work that out is
   the job the colour is for. */
.todo[data-late="yes"] { border-color: color-mix(in srgb, var(--redline, currentColor) 45%, transparent); }
.todo[data-late="yes"] .todo-meta { color: var(--redline, inherit); opacity: 0.9; }

.todo-promote-form { display: flex; align-items: center; gap: 0.3rem; margin: 0; }
/* Narrow on purpose: it is an optional refinement to the button beside it, not
   a field somebody has to fill in before they can act. */
.todo-promote-due { min-height: 44px; padding: 0 0.5rem; font-size: 16px; border-radius: 6px;
  border: 1px solid color-mix(in srgb, currentColor 20%, transparent); background: transparent;
  color: inherit; max-width: 9.5rem; }

/* An idea row is body + [when] + [make it a task] + [edit], which does not fit
   a phone in one line. Stacked below 34rem: the actions keep their thumb-sized
   targets instead of being squeezed until they overflow the page. */
@media (max-width: 34rem) {
  .todo-idea { flex-wrap: wrap; }
  .todo-idea-actions { width: 100%; flex-wrap: wrap; justify-content: flex-start; }
  /* ONE COLUMN, not two squeezed ones. Side by side at 390px, the date input
     and the button cannot both have their space: giving it to the input wrapped
     "MAKE IT A TASK" onto three lines inside a 44px box, and giving it to the
     button pushed the page 8px wider than the screen. Stacked, both are full
     width, both keep a thumb-sized target, and nothing overflows. */
  .todo-promote-form { flex: 1 1 100%; flex-direction: column; align-items: stretch; gap: 0.5rem; }
  .todo-promote-due { max-width: none; min-width: 0; width: 100%; }
  .todo-promote { width: 100%; }
}

/* ── Priority on a to-do row ──────────────────────────────────────────────
   Only 'high' and 'low' ever render (see priorityTag): 'medium' is the
   default, so a marker for it would appear on almost every row and mean
   nothing. High borrows the redline the rest of the portal uses for "this
   needs you"; low says its piece and gets out of the way. Both carry the WORD
   as well as the colour — a colour-only signal is no signal to about one man
   in twelve. */
.todo-pri { flex: 0 0 auto; font-family: var(--font-mono); font-size: 0.64rem;
  letter-spacing: 0.08em; text-transform: uppercase; padding: 0.1rem 0.4rem;
  border-radius: 3px; opacity: 1; }
.todo-pri[data-pri="high"] { color: var(--redline, currentColor);
  border: 1px solid color-mix(in srgb, var(--redline, currentColor) 45%, transparent); }
.todo-pri[data-pri="low"] { opacity: 0.55;
  border: 1px solid color-mix(in srgb, currentColor 22%, transparent); }
/* The meta line dims itself; the high marker must not be dimmed with it. */
.todo-meta > .todo-pri[data-pri="high"] { opacity: 1; }

/* ── The wait, and the area ───────────────────────────────────────────────
   Two markers on the meta line, deliberately unlike each other.

   THE WAIT is the one that changes what you do with the row — it says the
   work is not available and names who to chase — so it is bordered and
   undimmed, like the high-priority marker beside it. It is NOT redline:
   a wait is not a failure, and colouring it as one would make every row
   somebody is politely waiting on read as an emergency.

   THE AREA is the quietest thing on the line. It repeats a heading in the
   area view and is only occasionally useful in the date view, so it earns
   no border and no weight — dimmer than the meta line it sits on. */
.todo-wait { font-family: var(--font-mono); font-size: 0.66rem; letter-spacing: 0.06em;
  text-transform: uppercase; padding: 0.1rem 0.4rem; border-radius: 3px; opacity: 1;
  border: 1px solid color-mix(in srgb, currentColor 30%, transparent); }
.todo-meta > .todo-wait { opacity: 0.92; }
.todo-area { font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.05em; }
.todo-area::before { content: "·"; margin-right: 0.5rem; opacity: 0.6; }
.todo-meta > .todo-area:first-child::before { content: none; margin: 0; }

/* A whole group that is waiting. Marked once on the section rather than
   again on every row inside it — the heading already said so. */
.todo-group[data-group="waiting"] .todo-wait { border-color: transparent; padding-left: 0; }

/* The small mono caption over a field.
   ---------------------------------------------------------------------------
   Defined in app.css since the day it was written and NEVER here, while
   `src/ui/tasks.ts` has been rendering it on a page that loads this sheet. So
   every label on the founders' own to-do form — "What needs doing", "By when",
   "About" — has been shipping as browser-default text.

   Found on 02.09.2026 the moment the chart page was added to
   test/classes-exist.test.ts, which had never covered it. That is the whole
   argument for that test: nothing errors, nothing logs, and only somebody
   looking at the page would ever notice.

   Copied rather than shared. The two sheets overlap by name and not by
   contents on purpose, and merging them is a different piece of work. */
.mono-label {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  line-height: 1.5;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  font-weight: 400;
  margin: 0;
}

/* ── What somebody told us (0097) ─────────────────────────────────────────
   Colours come from currentColor the way `.todo` does, so these rows work on
   whichever ground the section is placed on and cannot become the ink-on-ink
   bug that a hard-coded colour on this estate has already produced twice. */
.snagr-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-8); }
.snagr { display: grid; gap: var(--sp-4); padding: var(--sp-12); border-radius: 6px;
  border: 1px solid currentColor; border-color: color-mix(in srgb, currentColor 16%, transparent); }
/* Broken carries the accent on its edge; a request does not. The distinction
   is which needs looking at today, and it is one border rather than a badge
   competing with the sentence beside it. */
.snagr[data-kind="broken"] { border-left: 2px solid var(--redline); }

.snagr-who { margin: 0; display: flex; justify-content: space-between; gap: var(--sp-8);
  font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.05em;
  text-transform: uppercase; opacity: 0.7; }
.snagr-when { opacity: 0.8; }
/* Their sentence, which is the thing being read. Everything else on the row
   supports it and is set quieter. */
.snagr-what { margin: 0; overflow-wrap: anywhere; }
.snagr-doing { margin: 0; font-size: 0.85rem; opacity: 0.75; overflow-wrap: anywhere; }
.snagr-where { margin: 0; font-family: var(--font-mono); font-size: 0.7rem;
  opacity: 0.6; overflow-wrap: anywhere; }
/* The stack the browser produced. Kept legible rather than dimmed to nothing:
   on a bug row this is usually the only line that says what is actually
   wrong. */
.snagr-threw { margin: var(--sp-4) 0 0; padding: var(--sp-8); border-radius: 4px;
  background: color-mix(in srgb, currentColor 7%, transparent);
  font-family: var(--font-mono); font-size: 0.72rem; overflow-wrap: anywhere; }

.snagr-state { margin: 0; font-size: 0.78rem; opacity: 0.7; }
/* The one line on this list that is an instruction rather than a note: the
   work is finished and a person is still waiting to be told. */
.snagr-owed { margin: 0; font-size: 0.82rem; color: var(--redline-deep); }

.snagr-act { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-12);
  margin-top: var(--sp-4); }
.snagr-act form { margin: 0; }
.snagr-close { flex: 1 1 16rem; min-width: 0; }
.snagr-close summary { cursor: pointer; list-style: none; min-height: 44px;
  display: flex; align-items: center; font-family: var(--font-mono);
  font-size: 0.7rem; letter-spacing: 0.06em; text-transform: uppercase; opacity: 0.7; }
.snagr-close summary::-webkit-details-marker { display: none; }
.snagr-close summary:hover { opacity: 1; }
.snagr-close summary:focus-visible { outline: 2px solid var(--redline); outline-offset: 2px; }
.snagr-close-form { display: grid; gap: var(--sp-8); margin: 0; }
.snagr-close-row { display: flex; flex-wrap: wrap; gap: var(--sp-8); }

/* The two readings of the list. Quieter than the lane tabs above it: those
   choose WHAT you are looking at, this only chooses how it is stacked. */
/* Clear of the Add form above it: sitting tight under the submit button, it
   read as part of the form rather than as a control over the list below. */
.lanes-quiet { margin: 2.25rem 0 0.5rem; border-bottom: 0; gap: 0.75rem; }
.lanes-quiet a { min-height: 44px; display: inline-flex; align-items: center;
  font-family: var(--font-mono); font-size: 0.7rem; letter-spacing: 0.06em;
  text-transform: uppercase; text-decoration: none; opacity: 0.55; }
.lanes-quiet a:hover { opacity: 0.85; }
.lanes-quiet a[aria-current="page"] { opacity: 1;
  border-bottom: 2px solid var(--redline, currentColor); }

/* Rename-or-remove, one row. Wraps rather than squeezing the name box: at
   390px a three-control row either scrolls the page sideways or gives the
   input 40px, and both are worse than two lines. */
.area-row { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
.area-row input[type="text"] { flex: 1 1 12rem; min-width: 0; }

/* ── The client's own read from Contrast ──────────────────────────────────
   The verdict has to LOOK like the verdict. Rendered as plain muted label
   text, "Needs attention today" read exactly the same as "Running as it
   should" — which is the one thing a status line must never do.

   Restrained on purpose: this is a client's own page, not an alarm panel, so
   the colour is carried by the word and a hairline stripe rather than by a
   filled block. The ambers and greens are darkened well past the founders'
   tile palette because those sit on near-black and these sit on cream. And the
   word says the state in words too — a colour-only signal is no signal at all
   to about one man in twelve. */
.contrast-read { border-left: 3px solid color-mix(in srgb, currentColor 20%, transparent); }
.contrast-verdict { opacity: 1; }
/* NOT `--redline`. That is a fill colour, chosen to sit under white text and on
   near-black grounds; as TEXT on this cream panel it measures 3.32:1, which
   fails AA — and it failed on the one status that has to be read, while amber
   and green passed. Darkened until it clears 4.5:1. The 3px stripe keeps the
   brand red: a stripe is a graphical object, not text, and the word beside it
   is what carries the meaning. */
.contrast-read[data-status="red"] { border-left-color: var(--redline, currentColor); }
.contrast-read[data-status="red"] .contrast-verdict { color: #a3170f; }
.contrast-read[data-status="amber"] { border-left-color: #9a6410; }
.contrast-read[data-status="amber"] .contrast-verdict { color: #8a5a0e; }
.contrast-read[data-status="green"] { border-left-color: #1f7a3d; }
.contrast-read[data-status="green"] .contrast-verdict { color: #1b6a35; }
/* Grey is "we cannot say yet", and it should look like an absence of a claim
   rather than like a fourth verdict. */
.contrast-read[data-status="grey"] .contrast-verdict { opacity: 0.62; }

/* The same four verdicts as a word beside an account name on the Overview's
   Contrast strip. The word carries the meaning; no stripe, and the colours are
   the ones above, so the two readings of one status can never disagree. */
.contrast-word { font-style: normal; }
.contrast-word[data-status="red"] { color: #a3170f; }
.contrast-word[data-status="amber"] { color: #8a5a0e; }
.contrast-word[data-status="green"] { color: #1b6a35; }
.contrast-word[data-status="grey"] { opacity: 0.62; }

/* Security beside Sign out on a client's masthead. A row, so the two read as
   one group of account controls rather than as a stray link near a button. */
.masthead-seat { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; }
.masthead-seat .signout-link { font-family: var(--font-mono); font-size: 0.74rem;
  letter-spacing: 0.06em; text-transform: uppercase; opacity: 0.75;
  min-height: 44px; display: inline-flex; align-items: center; }
.masthead-seat .signout-link:hover { opacity: 1; }

/* -- Angiogram -----------------------------------------------------------
   A Sankey drawn as markup. The ribbons sit UNDER the nodes in the document
   so the nodes paint on top without a z-index, which SVG does not have.

   Ribbons are translucent so overlaps read as density rather than as one
   band hiding another — where two origins feed one surface, the darker
   patch is true information.

   Coloured by DESTINATION, not by origin: the question a reader arrives
   with is "where did it stop", so the band that ends at a close should be
   the one that catches the eye. Colouring by origin makes the biggest
   source loudest, which is the thing they already knew. */

/* The two ends carry the meaning: who sent them, and how many got through.
   The middle is scaffolding and is drawn quieter. */

@media (prefers-reduced-motion: no-preference) {
}

/* ---- The rep's own funnel strip ---------------------------------------- */
/* Four numbers and three arrows: the gaps between them are the reading, so
   the stages sit on one line where the gaps can be seen. Numbers in mono at
   display size — on this page the count IS the graphic. */
.rep-funnel {
  display: flex;
  align-items: center;
  gap: var(--sp-16);
  flex-wrap: wrap;
  margin: var(--sp-24) 0;
}

.rep-funnel-stage {
  display: inline-flex;
  flex-direction: column;
  gap: var(--sp-4);
}

.rep-funnel-n {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 1.75rem;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

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

/* A stage nothing writes to. Rendered as a question, not as a zero: a zero
   claims the thing did not happen, and on an unmeasured stage that claim is
   false and every rate built on it is wrong. */
.rep-funnel-unknown .rep-funnel-n { opacity: 0.45; }
.rep-funnel-note {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: 0.6;
  margin-top: 0.15rem;
}

.rep-funnel-sep {
  color: var(--graphite);
  font-size: 1.1rem;
}

/* Four stages must hold one line on a phone — a wrapped arrow points at
   nothing. Smaller numbers, tighter gaps; the shape is the message. */
@media (max-width: 480px) {
  .rep-funnel { gap: var(--sp-8); }
  .rep-funnel-n { font-size: 1.3rem; }
  .rep-funnel-label { font-size: 0.5625rem; letter-spacing: 0.1em; }
  .rep-funnel-sep { font-size: 0.9rem; }
}

/* ── The Chart's own chrome ───────────────────────────────────────────────
   The Chart lives on its own hostname and was still being dressed in the
   portal's rail (Austin: "all the portal stuff shows on the left"). Its own
   header instead: one system, three lanes, so the navigation is a top bar —
   wordmark, lanes with counts, the one link back to the portal. Same ink and
   cream as the estate; a different building, the same brand. */
.chart-top { background: var(--ink); color: var(--cream); }
.chart-top-inner { display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: var(--sp-8) var(--sp-24); padding-block: var(--sp-12); }
/* The lanes sit IN the chrome: border-bottom tabs against the header's own
   bottom edge, redline underline on the lit one — the same tab language the
   body used, moved to where navigation belongs. */
.chart-lanes { display: flex; align-items: stretch; gap: 0.25rem; align-self: stretch; }
.chart-lanes .lane { margin-bottom: 0; align-items: center; color: var(--cream); }
.chart-lanes .lane-count { color: var(--cream-dim); opacity: 1; }
.chart-side { display: flex; align-items: center; gap: var(--sp-16); }
.chart-side .signout-link { font-family: var(--font-mono); font-size: 0.74rem;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--cream);
  opacity: 0.75; text-decoration: none; min-height: 44px; display: inline-flex;
  align-items: center; }
.chart-side .signout-link:hover { opacity: 1; }
.chart-main { padding-top: var(--sp-32); padding-bottom: var(--sp-48); }
/* On a phone the header wraps: wordmark row, then the lanes full-width so all
   three keep thumb-sized targets rather than shrinking into one another. */
@media (max-width: 40rem) {
  .chart-top-inner { padding-block: var(--sp-8); }
  .chart-lanes { order: 3; flex: 1 1 100%; }
  .chart-lanes .lane { flex: 1 1 0; justify-content: center; }
}

/* The first section under the Chart's masthead was sitting directly on the
   sub-line — the portal's canvas spaces this with its own padding, and the
   chart main needed the same breath. */
.chart-main .chart-body > .section:first-child { margin-top: var(--sp-32); }

/* The flow door on Pulse: one tile, capped so it does not stretch the full
   canvas like a banner. */

/* ── The Angiogram's stage ────────────────────────────────────────────────
   The flow lives on ink, like the rail and the Chart's header: the map is a
   dark instrument and the glow only works against night. The canvas is sized
   by flow.js; this box gives it its room and its frame. */
.flow-stage { position: relative; background: var(--ink); border-radius: 8px;
  padding: 0.5rem 0.5rem 0; margin-top: var(--sp-16); overflow: hidden; }
.flow-canvas { display: block; width: 100%; }
.flow-updated { position: absolute; top: 0.75rem; right: 1rem; margin: 0;
  color: var(--cream-dim); font-size: 0.625rem; letter-spacing: 0.12em;
  text-transform: uppercase; }
.flow-legend { margin-top: var(--sp-12); max-width: 62ch; }
.flow-legend-red { color: var(--redline); }
/* The numbers behind the picture, one click away. The summary row reuses the
   section head, so give the marker room and a hand cursor. */
.flow-table > summary { cursor: pointer; list-style: none; }
.flow-table > summary::-webkit-details-marker { display: none; }
.flow-table[open] > summary { margin-bottom: var(--sp-12); }

/* The numbered steps inside a disclosure, as on the guest upload form.
   NOT `.steps` — that is the funnel bar chart, and borrowing it here laid the
   list out as flex bars with the numbers on the floor. */
.howto {
  margin: 10px 0 0;
  padding-left: 22px;
}

.howto li {
  margin-bottom: 8px;
}

.howto li:last-child {
  margin-bottom: 0;
}

details > summary {
  cursor: pointer;
}

/* ---- Uploading a video from the guests screen ---- */
.upload-form input[type="file"] { font-size: 1rem; padding: 0.6rem 0; width: 100%; }
.upload-state { font-family: var(--font-mono); font-size: 0.85rem; color: var(--graphite);
  margin: 0.5rem 0 0; min-height: 1.2em; }

/* ─────────────────────────────────────────────────────────────────────────────
   THE BRAIN

   Two kinds of thing on one page: a paragraph somebody reads, and a list of
   findings somebody scans. They are set differently on purpose. The read is
   body text, serif, at a measure you can actually read a paragraph at. The
   findings are a ledger.

   The verdict is carried by a left rule and a mono label, never by colour
   alone: this sheet has no green, deliberately, and "fine" here is the absence
   of emphasis rather than a reassuring tick. The one that gets the most
   attention after `wrong` is `unknown`, because every expensive failure this
   estate has had looked exactly like a quiet week.
   ───────────────────────────────────────────────────────────────────────── */

.brain-brief { display: flex; flex-direction: column; gap: var(--sp-16); }

.brain-prose {
  display: flex;
  flex-direction: column;
  gap: var(--sp-12);
  max-width: 62ch;
}
.brain-prose .copy {
  font-family: var(--font-text-serif);
  font-size: 1.05rem;
  line-height: 1.55;
  color: var(--ink);
}

.brain-read {
  border-left: 3px solid var(--rule-mid);
  padding: var(--sp-16) var(--sp-24);
  background: var(--paper-deep);
  display: flex;
  flex-direction: column;
  gap: var(--sp-8);
  margin-bottom: var(--sp-16);
}
.brain-read[data-verdict="wrong"] { border-left-color: var(--redline); background: var(--paper-sunk); }
.brain-read[data-verdict="watch"] { border-left-color: var(--redline-deep); }
.brain-read[data-verdict="unknown"] { border-left-color: var(--rule-strong); }
.brain-read[data-verdict="good"] { border-left-color: var(--rule); background: transparent; }

.brain-read-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-16);
  flex-wrap: wrap;
}
.brain-read-title {
  font-family: var(--font-display);
  font-size: 1.15rem;
  font-weight: 600;
  line-height: 1.25;
  margin: 0;
  color: var(--ink);
  max-width: 60ch;
}

.brain-verdict {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--graphite);
}
.brain-wrong { color: var(--redline); }
.brain-watch { color: var(--redline-deep); }
.brain-unknown { color: var(--graphite); }
.brain-good { color: var(--cream-dim); }

.brain-because { font-family: var(--font-text-serif); }

.brain-facts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-8);
  margin: 0;
}
.brain-fact {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  color: var(--graphite);
  background: var(--paper);
  border: 1px solid var(--rule);
  padding: 2px var(--sp-8);
  white-space: nowrap;
}
.brain-fact b { color: var(--ink); font-weight: 500; }

/* Ending somebody from the roster. Quiet, because it sits in a row of
   ordinary controls and a loud red button beside "Copy welcome message" is an
   accident waiting to be clicked. It reads as what it is only once you are
   looking at it. */
.danger-quiet { color: var(--redline); border-color: var(--redline); }
.danger-quiet:hover { background: var(--redline); color: var(--paper); }

/* Whose team it is, on the roster. Two small forms side by side under the
   heading, stacking on a phone rather than squeezing to nothing. */
.team-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-8) var(--sp-12);
}
.team-note {
  max-width: 62ch;
  margin: var(--sp-8) 0 0;
}

@media (max-width: 560px) {
  .team-controls { flex-direction: column; align-items: stretch; gap: var(--sp-8); }
}

/* ── THE ACCOUNTS PAGE ────────────────────────────────────────────────────
   A paste surface. Somebody opens it with a phone in the other hand, about to
   edit a bio, so the copyable things are the loudest things on the card and
   the form furniture is quiet.
   ───────────────────────────────────────────────────────────────────────── */

.social { display: flex; flex-direction: column; gap: var(--sp-16); }

/* An unfinished account is not a neutral row: whatever it sends arrives as
   direct and cannot be told from anything else. */
.social[data-gap="no-link"] { border-left: 3px solid var(--redline); }
.social[data-gap="over-limit"] { border-left: 3px solid var(--redline); }
.social[data-gap="no-bio"] { border-left: 3px solid var(--redline-deep); }

.social-gap {
  color: var(--redline);
  font-family: var(--font-text-serif);
  margin: 0;
}

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

/* THE STACKING RULE FIGHTS THE GRID, AND IT WINS BY DEFAULT.
   `.field + .field { margin-top: 24px }` exists so fields stack with air
   between them in a normal form. Put two fields side by side in a grid and it
   still fires, pushing the SECOND column down by 24px while the first stays
   put: measured, handle at 533 and profile link at 557 on a row that is meant
   to share a baseline. Nothing about it looks like a bug in the markup, which
   is why it is worth naming here rather than nudging the padding until it
   lines up. Gap owns the spacing inside a grid; margins do not. */
.social-grid > .field + .field { margin-top: 0; }

/* Same class of thing. `.card-head` is 91px tall for one line of text because
   the heading inside it keeps its own margins, which reads as an empty band at
   the top of every card. */
.social .card-head { margin-bottom: 0; }
.social .card-head > * { margin: 0; }

.social-count {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  color: var(--graphite);
  font-variant-numeric: tabular-nums;
  margin-left: var(--sp-8);
}
.social-count.is-over { color: var(--redline); }

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

.social-link {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-12);
  margin: 0;
  padding-top: var(--sp-12);
  border-top: 1px solid var(--rule);
}
.social-link .mono {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  word-break: break-all;
  color: var(--ink);
}

@media (max-width: 560px) {
  /* Restated inside the query, as everything in this sheet is. Two fields side
     by side at 375 gives each of them about 150px, which is not a field. */
  .social-grid { grid-template-columns: minmax(0, 1fr); gap: var(--sp-12); }
  .social-actions { gap: var(--sp-8); }
}

/* ── THE QUEUE ────────────────────────────────────────────────────────────
   What to do next, above what is true. It has to read as a list of jobs, not
   as another block of findings: numbered, the action in the strongest weight
   on the page, the finding under it in the quietest.
   ───────────────────────────────────────────────────────────────────────── */

.brain-queue { display: flex; flex-direction: column; gap: var(--sp-16); }

.brain-jobs {
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: none;
  display: flex;
  flex-direction: column;
  gap: 1px;
  background: var(--rule);
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}
.brain-job {
  display: grid;
  grid-template-columns: 2.25rem 1fr auto;
  align-items: start;
  gap: var(--sp-12);
  padding: var(--sp-16) 0;
  background: var(--paper);
}
.brain-job[data-verdict="wrong"] { background: var(--paper-sunk); }

.brain-job-n {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  line-height: 1.6;
  color: var(--graphite);
  font-variant-numeric: tabular-nums;
}
.brain-job-body { display: flex; flex-direction: column; gap: 3px; }
.brain-job-do {
  color: var(--ink);
  font-weight: 500;
  line-height: 1.45;
}

@media (max-width: 560px) {
  .brain-job {
    grid-template-columns: 1.75rem 1fr;
    gap: var(--sp-8);
    padding: var(--sp-12) 0;
  }
  .brain-job .brain-note-go { grid-column: 2; justify-self: start; }
}

/* ── THE NOTE, WHICH IS THE BRAIN ON SOMEBODY ELSE'S PAGE ──────────────────
   The same readings, above the table they are about. It has to read as a
   remark on the page rather than as the page: quieter than the section it sits
   over, and short enough that the thing somebody actually came for is still
   above the fold on a phone.
   ───────────────────────────────────────────────────────────────────────── */

.brain-note {
  display: flex;
  flex-direction: column;
  gap: var(--sp-12);
  margin-bottom: var(--sp-24);
}
.brain-note .brain-prose { max-width: 68ch; }
.brain-note .brain-prose .copy { font-size: 1rem; }

.brain-note-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
  background: var(--rule);
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}
.brain-note-row {
  display: grid;
  grid-template-columns: 8.5rem 1fr auto;
  align-items: baseline;
  gap: var(--sp-12);
  padding: var(--sp-12) 0;
  background: var(--paper);
}
.brain-note-row[data-verdict="wrong"] { background: var(--paper-sunk); }

.brain-note-line {
  display: flex;
  flex-direction: column;
  gap: 2px;
  color: var(--ink);
  line-height: 1.45;
}
.brain-note-go {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
}

.brain-note-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-16);
  flex-wrap: wrap;
  margin: 0;
}

@media (max-width: 560px) {
  /* Restated inside the query on purpose, as everything in this sheet is: a
     later same-specificity rule in a media block silently cancels an earlier
     one, and that has bitten this file three times. */
  .brain-read { padding: var(--sp-12) var(--sp-16); }
  .brain-read-head { gap: var(--sp-8); }

  /* The three-column row cannot hold a verdict, a headline and a link at 375.
     It stacks, and the verdict keeps its own line so it still scans as a
     column when several rows are open. */
  .brain-note-row {
    grid-template-columns: 1fr auto;
    align-items: start;
    gap: var(--sp-8);
    padding: var(--sp-12) 0;
  }
  .brain-note-row .brain-verdict { grid-column: 1 / -1; }
  .brain-note-foot { align-items: flex-start; }
}

/* ── THE MUSTER ─────────────────────────────────────────────────────────────
   A team, with the reporting line drawn rather than described.

   The rail down the left of `.crew-list` IS the answer to "who is working for
   who". A label reading "reports to Michael Parrish" on every row states the
   same fact four times and still leaves the reader assembling the shape in
   their head; a line they can follow with their eye does not.

   Every value sits in a real column so two people can be compared without
   reading both in full. What was here before right-aligned eight to eleven
   ragged strings per person, which has no scan line at either edge. */

.crew { margin-top: var(--sp-32); }
.crew + .crew { margin-top: var(--sp-24); }

.crew-head {
  display: flex; align-items: baseline; gap: var(--sp-12); flex-wrap: wrap;
  padding-bottom: var(--sp-8); border-bottom: 1px solid var(--rule-mid);
}
.crew-title {
  margin: 0; font-family: var(--font-display); font-size: 1.25rem;
  font-weight: 600; letter-spacing: -0.01em;
}
/* The Slack room this team is in, printed exactly as Slack spells it. Every
   other mono label on this page is uppercased; this one is not, because it is
   a handle somebody is going to search for rather than a category. */
.crew-no {
  font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.04em;
  text-transform: none; color: var(--graphite); font-weight: 400;
}
.crew-sub { margin: 0; flex: 1 1 16rem; font-size: 0.86rem; color: var(--graphite); }
.crew-sub b { color: var(--ink); font-weight: 600; }
.crew-count {
  margin: 0; font-family: var(--font-mono); font-size: 0.74rem;
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--graphite);
}

/*
 * THE COLUMNS BELONG TO THE LIST, NOT TO THE ROW.
 *
 * The first version of this put `grid-template-columns` on each `.crew-row`,
 * which is five columns per row and therefore no columns at all: measured on
 * the rig, one row's name cell was 192px and the next was 583px, because each
 * row sized itself to its own content. That is the same fault as the ragged
 * right-aligned stack it replaced, arrived at from the other direction — and
 * it was invisible in the code and obvious the moment the page was driven.
 *
 * `subgrid` is the fix that keeps both halves: the track sizes are decided
 * once, here, over every row in the team, and each row still has a real box to
 * carry its border and its lead marker.
 */
.crew-list { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: 1fr; }

/*
 * THE COLUMNS APPEAR WHEN THERE IS ROOM FOR THEM, AND NOT BEFORE.
 *
 * Six fixed tracks plus a name need about 900px of content width. The first
 * version turned them on below 60rem of VIEWPORT, which is a different number:
 * the rail eats 230px and the shell another 64, so at 1024px the page scrolled
 * sideways, and at 390px the name column collapsed to twenty-seven pixels.
 * Both were invisible in the stylesheet and immediate on the rig.
 *
 * So the grid is the wide case and it is opt-in. Below it the row is flex and
 * wraps, which packs several short facts onto one line instead of giving each
 * of them a row of its own — the stacked-grid version was 8,634px on a phone,
 * taller than the page it replaced.
 */
@media (min-width: 80rem) {
  .crew-list {
    grid-template-columns:
      minmax(11rem, 1fr)   /* who      */
      12rem                /* stands   */ /* fits "Out for signature", the
                                                widest single chip; anything
                                                narrower cannot wrap it and it
                                                overflows into the next column */
      8.5rem               /* progress */
      9.5rem               /* paid     */
      7rem                 /* since    */
      10rem;               /* act      */
  }
  /* Every column but the name is fixed, and that is the point: the name takes
     whatever is left, so it is the same width on every row of every block and
     the eye has one edge to run down. With `auto` on the chips and the actions,
     each block sized itself to its own busiest row and the name column came out
     258px, 193px and 168px in three blocks of one page. A column that only
     lines up inside a block is most of a column. */
}

/* The rail. Inset so it starts under the head rather than beside it, and
   drawn on the list rather than on each row so it is one continuous line
   instead of a dashed stack of segments. */
.crew[data-kind] .crew-list { border-left: 3px solid var(--rule-strong); padding-left: var(--sp-16); }
.crew[data-kind="internal"] .crew-list { border-left-color: var(--ink); }
.crew[data-kind="none"] .crew-list { border-left-style: dashed; border-left-color: var(--rule-mid); }

.crew-row {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: var(--sp-8) var(--sp-16);
  padding: var(--sp-12) 0;
  border-bottom: 1px solid var(--rule);
}
@media (min-width: 80rem) {
  .crew-row {
    display: grid; grid-column: 1 / -1; grid-template-columns: subgrid;
    align-items: center;
  }
}
.crew-who { flex: 1 1 13rem; }
.crew-stands { flex: 0 1 auto; }
.crew-prog, .crew-paid, .crew-since { flex: 0 1 auto; }
.crew-act { flex: 1 1 100%; }
@media (min-width: 80rem) {
  .crew-who, .crew-stands, .crew-prog, .crew-paid, .crew-since, .crew-act { flex: initial; }
}
.crew-row:last-child { border-bottom: 0; }

/* The manager reads as the head of their own block: heavier name, and a mark
   on the rail at their row. Not a background tint, which would make the block
   look like two lists rather than one chain. */
/* The manager reads as the head of their block by WEIGHT and by saying so,
   not by a marker. The first version drew a 12px dash on the rail beside them;
   at the row's vertical centre that landed next to the second line of their
   name cell and read as a stray hyphen in front of their role. */
.crew-row[data-lead="1"] .crew-name { font-weight: 600; font-size: 1.02rem; }
.crew-row[data-lead="1"] .crew-lead {
  font-family: var(--font-mono); font-size: 0.62rem; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--redline-deep); font-weight: 600;
}

.crew-who { min-width: 0; display: grid; gap: 1px; }
.crew-name {
  font-family: var(--font-sans); font-size: 0.97rem; color: var(--ink);
  text-decoration: none; overflow-wrap: anywhere;
}
.crew-name:hover, .crew-name:focus-visible { color: var(--redline-deep); }
.crew-what { font-size: 0.76rem; color: var(--graphite); }
.crew-mail {
  font-family: var(--font-mono); font-size: 0.72rem; color: var(--cream-dim);
  overflow-wrap: anywhere;
}

.crew-stands { display: flex; flex-wrap: wrap; gap: var(--sp-4); align-items: center; }
.crew-prog, .crew-paid, .crew-since, .crew-act { min-width: 0; }
.crew-paid { font-size: 0.76rem; color: var(--graphite); }
.crew-since {
  font-family: var(--font-mono); font-size: 0.72rem; color: var(--graphite);
  white-space: nowrap;
}
.crew-since-l { color: var(--cream-dim); letter-spacing: 0.04em; }
@media (min-width: 80rem) { .crew-since-l { display: block; } }
.crew-since b { color: var(--ink); font-weight: 500; }
.crew-act { display: flex; flex-wrap: wrap; gap: var(--sp-8); justify-content: flex-end; }

.crew-ready { display: flex; gap: var(--sp-8); align-items: center; }
.crew-ready-l {
  font-family: var(--font-mono); font-size: 0.66rem; letter-spacing: 0.05em;
  color: var(--graphite); white-space: nowrap;
}
.crew-certified {
  font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--ink); font-weight: 600;
}

/* Segmented rather than a percentage bar: three gates is three things a
   person did, and a bar at 66% invites somebody to read it as a score. */
.meter { display: inline-flex; gap: 2px; }
.meter i {
  display: block; width: 0.5rem; height: 0.6rem; border-radius: 1px;
  background: var(--rule-mid);
}
.meter i[data-on] { background: var(--ink); }

.crew-controls {
  display: flex; flex-wrap: wrap; gap: var(--sp-8);
  padding-top: var(--sp-12); margin-left: var(--sp-16);
}
.crew-note {
  margin: var(--sp-8) 0 0 var(--sp-16); font-size: 0.78rem; color: var(--graphite);
}

.gauge-want b { color: var(--redline-deep); }

/* Under 60rem the columns stop earning their width: eleven characters of
   "No rate agreed" in a 7rem column is three lines. Two columns, and the
   facts wrap under the name in reading order. */
/* Actions sit under the row on the flex layout and to the right of it on the
   grid, because a full-width strip of controls under each person is how you
   press the wrong one on a phone. */
.crew-act { justify-content: flex-start; }
@media (min-width: 80rem) { .crew-act { justify-content: flex-end; } }

/* A password box with its Show button (reveal.js). The button is hidden in
   the markup and shown by the script, so a page with no script has no dead
   control. Mono and quiet, like every other secondary control on the gate. */
.field-inline { display: flex; gap: var(--sp-8); align-items: stretch; }
.field-inline > input { flex: 1 1 auto; min-width: 0; }
.reveal { flex: none; font-family: var(--font-mono); font-size: 0.6875rem;
  letter-spacing: 0.12em; text-transform: uppercase; background: none;
  border: 1px solid rgb(20 17 16 / 0.22); border-radius: 6px;
  padding: 0 var(--sp-16); color: var(--graphite); cursor: pointer; }
.reveal:hover { color: var(--ink); border-color: var(--ink); }
.reveal:focus-visible { outline: 2px solid var(--redline); outline-offset: 2px; }

/* ── The cold email console ────────────────────────────────────────────────
   Two chart shapes, both drawn server-side. No script and no library: the page
   is read on a phone by two people and has to survive a CSP that forbids
   inline styles.

   The ramp is an SVG rather than flexed boxes, because a column's HEIGHT is
   the reading and a height cannot be an inline style here. SVG geometry
   attributes are not styles, which is the same reason `trendChart` is drawn
   this way. The ceiling is the point rather than the columns: being over it
   costs a domain, so it is dashed redline and a column that breaches it
   takes that colour instead of staying quiet. */
.ramp { margin-top: var(--sp-24); }
.ramp-svg { display: block; width: 100%; height: 120px; }
.ramp-bar { fill: var(--ink-lift-2); }
.ramp-bar.is-today { fill: var(--ink); }
.ramp-bar.is-over { fill: var(--redline); }
.ramp-ceiling { stroke: var(--redline); stroke-width: 1; stroke-dasharray: 4 3; }
.ramp-base { stroke: var(--rule-mid); stroke-width: 1; }
.ramp-axis { display: flex; justify-content: space-between; margin-top: var(--sp-8);
  font-family: var(--font-mono); font-size: 0.625rem; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--graphite); }
.ramp-caption { margin-top: var(--sp-12); font-size: 0.8125rem; color: var(--graphite); }

/* A funnel stage. The bar is share of the FIRST stage, so the shape of the
   drop-off reads down the column; the percentage printed beside it is of the
   stage ABOVE, because that is where a loss actually happens. Two different
   questions, and the row answers both without either being a guess. The width
   comes from `bar()` and a `.w-N` rung, same as every other bar here. */
.stage-list { display: flex; flex-direction: column; gap: var(--sp-12);
  margin-top: var(--sp-24); }
.stage-row { display: grid; grid-template-columns: 8rem 1fr 7rem; gap: var(--sp-12);
  align-items: center; }
.stage-name { font-family: var(--font-mono); font-size: 0.6875rem;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--graphite); }
.stage-track { background: var(--paper-sunk); border-radius: 3px; height: 20px; }
.stage-fill { background: var(--ink-lift); height: 100%; min-width: 2px;
  border-radius: 3px; }
.stage-row.is-lead .stage-fill { background: var(--ink); }
.stage-figure { font-family: var(--font-mono); font-size: 0.8125rem;
  color: var(--ink); white-space: nowrap; text-align: right; }
.stage-figure span { color: var(--graphite); }
.stage-note { font-size: 0.75rem; color: var(--graphite); margin: 0;
  grid-column: 2 / 3; }

/* A guardrail. The headroom is the reading, not the rate, so the headroom is
   what gets the size. */
.rail-grid { display: grid; gap: var(--sp-16); margin-top: var(--sp-24);
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.rail { border: 1px solid var(--rule); border-radius: 8px; padding: var(--sp-16); }
.rail.is-over { border-color: var(--redline); }
.rail-label { font-family: var(--font-mono); font-size: 0.625rem;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--graphite);
  margin: 0 0 var(--sp-8); }
.rail-figure { font-family: var(--font-display); font-size: 1.75rem; line-height: 1;
  margin: 0; color: var(--ink); }
.rail.is-over .rail-figure { color: var(--redline); }
.rail-because { font-size: 0.8125rem; color: var(--graphite); margin: var(--sp-8) 0 0; }

@media (max-width: 40rem) {
  .stage-row { grid-template-columns: 1fr 4.5rem; }
  .stage-name { grid-column: 1 / -1; }
  .stage-note { grid-column: 1 / -1; }
}

/* ================================================== a form behind a button

   Austin, 08.09.2026: "i want to press a button then a screen pops up to add
   something ... and that's a principle for all of the ui we build on the
   portal." A native <dialog>, paper on an ink wash, hard-cornered like every
   other surface here. Opened modally by portal.js; rendered inline and open
   for a reader with scripts blocked, in which case it sits in the flow as a
   bordered panel rather than floating over nothing. */

.modal {
  width: min(38rem, calc(100vw - 2rem));
  max-height: calc(100dvh - 2rem);
  overflow: auto;
  margin: auto;
  padding: var(--sp-24);
  border: 1px solid var(--ink);
  border-radius: 0;
  background: var(--paper);
  color: var(--ink);
  box-shadow: 0 2px 4px rgb(20 17 16 / 0.08), 0 24px 64px rgb(20 17 16 / 0.22);
}

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

.modal::backdrop { background: rgb(20 17 16 / 0.55); }

/* The no-script fallback: open, but not modal, so it takes its place in the flow. */
.modal[open]:not(:modal) {
  position: static;
  margin: var(--sp-24) 0 0;
  box-shadow: none;
}

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

.modal-head .serif-h2 { margin: 0; }

.modal-close {
  flex: 0 0 auto;
  width: 2.75rem;
  height: 2.75rem;
  border: 1px solid var(--rule-mid);
  background: transparent;
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
  transition: border-color var(--dur) var(--ease);
}

.modal-close:hover { border-color: var(--ink); }

.modal-note { margin: var(--sp-16) 0 0; max-width: 60ch; }

.modal-body { margin-top: var(--sp-24); }

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

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

/* ================================================== the money page (0121)

   The figures first, as a row of small tiles: one label in mono, one figure
   in the display face with tabular digits, one line under it saying what the
   figure is made of. Then the goal as a bar. Everything a person adds sits
   behind a button. */

.figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: var(--sp-16);
}

.figure {
  min-width: 0;
  padding: var(--sp-16) var(--sp-24);
  background: var(--paper-deep);
}

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

.figure-value {
  margin: var(--sp-8) 0 0;
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 1.2rem + 1.2vw, 2rem);
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
  color: var(--ink);
}

.figure-value.is-quiet { color: var(--graphite); font-size: 1.125rem; font-family: var(--font-text-serif); }

.figure-note {
  margin: var(--sp-8) 0 0;
  font-size: 0.8125rem;
  line-height: 1.45;
  color: var(--graphite);
}

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

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

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

.goal-head .serif-h3 { margin: 0; }

.goal-figures {
  margin: var(--sp-8) 0 0;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  color: var(--graphite);
  font-variant-numeric: tabular-nums;
}

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

.goal-bar {
  position: relative;
  height: 6px;
  margin-top: var(--sp-12);
  background: var(--paper-sunk);
  overflow: hidden;
}

.goal-bar > i {
  position: absolute;
  inset: 0 auto 0 0;
  background: var(--ink);
}


.goal[data-verdict="met"] .goal-bar > i,
.goal[data-verdict="ahead"] .goal-bar > i { background: #1e7a38; }
.goal[data-verdict="behind"] .goal-bar > i { background: var(--redline-deep); }

.goal-says {
  margin: var(--sp-12) 0 0;
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--graphite);
  max-width: 64ch;
}

.money-lines td form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-8);
}

.money-lines select { min-width: 11rem; }

.money-lines .btn-quiet { min-height: 2.5rem; padding: var(--sp-4) var(--sp-12); }

.proposal {
  margin: var(--sp-8) 0 0;
  padding: var(--sp-8) var(--sp-12);
  border-left: 2px solid var(--redline-deep);
  background: var(--paper);
  font-size: 0.8125rem;
  line-height: 1.45;
}

.proposal b { font-weight: 600; }

.proposal-by {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--graphite);
}

.proposal form { margin-top: var(--sp-8); }

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

.candidate {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--sp-8) var(--sp-16);
  align-items: start;
  padding: var(--sp-12) var(--sp-16);
  border: 1px solid var(--rule-mid);
  cursor: pointer;
}

.candidate:hover, .candidate:focus-within { border-color: var(--ink); }

.candidate input { margin-top: 0.35rem; }

.candidate-label {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--graphite);
}

.candidate-figure {
  display: block;
  margin-top: var(--sp-4);
  font-family: var(--font-display);
  font-size: 1.375rem;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

.candidate-why { display: block; margin-top: var(--sp-4); font-size: 0.8125rem; line-height: 1.45; color: var(--graphite); }

.share-row { display: grid; grid-template-columns: minmax(0, 1fr) 6rem; gap: var(--sp-8); align-items: end; }

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

.money-toolbar form { margin: 0; }

/* ================================================== the money page's charts

   Austin, 08.09.2026: "some graphs and stuff like that will be ideal." Two,
   because two questions get asked about money every day: are we going to hit
   this month, and is this getting better or worse.

   No inline styles anywhere: the CSP has no unsafe-inline, so geometry lives
   in SVG attributes and colour lives in a class. Same constraint that made the
   funnel bars quantise to whole classes. */

.charts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
  gap: var(--sp-24);
}

.chart {
  margin: 0;
  min-width: 0;
  padding: var(--sp-16) var(--sp-24) var(--sp-12);
  background: var(--paper-deep);
}

.chart-caption {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-8) var(--sp-16);
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--graphite);
}

.chart-caption b {
  color: var(--ink);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}

.chart-svg {
  display: block;
  width: 100%;
  height: 6rem;
  margin-top: var(--sp-12);
  overflow: visible;
}

.chart-axis {
  stroke: var(--rule-mid);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

/* The flat line the goal needs. Dashed, because it is a claim about the rest
   of the month rather than something that happened. */
.chart-pace {
  stroke: var(--graphite);
  stroke-width: 1;
  stroke-dasharray: 3 3;
  vector-effect: non-scaling-stroke;
}

.chart-area {
  fill: rgb(204 46 37 / 0.1);
  stroke: none;
}

.chart-line {
  fill: none;
  stroke: var(--redline-deep);
  stroke-width: 1.5;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

.chart-dot {
  fill: var(--redline-deep);
  stroke: var(--paper-deep);
  stroke-width: 1;
}

.chart-bar { stroke: none; }
.chart-bar.is-in { fill: var(--ink); }
.chart-bar.is-out { fill: var(--redline-deep); }

.chart-goal {
  stroke: var(--graphite);
  stroke-width: 1.5;
  stroke-dasharray: 2 2;
  vector-effect: non-scaling-stroke;
}

.chart-tick {
  fill: var(--graphite);
  font-family: var(--font-mono);
  font-size: 8px;
  letter-spacing: 0.06em;
}

.chart-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-4) var(--sp-16);
  margin: var(--sp-12) 0 0;
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.08em;
  color: var(--graphite);
}

.chart-key {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-8);
  text-transform: uppercase;
}

.chart-key::before {
  content: "";
  width: 0.75rem;
  height: 0.5rem;
  background: var(--ink);
}

.chart-key.is-out::before { background: var(--redline-deep); }
.chart-key.is-goal::before { background: transparent; border-top: 2px dashed var(--graphite); height: 0; }

/* ================================================== talking to the agent

   Austin, 08.09.2026: "a specific chat to talk to an agent for money ect per
   each section." Two column-widths of prose, the question set apart from the
   answer by ground rather than by a bubble, and the lookups behind an answer
   openable underneath it. The lookups are not a debug view: they are what
   makes "our code produced every figure" checkable by the person reading. */

.chat {
  display: grid;
  gap: var(--sp-16);
  margin-bottom: var(--sp-24);
}

.chat-turn {
  padding: var(--sp-16) var(--sp-24);
  border-left: 2px solid var(--rule-mid);
}

.chat-turn.is-asked {
  border-left-color: var(--ink);
  background: var(--paper-deep);
}

.chat-turn.is-answered {
  border-left-color: var(--redline-deep);
}

.chat-who {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--graphite);
}

.chat-body {
  margin: var(--sp-8) 0 0;
  font-family: var(--font-text-serif);
  font-size: 1rem;
  line-height: 1.6;
  color: var(--ink);
  max-width: 68ch;
}

.chat-body + .chat-body { margin-top: var(--sp-12); }

.chat-steps {
  margin-top: var(--sp-16);
  border-top: 1px solid var(--rule);
  padding-top: var(--sp-12);
}

.chat-steps > summary {
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--graphite);
}

.chat-steps > summary:hover { color: var(--ink); }

.chat-steps ul {
  margin: var(--sp-12) 0 0;
  padding-left: var(--sp-16);
  display: grid;
  gap: var(--sp-8);
  font-size: 0.875rem;
  line-height: 1.5;
}

.chat-ms {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.08em;
  color: var(--graphite);
  margin-left: var(--sp-8);
}

.chat-ask textarea {
  width: 100%;
  font-family: var(--font-sans);
  font-size: 1rem;
  line-height: 1.5;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--rule-mid);
  border-radius: 0;
  padding: var(--sp-12) var(--sp-16);
  resize: vertical;
}

.chat-ask textarea:focus {
  outline: none;
  border-color: var(--ink);
  box-shadow: 0 0 0 3px rgb(20 17 16 / 0.08);
}

.chat-openers {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-8);
  margin-top: var(--sp-16);
}

.chat-opener { margin: 0; }

.chat-opener .btn-quiet {
  min-height: 2.5rem;
  padding: var(--sp-8) var(--sp-16);
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  letter-spacing: 0;
  text-transform: none;
}

/* ------------------------------------------------- the assistant in the rail

   Austin, 08.09.2026: "for the portal we make verry litle use of the left dise
   of the screen", and then "i want icon for ther chat in bottom left".

   The rail carries the DOOR and the page carries the conversation. 15.25rem is
   enough for a nav item and nowhere near enough to read an answer in: a
   sentence with a figure in it wraps to six lines at that width. */

/* STUCK TO THE FLOOR OF THE RAIL, not to the end of its content.
   Measured before this was written: at 1440x900 the spine is taller than the
   viewport (eleven nav items, the switcher, three clients and the account
   block), `.rail` scrolls, and a launcher placed at the end of the flow sat
   about 120px below the fold. "Bottom left" has to mean the corner of the
   SCREEN, or it is a button nobody finds without scrolling a nav they have no
   reason to scroll.

   The ink ground is repeated here because the block now floats over whatever
   it is covering, and `.frame`'s gradient is behind the rail rather than in
   it. */
.rail-chat {
  flex: none;
  padding: var(--sp-12) var(--sp-16);
  border-top: 1px solid var(--rule-dark);
  border-bottom: 0;
}

.chat-launch {
  display: flex;
  align-items: center;
  gap: var(--sp-8);
  padding: var(--sp-8) var(--sp-12);
  margin: 0 calc(var(--sp-12) * -1);
  border-left: 2px solid transparent;
  color: var(--cream-dim);
  font-size: 0.8125rem;
  text-decoration: none;
  transition: color var(--dur) var(--ease), background-color var(--dur) var(--ease),
    border-color var(--dur) var(--ease);
}

.chat-launch:hover {
  color: var(--cream);
  background: rgb(255 255 255 / 0.04);
}

/* Pressed. The rail is ink, so the affordance is the seam going redline rather
   than a shadow nobody would see on it. */
.chat-launch:active {
  background: rgb(255 255 255 / 0.08);
}

.chat-launch[aria-current="true"] {
  color: var(--cream);
  border-left-color: var(--redline);
  background: rgb(255 255 255 / 0.06);
}

.chat-icon {
  width: 1.125rem;
  height: 1.125rem;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linejoin: round;
}

.chat-launch-label {
  flex: 1 1 auto;
}

/* How many decisions are waiting. A number rather than a dot, because "3" is
   the difference between opening it now and opening it later, and a dot makes
   everybody open it to find out. */
.chat-waiting {
  flex: none;
  min-width: 1.25rem;
  padding: 0 var(--sp-4);
  border-radius: 999px;
  background: var(--redline);
  color: var(--paper);
  font-family: var(--font-mono);
  font-size: 0.625rem;
  line-height: 1.25rem;
  text-align: center;
}

/* ------------------------------------------------------------- the panel

   A <dialog open> rendered by the server, docked to the RIGHT EDGE rather than
   floating over the middle of the page.

   Austin, 09.09.2026: "i want it on the right side." It was docked beside the
   rail first, on the reasoning that a panel should open where its button is.
   The right edge is better and the reason is the layout: the spine is on the
   left and the conversation is on the right, so the page keeps the whole middle
   and nothing it draws is ever underneath either of them. The icon stays where
   he asked for it, bottom left.

   `position: fixed` here deliberately overrides `.modal[open]:not(:modal)`,
   which lays a server-rendered dialog into the flow. That fallback is right for
   a form, whose page has a place for it; this has no place in the grid. */
.chat-panel {
  inset: 0 0 0 auto;
  z-index: 40;
  width: min(27rem, calc(100vw - var(--rail) - 3rem));
  /* `height` STATED, because a dialog will not stretch without it. Chrome's
     own stylesheet gives every <dialog> `height: fit-content`, which wins
     against `inset`, and the panel sized itself to its conversation and left a
     band of page showing under it. */
  height: 100dvh;
  max-height: none;
  border: 0;
  border-left: 1px solid var(--ink);
  display: flex;
  flex-direction: column;
  /* `.modal` scrolls itself. Here the head is fixed and only the body moves,
     so a scrollbar on the panel as well is two bars for one overflow. */
  overflow: hidden;
}

/* THE PAGE MAKES ROOM, rather than being covered.
   Measured: at 27rem the panel sat exactly on top of the page's content, which
   defeats the reason it is docked rather than modal. A founder asking "is that
   right" wants the number still in front of them.

   The whole reason the panel is a page STATE and not a script is what makes
   this possible: the shell knows on render whether it is open, so the room is
   one attribute and no measuring. */
.frame[data-assistant="open"] .canvas-inner {
  padding-right: calc(min(27rem, 100vw - var(--rail) - 3rem) + var(--sp-32));
}

/* The panel's close is a LINK, because closing it is a navigation back to the
   page underneath rather than a script hiding an element. An anchor does not
   centre its own text the way a button does, so it says so here. */
.chat-panel .modal-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
}

/* THE SPECIFICITY HERE IS DELIBERATE, and it cost a render to find.
   `.modal[open]:not(:modal)` above lays a server-rendered dialog into the flow,
   which is the right fallback for a form on a page that has a place for it. It
   is 0,3,0 and it beat a plain `.chat-panel`, so the panel rendered with
   `position: static` inside a two-column grid and was simply not visible on the
   page: correct markup, `open` attribute present, nothing on screen.

   Matching its shape rather than reaching for `!important` keeps the next
   person able to override this in turn. */
.chat-panel[open]:not(:modal) {
  position: fixed;
  margin: 0;
  box-shadow: -12px 0 32px rgb(20 17 16 / 0.14);
}

.chat-panel .modal-body {
  flex: 1 1 auto;
  overflow-y: auto;
  margin-top: var(--sp-16);
}

.chat-panel .chat-turn {
  padding: var(--sp-12) var(--sp-16);
}

/* The buttons under the box, and under a proposal. One row, wrapping, so a
   narrow panel does not put "Start a new one" half off the edge. */
.chat-tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-8);
}

.chat-tools form {
  margin: 0;
}

.chat-recent {
  margin-top: var(--sp-24);
  font-size: 0.8125rem;
}

/* ------------------------------------------------ what it wants to do

   Above the conversation, not inside it. A decision waiting is not a message:
   it does not scroll away with the chat it came out of, and somebody opening
   the panel for an unrelated question should still see that something is
   waiting on them. */
.chat-waiting-list {
  display: grid;
  gap: var(--sp-12);
  margin-top: var(--sp-16);
}

.chat-proposal {
  padding: var(--sp-16);
  border: 1px solid var(--ink);
  border-left: 2px solid var(--redline);
  background: var(--paper-deep);
}

.chat-proposal-says {
  margin: 0 0 var(--sp-12);
  font-family: var(--font-text-serif);
  font-size: 1rem;
  line-height: 1.5;
  color: var(--ink);
}

@media (max-width: 63.9375rem) {
  /* The rail is a header here, so there is nothing to dock beside. A sheet up
     from the floor, which is where the thumb already is. */
  /* No room made: the panel is a sheet up from the floor here, so it covers
     the bottom of the page rather than the side of it, and 100vw of padding
     would leave nothing to read. */
  .frame[data-assistant="open"] .canvas-inner {
    padding-right: var(--sp-24);
  }

  .chat-panel {
    inset: auto 0 0 0;
    width: 100%;
    height: auto;
    max-height: 85dvh;
    border-left: 0;
    border-top: 2px solid var(--redline);
  }

  .chat-panel[open]:not(:modal) {
    box-shadow: 0 -12px 32px rgb(20 17 16 / 0.18);
  }

  /* In the header the launcher is one item among the utility row rather than a
     corner, so it keeps its own line and reads as a control. Not sticky: the
     rail is in the flow here, and a sticky child of a static parent would pin
     itself to the bottom of the header and travel with it. */
  .rail-chat {
    border-top: 1px solid var(--rule-dark);
  }
}

/* ── THE CUTTING ROOM ──────────────────────────────────────────────────────
   Austin, 09.09.2026: "a besic ui for the editor will be good so we can keep
   uo do date of where they are."

   One block per episode, and the blocker sits directly under the heading. The
   drafts are cards rather than table rows because each one carries three
   sentences a person has to read before pressing anything, and a table row
   that holds a paragraph is a table nobody scans. */
.cut-list { display: grid; gap: 1.6rem; }
.cut-episode { border: 1px solid var(--rule); border-radius: 8px; padding: 1.1rem 1.2rem 1.2rem; }
.cut-episode-head { display: flex; flex-wrap: wrap; gap: 0.6rem 1rem; align-items: flex-start;
  justify-content: space-between; }
.cut-episode-head .serif-h2 { margin: 0; }
.cut-episode-meta { margin: 0.2rem 0 0; font-family: var(--font-mono); font-size: 0.78rem;
  letter-spacing: 0.04em; color: var(--graphite); }

/* The one line the page exists for. Left rule rather than a filled panel: it
   is a sentence to read, not a figure to weigh. */
.cut-next { margin: 0.8rem 0 0; padding-left: 0.8rem; border-left: 3px solid var(--redline);
  font-size: 1.02rem; line-height: 1.45; }

.cut-tracks { list-style: none; margin: 0.9rem 0 0; padding: 0; display: grid; gap: 0.4rem; }
.cut-track { display: flex; flex-wrap: wrap; gap: 0.4rem 0.7rem; align-items: baseline; }
.cut-track-name { font-weight: 500; }
.cut-track-meta { font-family: var(--font-mono); font-size: 0.78rem; color: var(--graphite); }

.cut-counts { margin: 0.8rem 0 0; font-family: var(--font-mono); font-size: 0.8rem;
  letter-spacing: 0.04em; color: var(--graphite); }

.cut-picks { display: grid; gap: 0.9rem; margin-top: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr)); }
.cut-pick { border: 1px solid var(--rule); border-radius: 6px; padding: 0.9rem 1rem;
  display: flex; flex-direction: column; gap: 0.45rem; }
/* A decided card is still readable and stops competing. Opacity rather than a
   colour, so it works the same on both surfaces. */
.cut-pick[data-pick-state="dropped"] { opacity: 0.62; }
.cut-pick-head { display: flex; flex-wrap: wrap; gap: 0.4rem 0.6rem; align-items: baseline; }
.cut-pick-score { font-family: var(--font-mono); font-size: 0.82rem; font-weight: 600; }
.cut-pick-meta { font-family: var(--font-mono); font-size: 0.76rem; color: var(--graphite); }
.cut-pick-title { margin: 0; font-size: 1.02rem; font-weight: 600; letter-spacing: -0.01em; }
/* The words that go on screen, shown the way they will look: capitals and
   heavy, so a card that reads badly at that weight reads badly here too. */
.cut-pick-card { margin: 0; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.02em; font-size: 0.9rem; }
.cut-pick-hook { margin: 0; font-size: 0.94rem; line-height: 1.45; }
.cut-pick-why { margin: 0; font-size: 0.86rem; color: var(--graphite); }
.cut-pick-verdict { margin: 0; font-size: 0.86rem; padding-left: 0.6rem;
  border-left: 2px solid var(--rule); color: var(--graphite); }
.cut-pick-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.2rem; }
.cut-pick-actions form { margin: 0; }

/* ── THE CLIPPING ROOM ───────────────────────────────────────────────────────
   Austin, 11.09.2026, on the six-column board: "the cutting room UI is bad",
   scrolling fast "messes up with the left-hand side", "a display that does not
   require endless scrolling", "usable on mobile as well". Measured before it
   was replaced: 17,616px tall on a phone, 1,824px of columns in a 1,216px
   canvas, a player on every card. This is a contact sheet, one pane, and a
   filter strip. Every class is prefixed cr-.

   The filter strip: count chips you can press. Bigger than a .chip, because a
   .chip is 30px tall and this is the primary control on a phone. */
.cr-filters {
  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);
}
.cr-filter {
  display: inline-flex; align-items: center; gap: var(--sp-8);
  min-height: 2.75rem; padding: var(--sp-8) var(--sp-12);
  font-family: var(--font-mono); font-size: 0.6875rem; letter-spacing: 0.1em;
  text-transform: uppercase; text-decoration: none; white-space: nowrap;
  color: var(--graphite); border: 1px solid var(--rule-mid); background: transparent;
  font-variant-numeric: tabular-nums;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}
.cr-filter b { font-weight: 600; color: var(--ink); }
.cr-filter:hover { border-color: var(--ink); color: var(--ink); }
.cr-filter[aria-current="true"] { background: var(--ink); border-color: var(--ink); color: var(--cream); }
.cr-filter[aria-current="true"] b { color: var(--cream); }
.cr-filter[data-show="ready"]:not([aria-current="true"]) b { color: var(--redline-deep); }
.cr-filter[data-zero="true"] { opacity: 0.55; }
/* One row on a phone, scrolling, the way every chip strip a person already
   knows behaves. Seven chips wrapped is three rows of chrome before the first
   clip. The fade at the right edge is the affordance that it continues, the
   same mask the rail's own nav uses. */
@media (max-width: 47.9375rem) {
  .cr-filters {
    flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; overscroll-behavior-x: contain;
    margin-inline: calc(-1 * var(--sp-24)); padding-inline: var(--sp-24);
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 2.5rem), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 2.5rem), transparent);
  }
  .cr-filters::-webkit-scrollbar { display: none; }
  .cr-filter { flex: none; }
}

/* What is waiting, before any scrolling. */
.cr-next-up {
  display: grid; grid-template-columns: 3.25rem minmax(0, 1fr) auto; gap: var(--sp-12); align-items: center;
  border: 1px solid var(--ink); background: var(--paper-deep); padding: var(--sp-12);
  text-decoration: none; color: var(--ink); margin-top: var(--sp-24);
}
.cr-next-up img, .cr-next-up-blank { width: 3.25rem; aspect-ratio: 9 / 16; object-fit: cover; display: block; background: #08090b; }
.cr-next-up-label { display: block; font-family: var(--font-mono); font-size: 0.625rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--redline-deep); }
.cr-next-up-title { display: block; margin-top: 2px; font-weight: 600; font-size: 0.95rem; line-height: 1.3; }
.cr-next-up .btn { min-height: 2.75rem; padding-inline: var(--sp-16); }
.cr-next-up:hover { border-color: var(--redline-deep); }
@media (max-width: 40rem) {
  .cr-next-up { grid-template-columns: 3.25rem minmax(0, 1fr); }
  .cr-next-up .btn { grid-column: 1 / -1; }
}
.cr-next-none {
  margin: var(--sp-24) 0 0; padding: var(--sp-16) var(--sp-24); border: 1px solid var(--rule-mid);
  color: var(--graphite); font-size: 0.9375rem;
}

/* The contact sheet: one block per episode. The strip scrolls sideways INSIDE
   the block, inset from the viewport edge so an edge-swipe on iOS still means
   "back". The page body never scrolls horizontally. */
.cr-sheet { display: grid; gap: var(--sp-32); margin-top: var(--sp-32); }
.cr-sheet .section-head { margin-bottom: 0; }
.cr-ep { display: grid; gap: var(--sp-12); min-width: 0; }
.cr-ep-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-4) var(--sp-16); }
.cr-ep-title { margin: 0; font-size: 1.02rem; font-weight: 600; letter-spacing: -0.01em; }
.cr-ep-title a { color: inherit; text-decoration: none; }
.cr-ep-title a:hover { text-decoration: underline; text-underline-offset: 0.2em; }
.cr-ep-num {
  font-family: var(--font-mono); font-size: 0.72rem; font-weight: 400; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--graphite); margin-right: var(--sp-8);
}
.cr-ep-tally {
  margin: 0 0 0 auto; font-family: var(--font-mono); font-size: 0.72rem; color: var(--graphite);
  font-variant-numeric: tabular-nums; letter-spacing: 0.02em;
}
.cr-ep-tally b { color: var(--redline-deep); font-weight: 600; }
/* The progress bar: one cell per clip, coloured by state, in strip order.
   Beside the title from 40rem and on its own line under it on a phone, where
   a bar squeezed beside a tally is two things nobody can read. */
.cr-bar { display: flex; gap: 2px; height: 6px; flex: 1 1 8rem; max-width: 18rem; align-self: center; }
.cr-bar i { flex: 1 1 0; background: var(--paper-sunk); }
.cr-bar i[data-s="to-watch"] { background: var(--redline); }
.cr-bar i[data-s="kept"] { background: var(--ink); }
.cr-bar i[data-s="held"] { background: #e8a33d; }
.cr-bar i[data-s="changes"] { background: var(--redline-lift); }
.cr-bar i[data-s="binned"] { background: var(--rule-mid); }
.cr-bar i[data-s="cutting"] { background: transparent; border: 1px dashed var(--rule-mid); }
@media (max-width: 39.9375rem) { .cr-bar { flex-basis: 100%; max-width: none; order: 3; margin-top: var(--sp-4); } }

.cr-strip {
  display: flex; gap: var(--sp-12);
  overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x proximity; scrollbar-width: thin;
  padding: var(--sp-4) var(--sp-4) var(--sp-12);
  margin: 0 calc(-1 * var(--sp-4));
}
/* 6.5rem is 104px wide and 185 tall: over the 48px touch floor with room. */
.cr-frame {
  position: relative; flex: none; width: 6.5rem;
  display: grid; gap: var(--sp-4); align-content: start;
  color: var(--ink); text-decoration: none; scroll-snap-align: start;
}
.cr-frame-pic {
  position: relative; display: block; aspect-ratio: 9 / 16; width: 100%;
  background: #08090b; border: 1px solid var(--rule-mid); overflow: hidden;
}
.cr-frame-pic img { width: 100%; height: 100%; object-fit: cover; display: block; }
.cr-frame:hover .cr-frame-pic, .cr-frame:focus-visible .cr-frame-pic { border-color: var(--ink); }
/* State by form, not only by word: an amber tag, ink for kept, a redline
   strike for binned, a dashed empty frame for one the box still owes. The tag
   carries a word, which is what keeps it from being decoration. */
.cr-frame-tag {
  position: absolute; left: 0; top: 0; padding: 2px 6px;
  font-family: var(--font-mono); font-size: 0.5625rem; font-weight: 600;
  letter-spacing: 0.12em; text-transform: uppercase; line-height: 1.5;
  background: var(--ink); color: var(--cream);
}
.cr-frame[data-s="held"] .cr-frame-tag { background: #e8a33d; color: var(--ink); }
.cr-frame[data-s="changes"] .cr-frame-tag { background: var(--redline-deep); }
.cr-frame[data-s="binned"] .cr-frame-pic { opacity: 0.45; }
.cr-frame[data-s="binned"] .cr-frame-pic::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(to top right, transparent calc(50% - 1px), var(--redline) calc(50% - 1px), var(--redline) calc(50% + 1px), transparent calc(50% + 1px));
}
.cr-frame[data-s="cutting"] .cr-frame-pic { background: transparent; border-style: dashed; }
.cr-frame[data-s="cutting"] .cr-frame-pic::after {
  content: "cutting"; position: absolute; inset: 0; display: grid; place-items: center;
  font-family: var(--font-mono); font-size: 0.625rem; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--graphite);
}
/* Dimmed, not hidden, outside the lit filter: a filter that removes frames
   makes an episode look smaller than it is. */
.cr-frame[data-dim="true"] { opacity: 0.32; }
.cr-frame[data-dim="true"]:hover, .cr-frame[data-dim="true"]:focus-visible { opacity: 0.8; }
.cr-frame-score { font-family: var(--font-mono); font-size: 0.72rem; color: var(--graphite); font-variant-numeric: tabular-nums; }
.cr-frame-score b { color: var(--ink); font-weight: 600; }
.cr-frame-title {
  font-size: 0.78rem; line-height: 1.3; margin: 0;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

/* An episode upstream of the sheet: no strip, one sentence, one button. */
.cr-ep-next { margin: 0; padding-left: var(--sp-12); border-left: 3px solid var(--redline); font-size: 0.94rem; line-height: 1.5; }
.cr-ep-meta { margin: 0; font-family: var(--font-mono); font-size: 0.78rem; color: var(--graphite); }
.cr-ep-go { display: flex; flex-wrap: wrap; gap: var(--sp-12); align-items: flex-end; margin: 0; }
/* Tighter than a block with a strip: these are lines of prose, not frames. */
.cr-ep-upstream { gap: var(--sp-8); }

/* The pane: the ONLY place a clip plays and the ONLY place a verdict is given.
   A native dialog, like every other form behind a button here. Full screen on
   a phone; two columns from 48rem, player left, words and buttons right. */
.cr-review {
  width: min(64rem, 100vw); max-width: none; max-height: 100dvh; height: 100dvh;
  margin: auto; padding: 0; border: 0; background: var(--paper); color: var(--ink);
  overflow: hidden;
}
@media (min-width: 48rem) {
  .cr-review {
    /* fit-content, not auto: a modal dialog is positioned with inset-block: 0
       by the browser, so auto means "as tall as the screen". */
    width: min(64rem, calc(100vw - 4rem)); height: fit-content; max-height: calc(100dvh - 3rem);
    border: 1px solid var(--ink);
    box-shadow: 0 2px 4px rgb(20 17 16 / 0.08), 0 24px 64px rgb(20 17 16 / 0.22);
  }
}
.cr-review::backdrop { background: rgb(20 17 16 / 0.62); }
/* The no-script fallback: open, but not modal, so it takes its place in the
   flow under the sheet, the same way .modal does. */
.cr-review[open]:not(:modal) {
  position: static; width: 100%; height: auto; max-height: none;
  margin: var(--sp-24) 0 0; border: 1px solid var(--ink); box-shadow: none; overflow: visible;
}
.cr-review[open]:not(:modal) .cr-review-grid { max-height: none; height: auto; }
.cr-review-grid {
  /* Four blocks, four rows: head, stage, words, verdict. minmax(0, 1fr) for
     the column and not auto: an auto track grows to the widest item's
     min-content, and the head's one-line caption made the pane 556px wide on
     a 390px phone. Measured, not guessed. */
  display: grid; grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto auto minmax(0, 1fr) auto; height: 100%; max-height: inherit;
}
@media (min-width: 48rem) {
  .cr-review-grid {
    grid-template-columns: 20rem minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr) auto;
    grid-template-areas: "head head" "stage body" "stage actions";
    max-height: calc(100dvh - 3rem);
  }
  /* Scoped to the pane's own grid: a named area leaks into any other grid
     these classes are reused in. */
  .cr-review-grid > .cr-review-head { grid-area: head; }
  .cr-review-grid > .cr-stage { grid-area: stage; align-items: start; }
  .cr-review-grid > .cr-review-body { grid-area: body; }
  .cr-review-grid > .cr-review-actions { grid-area: actions; }
}
.cr-review-head {
  display: flex; align-items: center; gap: var(--sp-8); min-width: 0;
  padding: var(--sp-8) var(--sp-12) var(--sp-8) var(--sp-16);
  border-bottom: 1px solid var(--rule); background: var(--paper);
}
.cr-review-where {
  flex: 1 1 0; 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;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cr-review-where b { color: var(--ink); font-weight: 600; }
.cr-step {
  flex: none; display: inline-grid; place-items: center; width: 2.75rem; height: 2.75rem;
  border: 1px solid var(--rule-mid); background: transparent; color: var(--ink);
  text-decoration: none; font-family: var(--font-sans); font-size: 1.1rem; line-height: 1;
  cursor: pointer; transition: border-color var(--dur) var(--ease);
}
.cr-step:hover { border-color: var(--ink); }
.cr-step[aria-disabled="true"] { opacity: 0.35; cursor: default; }
.cr-stage { background: #08090b; display: grid; place-items: center; min-height: 0; }
.cr-video, .cr-still { display: block; width: 100%; max-height: 48dvh; aspect-ratio: 9 / 16; object-fit: contain; background: #08090b; }
@media (min-width: 48rem) {
  /* Sized by its own 9:16 and capped by the screen, never stretched to the
     pane: height: 100% resolved against the pane's max-height and drew a
     626px video in an 852px well with black above and below it. */
  .cr-review-grid .cr-video, .cr-review-grid .cr-still { max-height: min(calc(100dvh - 6.5rem), 40rem); }
}
.cr-stage-says { margin: 0; padding: var(--sp-32) var(--sp-24); color: var(--cream-dim); font-family: var(--font-mono); font-size: 0.75rem; letter-spacing: 0.08em; text-transform: uppercase; text-align: center; }
.cr-review-body {
  padding: var(--sp-16); overflow-y: auto; min-height: 0;
  display: grid; gap: var(--sp-12); align-content: start;
}
@media (min-width: 48rem) { .cr-review-body { padding: var(--sp-24); gap: var(--sp-16); } }
.cr-review-title { margin: 0; font-size: 1.15rem; font-weight: 600; letter-spacing: -0.012em; line-height: 1.3; font-family: var(--font-sans); }
.cr-review-card { margin: 0; font-weight: 700; text-transform: uppercase; font-size: 0.76rem; letter-spacing: 0.06em; color: var(--redline-press); }
.cr-review-hook { margin: 0; font-size: 0.94rem; line-height: 1.5; font-family: var(--font-text-serif); }
.cr-review-meta {
  margin: 0; font-family: var(--font-mono); font-size: 0.72rem; color: var(--graphite);
  display: flex; flex-wrap: wrap; gap: var(--sp-4) var(--sp-12); font-variant-numeric: tabular-nums;
}
.cr-review-meta b { color: var(--ink); }
.cr-review-audit { margin: 0; }
/* What was measured, folded under its one-line verdict. Unmet in red. */
.cr-footage { font-size: 0.84rem; color: var(--graphite); margin: var(--sp-8) 0 0; }
.cr-footage a { color: inherit; text-decoration: underline; text-underline-offset: 0.15em; }
.cr-readings { font-size: 0.84rem; }
.cr-readings summary { cursor: pointer; list-style: none; display: inline-flex; align-items: center; gap: var(--sp-8); min-height: 2.75rem; }
.cr-readings summary::-webkit-details-marker { display: none; }
.cr-readings summary::after { content: "▾"; color: var(--graphite); transition: transform var(--dur) var(--ease); }
.cr-readings[open] summary::after { transform: rotate(180deg); }
.cr-readings ul { margin: var(--sp-4) 0 0; padding-left: var(--sp-16); display: grid; gap: var(--sp-4); }
.cr-readings li[data-met="false"] { color: var(--redline-press); }
.cr-readings li[data-met="unknown"] { color: var(--graphite); }
/* Why a clip is held: every reason, not the first one. A clip refused for four
   things and told about one is a clip somebody fixes four times. */
.cr-held { border-left: 3px solid var(--redline-press); padding-left: var(--sp-12); }
.cr-held-lead { margin: 0 0 var(--sp-4); font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--redline-press); }
.cr-held ul { margin: 0; padding-left: var(--sp-16); font-size: 0.84rem; display: grid; gap: var(--sp-4); }
.cr-asked { margin: 0; padding-left: var(--sp-12); border-left: 2px solid var(--redline); font-size: 0.86rem; line-height: 1.45; }
.cr-verdict { margin: 0; padding-left: var(--sp-12); border-left: 2px solid var(--rule); font-size: 0.86rem; color: var(--graphite); }
.cr-notes { list-style: none; margin: var(--sp-8) 0 0; padding: 0; display: grid; gap: var(--sp-8); }
.cr-note { display: grid; gap: var(--sp-4); font-size: 0.82rem; }
.cr-note-body { line-height: 1.45; }
.cr-note-change { font-family: var(--font-mono); font-size: 0.74rem; color: var(--redline-press); }
.cr-note-done { font-family: var(--font-mono); font-size: 0.74rem; color: var(--graphite); }
/* The verdict: pinned to the bottom, Keep the whole width of the bottom row.
   On a phone the bottom edge is the easiest reach there is and it is the
   press made most often. The escape hatch sits above the buttons, small, at
   the right: read before a verdict, pressed less often than any of them. */
.cr-review-actions {
  position: sticky; bottom: 0; background: var(--paper); border-top: 1px solid var(--rule);
  padding: var(--sp-12) var(--sp-16) calc(var(--sp-12) + env(safe-area-inset-bottom));
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-8);
}
@media (min-width: 48rem) { .cr-review-actions { padding-inline: var(--sp-24); } }
.cr-review-actions .btn { min-height: 3rem; padding-inline: var(--sp-12); font-size: 0.7rem; }
.cr-review-actions form { margin: 0; display: contents; }
.cr-review-actions .cr-primary { grid-column: 1 / -1; }
.cr-review-actions .btn-quiet { color: var(--ink); }
/* The row above the answers: saving the file on the left, the fault on the right. Neither is a verdict. */
.cr-review-utility {
  grid-column: 1 / -1; order: -1; display: flex; align-items: center; justify-content: space-between; gap: var(--sp-16);
}
.cr-bad, .cr-save {
  grid-column: 1 / -1; order: -1; justify-self: end; min-height: 2.25rem; margin-inline-start: auto;
  display: inline-flex; align-items: center; border: 0; background: transparent;
  color: var(--graphite); text-decoration: underline; text-underline-offset: 0.25em;
  font-family: var(--font-mono); font-size: 0.6875rem; letter-spacing: 0.1em; text-transform: uppercase;
  cursor: pointer;
}
.cr-bad:hover { color: var(--redline-deep); }
.cr-save { margin-inline-start: 0; color: var(--ink); }
.cr-save:hover { color: var(--redline-deep); }
/* The change form: two forms in one box, a lead over each, a rule between. */
.cr-form-lead { margin: 0; font-family: var(--font-mono); font-size: 0.6875rem; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase; color: var(--graphite); }
.cr-trims { display: grid; gap: var(--sp-16); }
@media (min-width: 40rem) { .cr-trims { grid-template-columns: 1fr 1fr; } }
.cr-form-rule { border: 0; border-top: 1px solid var(--rule-mid); margin: var(--sp-24) 0; }

/* ── WHAT THE EDITOR HAS BEEN TAUGHT ──────────────────────────────────────
   A lesson is read as: the rule first, then who has to fix it, then the words
   that produced it. The rule carries the weight because it is the half that
   survives the clip, and the quote sits under it in the speaking voice so the
   two are never mistaken for each other. */
.lesson-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-16); }
.lesson-row {
  border: 1px solid var(--rule); border-radius: 6px;
  padding: var(--sp-16); background: var(--paper);
  display: grid; gap: var(--sp-8);
}
/* Closed lessons sit back rather than disappear: the history is the evidence
   that the loop is closing things, and hiding it would leave only the open
   list, which is the half that looks like failure. */
.lesson-row[data-retired="yes"] { background: var(--shell); border-style: dashed; }
.lesson-cause { margin: 0; font-weight: 700; font-size: 0.98rem; line-height: 1.35; }
.lesson-meta { margin: 0; display: flex; flex-wrap: wrap; gap: var(--sp-8); align-items: center; }
.lesson-reading, .lesson-when { font-family: var(--font-mono); font-size: 0.74rem; color: var(--graphite); }
.lesson-knew { margin: 0; font-size: 0.82rem; color: var(--graphite); }
.lesson-said { margin: 0; font-size: 0.86rem; font-style: italic; color: var(--ink); }
.lesson-retired { margin: 0; font-family: var(--font-mono); font-size: 0.74rem; color: var(--graphite); }

/* ── A section's own nav bar (#tabs) ─────────────────────────────────────────
   Austin asked for tabs rather than buttons on a landing page, so the whole
   section is reachable from any room in it. Real tabs: a rule under the bar and
   the current one sitting on top of it, which is what makes the row read as a
   set of rooms rather than a row of pills. */
.tabs {
  display: flex;
  flex-wrap: nowrap;
  gap: var(--sp-24);
  /* Space above as well as below: the bar sat hard against the page's own
     standfirst and read as part of the sentence. */
  margin: var(--sp-24) 0 var(--sp-32);
  border-bottom: 1px solid var(--rule-mid);
  /* Scrolls rather than wraps: a wrapped tab bar changes height between pages
     and the content under it jumps. */
  overflow-x: auto;
  scrollbar-width: none;
}

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

.tabs .tab {
  flex: none;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 0 var(--sp-12);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--graphite);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  transition: color 120ms ease, border-color 120ms ease;
}

.tabs .tab:hover {
  color: var(--ink);
}

/* The current room. Driven by `aria-current` and not by a class, so the thing
   a screen reader reads and the thing an eye sees cannot drift apart. */
.tabs .tab[aria-current="page"] {
  color: var(--ink);
  border-bottom-color: var(--redline);
}

.tabs .tab:focus-visible {
  outline: 2px solid var(--redline);
  outline-offset: 3px;
  border-radius: 2px;
}

/* The Proteus order dialog's running total (spec 2026-09-15-proteus-orders-design.md).
   Three figures side by side on a desk, stacked on a phone. */
.order-summary {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-16);
  margin: 0;
}

.order-summary dt {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.02em;
}

.order-summary dd {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 1.125rem;
}

@media (max-width: 480px) {
  .order-summary {
    grid-template-columns: 1fr;
  }
}

/* A Proteus invoice row's links and buttons, side by side and wrapping as a
   group rather than stacking one per line; the number never breaks at its hyphen. */
.invoice-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-8);
}

.invoice-actions form {
  margin: 0;
}

.invoice-number {
  white-space: nowrap;
}

/*
 * THE GUESTS PAGE, WITH THE CUTTING ROOM IN IT (15.09.2026).
 *
 * Austin: "merge guests and episodes and cutting room into one page and
 * structure that properly." Each guest heads their own episodes, so a rule
 * between guests and none inside one keeps the grouping readable on a phone.
 */
.guest-list { display: grid; gap: var(--sp-32); }
.guest-block { display: grid; gap: var(--sp-16); padding-top: var(--sp-24); border-top: 1px solid var(--rule); min-width: 0; }
.guest-list > .guest-block:first-child { padding-top: 0; border-top: 0; }
.guest-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--sp-12) var(--sp-16); }
.guest-name { flex: 1 1 16rem; min-width: 0; color: inherit; text-decoration: none; }
.guest-name .name { margin: 0; font-family: inherit; font-size: 1rem; font-weight: 600; letter-spacing: normal; }
.guest-name .job { margin: 0; color: var(--graphite); font-size: 0.9rem; overflow-wrap: anywhere; }
.guest-name:hover .name { text-decoration: underline; text-underline-offset: 0.2em; }
.guest-block .cr-sheet { margin-top: 0; }

/* The Team page's two tables share their columns, so Current and Left line up
   (15.09.2026). Only where it is a table: below 48rem each row is a record. */
@media (min-width: 48rem) {
  .team-table { table-layout: fixed; }
  .team-col-name { width: 34%; }
  .team-col-part { width: 16%; }
  .team-col-role { width: 26%; }
  .team-col-status { width: 24%; }
}
