/* Tokens live in tokens.css and are the only source of colour, size, space,
   radius, shadow and duration. Nothing in this file carries a literal.

   Written mobile first: the base rules are the phone, and the two min-width
   queries at the end add air rather than taking it away. */

* { box-sizing: border-box; }

/* [hidden] is a user-agent rule, so any author rule that sets display beats it
   and the element stays on screen. Every region below is laid out with flex,
   which is exactly such a rule, so the attribute has to win explicitly. */
[hidden] { display: none !important; }

html {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  overflow-wrap: break-word;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  font-weight: var(--fw-regular);
  letter-spacing: var(--ls-body);
}

::selection { background: var(--accent-line); color: var(--ink); }

/* ============================================================== typography ==
   The hierarchy on every screen reads h2 > h3 > h4, and each step changes
   voice as well as size: the two titles are set in the serif, and h4 drops
   to a mono eyebrow, so a section label never competes with the title above
   it for the same kind of attention.

   The brand in the header is deliberately the quietest piece of type in the
   chrome. It is a lockup, not a page title: the screen's own title is the h2
   below it, and an app that shouts its own name at the reader is one that
   thinks the reader came to see the app rather than their own work. */

h1, h2, h3, h4 { margin: 0; font-weight: var(--fw-semibold); text-wrap: balance; }

h1, h2, h3 { font-family: var(--font-display); font-optical-sizing: auto; }

h1, h2 {
  font-size: var(--fs-heading);
  line-height: var(--lh-heading);
  letter-spacing: var(--ls-heading);
}

h3 {
  font-size: var(--fs-title);
  line-height: var(--lh-title);
  letter-spacing: var(--ls-title);
}

/* The eyebrow: small uppercase mono, used for every label that names what
   sits beneath it. Section labels take the accent; the labels on fields and
   figures stay grey, because there are many of them on a screen and only
   one section at a time. */
h4 {
  font-family: var(--font-mono);
  font-size: var(--fs-eyebrow);
  line-height: var(--lh-label);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--accent);
  font-weight: var(--fw-medium);
}

.field > span, .kind-chip, .loop-summary .k, .repair .k, .drift-pair .k,
.jd-chip .k, .edit-reason, .hero-k, .app-head-company, .status-field > span {
  font-family: var(--font-mono);
  font-size: var(--fs-eyebrow);
  line-height: var(--lh-label);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  font-weight: var(--fw-medium);
  color: var(--ink-3);
}

/* A screen's own title is a masthead: a heavy rule under it separates the
   page from the chrome above in the way a printed heading would, without a
   box around it. Only the first head in a view gets it, never one inside a
   card. */
:is(#home, #loop, #cv-view, #settings-view, #list-section) > :is(.list-head, .detail-head, .app-head):first-child {
  padding-bottom: var(--sp-4);
  border-bottom: 2px solid var(--ink);
}

/* Data rather than prose: timestamps, counts, sizes. */
.seg time, button.ts, .elapsed, .prov, .app-card-foot .row-sub, .edit-actions .state,
.sticky-actions .count, .live-stats, .round-no {
  font-family: var(--font-mono);
}

p { margin: 0; }

/* Running prose is capped at a comfortable measure wherever it appears. Cards
   and grids keep the full width; only what gets read is constrained. */
p, .hint, .note, .empty, .overall-line, .dx-headline { max-width: var(--measure); }

/* Secondary text is differentiated by tone and weight, never by shrinking. */
.row-sub, .hint { font-size: var(--fs-small); line-height: var(--lh-small); color: var(--ink-2); }
/* No reading cap on helper text. The 68ch measure is right for prose somebody
   reads top to bottom, and wrong for two lines explaining the control above
   them: it stopped about two thirds across a panel whose buttons run the full
   width, which reads as a layout fault rather than as a measure. Widening it
   to match body prose was not enough, because the problem was having the cap
   at all. Panels here are at most 960px, so a hint fills one at around a
   hundred characters, and hints are one to three lines. */
.hint, .note { max-width: none; }
.note { font-size: var(--fs-small); line-height: var(--lh-small); color: var(--ink-3); }
.row-title { font-weight: var(--fw-medium); }
.warn-text { color: var(--warn); }
.muted { color: var(--ink-2); }

q { quotes: "\201C" "\201D"; }

/* Anything numeric that lines up in a column. */
.elapsed, .score, .loop-summary .v, .seg time, button.ts, .round-no {
  font-variant-numeric: tabular-nums;
}

/* ================================================================== chrome ==*/

header {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 0 var(--sp-4);
  min-width: 0;
  height: var(--header-h);
  border-bottom: 1px solid var(--line-soft);
  background: var(--ground);
  position: sticky;
  top: 0;
  z-index: 20;
}

@supports (backdrop-filter: blur(1px)) {
  header { background: color-mix(in srgb, var(--ground) 82%, transparent); backdrop-filter: blur(12px); }
}

header h1 {
  font-size: var(--fs-body);
  line-height: 1;
  letter-spacing: var(--ls-title);
  white-space: nowrap;
  color: var(--ink);
  /* The product name never truncates. When the header runs out of room the
     tab strip scrolls instead, because a clipped brand reads as a bug and a
     scrollable strip reads as a strip. */
  flex: none;
}


main {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: var(--sp-5) var(--sp-4) var(--sp-8);
  display: flex;
  flex-direction: column;
  gap: var(--sp-6);
  width: 100%;
}

/* =================================================================== tabs ==*/

nav.tabs.top {
  display: flex;
  gap: var(--sp-1);
  flex: 1 1 auto;
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
}
nav.tabs.top::-webkit-scrollbar { display: none; }
header > button { flex: none; }

/* On a phone the brand, Settings and Sign out leave the strip about 140px to
   work with, which is one tab out of four. The other three sit past the edge
   behind a scrollbar that is deliberately invisible, so the app looks like it
   has one destination. Below this width the strip takes a row of its own and
   shows all four. */
@media (max-width: 40rem) {
  header {
    flex-wrap: wrap;
    height: auto;
    padding-top: var(--sp-2);
    row-gap: var(--sp-2);
  }
  header h1 { margin-right: auto; }
  nav.tabs.top {
    order: 3;
    flex: 1 0 100%;
    gap: var(--sp-3);
  }
}

/* The current tab is marked with a rule under it rather than a filled
   capsule, so the chrome stays line-drawn like the rest of the page. */
.tab {
  flex: none;
  background: none;
  border: none;
  color: var(--ink-2);
  padding: var(--sp-2) var(--sp-3);
  border-radius: 0;
  font: inherit;
  font-size: var(--fs-small);
  font-weight: var(--fw-medium);
  cursor: pointer;
  white-space: nowrap;
  box-shadow: inset 0 -2px 0 transparent;
  transition: box-shadow var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.tab:hover { color: var(--ink); background: none; box-shadow: inset 0 -2px 0 var(--line-strong); }
.tab:active { background: none; }
.tab.active { color: var(--ink); box-shadow: inset 0 -2px 0 var(--accent); }

.tabs:not(.top) { display: flex; gap: var(--sp-1); flex-wrap: wrap; }

/* =============================================================== surfaces ==*/

.card, .panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--sp-5);
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}

/* Panels are spaced by the gap of their parent, not by their own margins.
   Margins are what made twelve inline "margin-top:0" overrides necessary, and
   a margin here would stack on top of that gap. */

/* A flex column stretches its children to the full width. That is right for a
   field and wrong for a button, so buttons opt out. */
.card > button, .panel > button, .card > .actions, .panel > .actions { align-self: flex-start; }
.card > button.big, .panel > button.big { align-self: stretch; }

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

.detail-head {
  display: flex;
  align-items: baseline;
  gap: var(--sp-3);
  flex-wrap: wrap;
}
/* flex: 1 gives the title a basis of zero, so on a narrow screen it is crushed
   into a column a few characters wide by the score and the back link rather
   than pushing them onto the next row. A floor makes it wrap instead. */
.detail-head h3 { flex: 1 1 auto; min-width: min(100%, 16ch); }

/* The debrief head has no title of its own: the sheet it opens in carries the
   name of the interview. What identifies it here is the round and the score,
   which belong together and take the space the title would have, so the
   actions sit on the right the way they do in every other head. */
/* Actions stay on one line and wrap as a group. Individually they broke
   apart, leaving the last one alone on a row looking like it belonged to
   something else. */
.head-actions { display: flex; align-items: baseline; gap: var(--sp-2); flex: 0 0 auto; }

.debrief-id { flex: 1 1 auto; min-width: min(100%, 16ch); display: flex; align-items: baseline; gap: var(--sp-3); }

/* =========================================================== application ==
   One column, read top to bottom. Back is a quiet control pulled flush with
   the left edge of the title, so its text lines up with the words below it
   rather than with the button's padding. */

.app-head { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-1); }
.app-head .back-link { margin: 0 0 var(--sp-3) calc(-1 * var(--sp-2)); }
.app-head-meta {
  display: flex;
  align-items: center;
  gap: var(--sp-2) var(--sp-5);
  flex-wrap: wrap;
  margin-top: var(--sp-3);
  font-size: var(--fs-small);
}
.status-field { display: inline-flex; align-items: center; gap: var(--sp-2); cursor: pointer; }

/* Removing is possible from the page, never adjacent to getting around it. */
.danger-zone {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3) var(--sp-5);
  flex-wrap: wrap;
  padding-top: var(--sp-5);
  border-top: 1px solid var(--line);
}

.section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding-top: var(--sp-5);
  border-top: 1px solid var(--line);
}
.tab-body > .section-head:first-child { border-top: none; padding-top: 0; }

/* ================================================================ buttons ==
   Three tiers. The primary is the only filled accent element on a screen, and
   it is the only control that moves on press, because it is the action worth
   confirming happened. */

button {
  font: inherit;
  font-size: var(--fs-small);
  font-weight: var(--fw-medium);
  line-height: 1.2;
  cursor: pointer;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--ink);
  padding: var(--sp-2) var(--sp-3);
  min-height: 36px;
  white-space: nowrap;
  transition:
    background var(--dur-fast) var(--ease),
    border-color var(--dur-fast) var(--ease),
    color var(--dur-fast) var(--ease),
    box-shadow var(--dur-fast) var(--ease),
    transform var(--dur-fast) var(--ease);
}
button:hover:not(:disabled) { background: var(--sunken); border-color: var(--ink-3); }
button:active:not(:disabled) { background: var(--line-soft); transform: translateY(1px); }
button:disabled { opacity: .45; cursor: not-allowed; }

/* One focus treatment, with a gap, so it reads as designed rather than as a
   browser default fighting the border underneath it. */
:where(button, a, input, textarea, select, [tabindex]):focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

button.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
  font-weight: var(--fw-semibold);
  box-shadow: var(--elev-1);
}
button.primary:hover:not(:disabled) { background: var(--accent-hover); border-color: var(--accent-hover); }
button.primary:active:not(:disabled) { background: var(--accent-hover); box-shadow: none; transform: translateY(1px); }

button.secondary { background: var(--surface); }

button.danger { background: var(--crit); border-color: var(--crit); color: var(--surface); font-weight: var(--fw-semibold); }
button.danger:hover:not(:disabled) { background: var(--crit); filter: brightness(1.08); }

button.link {
  background: none;
  border-color: transparent;
  color: var(--ink-2);
  padding: var(--sp-2);
  min-height: 32px;
}
button.link:hover:not(:disabled) { color: var(--ink); background: var(--sunken); border-color: transparent; }
button.danger-link { color: var(--ink-3); }
button.danger-link:hover:not(:disabled) { color: var(--crit); background: var(--crit-soft); }

button.small { font-size: var(--fs-label); padding: var(--sp-1) var(--sp-2); min-height: 32px; }
button.big { width: 100%; padding: var(--sp-3); font-size: var(--fs-body); min-height: 48px; }

/* A button mid-operation keeps its size, so nothing around it moves. */
button[aria-busy="true"] { color: transparent; position: relative; pointer-events: none; }
button[aria-busy="true"]::after {
  content: "";
  position: absolute;
  inset: 50% auto auto 50%;
  width: 16px; height: 16px;
  margin: -8px 0 0 -8px;
  border-radius: var(--radius-full);
  border: 2px solid var(--ink-3);
  border-top-color: transparent;
  animation: spin .7s linear infinite;
}
button.primary[aria-busy="true"]::after { border-color: var(--accent-ink); border-top-color: transparent; }

/* Links are navigation, not the primary action, so they do not take the accent
   at rest. The underline carries them; the accent arrives on hover. */
a { color: inherit; text-decoration: underline; text-decoration-color: var(--line-strong); text-underline-offset: 2px; }
a:hover { color: var(--accent); text-decoration-color: var(--accent-line); }
a:has(button) { text-decoration: none; display: inline-flex; }

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

/* ================================================================== forms ==*/

.field { display: flex; flex-direction: column; gap: var(--sp-2); }
.field > span em { font-family: var(--font-sans); font-size: var(--fs-label); font-style: normal; color: var(--ink-3); font-weight: var(--fw-regular); text-transform: none; letter-spacing: 0; }

input[type=text], textarea, select {
  width: 100%;
  padding: var(--sp-3);
  border-radius: var(--radius-sm);
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-small);
  min-height: 44px;
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
input[type=text]::placeholder, textarea::placeholder { color: var(--ink-3); }
input[type=text]:hover:not(:disabled), textarea:hover:not(:disabled), select:hover:not(:disabled) { border-color: var(--ink-3); }
input[type=text]:focus, textarea:focus, select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
input[type=text]:disabled, textarea:disabled, select:disabled { opacity: .5; cursor: not-allowed; }
input[type=text][aria-invalid="true"], textarea[aria-invalid="true"] { border-color: var(--crit); }

textarea { resize: vertical; line-height: var(--lh-small); min-height: 96px; }

/* A checkbox is a selected state, which is one of the two things the accent is
   reserved for. */
input[type=checkbox] {
  accent-color: var(--accent);
  width: 16px; height: 16px;
  cursor: pointer;
  flex: none;
}

.round-type-pick, .status-select {
  font: inherit;
  font-size: var(--fs-label);
  padding: var(--sp-1) var(--sp-2);
  border-radius: var(--radius-sm);
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--ink);
  width: auto;
  min-height: 32px;
}

/* ================================================================ banners ==
   A tinted block with a heavy rule down its left edge, like a margin note.
   The text stays ink so a long warning is still comfortable to read; the
   colour is carried by the rule, the tint and the strong line that leads. */

.banner {
  padding: var(--sp-4) var(--sp-4) var(--sp-4) calc(var(--sp-4) + 2px);
  border-radius: 0;
  font-size: var(--fs-small);
  line-height: var(--lh-small);
  border: none;
  border-left: 3px solid;
  color: var(--ink);
}
.banner.warn { background: var(--warn-soft); border-color: var(--warn); }
.banner.bad { background: var(--crit-soft); border-color: var(--crit); }
.banner.warn > strong:first-child { color: var(--warn); }
.banner.bad > strong:first-child { color: var(--crit); }
.banner a { color: inherit; text-decoration-color: currentcolor; }
.banner code, #setup-banner code {
  background: var(--surface);
  border: 1px solid var(--line);
  padding: 1px var(--sp-1);
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: var(--fs-label);
}

/* ================================================================== pills ==
   Status, so the semantic colours are the ones that carry meaning. Neutral
   states stay neutral rather than borrowing the accent. */

.pill {
  font-family: var(--font-mono);
  font-size: var(--fs-eyebrow);
  font-weight: var(--fw-medium);
  line-height: var(--lh-label);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  padding: 3px var(--sp-2);
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  color: var(--ink-2);
  background: var(--surface);
  white-space: nowrap;
}
/* Applied is the quietest state on the board: nothing has happened yet.
   Interviewing carries a little more presence because something is underway.
   If the two looked alike there would be no point separating them. */
.pill.applied { color: var(--ink-3); border-color: var(--line); background: var(--surface); }
.pill.recorded, .pill.interviewing { color: var(--ink-2); border-color: var(--line-strong); background: var(--sunken); }
.pill.transcribing { color: var(--warn); border-color: var(--warn-line); background: var(--warn-soft); }
.pill.transcribed, .pill.offer { color: var(--ok); border-color: var(--ok-line); background: var(--ok-soft); }
.pill.failed, .pill.closed_lost { color: var(--crit); border-color: var(--crit-line); background: var(--crit-soft); }

.kind-chip {
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 0 var(--sp-1);
  margin-right: var(--sp-2);
}

/* ================================================================== score ==*/

.score {
  font-family: var(--font-mono);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-label);
  padding: 2px var(--sp-2);
  border-radius: var(--radius-sm);
  background: var(--sunken);
}
.score.big { font-family: var(--font-display); font-size: var(--fs-heading); padding: var(--sp-1) var(--sp-3); letter-spacing: var(--ls-heading); }
.score.big small { font-family: var(--font-mono); font-size: var(--fs-label); color: var(--ink-3); font-weight: var(--fw-regular); letter-spacing: 0; }
.score.good { color: var(--ok); }
.score.ok { color: var(--warn); }
.score.low { color: var(--crit); }

/* ================================================================ metrics ==*/

.loop-summary {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(112px, 1fr));
  gap: var(--sp-5) var(--sp-4);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--sp-5);
}
.loop-summary .k {
  display: block;
  margin-bottom: var(--sp-1);
}
.loop-summary .v {
  font-family: var(--font-display);
  font-size: var(--fs-heading);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-heading);
  line-height: var(--lh-heading);
}
.loop-summary .v small { font-family: var(--font-mono); font-size: var(--fs-label); color: var(--ink-3); font-weight: var(--fw-regular); letter-spacing: 0; }
.loop-summary .v.good { color: var(--ok); }
.loop-summary .v.ok { color: var(--warn); }
.loop-summary .v.low { color: var(--crit); }

/* =================================================================== rows ==*/

#list { display: flex; flex-direction: column; gap: var(--sp-2); }

.row, .app-row, .round-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: var(--sp-3) var(--sp-4);
  min-height: 56px;
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.row:hover, .app-row:hover, .round-row:hover { border-color: var(--line-strong); background: var(--sunken); }
/* The round a click elsewhere asked for, so landing on it is visible. */
/* On a phone the controls drop below the title rather than squeezing it into
   a column two words wide. */
.row, .app-row, .round-row { flex-wrap: wrap; }
.row-main { flex: 1 1 220px; min-width: 0; display: flex; flex-direction: column; gap: var(--sp-1); }

/* What follows, written as a short passage with one thing to press, rather than
   a stack of cards each repeating a verb. Three panels of equal weight read as
   a form to be filled in; this reads as a sentence that ends in an action. */
.next {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  border-color: var(--accent-line);
}
.next .actions { margin-top: var(--sp-2); }
/* The opening line of a panel, set a step up from the prose under it. */
.lede-line { font-size: var(--fs-body); color: var(--ink); }
/* The line that says what the story below it is for. */
.story-lead { margin-top: var(--sp-5); }
.app-row .row-main { cursor: pointer; }


/* A counter, not a badge: the number in the accent with nothing around it. */
.round-no {
  width: 28px; height: 28px;
  display: grid;
  place-items: center;
  font-size: var(--fs-label);
  font-weight: var(--fw-semibold);
  color: var(--accent);
  flex: none;
}

.company-group { display: flex; flex-direction: column; gap: var(--sp-3); }
.company-head { display: flex; align-items: baseline; gap: var(--sp-3); flex-wrap: wrap; }
.company-head h3 { font-size: var(--fs-title); }
.company-head .row-sub { flex: 1; }
.rounds { display: flex; flex-direction: column; gap: var(--sp-2); }

/* ========================================================== application ==
   One card per role applied for, ordered by what happened most recently.

   The card is split in two: a body that opens the application, and a foot
   carrying its own controls. Nesting a button inside the clickable region
   would make Remove both delete and open, because the click handler resolves
   with closest() and would walk past the button to the card. */

/* ============================================================ list filters ==
   A search box and the statuses, on one line once there is room. The chips
   carry their own counts, so the shape of the pile is readable without
   opening anything. */

.app-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
}
.app-search { flex: 1 1 15rem; max-width: 24rem; min-height: 40px; }
.app-filters .chips { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.app-filters .count { margin-left: auto; font-variant-numeric: tabular-nums; white-space: nowrap; }

.chip { display: inline-flex; align-items: center; gap: var(--sp-2); }
.chip .n { font-family: var(--font-mono); font-size: var(--fs-eyebrow); color: var(--ink-3); font-variant-numeric: tabular-nums; }
.chip[aria-pressed="true"] { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent); }
.chip[aria-pressed="true"] .n { color: var(--accent); }

.app-grid { display: grid; grid-template-columns: 1fr; gap: var(--sp-3); }

.app-card {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  overflow: hidden;
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
.app-card:hover { border-color: var(--ink-3); }
.app-card:focus-within { border-color: var(--accent); }

.app-card-body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-4) var(--sp-5) var(--sp-5);
  cursor: pointer;
  flex: 1;
}

.app-card-top { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); }

/* The company is the label the role hangs under, so it sits above it and
   quieter than it. Two companies with the same role title still read apart. */
/* You look for the company first and the role second: a list of applications
   is read as "where did I apply", and the role is how you tell two at the same
   place apart. The company was set as a small uppercase label and the role as
   a display heading, which is that the wrong way round. */
.app-card-company { font-family: var(--font-display); font-size: var(--fs-title); line-height: var(--lh-title); letter-spacing: var(--ls-title); color: var(--ink); }
.app-card-role { font-family: var(--font-sans); font-size: var(--fs-small); line-height: var(--lh-small); color: var(--ink-2); font-weight: var(--fw-regular); margin: 0; }
.app-card-meta { font-size: var(--fs-small); line-height: var(--lh-small); color: var(--ink-2); }

.app-card-fit { display: flex; align-items: baseline; gap: var(--sp-2); margin-top: var(--sp-1); }
.app-card-fit .row-sub { font-size: var(--fs-label); color: var(--ink-3); }

.app-card-foot {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3) var(--sp-2) var(--sp-5);
  border-top: 1px solid var(--line-soft);
  background: var(--sunken);
}
.app-card-foot .row-sub { margin-right: auto; font-size: var(--fs-eyebrow); letter-spacing: var(--ls-label); }

/* ============================================================== recording ==*/

.live-head { display: flex; align-items: center; gap: var(--sp-3); }
.live-head #live-title { font-weight: var(--fw-medium); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dot {
  width: 8px; height: 8px;
  border-radius: var(--radius-full);
  background: var(--crit);
  animation: pulse 1.6s ease-in-out infinite;
  flex: none;
}
@keyframes pulse { 50% { opacity: .25; } }

.elapsed { font-size: var(--fs-display); font-weight: var(--fw-medium); letter-spacing: var(--ls-display); line-height: var(--lh-display); }
.meters { display: flex; flex-direction: column; gap: var(--sp-3); }
.meter-label { display: block; font-size: var(--fs-label); color: var(--ink-2); margin-bottom: var(--sp-1); }
.bar { height: 8px; background: var(--sunken); border: 1px solid var(--line); border-radius: var(--radius-full); overflow: hidden; }
.bar i { display: block; height: 100%; width: 0%; background: var(--ok); transition: width 80ms linear; }
.live-stats { display: flex; gap: var(--sp-4); flex-wrap: wrap; font-size: var(--fs-small); color: var(--ink-2); }
.verdict { display: flex; flex-direction: column; gap: var(--sp-2); font-size: var(--fs-small); }
.verdict div { color: var(--ink-2); }
.verdict div.ok { color: var(--ok); }
.verdict div.pending { color: var(--warn); }
.verdict div.bad { color: var(--crit); }

/* ================================================================ moments ==*/

.moment { border-left: 3px solid var(--line); padding-left: var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-2); }
.moment.good-moment { border-color: var(--ok); }
.moment.fix-moment { border-color: var(--crit); }
.moment q, q { display: block; font-size: var(--fs-body); line-height: var(--lh-body); color: var(--ink); }
.moment p { font-size: var(--fs-small); line-height: var(--lh-small); color: var(--ink-2); }
.moment .cost { color: var(--crit); }
/* What the room did next, set quieter than the candidate's own words: it is
   evidence for the verdict above it, not another thing to read. */
.moment .reply { border-left: 2px solid var(--line-strong); padding-left: var(--sp-3); margin-block: var(--sp-2); }
.moment .reply q { font-size: var(--fs-small); color: var(--ink-2); font-style: italic; }
.moment .reply .k { display: block; margin-bottom: 2px; }
/* The thing they already do that the fix builds on. In the accent rather than
   the critical colour, because it is the half of a correction worth keeping. */
.moment .strength { color: var(--accent); }

/* Where a posting came from, shown with the posting. Long URLs break rather
   than push the sheet sideways. */
/* A doubt, not a fault: warn rather than critical, so it never reads as one
   more thing done wrong. */
.moment.verify-moment { border-color: var(--warn); }
.verify-head { margin-top: var(--sp-5); font-size: var(--fs-body); }

.jd-source { font-size: var(--fs-small); color: var(--ink-2); margin-bottom: var(--sp-3); overflow-wrap: anywhere; }
.jd-source .k { display: block; }

.repair { background: var(--sunken); border: 1px solid var(--line-soft); border-radius: var(--radius-sm); padding: var(--sp-3); font-size: var(--fs-small); line-height: var(--lh-small); }
.repair .k { display: block; margin-bottom: var(--sp-1); }

.flag { display: flex; align-items: flex-start; gap: var(--sp-3); padding: var(--sp-1) 0; font-size: var(--fs-small); line-height: var(--lh-small); }
.flag .row-sub { margin-left: auto; white-space: nowrap; }
/* Optically centred against the cap height of the line beside it, which is
   not a grid value because eyes do not align to grids. */
.dot-flag { width: 8px; height: 8px; border-radius: var(--radius-full); flex: none; margin-top: 7px; }
.flag.open .dot-flag { background: var(--crit); }
.flag.fixed .dot-flag { background: var(--ok); }
.flag.fixed strong { text-decoration: line-through; color: var(--ink-3); }

/* ================================================================ debrief ==*/

.debrief { display: flex; flex-direction: column; gap: var(--sp-4); }
.debrief .tabs { border-bottom: 1px solid var(--line-soft); padding-bottom: var(--sp-2); }
.overall-line { color: var(--ink-2); font-size: var(--fs-small); line-height: var(--lh-small); }
.aspect { display: flex; flex-direction: column; gap: var(--sp-2); }
.aspect-head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--sp-3); font-size: var(--fs-small); font-weight: var(--fw-medium); }
.aspect-head span:first-child { text-transform: capitalize; }
.aspect p { font-size: var(--fs-small); line-height: var(--lh-small); color: var(--ink-2); }
.meter-line { height: 3px; background: var(--line); border-radius: var(--radius-full); overflow: hidden; max-width: 320px; }
.meter-line i { display: block; height: 100%; background: var(--accent); border-radius: var(--radius-full); }
.tab-body { display: flex; flex-direction: column; gap: var(--sp-4); }

/* Secondary and destructive actions, tucked behind one control so the row
   carries only what somebody came to do. */
.overflow { position: relative; display: inline-flex; }
.overflow .more { letter-spacing: 2px; line-height: 1; }
.overflow .menu {
  position: absolute; right: 0; top: calc(100% + var(--sp-1)); z-index: 20;
  min-width: 13rem; display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-md); box-shadow: var(--elev-2); padding: var(--sp-1);
}
.overflow .menu > * {
  display: block; width: 100%; text-align: left; background: none; border: 0;
  padding: var(--sp-2) var(--sp-3); font: inherit; font-size: var(--fs-small);
  color: var(--ink); text-decoration: none; border-radius: var(--radius-sm);
  min-height: 0; cursor: pointer;
}
.overflow .menu > *:hover { background: var(--sunken); }
/* Separated and coloured, because it is the one item here that cannot be undone. */
.overflow .menu .danger-item { color: var(--crit); margin-top: var(--sp-1); border-top: 1px solid var(--line-soft); padding-top: var(--sp-2); }
.overflow .menu .danger-item:hover { background: var(--crit-soft); }

.transcript-note { font-size: var(--fs-small); color: var(--ink-3); }
.transcript { max-height: 60vh; overflow-y: auto; border-top: 1px solid var(--line-soft); padding-top: var(--sp-4); }

/* Speakers are a category, not a hierarchy, so weight separates them rather
   than two competing colours. */
.seg { display: grid; grid-template-columns: 56px 1fr; gap: var(--sp-1) var(--sp-3); padding: var(--sp-2) 0; align-items: baseline; }
.seg time { font-size: var(--fs-label); color: var(--ink-3); cursor: pointer; }
.seg time:hover { color: var(--accent); }
.seg .who { grid-column: 2; font-size: var(--fs-label); font-weight: var(--fw-semibold); color: var(--ink-2); }
.seg .who em { display: block; font-style: normal; font-weight: var(--fw-regular); font-size: var(--fs-label); color: var(--ink-3); }
.seg.you .who { color: var(--ink); }
.seg p { grid-column: 2; font-size: var(--fs-body); line-height: var(--lh-body); }

/* ================================================================== drift ==*/

.panel.drift { border-color: var(--warn-line); }
.drift-row { border-top: 1px solid var(--line-soft); padding-top: var(--sp-3); display: flex; flex-direction: column; gap: var(--sp-2); }
.drift-row:first-of-type { border-top: none; padding-top: 0; }
.drift-label { font-weight: var(--fw-semibold); }
.drift-pair { display: grid; grid-template-columns: 1fr; gap: var(--sp-3); }
.drift-pair .k { display: block; }
.drift-pair q { display: block; color: var(--ink-2); font-size: var(--fs-small); margin-top: var(--sp-1); }

/* ================================================================= modals ==*/

.modal {
  position: fixed;
  inset: 0;
  background: var(--scrim);
  display: grid;
  place-items: end center;
  z-index: 50;
  padding: 0;
  animation: fade var(--dur-base) var(--ease);
}
@supports (backdrop-filter: blur(1px)) { .modal { backdrop-filter: blur(3px); } }

/* On a phone the dialog is a sheet from the bottom, which is where a thumb can
   reach it. It becomes a centred card once there is room. */
.modal-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  padding: var(--sp-5);
  width: 100%;
  max-height: 92vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  box-shadow: var(--elev-3);
  animation: sheet var(--dur-slow) var(--ease);
}
.modal-card > button.big:last-child { position: sticky; bottom: 0; }
.modal-head { display: flex; align-items: center; gap: var(--sp-3); }
.modal-head h3 { flex: 1; min-width: 0; }
.modal-card p { font-size: var(--fs-small); line-height: var(--lh-body); color: var(--ink-2); }

.consent-check { display: flex; gap: var(--sp-3); align-items: flex-start; font-size: var(--fs-small); line-height: var(--lh-small); cursor: pointer; }
.consent-check.per-recording { color: var(--ink-2); }

/* ================================================================ waiting ==*/

/* A step that is running, shown where its answer will appear rather than only
   on the button that started it. Reading a posting and then checking the fit
   are two model calls one after the other: the first answer lands on screen
   while the second is still going, and at that moment nobody is looking at the
   button any more. The bar is indeterminate because the wait genuinely is. */
.waiting {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-5);
  border: 1px dashed var(--line);
  border-radius: var(--radius-md);
  background: var(--sunken);
  text-align: center;
  margin-bottom: var(--sp-4);
}
.waiting strong { font-size: var(--fs-small); font-weight: var(--fw-semibold); }
.waiting .row-sub { max-width: 34ch; }

/* The steps, so the one running is read as part of a sequence rather than as
   an unexplained pause. */
.waiting-steps { display: flex; gap: var(--sp-3); flex-wrap: wrap; justify-content: center; font-size: var(--fs-label); color: var(--ink-3); }
.waiting-steps span { display: flex; align-items: center; gap: var(--sp-1); }
.waiting-steps .done { color: var(--ok); }
.waiting-steps .done::before { content: "\2713"; }
.waiting-steps .now { color: var(--ink); font-weight: var(--fw-medium); }

/* =============================================================== dropzone ==*/

.dropzone {
  border: 2px dashed var(--line-strong);
  border-radius: var(--radius-lg);
  padding: var(--sp-7) var(--sp-5);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
  text-align: center;
  background: var(--surface);
  transition: border-color var(--dur-base) var(--ease), background var(--dur-base) var(--ease);
}
.dropzone strong { font-family: var(--font-display); font-size: var(--fs-title); font-weight: var(--fw-semibold); letter-spacing: var(--ls-title); }

/* A control sitting inside a line of prose takes the shape of the prose, not
   the shape of a button. */
.dropzone button.link {
  padding: 0;
  min-height: 0;
  color: var(--accent);
  text-decoration: underline;
  text-decoration-color: var(--accent-line);
  text-underline-offset: 2px;
}
.dropzone button.link:hover { background: none; color: var(--accent-hover); }
.dropzone.over { border-color: var(--accent); background: var(--accent-soft); }
.dropzone.busy { border-style: solid; color: var(--ink-2); }

/* =============================================================== cv intro ==
   What a new account sees before it has a CV: why the CV comes first, told in
   three sketched frames, then the two worries answered right above the place
   they are asked to act. It disappears once a CV exists. */

.intro { display: flex; flex-direction: column; gap: var(--sp-5); }
.intro-lede {
  font-family: var(--font-display);
  font-size: var(--fs-title);
  line-height: var(--lh-title);
  letter-spacing: var(--ls-title);
  max-width: var(--measure);
}

/* A strip to swipe on a phone, three across once there is room. */
.intro-frames {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 80%;
  gap: var(--sp-3);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding-bottom: var(--sp-2);
}
.intro-frame {
  scroll-snap-align: start;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--sp-3) var(--sp-3) var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
/* The drawings are a note in the margin, not the point of the screen. Capped
   so three of them cannot push the words people came to read off the fold. */
.intro-frame .sketch-box { background: var(--ground); border-radius: var(--radius-md); padding: var(--sp-2) var(--sp-3); }
.intro-frame .sketch-box .sketch { max-height: 5.5rem; margin-inline: auto; }
.intro-frame .step {
  font-family: var(--font-mono);
  font-size: var(--fs-eyebrow);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--accent);
  margin-top: var(--sp-1);
}
.intro-frame strong { font-family: var(--font-display); font-size: var(--fs-body); font-weight: var(--fw-semibold); }
.intro-frame p { font-size: var(--fs-small); line-height: var(--lh-small); color: var(--ink-2); }

.intro-assure { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-5); }
.intro-assure li { display: flex; gap: var(--sp-2); font-size: var(--fs-small); color: var(--ink-2); }
.intro-assure li::before { content: "\2713"; color: var(--accent); font-weight: var(--fw-semibold); }

/* Frames that are steps in order get an arrow between them, once they sit side
   by side. On a phone the swipe itself is the sequence. */
.intro-frame { position: relative; }

/* One line of advice, set apart so it reads as a condition rather than a
   fourth step. No drawing: the class it shares lost its styles when the
   applications page stopped using it, and a sketch at width 100% with no
   column to sit in filled the panel. */
.intro-later {
  padding: var(--sp-3) var(--sp-4);
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius-lg);
}
.intro-later p { font-size: var(--fs-small); line-height: var(--lh-small); color: var(--ink-2); }
.intro-later strong { color: var(--ink); }

.intro-next { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-2); }

@media (min-width: 40rem) {
  .intro-frames { grid-auto-flow: row; grid-template-columns: repeat(3, 1fr); overflow: visible; padding-bottom: 0; }
  .intro-frames.flow .intro-frame + .intro-frame::before {
    content: "\2192";
    position: absolute;
    left: calc(-1 * var(--sp-3));
    top: 30%;
    width: var(--sp-3);
    text-align: center;
    color: var(--accent);
    font-family: var(--font-mono);
    font-size: var(--fs-small);
  }
}

.spinner {
  width: 24px; height: 24px;
  border-radius: var(--radius-full);
  border: 2px solid var(--line);
  border-top-color: var(--ink-3);
  animation: spin .8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.progress { width: min(280px, 80%); height: 4px; background: var(--sunken); border-radius: var(--radius-full); overflow: hidden; }
.progress.indeterminate i { display: block; width: 40%; height: 100%; background: var(--ink-3); border-radius: var(--radius-full); animation: slide 1.1s var(--ease) infinite; }
@keyframes slide { 0% { transform: translateX(-100%); } 100% { transform: translateX(250%); } }

/* A page that looks finished before its content arrives, and does not shift
   when it does. */
.skeleton-block { display: flex; flex-direction: column; gap: var(--sp-2); }
.skeleton.line-head { height: calc(var(--fs-heading) * var(--lh-heading)); width: 190px; }
.skeleton.line-title { height: calc(var(--fs-title) * var(--lh-title)); width: 44%; }
.skeleton.line-body { height: calc(var(--fs-body) * var(--lh-body)); width: 72%; }
.skeleton.line-sub { height: calc(var(--fs-small) * var(--lh-small)); width: 30%; }

.skeleton {
  background: var(--skeleton);
  border-radius: var(--radius-sm);
  position: relative;
  overflow: hidden;
  color: transparent;
  user-select: none;
}
.skeleton::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, var(--skeleton-sheen), transparent);
  animation: sweep 1.4s var(--ease) infinite;
}
@keyframes sweep { 0% { transform: translateX(-100%); } 100% { transform: translateX(100%); } }

.empty {
  color: var(--ink-2);
  font-size: var(--fs-small);
  line-height: var(--lh-small);
  padding: var(--sp-7) 0;
  text-align: center;
  margin-inline: auto;
}

@keyframes fade { from { opacity: 0; } }
@keyframes sheet { from { opacity: 0; transform: translateY(12px); } }
@keyframes rise { from { opacity: 0; transform: translateY(4px); } }

/* ================================================================= toasts ==*/

/* Above the modal, because a notice about what just happened has to be
   readable while the thing it happened to is still on screen. Centred at the
   bottom on a phone: that is where the thumb already is, and it is the one
   place no sticky control lives. */
.toasts {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(var(--sp-4) + env(safe-area-inset-bottom, 0px));
  /* Above .ask, not below it. A question can fail while it is still open, and
     the answer to "why did nothing happen" must not be sitting behind the
     scrim that is blurring it. */
  z-index: 80;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  width: min(30rem, calc(100vw - var(--sp-4) * 2));
  pointer-events: none;
}
.toast {
  pointer-events: auto;
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-left-width: 3px;
  border-radius: var(--radius-md);
  padding: var(--sp-3) var(--sp-3) var(--sp-3) var(--sp-4);
  box-shadow: var(--elev-3);
  animation: rise var(--dur-base) var(--ease);
}
.toast p {
  flex: 1;
  min-width: 0;
  margin: 0;
  font-size: var(--fs-small);
  line-height: var(--lh-small);
  color: var(--ink);
}
.toast.ok { border-left-color: var(--ok); }
/* The tint is composited over the surface rather than used as the background,
   because --crit-soft is semi-transparent: on its own it lets the dialog scrim
   through and the toast turns muddy exactly when something has gone wrong. */
.toast.bad {
  border-left-color: var(--crit);
  background: linear-gradient(var(--crit-soft), var(--crit-soft)) var(--surface);
}
.toast-x {
  flex: none;
  min-height: 0;
  padding: 0 var(--sp-1);
  border: 0;
  background: none;
  color: var(--ink-3);
  font-size: var(--fs-body);
  line-height: 1;
}
.toast-x:hover:not(:disabled) { color: var(--ink); background: none; border-color: transparent; }
.toast.going { animation: sink var(--dur-fast) var(--ease) forwards; }
@keyframes sink { to { opacity: 0; transform: translateY(6px); } }

/* ==================================================================== ask ==*/

/* Separate from .modal on purpose: nearly every question is asked while that
   modal is open and showing the thing being changed. Reusing it would replace
   the evidence with the question about it. */
.ask {
  position: fixed;
  inset: 0;
  z-index: 70;
  background: var(--scrim);
  display: grid;
  place-items: center;
  padding: var(--sp-4);
  animation: fade var(--dur-fast) var(--ease);
}
@supports (backdrop-filter: blur(1px)) { .ask { backdrop-filter: blur(3px); } }
.ask-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-3);
  padding: var(--sp-5);
  width: min(26rem, 100%);
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  animation: sheet var(--dur-base) var(--ease);
}
.ask-card h3 { font-size: var(--fs-title); line-height: var(--lh-title); }
.ask-card p {
  font-size: var(--fs-small);
  line-height: var(--lh-body);
  color: var(--ink-2);
  white-space: pre-line;
}
.ask-actions { display: flex; gap: var(--sp-2); justify-content: flex-end; margin-top: var(--sp-1); }
.ask-actions button { min-width: 6rem; }
body.ask-open { overflow: hidden; }

@media (max-width: 26rem) {
  .ask-actions { flex-direction: column-reverse; }
  .ask-actions button { width: 100%; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
  }
  .spinner, .progress.indeterminate i, .dot, .skeleton::after { animation: none; }
  .progress.indeterminate i { width: 100%; opacity: .5; }
  button:active:not(:disabled) { transform: none; }
}

/* ============================================================== job chips ==*/

.jd-chips {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: var(--sp-2);
}
.jd-chip {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: var(--sp-3);
  min-width: 0;
}
.jd-chip .k { display: block; margin-bottom: var(--sp-1); }
.jd-chip .v { font-size: var(--fs-small); font-weight: var(--fw-medium); }
.jd-chip.good .v { color: var(--ok); }
.jd-chip.muted .v { color: var(--ink-3); font-weight: var(--fw-regular); }

.jd-reqs { display: grid; grid-template-columns: 1fr; gap: var(--sp-5); }
.jd-reqs ul { margin: 0; padding-left: var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-2); }
.jd-reqs li { font-size: var(--fs-small); line-height: var(--lh-small); color: var(--ink-2); }
.jd-reqs li::marker, .tailored-role li::marker { color: var(--accent); }
.jd-tabs { display: flex; gap: var(--sp-1); flex-wrap: wrap; }

.req { display: flex; gap: var(--sp-3); padding: var(--sp-3) 0; border-top: 1px solid var(--line-soft); align-items: flex-start; }
.req:first-of-type { border-top: none; padding-top: 0; }
.req-mark { width: 16px; text-align: center; font-weight: var(--fw-semibold); flex: none; }
.req-strong .req-mark { color: var(--ok); }
.req-partial .req-mark { color: var(--warn); }
.req-missing .req-mark { color: var(--crit); }
.req-missing .row-title { color: var(--crit); }
.prov { display: block; font-size: var(--fs-label); color: var(--ink-3); margin-top: var(--sp-1); }

/* The offer to answer a requirement the CV could not. It sits on its own line,
   clear of the assessment text above it, and carries the accent on its edge so
   it reads as the thing to do on the row rather than another remark about it. */
.req-evidence { margin-top: var(--sp-3); }
/* The shared treatment for a control offered inside running text: adding
   evidence, adding more of a posting, opening the written story. The accent on
   the edge is what separates it from the prose around it. */
.btn-accent { border-color: var(--accent-line); color: var(--ink); }
.btn-accent:hover:not(:disabled) { border-color: var(--accent); background: var(--sunken); }

/* Evidence added against a requirement the CV could not answer. The form sits
   inside the row it belongs to, on the sunken ground that marks it as an input
   surface rather than another line of the assessment. */
.req-form {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  margin-top: var(--sp-3);
  padding: var(--sp-3);
  background: var(--sunken);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
}
.req-form-row { display: flex; gap: var(--sp-3); flex-wrap: wrap; }
/* Adding the part of a posting that lived on another page. */
.posting-add { margin: var(--sp-3) 0; }
.req-form-row .field { flex: 1 1 160px; }
/* One strip, under the score it affects, instead of a re-check button beside
   every answered gap. The count is the prompt: it gets more persuasive as it
   grows, where three identical buttons said nothing. Pressing it is how
   somebody signals they have finished adding — nothing has to infer that, and
   if they never press it the assessment refreshes on its own next time, so
   the button is for immediacy rather than correctness. */
.fit-restale {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-3); flex-wrap: wrap;
  padding: var(--sp-3);
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  border-radius: var(--radius);
  font-size: var(--fs-small); color: var(--ink-2);
}
.fit-restale strong { color: var(--ink); }

.req-added {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
  margin-top: var(--sp-2);
}

/* ============================================================= posting ==
   The description as posted, kept scrollable so a long one does not push the
   rest of the application off the screen. Whitespace is preserved because the
   structure of a posting, its headings and bullets, is part of reading it.

   It spans the card rather than stopping at the reading measure. It is a
   scrolling box, and a box that ends halfway across its parent looks
   broken, with its scrollbar floating in the middle of the card. The inset
   ground and hairline mark it as a quoted document. */

.jd-text {
  white-space: pre-wrap;
  max-height: 340px;
  overflow-y: auto;
  font-size: var(--fs-small);
  line-height: var(--lh-body);
  color: var(--ink-2);
  background: var(--ground);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: var(--sp-3) var(--sp-4);
}

/* =============================================================== tailored ==*/

.tailored-headline { font-family: var(--font-display); font-size: var(--fs-title); font-weight: var(--fw-semibold); letter-spacing: var(--ls-title); }
.tailored-role { display: flex; flex-direction: column; gap: var(--sp-2); }
.tailored-role ul { margin: 0; padding-left: var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-2); }
.tailored-role li { font-size: var(--fs-body); line-height: var(--lh-body); }

/* ============================================================== cv review ==*/

.dx-headline { font-family: var(--font-display); font-size: var(--fs-title); line-height: var(--lh-title); color: var(--ink); font-weight: var(--fw-medium); letter-spacing: var(--ls-title); }
.review-tools { display: flex; gap: var(--sp-2); align-items: center; flex-wrap: wrap; }
.review-list { max-height: 58vh; overflow-y: auto; display: flex; flex-direction: column; gap: var(--sp-2); }
.review-row { border: 1px solid var(--line); border-radius: var(--radius-md); padding: var(--sp-3); background: var(--surface); transition: opacity var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease); }
.review-row.drop { opacity: .45; }
.review-row.keep { border-color: var(--accent-line); background: var(--accent-soft); }
.evidence { display: block; color: var(--ink-2); font-size: var(--fs-small); line-height: var(--lh-small); margin-top: var(--sp-1); }

.assign-list { max-height: 46vh; overflow-y: auto; }
.assign-row { display: grid; grid-template-columns: 1fr auto; gap: var(--sp-3); align-items: center; padding: var(--sp-3) 0; border-top: 1px solid var(--line-soft); }
.assign-row:first-child { border-top: none; }
.assign-pick { display: flex; gap: var(--sp-3); align-items: flex-start; cursor: pointer; }
.assign-pick > span { display: flex; flex-direction: column; gap: var(--sp-1); }
.suggestion { font-size: var(--fs-label); color: var(--ink-2); }

/* ======================================================== suggested edits ==
   Each proposed change is its own decision: approve it, deny it, or leave it.
   The old and new lines sit one above the other so the difference is read
   rather than hunted for. */

.edit {
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
  padding: var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  transition: border-color var(--dur-fast) var(--ease), opacity var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.edit[data-state="approved"] { border-color: var(--ok-line); background: var(--ok-soft); }
/* Some figures in, some still missing. Distinct from both untouched and done,
   because it is a real state somebody can leave a question in. */
.edit[data-state="edited"] { border-color: var(--warn-line); background: var(--warn-soft); }
.edit[data-state="denied"] { opacity: .45; }
.edit-diff { display: flex; flex-direction: column; gap: var(--sp-2); }
.edit-line { display: flex; gap: var(--sp-3); font-size: var(--fs-body); line-height: var(--lh-body); }
.edit-line .mark { flex: none; width: var(--sp-4); font-weight: var(--fw-semibold); font-family: var(--font-mono); }
.edit-line.was .mark { color: var(--crit); }
.edit-line.now .mark { color: var(--ok); }
.edit-line.was .body { color: var(--ink-3); text-decoration: line-through; text-decoration-color: var(--crit-line); }
.edit-line.now .body { color: var(--ink); }
.edit-actions { display: flex; gap: var(--sp-2); align-items: center; flex-wrap: wrap; padding-top: var(--sp-3); border-top: 1px solid var(--line-soft); }
.edit-actions .state { margin-left: auto; font-size: var(--fs-label); color: var(--ink-3); }
.edit-actions .state.approved { color: var(--ok); }

button.approve[aria-pressed="true"] { background: var(--ok-soft); border-color: var(--ok-line); color: var(--ok); }
button.deny[aria-pressed="true"] { background: var(--crit-soft); border-color: var(--crit-line); color: var(--crit); }

.sticky-actions {
  position: sticky;
  bottom: 0;
  background: linear-gradient(to top, var(--ground) 66%, transparent);
  padding: var(--sp-4) 0 var(--sp-2);
  display: flex;
  gap: var(--sp-3);
  align-items: center;
  flex-wrap: wrap;
}
.sticky-actions .count { font-size: var(--fs-small); color: var(--ink-2); margin-right: auto; }

/* =============================================================== regions ==
   Every container that receives innerHTML lays its children out with a gap.
   This is what removes the need for the inline margins the JS used to write,
   including the twelve that existed only to cancel a margin set elsewhere. */

#view-record, #view-loop, #view-settings, #view-home,
#home, #loop, #cv-view, #settings-view, #detail, #list-section,
#idle-view, #test-view, #live-view, #modal-body,
#test-warnings, #live-warnings, #debrief-panel, #jd-result {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}

#view-record, #view-loop, #view-settings, #home, #loop, #cv-view, #settings-view { gap: var(--sp-6); }

.banner { display: flex; flex-direction: column; gap: var(--sp-3); align-items: flex-start; }

/* A whole screen given over to one piece of state: reading a CV, or nothing
   uploaded yet. Centred, generous, and the same height as the content it is
   standing in for, so the page does not jump when the content arrives. */
.state-panel {
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: var(--sp-3);
  padding: var(--sp-9) var(--sp-5);
}
.state-panel strong { font-family: var(--font-display); font-size: var(--fs-title); font-weight: var(--fw-semibold); letter-spacing: var(--ls-title); }

/* A metric slot holding words rather than a number. */
.loop-summary .v.text { font-size: var(--fs-small); font-weight: var(--fw-medium); letter-spacing: var(--ls-body); line-height: var(--lh-small); }

/* =================================================================== home ==
   The landing screen leads with one figure at display size, and everything
   else steps down from it. A stack of equal panels gives the eye nowhere to
   land, which is why the rest of the app reads flat.

   The score is drawn as well as counted. Five numbers in a row cannot show
   that a loop is improving; a line can. */

/* Eight figures, laid out as two rows of four rather than left to auto-fit,
   which orphans the last one onto a row of its own. The break falls where the
   meaning does: the first row is the pipeline going forward, the second is
   what closed and what has happened since. */
.loop-summary.counts { grid-template-columns: repeat(2, 1fr); }

.hero { display: grid; grid-template-columns: 1fr; gap: var(--sp-5); }
.hero-lead, .hero-aside { display: flex; flex-direction: column; gap: var(--sp-1); }

.hero-v {
  font-family: var(--font-display);
  font-optical-sizing: auto;
  font-size: var(--fs-display);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
}
.hero-v small { font-family: var(--font-mono); font-size: var(--fs-label); color: var(--ink-3); font-weight: var(--fw-regular); letter-spacing: var(--ls-body); }
.hero-v.good { color: var(--ok); }
.hero-v.low { color: var(--crit); }

/* The supporting figure is a step down from the headline, not a second one. */
.hero-aside .hero-v { font-size: var(--fs-heading); line-height: var(--lh-heading); letter-spacing: var(--ls-heading); }

.hero-sub { font-size: var(--fs-small); line-height: var(--lh-small); color: var(--ink-2); }

.spark { display: block; margin-top: var(--sp-2); overflow: visible; }
.spark-line { stroke: var(--ink-2); stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.spark-end { fill: var(--accent); }

/* ==================================================================== gaps ==
   A sentence with holes in it, rather than a form asking for "metric 1". The
   input sits on the line so the question answers itself. */

.gap-sentence {
  font-size: var(--fs-body);
  line-height: 2;
  color: var(--ink);
}

/* input[type=text] sets width to 100% and outranks a bare class, so the
   selector has to carry the attribute too or the field fills the line and the
   sentence stops being a sentence. */
input[type=text].gap-input {
  width: 7rem;
  display: inline-block;
  vertical-align: baseline;
  display: inline;
  padding: var(--sp-1) var(--sp-2);
  min-height: 0;
  font-size: var(--fs-body);
  font-variant-numeric: tabular-nums;
  border-color: var(--accent-line);
  background: var(--accent-soft);
  text-align: center;
}
input[type=text].gap-input::placeholder { color: var(--ink-3); }

/* =================================================================== misc ==*/

audio { width: 100%; }
.cv-card { gap: var(--sp-3); }

/* The stored review's verdict, sitting on the version it belongs to, so the
   answer is on the card rather than behind a button. */
.cv-verdict {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  background: var(--ground);
  border-left: 3px solid var(--accent);
}

/* ============================================================ breakpoints ==
   Wide screens get more air, not stretched layouts. */

@media (min-width: 40rem) {
  header { padding: 0 var(--sp-5); }
  header h1 { font-size: var(--fs-title); }

  .loop-summary.counts { grid-template-columns: repeat(4, 1fr); }
  .hero { grid-template-columns: 1fr auto; align-items: start; }
  .hero-aside { align-items: flex-end; text-align: right; }
  main { padding: var(--sp-6) var(--sp-5) var(--sp-9); gap: var(--sp-7); }

    .app-grid { grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); }
  .drift-pair, .jd-reqs { grid-template-columns: 1fr 1fr; }
  .seg { grid-template-columns: 64px 104px 1fr; }
  .seg .who, .seg p { grid-column: auto; }

  .modal { place-items: center; padding: var(--sp-5); }
  .modal-card { width: min(600px, 100%); max-height: 86vh; border-radius: var(--radius-lg); animation-name: rise; }

  button.big { width: auto; min-width: 200px; }
  .modal-card > button.big:last-child { width: 100%; }
}

@media (min-width: 64rem) {
  main { padding-inline: var(--sp-6); }
  .loop-summary { padding: var(--sp-5) var(--sp-6); }
}

/* ================================================ before the recruiter calls
   Five lines, not five essays. The row is the whole control, so the tap target
   is the width of the panel rather than the words in it. */

/* state-panel is normally the panel itself. Here it sits inside one, under a
   heading, so it has to do its own centring. */
.panel.prep .state-panel {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-5) 0 var(--sp-4);
  text-align: center;
}

.panel.prep .prep-list {
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--line-soft);
  margin-top: var(--sp-2);
}
.prep-item { border-bottom: 1px solid var(--line-soft); }
.prep-head {
  display: flex;
  align-items: baseline;
  gap: var(--sp-3);
  width: 100%;
  padding: var(--sp-3) 0;
  background: none;
  border: 0;
  border-radius: 0;
  text-align: left;
  color: var(--ink);
  font: inherit;
  cursor: pointer;
  /* The base button rule sets nowrap, which is right for a control with two
     words on it and wrong for a row whose title is a sentence. Without this
     the longest title runs underneath its own time badge. */
  white-space: normal;
}
.prep-head:hover { background: none; color: var(--accent); }
.prep-head:active { transform: none; }
.prep-n {
  font-family: var(--font-mono);
  font-size: var(--fs-eyebrow);
  font-weight: 500;
  line-height: 1.6;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  flex: none;
}
.prep-title { flex: 1; min-width: 0; font-weight: 600; }
.prep-secs {
  font-family: var(--font-mono);
  font-size: var(--fs-eyebrow);
  line-height: 1.6;
  color: var(--ink-3);
  flex: none;
}
.prep-item.open .prep-title { color: var(--accent); }

.prep-body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: 0 0 var(--sp-4) calc(var(--sp-3) + 1.2em);
}
/* What they would actually say. Set apart from the notes underneath it,
   because one is spoken and the others are only reminders. */
.prep-say {
  margin: 0;
  padding-left: var(--sp-3);
  border-left: 2px solid var(--accent-line);
  font-size: var(--fs-body);
  line-height: 1.6;
}
.prep-ready {
  margin: 0;
  padding-left: 1.1em;
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}
.prep-ready li { font-size: var(--fs-small); line-height: 1.55; color: var(--ink-2); }
.prep-source { font-family: var(--font-mono);
  font-size: var(--fs-eyebrow);
  line-height: 1.5; color: var(--ink-3); }

@media (max-width: 40rem) {
  .prep-body { padding-left: 0; }
}

/* ==================================================================== story
   The thread through somebody's roles, then the same thing cut for speech.
   Set wider than a panel of controls and narrower than the page, because this
   one is actually read. */

.panel.story .state-panel {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-5) 0 var(--sp-4);
  text-align: center;
}
.story-thread {
  display: block;
  margin: 0;
  padding-left: var(--sp-3);
  border-left: 2px solid var(--accent-line);
  font-family: var(--font-display);
  font-size: var(--fs-title);
  line-height: 1.3;
  color: var(--ink);
  text-wrap: balance;
}
.story-body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  margin-top: var(--sp-4);
  max-width: 62ch;
}
.story-body p {
  margin: 0;
  font-size: var(--fs-body);
  line-height: 1.7;
  color: var(--ink-2);
}
.story-spoken-head {
  margin-top: var(--sp-3);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  flex-wrap: wrap;
}
/* The answer and what they will be probed on. Stacked on a phone; side by side
   once there is room, where the answer keeps its reading measure and the hooks
   take the width that measure used to leave empty. */
.story-spoken {
  margin-top: var(--sp-3);
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}
.story-say-col {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  min-width: 0;
}
.story-say {
  margin: 0;
  max-width: 62ch;
  font-size: var(--fs-body);
  line-height: 1.7;
  color: var(--ink);
}
/* Spoken answers arrive in paragraphs, one per pattern. The gap between them is
   where somebody breathes, so it is the same gap the written story uses. */
.story-say + .story-say { margin-top: var(--sp-3); }
@media (min-width: 64rem) {
  .story-spoken.has-hooks {
    display: grid;
    grid-template-columns: minmax(0, 62ch) minmax(0, 1fr);
    column-gap: var(--sp-6);
    align-items: start;
  }
}
/* What to have ready when the interviewer asks for more: a short name for each
   hook, with the story behind it underneath. */
.story-hooks {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  min-width: 0;
}
.story-hook {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  padding-left: var(--sp-3);
  border-left: 2px solid var(--line);
}
/* The written story is for reading on your own, not for the call, so it waits
   behind its one line until somebody opens it. */
.story-more {
  margin-top: var(--sp-5);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--line-soft);
}
.story-more-actions { margin-top: var(--sp-3); }
.story-more-note { margin-top: var(--sp-3); }

/* A note that changes while somebody waits. It fades rather than snaps,
   because a hard swap reads as a glitch on a screen that is otherwise still. */
.wait-note {
  margin: 0;
  max-width: 46ch;
  font-size: var(--fs-small);
  line-height: 1.6;
  color: var(--ink-2);
  opacity: 0;
  transition: opacity var(--dur-slow, 320ms) var(--ease, ease);
}
.wait-note.in { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .wait-note { transition: none; opacity: 1; }
}

/* The control that rewrites the story belongs beside its heading, not under
   three hundred words of it. */
.story-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
  flex-wrap: wrap;
}

/* ======================================================= the application ==
   One container idiom: rows separated by hairlines inside one panel. The page
   had four, which is why it read as three screens glued together.

   Every row leads with its date, so the sequence is legible without a heading
   over it and without a numbered badge, which is decoration: nobody thinks in
   round numbers, they think in "the one after the recruiter". */

.app-state {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
  flex-wrap: wrap;
  padding: var(--sp-2) 0 var(--sp-4);
}
/* A sentence, set as one. It was in the display serif at title size, competing
   with the only heading on the page. */
.app-state-line {
  margin: 0;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--ink);
  flex: 1 1 20ch;
}
.app-state.warn .app-state-line { color: var(--warn); }
.app-state.good .app-state-line { color: var(--ok); }
.app-state.quiet .app-state-line { color: var(--ink-3); }

.panel.timeline { padding: 0; }
.ev {
  display: grid;
  grid-template-columns: 4.25rem 1fr auto;
  gap: var(--sp-3);
  align-items: baseline;
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--line-soft);
}
.ev:last-child { border-bottom: 0; }
.ev-date {
  font-family: var(--font-mono);
  font-size: var(--fs-eyebrow);
  letter-spacing: .04em;
  color: var(--ink-3);
  white-space: nowrap;
}
.ev-main { min-width: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
.ev-title {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  flex-wrap: wrap;
  font-weight: var(--fw-semibold);
}
.ev-meta { font-weight: 400; font-size: var(--fs-small); color: var(--ink-3); }
.ev-round.open { background: var(--sunken); }

/* Attachments: what an event left behind. Quiet and on one line, because an
   attachment is not the equal of the thing it belongs to. Buttons at the same
   weight as the event is what made the cards feel cramped. */
.ev-attach { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-2); }
.ev-attach .att {
  padding: 0;
  min-height: 0;
  font-size: var(--fs-small);
  border: 0;
  background: none;
}
.ev-attach .att:hover { background: none; text-decoration: underline; }
.ev-dot { color: var(--ink-3); }
.att-note { font-size: var(--fs-small); color: var(--warn); }

.ev-flags { margin: 0; padding-left: 1.1em; list-style: none; display: flex; flex-direction: column; gap: var(--sp-1); }
.ev-flags li { position: relative; font-size: var(--fs-small); line-height: 1.5; color: var(--ink-2); }
.ev-flags li::before {
  content: ""; position: absolute; left: -1.1em; top: .5em;
  width: 6px; height: 6px; border-radius: 50%; background: var(--warn);
}
.ev-flags li.repaired { color: var(--ink-3); }
.ev-flags li.repaired::before { background: var(--line-strong); }
.ev-flag-note { font-family: var(--font-mono); font-size: var(--fs-eyebrow); color: var(--ink-3); margin-left: var(--sp-2); }

.ev-more {
  padding: 0 var(--sp-2);
  min-height: 0;
  border: 0;
  background: none;
  color: var(--ink-3);
  font-family: var(--font-mono);
}
.ev-more:hover { background: none; color: var(--accent); }
.ev-add .ev-main { padding: var(--sp-1) 0; }
.ev-outcome .ev-title { color: var(--accent); }

/* A document opened over the page reads at body size, not at the small size a
   dialog of controls uses. */
.modal-card .doc { display: flex; flex-direction: column; gap: var(--sp-3); }
.modal-card .doc p { font-size: var(--fs-body); line-height: var(--lh-body); }
.modal-card .doc .panel { border: 0; padding: 0; }

@media (max-width: 40rem) {
  .ev { grid-template-columns: 1fr auto; row-gap: var(--sp-1); padding: var(--sp-3); }
  .ev-date { grid-column: 1 / -1; }
}

/* A document's head, on the page. The body opens. Hiding both was the mistake
   in the first pass: the head is the only part anybody needs at a glance, and
   for a tailored CV it is also where a wrong job title would be spotted. */
.preview { display: flex; flex-direction: column; gap: var(--sp-2); }
.preview-lead {
  margin: 0;
  font-size: var(--fs-small);
  line-height: 1.5;
  color: var(--ink);
  font-weight: var(--fw-medium);
}
.preview-body {
  margin: 0;
  font-size: var(--fs-small);
  line-height: 1.55;
  color: var(--ink-2);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.preview-meta { margin: 0; font-family: var(--font-mono); font-size: var(--fs-eyebrow); color: var(--ink-3); }
.preview-none { margin: 0; font-size: var(--fs-small); color: var(--ink-3); }
.preview-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }
.preview-actions .att { padding: 0; min-height: 0; border: 0; background: none; font-size: var(--fs-small); }
.preview-actions .att:hover { background: none; text-decoration: underline; }

/* Shadow metering in the admin list: a counter already past its plan. */
.who-meta.over { color: var(--crit); }

/* What a plan includes and what is left of it. A bar rather than a number
   alone, because "34 of 40" is read as a fraction long before it is read as
   two numbers. */
.usage { display: flex; flex-direction: column; gap: var(--sp-3); margin-top: var(--sp-3); }
.usage-row { display: grid; grid-template-columns: minmax(9rem, 12rem) 1fr auto; gap: var(--sp-3); align-items: center; }
.usage-label { font-size: var(--fs-small); }
.usage-note { color: var(--ink-3); font-size: var(--fs-label); }

/* A receipt, not another card. It sits above the plan it just changed and
   stays until it is dismissed, because somebody who has been charged should
   be able to read what they bought more than once. */
.panel.receipt { border-color: var(--ok-line); background: var(--ok-soft); }
.panel.receipt .row-title { color: var(--ok); }

/* Plans, as things you get rather than prices in a list. One card per plan
   with both ways to pay inside it, because how often you are billed is the
   second question and does not deserve a row of its own. */
.plan-intro { font-size: var(--fs-small); color: var(--ink-2); margin: 0 0 var(--sp-3); max-width: 60ch; }
/* Stretched rather than content-height on purpose: two plans side by side
   are being compared, and comparison is easier when the bullet lists and the
   buttons line up across cards. */
.plan-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr)); gap: var(--sp-3); }
.plan-card { gap: var(--sp-3); }
.plan-card.current { border-color: var(--accent-line, var(--line-strong)); }
.plan-card .row-title { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.plan-features { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
.plan-features li {
  position: relative; padding-left: 1.15rem;
  font-size: var(--fs-small); line-height: var(--lh-small); color: var(--ink-2);
}
.plan-features li::before {
  content: ""; position: absolute; left: 0; top: .55em;
  width: 5px; height: 5px; border-radius: 50%; background: var(--accent);
}
/* .row-main grows by default, which is right in a list row and wrong here:
   in a flex column it swallows the card's spare height and leaves a hole
   between the description and the features. The cards still match heights —
   that is what makes two plans comparable — but the slack collects above the
   buttons, where it belongs. */
.plan-card .row-main, .once-card .row-main { flex: 0 0 auto; }
.plan-card .actions, .once-card .actions { margin-top: auto; }

/* A note inside a filled button cannot use the muted ink it uses on paper. */
.actions button.primary .usage-note { color: inherit; opacity: .75; }

/* One drawn mark per plan, so three cards of grey text are not identical at a
   glance. Same stroke weight as the rest of the app's line work. */
.plan-head { display: flex; align-items: center; gap: var(--sp-2); }
.plan-mark { width: 22px; height: 22px; color: var(--accent); flex: none; }
.plan-card.current .plan-mark { color: var(--ink-3); }
.plan-head .row-title { font-size: var(--fs-body); }
.plan-desc { font-size: var(--fs-small); color: var(--ink-2); margin: 0; line-height: var(--lh-small); }

/* The upgrade argument, in the one sentence somebody will actually read. */
.plan-delta {
  margin: 0; padding: var(--sp-2) var(--sp-3);
  background: var(--accent-soft); border-left: 2px solid var(--accent);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  font-size: var(--fs-label); color: var(--ink-2); line-height: var(--lh-small);
}
.plan-delta strong { color: var(--accent); font-weight: 600; }

/* Prices as rows rather than buttons in a line: each one carries what it
   works out to a month, which is the number that actually persuades. */
.price-rows { margin-top: auto; display: flex; flex-direction: column; gap: var(--sp-2); }
.price-row {
  display: grid; grid-template-columns: 1fr auto; gap: var(--sp-1) var(--sp-3);
  align-items: center; padding-top: var(--sp-2); border-top: 1px solid var(--line);
}
.price-row.here { opacity: .68; }
.price-main { grid-column: 1; display: flex; align-items: baseline; gap: var(--sp-2); flex-wrap: wrap; }
.price-amt { font-family: var(--font-mono); font-size: 1.15rem; font-weight: 600; color: var(--ink); }
.price-int { font-size: var(--fs-label); color: var(--ink-3); }
.price-sub { grid-column: 1; font-size: var(--fs-label); color: var(--ink-3); }
.price-row > button, .price-here { grid-column: 2; grid-row: 1 / span 2; align-self: center; }
.price-here { font-size: var(--fs-label); color: var(--ink-3); white-space: nowrap; }

/* The saving, said once and loudly, next to the price it applies to. */
.tag-off {
  font-family: var(--font-mono); font-size: var(--fs-label); font-weight: 600;
  color: var(--ok); background: var(--ok-soft); border: 1px solid var(--ok-line);
  border-radius: 999px; padding: 1px 7px; letter-spacing: .02em;
}
@media (max-width: 30rem) {
  .price-row { grid-template-columns: 1fr; }
  .price-row > button, .price-here { grid-column: 1; grid-row: auto; justify-self: start; }
}
.once-head { display: flex; align-items: center; gap: var(--sp-2); }
.once-head .row-title { font-size: var(--fs-body); }
.once-card .plan-mark { width: 20px; height: 20px; color: var(--ink-3); }

/* The pack is a product somebody chooses; the top-ups are a patch on a month
   that ran short. Giving the pack the accent and the filled button says which
   is which without a word of copy. */
.once-card.pack-lead { border-color: var(--accent-line); }
.once-card.pack-lead .plan-mark { color: var(--accent); }
.usage-track { height: 6px; background: var(--ground); border-radius: 999px; overflow: hidden; }
.usage-track i { display: block; height: 100%; background: var(--accent); }
.usage-track i.full { background: var(--crit); }
.usage-count { font-family: var(--font-mono); font-size: var(--fs-label); white-space: nowrap; }
@media (max-width: 34rem) {
  .usage-row { grid-template-columns: 1fr auto; }
  .usage-track { grid-column: 1 / -1; }
}

/* A repeat rate worth looking at: the same work running again and again. */
.num.over { color: var(--warn); font-weight: var(--fw-semibold); }

/* What has been done to an account, and by whom. */
.audit { list-style: none; margin: var(--sp-2) 0 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-2); font-size: var(--fs-small); }
.audit li { border-left: 2px solid var(--line); padding-left: var(--sp-3); }
.who-meta.over { color: var(--warn); }
