/* ═════════════════════════════════════════════════════════════════════════
   Nocturne components — THE ONE component library this product uses.

   Chosen on `Component library + app shell` (Epic 0), before any screen
   exists, so a codebase never ends up with two of everything. Seeded from
   the harvested design system and trimmed to the generic parts; everything
   that belonged to the abandoned product's screens (the five-step rail, the
   reconciliation findings, the freeze/flip badges) was left behind. It is
   re-verified against docs/DESIGN-BAR.md, not trusted because it was ported.

   Rules, in order of how often they get broken:
     1. Values come from tokens.css. Never a hex colour here, never in a
        template; the token lint fails the build on either.
     2. Status is words first, colour second. Every badge, dot and callout
        carries text a screen reader will say; colour alone is decoration.
     3. Focus is visible: :focus-visible gets the accent ring, everywhere.
     4. Wide content scrolls inside .scroll-x; the page never scrolls sideways.
   ═══════════════════════════════════════════════════════════════════════ */

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

html { color-scheme: light dark; }

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: var(--text-md);
  line-height: var(--leading-body);
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  line-height: var(--leading-tight);
  letter-spacing: -0.015em;
  margin: 0 0 var(--space-2);
}
h1 { font-size: var(--text-2xl); letter-spacing: -0.01em; }
h2 { font-size: var(--text-xl); }
h3 { font-size: var(--text-lg); }

p { margin: 0 0 var(--space-3); }
a { color: var(--color-link); text-underline-offset: 0.2em; }
a:hover { color: var(--color-link-hover); }
img { display: block; max-width: 100%; }
code, kbd, samp { font-family: var(--font-mono); font-size: 0.92em; }

/* `hidden` means hidden. The browser's own rule is the weakest there is, so
   any component that sets `display` (.card, .btn) silently un-hides an element
   carrying the attribute - which is how the load cell's empty "What you
   picked" preview showed before any file was chosen. */
[hidden] { display: none !important; }

:focus { outline: none; }
:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }
::selection { background: color-mix(in srgb, var(--color-accent) 30%, transparent); }

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

/* Skip link: first focusable thing on every page, visible only when focused. */
.skip-link {
  position: absolute;
  left: var(--space-4);
  top: calc(-1 * var(--space-12));
  padding: var(--space-2) var(--space-4);
  background: var(--color-surface);
  color: var(--color-text);
  border-radius: var(--radius-md);
  z-index: 200;
}
/* The shadow only when it shows: parked off-screen, a shadow's blur still
   reached into the top-left corner of every page as a grey smudge. (Never
   visibility:hidden or display:none here - either would drop it from the tab
   order, and it must stay the first tab stop.) */
.skip-link:focus { top: var(--space-4); box-shadow: var(--shadow-md); }

.num { font-variant-numeric: tabular-nums; }
.right { text-align: right; }
.muted { color: var(--color-muted); }
.dim { color: var(--color-dim); }
.strong { font-weight: 600; color: var(--color-text); }
.mono { font-family: var(--font-mono); font-size: 0.92em; }
.nowrap { white-space: nowrap; }
.push { margin-left: auto; }
.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* A Nocturne signature: freestanding rules fade out at both ends over one
   baseline unit instead of stopping cleanly. */
.hr {
  height: 1px;
  border: 0;
  margin: var(--space-4) 0;
  background: linear-gradient(to right,
    transparent, var(--color-divider) var(--fade),
    var(--color-divider) calc(100% - var(--fade)), transparent);
}

/* ── app shell: header, nav, main, footer ─────────────────────────────── */
.nav {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  padding: var(--space-3) var(--space-7);
  border-bottom: 1px solid transparent;
  border-image: linear-gradient(90deg,
    transparent, var(--color-divider) var(--fade),
    var(--color-divider) calc(100% - var(--fade)), transparent) 1;
}
.nav-brand {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: var(--text-md);
  margin-right: auto;
  color: var(--color-text);
  text-decoration: none;
}
.nav-brand .dot {
  width: var(--space-2); height: var(--space-2); border-radius: 50%;
  background: var(--color-accent);
  box-shadow: 0 0 var(--glow) var(--color-accent);
}
.nav ul { display: flex; gap: var(--space-5); list-style: none; margin: 0; padding: 0; }
.nav a { color: inherit; text-decoration: none; font-size: var(--text-sm); }
.nav a:hover,
.nav a[aria-current="page"] { color: var(--color-accent); }

main { flex: 1 0 auto; }

.footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2) var(--space-6);
  padding: var(--space-4) var(--space-7);
  font-size: var(--text-xs);
  color: var(--color-muted);
}
.footer-text { margin: 0; }
/* Signed out, the status page's only link: small, but a full tap target. */
.footer-link { display: inline-flex; align-items: center; min-height: var(--tap); color: var(--color-link); }
.footer-link[aria-current="page"] { font-weight: 600; }
@media (max-width: 40em) { .footer { padding-inline: var(--space-4); } }

/* ── page shells ──────────────────────────────────────────────────────── */
.page {
  max-width: var(--measure-page);
  margin: 0 auto;
  padding: var(--space-7) var(--space-7) var(--space-12);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.page-narrow { max-width: var(--measure-narrow); }
.page-wide { max-width: var(--measure-wide); }
.col { display: flex; flex-direction: column; gap: var(--space-4); min-width: 0; }
.cols-2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4); }

/* A flex or grid item defaults to min-width:auto, so one wide table pushes the
   whole page sideways instead of scrolling inside its own card. */
.page > *, .col > *, .cols-2 > * { min-width: 0; }
@media (max-width: 52em) { .cols-2 { grid-template-columns: 1fr; } }
@media (max-width: 40em) { .page { padding: var(--space-4) var(--space-4) var(--space-8); } .nav { padding-inline: var(--space-4); } }

.page-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
}

.kicker {
  font-size: var(--text-2xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-accent-text);
}
.kicker-ok { color: var(--ok-text); }
.kicker-warn { color: var(--warn-text); }
.kicker-bad { color: var(--bad-text); }

.lede { font-size: var(--text-sm); color: var(--color-muted); }

/* ── cards and callouts ───────────────────────────────────────────────── */
.card {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
  padding: var(--space-5);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  box-shadow: var(--shadow-sm);
}
.card > * { min-width: 0; }
.card-tight { padding: var(--space-4); gap: var(--space-2); }
.card-flush { padding: 0; overflow: hidden; }

.card-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-3);
  flex-wrap: wrap;
}
.card-head .meta { font-size: var(--text-xs); color: var(--color-muted); }

/* The accent-bordered callout that carries each screen's one idea:
   verdict first, evidence second. */
.callout { border-left: 2px solid var(--color-accent); gap: var(--space-2); padding: var(--space-4); }
.callout .head {
  font-size: var(--text-2xs); letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--color-accent-text); font-weight: 600;
}
.callout .claim { font-size: var(--text-lg); font-weight: 500; line-height: var(--leading-snug); }
.callout .body { font-size: var(--text-sm); color: var(--color-dim); line-height: var(--leading-body); }
/* A verdict that fits on one line: the summary, and its explanation folded
   into a disclosure underneath, so content below starts on the first screen. */
.callout-compact { padding: var(--space-3) var(--space-4); gap: var(--space-1); }
.callout-compact .claim { font-size: var(--text-md); margin: 0; }
.callout-compact .disclosure > summary { padding: var(--space-1) 0; }
.callout-compact .body { margin: var(--space-2) 0 0; }
.callout-ok { border-left-color: var(--ok); }
.callout-ok .head { color: var(--ok-text); }
.callout-warn { border-left-color: var(--warn); }
.callout-warn .head { color: var(--warn-text); }
.callout-bad { border-left-color: var(--bad); }
.callout-bad .head { color: var(--bad-text); }

/* ── buttons ──────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--tap);
  cursor: pointer;
  text-decoration: none;
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: var(--text-md);
  line-height: 1.2;
  color: var(--color-text);
  background: transparent;
  border: 1px solid transparent;
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-md);
  transition: background-color var(--motion-fast) var(--ease);
}
.btn:hover { color: var(--color-text); }
.btn:disabled,
.btn[aria-disabled="true"] { opacity: 0.45; cursor: not-allowed; pointer-events: none; }
.btn-primary { color: var(--color-accent); border-color: var(--color-accent); }
.btn-primary:hover { background: color-mix(in srgb, var(--color-accent) 12%, transparent); color: var(--color-accent); }
.btn-primary:active { background: color-mix(in srgb, var(--color-accent) 22%, transparent); }
/* The filled primary: the one action a page leads with (the landing page's
   Sign in). The page background on the accent clears 4.5:1 in both themes. */
.btn-filled,
.btn-filled:hover { background: var(--color-accent); border-color: var(--color-accent); color: var(--color-bg); }
.btn-filled:hover { background: color-mix(in srgb, var(--color-accent) 88%, var(--color-text)); }
.btn-filled:active { background: color-mix(in srgb, var(--color-accent) 78%, var(--color-text)); }
.btn-secondary { border-color: var(--color-divider); }
.btn-secondary:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.btn-ghost { color: var(--color-accent); padding-inline: var(--space-3); }
.btn-ghost:hover { background: color-mix(in srgb, var(--color-accent) 10%, transparent); color: var(--color-accent); }
.btn-danger { color: var(--bad-text); }
.btn-danger:hover { background: color-mix(in srgb, var(--bad) 12%, transparent); color: var(--bad-text); }
.btn-block { width: 100%; }
.btn-row { display: flex; gap: var(--space-2); flex-wrap: wrap; }
/* Busy (Ben, 2026-11-06): a button or link still waiting after a quarter of a
   second gets a spinning ring before its label, so a pressed button that is
   working never looks like one that did nothing. busy.js decides when; the
   upload's own aria-busy button gets the same ring. Busy is not disabled: the
   button keeps its full colour even while htmx holds it disabled. With reduced
   motion the ring stays but does not turn. */
.btn[data-busy],
.btn[aria-busy="true"] { opacity: 1; cursor: progress; }
.btn[data-busy]::before,
.btn[aria-busy="true"]::before,
a[data-busy]:not(.btn)::after {
  content: "";
  display: inline-block;
  flex: none;
  width: 1em;
  height: 1em;
  border: 0.15em solid color-mix(in srgb, var(--color-accent) 25%, transparent);
  border-top-color: var(--color-accent);
  border-radius: 50%;
  vertical-align: -0.15em;
  animation: busy-spin var(--motion-spin) linear infinite;
}
a[data-busy]:not(.btn)::after { margin-left: var(--space-2); }
.btn-filled[data-busy]::before,
.btn-filled[aria-busy="true"]::before {
  border-color: color-mix(in srgb, currentColor 30%, transparent);
  border-top-color: currentColor;
}
@keyframes busy-spin { to { transform: rotate(1turn); } }

/* ── forms ────────────────────────────────────────────────────────────── */
.field { display: flex; flex-direction: column; gap: var(--space-1); }
.field > label,
.field-label {
  font-size: var(--text-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-muted);
}
.field .help { font-size: var(--text-xs); color: var(--color-muted); line-height: var(--leading-body); }
.field .error { font-size: var(--text-xs); color: var(--bad-text); }
.input {
  width: 100%;
  min-height: var(--tap);
  padding: var(--space-2) var(--space-3);
  font: inherit;
  font-size: var(--text-sm);
  color: var(--color-text);
  caret-color: var(--color-accent);
  background: var(--color-bg);
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-md);
}
.input:hover { border-color: color-mix(in srgb, var(--color-text) 45%, transparent); }
.input:focus-visible { border-color: var(--color-accent); outline-offset: 0; }
.input::placeholder { color: var(--color-placeholder); }
/* A multi-line box that takes secret values (Assets' paste-a-.env box): masked
   as typed where the browser supports it, like a password input. A textarea has
   no type="password", so this is the closest the platform offers. */
.input-secret { -webkit-text-security: disc; }
.input[aria-invalid="true"] { border-color: var(--bad); }

/* ── tags & badges: word first, colour second ─────────────────────────── */
.tag {
  display: inline-flex;
  align-items: center;
  font-size: var(--text-xs);
  letter-spacing: 0.02em;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-sm);
  white-space: nowrap;
}
.tag-accent { background: var(--color-accent-800); color: var(--color-accent-100); }
.tag-neutral { background: var(--color-neutral-800); color: var(--color-neutral-100); }
.tag-outline { border: 1px solid var(--color-accent); color: var(--color-accent); }

.badge {
  display: inline-block;
  font-size: var(--text-2xs);
  font-weight: 600;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-sm);
  text-align: center;
  white-space: nowrap;
}
.badge-pass { background: color-mix(in srgb, var(--ok) 14%, transparent); color: var(--ok-text); }
.badge-warn { background: color-mix(in srgb, var(--warn) 15%, transparent); color: var(--warn-text); }
.badge-fail { background: color-mix(in srgb, var(--bad) 16%, transparent); color: var(--bad-text); }
.badge-neutral { background: var(--color-neutral-800); color: var(--color-neutral-300); }
.badge-accent { background: var(--color-accent-800); color: var(--color-accent-100); }

/* ── tables ───────────────────────────────────────────────────────────── */
.scroll-x { overflow-x: auto; min-width: 0; max-width: 100%; }
.table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
}
.table caption { text-align: left; font-size: var(--text-xs); color: var(--color-muted); padding-bottom: var(--space-2); }
.table th {
  text-align: left;
  font-size: var(--text-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-muted);
  padding: var(--space-2) var(--space-3);
  white-space: nowrap;
}
.table td { padding: var(--space-2) var(--space-3); }
.table th.num, .table td.num { text-align: right; }
.table thead tr {
  background: linear-gradient(to right,
    transparent, var(--color-divider) var(--fade),
    var(--color-divider) calc(100% - var(--fade)), transparent) no-repeat bottom / 100% 1px;
}
.table tbody tr {
  background: linear-gradient(to right,
    transparent, var(--color-row-rule) var(--fade),
    var(--color-row-rule) calc(100% - var(--fade)), transparent) no-repeat bottom / 100% 1px;
}
.table tfoot td { padding-top: var(--space-3); font-weight: 600; }

/* ── key/value grids ──────────────────────────────────────────────────── */
.kv {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-2) var(--space-4);
  font-size: var(--text-sm);
}
.kv dt { color: var(--color-muted); }
.kv dd { margin: 0; }

/* ── dialog ───────────────────────────────────────────────────────────── */
.scrim {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: color-mix(in srgb, var(--color-bg) 74%, transparent);
  backdrop-filter: blur(var(--blur));
  display: flex;
  /* flex-start + margin:auto on the child, not align-items:center: a dialog
     taller than the viewport would otherwise clip beyond reach at the top. */
  align-items: flex-start;
  justify-content: center;
  padding: var(--space-6);
  overflow: auto;
}
.dialog {
  margin: auto;
  width: var(--measure-dialog);
  max-width: calc(100vw - var(--space-12));
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  background: var(--color-surface);
  border: 1px solid var(--color-neutral-700);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  padding: var(--space-6);
}
.dialog .title { font-size: var(--text-lg); font-weight: 600; }
.dialog .sub { font-size: var(--text-xs); color: var(--color-muted); }
.dialog-actions { display: flex; justify-content: flex-end; gap: var(--space-3); }

/* ── notes and code ───────────────────────────────────────────────────── */
.note { font-size: var(--text-xs); color: var(--color-muted); line-height: var(--leading-body); }
.note-wide { max-width: 74ch; }

.snippet {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  background: var(--color-neutral-800);
  border-radius: var(--radius-sm);
  padding: var(--space-3) var(--space-4);
  color: var(--color-neutral-300);
  line-height: 1.7;
  overflow-x: auto;
}

/* A long snippet - the repair prompt is ~3000 characters - is scrollable rather
   than endless, so the action above it stays reachable without a page of
   scrolling first. */
.snippet-scroll { max-height: 24rem; overflow-y: auto; white-space: pre-wrap; }

/* Native disclosure, so it is keyboard- and screen-reader-operable with no JS
   and no ARIA of our own. */
.disclosure > summary {
  cursor: pointer;
  font-size: var(--text-xs);
  color: var(--color-link);
  padding: var(--space-2) 0;
}
.disclosure > summary:hover { color: var(--color-link-hover); }

/* ── the state bloc: loading | empty | error | no-permission ──────────── */
/* One component for every absent-content moment, rendered only through the
   {% state_bloc %} tag. Verdict first (title), then what to do (detail), then
   the one action. Usable at 360px: the action goes full width and padding
   steps down. Status words are inside the title so a screen reader says them. */
.state {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-6);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  box-shadow: var(--shadow-sm);
}
.state-compact { padding: var(--space-4); }
.state .state-title { font-size: var(--text-lg); font-weight: 500; line-height: var(--leading-snug); margin: 0; }
.state .state-title .badge { vertical-align: middle; margin-right: var(--space-2); }
.state .state-detail { font-size: var(--text-sm); color: var(--color-dim); max-width: 60ch; margin: 0; }
.state .state-action { margin: var(--space-2) 0 0; }
.state[data-state="error"] { border-left: 2px solid var(--bad); }
.state[data-state="no-permission"] { border-left: 2px solid var(--warn); }
.state[data-state="loading"] .state-title::after {
  content: "…";
  display: inline-block;
  animation: state-pulse var(--motion-slow) var(--ease) infinite alternate;
}
@keyframes state-pulse { from { opacity: 0.35; } to { opacity: 1; } }
@media (max-width: 26em) {
  .state { padding: var(--space-4); }
  .state .state-action .btn { width: 100%; }
}

/* --- preview-as-user: the simulation frame (Preview-as-user experience pass) ---
   An unmistakable region: a thick accent border, a solid header bar carrying
   "Viewing as <name> - preview", the platform surface inside. Tokens only. */
.sim-frame {
  border: var(--space-1) solid var(--color-accent);
  border-radius: var(--radius-lg);
  overflow: hidden;
  margin-block: var(--space-4);
}
.sim-frame-bar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  align-items: baseline;
  padding: var(--space-2) var(--space-4);
  background: var(--color-accent-800);
  color: var(--color-accent-100);
}
.sim-frame-bar .who {
  font-weight: var(--font-heading-weight);
}
.sim-frame-body {
  padding: var(--space-4);
  background: var(--color-surface);
}
.sim-numbers {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  margin: 0 0 var(--space-4);
  padding: 0;
  list-style: none;
}
.sim-numbers .num {
  font-size: var(--text-xl);
  font-weight: var(--font-heading-weight);
  display: block;
}
.sim-numbers .label {
  color: var(--color-muted);
}


/* --- the notebook run: one page of cells under a fixed rail ------------------- */
/* Onboard run page + run rail. The rail renders derived state only; every    */
/* status is also in text (the label and the status word), never colour alone. */
/* The cells sit under four plain stage headings and each is a native          */
/* <details>: open when you can act on it, folded to one line when it is done  */
/* or locked. Not a stepper - every step is on the page, in any state.         */

/* The run takes the main column's whole width: the tick rail is a slim strip at
   its left edge, beside the sidebar, and the cells take all the rest - the
   column grids use it, while prose keeps a reading measure (below). A cap only on
   very wide screens, and it stays left so the rail stays at the side. */
.run {
  display: grid;
  grid-template-columns: var(--tick-rail-width) minmax(0, 1fr);
  align-items: start;
  max-width: var(--measure-run);
}
/* htmx shows its indicator with opacity only, so a hidden "Testing..." bloc
   still took a screen of blank space under the connect form. Out of the flow
   until the request is actually running. */
.htmx-indicator:not(.htmx-request) { display: none; }
/* The tick rail (Ben's ruling 2026-11-03): like the table-of-contents strip in a
   notebook. A sideways pill per stage, a dash per step, in view while the page
   scrolls. Hovering or focusing it opens a panel over the work area, without
   moving it, naming every stage and step with its status in words. Closing waits
   a beat, so crossing into the panel does not flicker. tick_rail.js adds the
   close after a jump, Escape, and tap-to-open on touch. */
.tickrail {
  position: sticky;
  top: 0;
  z-index: 4;
  height: var(--viewport-height);
  background: var(--color-surface);
  border-right: 1px solid var(--color-divider);
}
.tickrail ol { list-style: none; margin: 0; padding: 0; }
.tickrail .tickrail-strip { height: 100%; overflow-y: auto; scrollbar-width: none; padding-block: var(--space-5); }
.tickrail-stage + .tickrail-stage { margin-top: var(--space-4); }
.tick { display: flex; align-items: center; justify-content: center; width: 100%; }
.tick-stage { height: var(--tick-stage-row); }
.tick-step { height: var(--tick-step-row); }
.tick:focus-visible { outline: var(--focus-ring); outline-offset: -1px; border-radius: var(--radius-sm); }
/* Roomier targets where a finger does the pointing. */
@media (hover: none), (max-width: 40em) {
  .tick-stage { height: var(--tick-stage-row-touch); }
  .tick-step { height: var(--tick-step-row-touch); }
}
.tick-mark { display: block; flex: none; background: var(--color-muted); }
.tick-stage .tick-mark { width: var(--tick-stage-width); height: var(--tick-stage-height); border-radius: var(--radius-lg); }
.tick-step .tick-mark { width: var(--tick-step-width); height: var(--tick-step-height); border-radius: var(--radius-sm); }
.tick:hover .tick-mark { filter: brightness(1.15); }
/* The statuses, on a tick and on its row in the panel. Colour is never alone:
   the current one is longer and ringed, a stale one hatched. */
.st-passed > .tick-mark { background: var(--ok); }
.st-in-progress > .tick-mark { background: var(--color-accent); }
.st-failed > .tick-mark { background: var(--bad); box-shadow: 0 0 0 1px var(--bad); }
.st-stale > .tick-mark {
  background: repeating-linear-gradient(135deg, var(--warn) 0 var(--tick-hatch), transparent var(--tick-hatch) calc(var(--tick-hatch) * 1.6));
  box-shadow: 0 0 0 1px var(--warn);
}
/* A finished stage, and every finished or failed name in the panel, shows a
   mark, not just a colour (Jenna's UAT, 2026-10-29: "obvious check marks").
   The step dashes stay dashes - too small to carry a mark. */
.tick-stage.st-passed > .tick-mark,
.tickrail-name.st-passed > .tick-mark,
.tick-stage.st-failed > .tick-mark,
.tickrail-name.st-failed > .tick-mark {
  position: relative;
  width: var(--space-4);
  height: var(--space-4);
  border-radius: 50%;
}
.tickrail-name-step.st-passed > .tick-mark,
.tickrail-name-step.st-failed > .tick-mark { width: var(--space-3); height: var(--space-3); }
.tick-stage.st-passed > .tick-mark::after,
.tickrail-name.st-passed > .tick-mark::after,
.tick-stage.st-failed > .tick-mark::after,
.tickrail-name.st-failed > .tick-mark::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--color-surface);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6.5 12.5l3.7 3.7L17.5 8.5' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6.5 12.5l3.7 3.7L17.5 8.5' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
}
.tick-stage.st-failed > .tick-mark::after,
.tickrail-name.st-failed > .tick-mark::after { -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M8 8l8 8M16 8l-8 8' fill='none' stroke='black' stroke-width='3' stroke-linecap='round'/%3E%3C/svg%3E") center / contain no-repeat; mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M8 8l8 8M16 8l-8 8' fill='none' stroke='black' stroke-width='3' stroke-linecap='round'/%3E%3C/svg%3E") center / contain no-repeat; }
.st-current > .tick-mark { box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-accent) 35%, transparent); }
.st-current:not(.st-stale, .st-failed) > .tick-mark { background: var(--color-accent); }
.tick-step.st-current > .tick-mark { width: var(--tick-step-width-current); }
.tickrail-panel {
  position: absolute;
  left: 100%;
  top: 0;
  width: min(var(--tick-panel-width), calc(100vw - var(--tick-rail-width) - var(--space-2)));
  height: 100%;
  overflow-y: auto;
  padding: var(--space-4) var(--space-3);
  background: var(--color-surface);
  border-right: 1px solid var(--color-divider);
  box-shadow: var(--shadow-lg);
  clip-path: inset(0 calc(-1 * var(--space-12)) 0 0); /* its shadow falls on the work area, not the strip */
  visibility: hidden;
  opacity: 0;
  transition: opacity var(--motion-fast) ease var(--tick-close-delay), visibility 0s linear calc(var(--motion-fast) + var(--tick-close-delay));
}
.tickrail:focus-within .tickrail-panel,
.tickrail[data-open] .tickrail-panel { visibility: visible; opacity: 1; transition-delay: 0s; }
@media (hover: hover) {
  .tickrail:hover .tickrail-panel { visibility: visible; opacity: 1; transition-delay: 0s; }
}
/* Just jumped: closed at once, until the pointer leaves or focus comes back. */
.tickrail[data-dismissed]:not([data-open]) .tickrail-panel { visibility: hidden; opacity: 0; transition: none; }
.tickrail-head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-2); margin: 0; padding-inline: var(--space-2); }
.tickrail-count { font-size: var(--text-xs); color: var(--color-muted); }
.tickrail-next { margin: var(--space-1) 0 0; padding-inline: var(--space-2); font-size: var(--text-xs); color: var(--color-dim); }
.tickrail .tickrail-list { display: grid; gap: var(--space-3); margin-top: var(--space-3); }
.tickrail-name {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--tick-name-row);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  color: var(--color-text);
  text-decoration: none;
  font-size: var(--text-sm);
}
.tickrail-name:hover, .tickrail-name.is-active { background: color-mix(in srgb, var(--color-accent) 10%, transparent); }
.tickrail-name:focus-visible { outline: var(--focus-ring); outline-offset: -1px; }
.tickrail-name-stage { font-size: var(--text-xs); font-weight: 600; }
.tickrail-name-stage .tick-mark { width: var(--tick-step-width-current); height: var(--tick-name-stage-height); border-radius: var(--radius-lg); }
.tickrail-name-step { padding-left: var(--space-5); }
.tickrail-name-step .tick-mark { width: var(--tick-step-width); height: var(--tick-step-height); border-radius: var(--radius-sm); }
.tickrail-label { flex: 1 1 auto; min-width: 0; }
.tickrail-word { flex: none; font-size: var(--text-2xs); font-weight: 400; color: var(--color-muted); }
.st-passed > .tickrail-word { color: var(--ok-text); }
.st-stale > .tickrail-word { color: var(--warn-text); font-weight: 600; }
.st-failed > .tickrail-word { color: var(--bad-text); font-weight: 600; }
.st-current > .tickrail-word, .st-in-progress > .tickrail-word { color: var(--color-accent-text); font-weight: 600; }

.run-cells { display: grid; gap: var(--space-8); min-width: 0; padding: var(--space-6) var(--space-6) var(--space-12); }
@media (max-width: 40em) { .run-cells { gap: var(--space-4); padding: var(--space-4) var(--space-3) var(--space-8); } }
.run-head { margin-bottom: 0; }
.run-stage { display: grid; gap: var(--space-2); min-width: 0; scroll-margin-top: var(--space-6); }
.run-stage-head { margin-bottom: var(--space-1); }
.run-stage-head h2 { display: flex; align-items: center; gap: var(--space-3); font-size: var(--text-xl); margin: 0; }
.run-stage-num {
  display: inline-grid;
  place-items: center;
  flex: none;
  width: var(--tap);
  height: var(--tap);
  border-radius: 50%;
  background: var(--color-accent-800);
  color: var(--color-accent-100);
  font-size: var(--text-md);
  font-weight: 600;
}
.run-stage-blurb { margin: var(--space-1) 0 0; font-size: var(--text-sm); color: var(--color-dim); }
.run-stage-count { color: var(--color-muted); white-space: nowrap; }
.run-stage-count::before { content: "· "; }

.run-cell {
  min-width: 0;
  scroll-margin-top: var(--space-6);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  box-shadow: var(--shadow-sm);
}
.run-cell-blocked { background: transparent; }
.run-cell-passed { box-shadow: inset 2px 0 0 var(--ok), var(--shadow-sm); }
.run-cell-failed { box-shadow: inset 2px 0 0 var(--bad), var(--shadow-sm); }
.run-cell-stale { box-shadow: inset 2px 0 0 var(--warn), var(--shadow-sm); }
.run-fold-summary {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--space-1) var(--space-3);
  align-items: center;
  min-height: var(--tap);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  cursor: pointer;
  list-style: none;
}
.run-fold-summary::-webkit-details-marker { display: none; }
.run-fold-summary::marker { content: ""; }
.run-fold-summary:hover { background: color-mix(in srgb, var(--color-accent) 6%, transparent); }
.run-fold-summary:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }
.run-fold-title { grid-column: 1; display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-1) var(--space-3); min-width: 0; }
.run-cell-head { font-size: var(--text-lg); margin: 0; }
.run-cell-blocked .run-cell-head { color: var(--color-dim); font-size: var(--text-md); }
.run-fold-line { grid-column: 1; min-width: 0; font-size: var(--text-sm); color: var(--color-dim); overflow-wrap: anywhere; }
.run-fold-toggle { grid-column: 2; grid-row: 1 / span 2; font-size: var(--text-xs); color: var(--color-link); white-space: nowrap; }
.run-fold-toggle::after { content: " ▾"; }
.run-fold[open] > .run-fold-summary .run-fold-toggle::after { content: " ▴"; }
.run-fold[open] > .run-fold-summary .when-closed,
.run-fold:not([open]) > .run-fold-summary .when-open { display: none; }
.run-fold-body { display: grid; gap: var(--space-3); min-width: 0; padding: 0 var(--space-4) var(--space-4); }
.run-cell-body .cell-needs,
.run-cell-body .data-note,
.run-cell-body .callout .claim,
.run-cell-wait,
.run-stage-blurb,
.run-head .lede,
.run-fold-body .banner p { max-width: 74ch; }
.run-cell-purpose { margin: 0; max-width: 74ch; font-size: var(--text-sm); color: var(--color-dim); }
.run-cell-wait { margin: 0; font-size: var(--text-sm); }
.run-cell-body { min-width: 0; }
.run-cell-body > .page { padding: 0; margin: 0; max-width: var(--measure-wide); gap: var(--space-3); }
.run-cell-body > .page > .page-head { display: none; }
.run-cell-body .card { background: var(--color-surface); box-shadow: inset 0 0 0 1px var(--color-divider); padding: var(--space-4); }
.run-cell-body .card.callout { padding: var(--space-3) var(--space-4); }
.run-cell-body .state { box-shadow: inset 0 0 0 1px var(--color-divider); }

/* The status chip: the word is the status; the tint only repeats it. */
.run-chip {
  display: inline-block;
  padding: 0 var(--space-2);
  border-radius: var(--radius-sm);
  font-size: var(--text-2xs);
  font-weight: 600;
  line-height: 1.7;
  white-space: nowrap;
  background: var(--color-neutral-800);
  color: var(--color-neutral-300);
}
.run-chip-passed { background: color-mix(in srgb, var(--ok) 14%, transparent); color: var(--ok-text); font-size: var(--text-xs); }
/* A done step says so at a glance, not only in its small chip (Jenna's UAT). */
.run-chip-passed::before { content: "\2713\00a0"; }
.run-chip-failed { background: color-mix(in srgb, var(--bad) 16%, transparent); color: var(--bad-text); }
.run-chip-stale, .run-chip-in-progress { background: color-mix(in srgb, var(--warn) 15%, transparent); color: var(--warn-text); }
.run-chip-not-started { background: var(--color-accent-800); color: var(--color-accent-100); }

/* One sentence where a cell used to have a "This cell needs" box. */
.cell-needs { margin: 0; font-size: var(--text-sm); }
.cell-needs strong { font-weight: 600; }
/* The one-sentence line also sits straight on the page background on a
   standalone cell page, where a tint over --color-bg falls under AA in light;
   tint over the surface instead, as it reads inside a run cell. */
.cell-needs .badge-pass { background: color-mix(in srgb, var(--ok) 14%, var(--color-surface)); }
.cell-needs .badge-warn { background: color-mix(in srgb, var(--warn) 15%, var(--color-surface)); }
.preview-done { margin: 0; font-size: var(--text-sm); color: var(--ok-text); }
.who-name { font-weight: 500; overflow-wrap: anywhere; }
.no-access-pick { display: block; margin-top: var(--space-2); max-width: 24rem; }

.banner { padding: var(--space-3) var(--space-4); border-radius: var(--radius-md); border-left: 2px solid var(--warn); background: var(--color-surface); box-shadow: inset 0 0 0 1px var(--color-divider); }
.banner p { margin: 0; font-size: var(--text-sm); }
.banner-blocked { border-left-color: var(--bad); }
.banner-failed { border-left-color: var(--bad); }
.banner-stale { border-left-color: var(--warn); }
.gate-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-1); font-size: var(--text-sm); }

/* --- compact data: a table that scrolls inside itself, never the page -------- */
/* The "blown out" fix. A bordered box with its own scroll (both ways), a sticky */
/* header and first column, small tabular numbers, and long values cut short    */
/* with the full text in a title. Rows past the first few hide behind a native  */
/* checkbox toggle, so "Show all" works with no script.                          */
.data-box {
  max-width: 100%;
  max-height: 22rem;
  overflow: auto;
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}
.data-box:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }
.table-compact { width: max-content; min-width: 100%; font-size: var(--text-xs); }
.table-compact th,
.table-compact td { padding: var(--space-1) var(--space-3); white-space: nowrap; }
.table-compact td { max-width: 14rem; overflow: hidden; text-overflow: ellipsis; }
.table-compact thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--color-surface);
  box-shadow: inset 0 -1px 0 var(--color-divider);
  font-size: var(--text-2xs);
}
.table-compact tbody th,
.table-compact td:first-child,
.table-compact th:first-child { position: sticky; left: 0; z-index: 1; background: var(--color-surface); }
.table-compact thead th:first-child { z-index: 3; }
.table-compact tbody th { font-weight: 500; text-transform: none; letter-spacing: 0; color: var(--color-text); font-size: var(--text-xs); }
.table-compact .num { text-align: right; }
.table-compact select.input { min-width: 12rem; }
.table-compact .wrap { white-space: normal; min-width: 12rem; }
/* A table whose rows hold controls (the column matches, who can open it) does
   not scroll sideways on a phone: each row becomes a small card, label beside
   value, so the select or button is always on screen. */
@media (max-width: 40em) {
  .table-stack, .table-stack tbody, .table-stack tr { display: block; width: 100%; min-width: 0; }
  .table-stack thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0, 0, 0, 0); }
  .table-stack tr {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-3);
    border-bottom: 1px solid var(--color-divider);
    background: none;
  }
  .table-stack.table-compact th, .table-stack.table-compact td, .table-stack.table-compact th:first-child {
    position: static;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    max-width: none;
    padding: 0;
    white-space: normal;
    background: none;
  }
  .table-stack td[data-label]::before {
    content: attr(data-label);
    min-width: 6rem;
    font-size: var(--text-2xs);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-muted);
  }
  .table-stack td[data-label="Comes from"] { order: 1; }
  .table-stack td[data-label="How"] { order: 3; }
  .table-stack select.input { min-width: 0; flex: 1; }
  .data-box:has(.table-stack) { max-height: none; overflow: visible; }
}
.more-toggle { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-xs); color: var(--color-dim); margin: 0; }
.more-toggle input { margin: 0; }
.more-toggle:has(input:not(:checked)) + .data-box .row-more { display: none; }
.data-note { margin: 0; font-size: var(--text-xs); color: var(--color-muted); }

/* --- the sample-data grid: a SQL results grid, not a report table ------------ */
/* Dense rows, thin grid lines, a sticky header and a sticky muted row-number   */
/* column, a tiny type marker per header, numbers right, nulls muted italic.    */
/* The box scrolls both ways inside itself; about twelve rows show at a time.   */
.sample { display: grid; gap: 0; min-width: 0; margin-top: var(--space-2); }
.sample-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-3);
  margin: 0;
  padding: var(--space-1) var(--space-3);
  font-size: var(--text-xs);
  color: var(--color-muted);
  border: 1px solid var(--color-divider);
  border-bottom: 0;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  background: var(--color-surface);
}
.sample-bar-name { font-weight: 600; color: var(--color-text); }
.sample-bar > span + span::before { content: "· "; }
.sample-box {
  max-width: 100%;
  max-height: 21rem;
  overflow: auto;
  border: 1px solid var(--color-divider);
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  background: var(--color-surface);
}
.sample-box:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }
.sample-grid {
  width: max-content;
  min-width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 1.35;
  font-variant-numeric: tabular-nums;
}
.sample-grid th,
.sample-grid td {
  padding: var(--space-1) var(--space-2);
  max-width: 16rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: left;
  border-right: 1px solid var(--color-row-rule);
  border-bottom: 1px solid var(--color-row-rule);
  background: var(--color-surface);
}
.sample-grid .num { text-align: right; }
.sample-grid thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  font-family: var(--font-body);
  font-weight: 600;
  color: var(--color-text);
  border-bottom: 1px solid var(--color-divider);
  background: var(--color-bg);
}
.sample-grid .sample-rownum {
  position: sticky;
  left: 0;
  z-index: 1;
  min-width: 2.5rem;
  text-align: right;
  font-weight: 400;
  color: var(--color-muted);
  background: var(--color-bg);
  border-right: 1px solid var(--color-divider);
}
.sample-grid thead .sample-rownum { z-index: 3; }
.sample-grid tbody tr:hover td { background: color-mix(in srgb, var(--color-accent) 7%, var(--color-surface)); }
.sample-type {
  display: inline-flex;
  align-items: center;
  margin-right: var(--space-1);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  font-weight: 400;
  color: var(--color-muted);
}
.sample-cal { width: 0.8em; height: 0.8em; }
.sample-null { font-style: italic; color: var(--color-muted); }
.sample-note { margin: var(--space-2) 0 0; font-size: var(--text-xs); color: var(--color-muted); }

/* "Check the columns": the same grid with each column's settings as header   */
/* rows over its own sample data. The four setting rows stick together as one */
/* head while the rows scroll under them; the label column sticks left.       */
.col-table .sample-box { max-height: 34rem; }
.col-table-grid thead { position: sticky; top: 0; z-index: 2; }
.col-table-grid thead th,
.col-table-grid thead td { position: static; overflow: visible; max-width: none; background: var(--color-bg); font-family: var(--font-body); }
.col-table-grid thead .sample-rownum { position: sticky; left: 0; z-index: 3; }
.col-table-grid thead tr.col-table-last > * { border-bottom: 1px solid var(--color-divider); }
.col-table-label { text-align: left; white-space: nowrap; font-size: var(--text-2xs); letter-spacing: 0.06em; text-transform: uppercase; }
.col-table-head,
.col-table-set { min-width: 10rem; vertical-align: middle; }
.col-table-grid .input { width: 100%; min-height: var(--tap); padding: var(--space-1) var(--space-2); font-size: var(--text-xs); }
.col-table-head .input { font-weight: 600; }
.col-needs-fix { display: block; width: fit-content; margin-top: var(--space-1); }
.col-table-grid .kv { display: flex; align-items: center; gap: var(--space-1); font-size: var(--text-xs); white-space: nowrap; }

/* Column lists as compact rows with a kind-of-value badge, not a form per column. */
.col-list { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--space-2); }
.col-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  max-width: 100%;
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-sm);
  font-size: var(--text-xs);
  background: var(--color-surface);
}
.col-chip .mono { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kind { font-size: var(--text-2xs); font-weight: 600; padding: 0 var(--space-1); border-radius: var(--radius-sm); background: var(--color-neutral-800); color: var(--color-neutral-300); white-space: nowrap; }
.kind-number { background: var(--color-accent-800); color: var(--color-accent-100); }
.kind-date { background: color-mix(in srgb, var(--ok) 14%, transparent); color: var(--ok-text); }

/* Choosing one column: chips you pick from, wrapping, never a tall list. */
.pick-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 13rem), 1fr)); gap: var(--space-2); border: 0; padding: 0; margin: 0; min-width: 0; }
.pick-grid > legend { padding: 0; margin-bottom: var(--space-2); }
.pick {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--tap);
  min-width: 0;
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  background: var(--color-surface);
  cursor: pointer;
}
.pick:has(input:checked) { border-color: var(--color-accent); background: color-mix(in srgb, var(--color-accent) 10%, var(--color-surface)); }
.pick:focus-within { outline: var(--focus-ring); outline-offset: 1px; }
.pick .mono { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Viewer session (Epic 2): the platform chrome around a sandboxed dashboard.
   The identity strip and the incident notice are the ONLY things the platform
   puts on the customer's document, and they live outside the iframe. */
.viewer {
  display: flex;
  flex-direction: column;
  min-height: 70vh;
}
.identity-strip {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-4);
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-divider);
  font-size: var(--text-sm);
  color: var(--color-text);
}
.identity-strip[data-collapsed="true"] .identity-scope {
  visibility: hidden;
}
.identity-you {
  font-weight: var(--font-heading-weight);
  color: var(--color-accent-text);
}
.identity-scope {
  margin: 0;
}
.identity-toggle {
  border: 1px solid var(--color-divider);
  background: var(--color-bg);
  color: var(--color-link);
  border-radius: var(--radius-sm);
  padding: var(--space-1) var(--space-3);
  font-size: var(--text-xs);
  min-height: var(--tap);
  cursor: pointer;
}
.identity-toggle:focus-visible {
  outline: var(--focus-ring);
}
/* The dashboard's own code threw on the live rows: a slim, non-blocking note
   above the frame, never over it. */
.render-notice {
  padding: var(--space-2) var(--space-4);
  border-bottom: 1px solid var(--warn);
  color: var(--warn-text);
  background: var(--color-surface);
  font-size: var(--text-sm);
}
.render-notice[hidden] {
  display: none;
}
.render-notice p {
  margin: 0;
}
.viewer-frame {
  position: relative;
  flex: 1 1 auto;
  display: flex;
}
.dash-frame {
  flex: 1 1 auto;
  width: 100%;
  min-height: 60vh;
  border: 0;
  background: var(--color-bg);
}
.handshake {
  display: none;
}
/* The serve-failure panel stands in place of the frame (which is hidden) when
   the serve-grant is refused or the serving host never answers. */
.serve-failure-region:not(:empty) {
  flex: 1 1 auto;
  padding: var(--space-4);
}
.dash-frame[hidden] {
  display: none;
}
/* In the frame's flex row it would otherwise stretch to the frame's full
   height: a near-empty red box. Sized to its words, at the top. */
.incident-notice {
  flex: 1 1 auto;
  align-self: flex-start;
  margin: var(--space-4);
  padding: var(--space-4);
  border: 1px solid var(--bad);
  border-radius: var(--radius-md);
  color: var(--bad-text);
  background: var(--color-surface);
}

/* ── the shell's session area ─────────────────────────────────────────────
   Right of the primary nav: who you are, or the way in. A signed-in person
   who cannot tell they are signed in has no way to know whose data they are
   looking at, which on this product is the whole question. */
.nav-session { display: flex; align-items: center; gap: var(--space-3); margin-left: auto; }
.nav-who {
  font-size: var(--text-xs);
  color: var(--color-muted);
  max-width: 22ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.nav-signout { margin: 0; }

/* At 360px the address would crowd out the action, and the action matters more. */
@media (max-width: 30rem) {
  .nav-who { display: none; }
}

/* --- the pre-upload file preview (load cell) ------------------------------- */
/* The builder's own file, rendered in the browser before anything is sent, so
   they can see they picked the right dashboard. The frame is deliberately short:
   it is a "yes, that's the one" check, not a place to read a dashboard. */
.file-preview { margin-block-start: var(--space-4); }
.file-preview-frame {
  width: 100%;
  height: 24rem;
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-md);
  background: var(--color-neutral-100);
}
@media (max-width: 40em) {
  .file-preview-frame { height: 16rem; }
}
/* --- a radio choice with an explanation, not a bare label ------------------ */
/* The load cell's governed-or-hosted question: each option carries the
   consequence of picking it, so the text has to sit beside the control and wrap
   under itself rather than on one line. */
.field-choice { display: grid; gap: var(--space-3); }
fieldset.field-choice { min-width: 0; margin: 0; padding: 0; border: 0; }
fieldset.field-choice > legend { padding: 0; margin-bottom: var(--space-2); }
.choice {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-3);
  align-items: start;
  padding: var(--space-3);
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-md);
}
.choice:focus-within { border-color: var(--color-accent); }

/* ── the signed-in shell: a left sidebar beside the page ───────────────────
   Signed out, the shell is the top bar above. Signed in, the same items move
   into a sidebar - brand, the role's own navigation, then who you are and the
   way out at the foot - and the page takes the rest of the width. Below
   52em, and at every width on a screen that opts into `shell-compact` (the
   viewer page, which frames a whole dashboard), the sidebar folds into a
   compact top bar whose menu button (sidebar.js) opens the same panel.
   Without script the button never appears and the panel stays open: every
   link is still reachable, stacked above the page. */
body.shell {
  display: grid;
  grid-template-columns: var(--sidebar-now) minmax(0, 1fr);
  align-items: start;
}
/* Collapsed (the default, Ben's ruling 2026-11-01): an icon strip. Each link
   keeps its words for a screen reader (visually hidden, not removed) and as a
   tooltip; the collapse button at the top expands it to labels. */
body.shell[data-sidebar="collapsed"] { --sidebar-now: var(--sidebar-rail); }
.shell-main {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 100vh;
}
.sidebar {
  position: sticky;
  top: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  height: 100vh;
  overflow-y: auto;
  padding: var(--space-5) var(--space-4);
  background: var(--color-surface);
  border-right: 1px solid var(--color-divider);
}
.sidebar-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  min-height: var(--tap);
}
.sidebar-toggle { display: none; }
.sidebar-panel {
  display: flex;
  flex: 1 0 auto;
  flex-direction: column;
  gap: var(--space-5);
}
.sidebar-nav ul { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-1); }
.sidebar-nav a {
  display: flex;
  align-items: center;
  min-height: var(--tap);
  padding: 0 var(--space-3);
  border-radius: var(--radius-md);
  color: var(--color-text);
  text-decoration: none;
  font-size: var(--text-sm);
}
.sidebar-nav a:hover { background: color-mix(in srgb, var(--color-accent) 10%, transparent); }
.sidebar-nav a, .sidebar-signout { gap: var(--space-3); }
.sidebar-icon {
  flex: none;
  width: var(--space-5);
  height: var(--space-5);
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.sidebar-signout { display: inline-flex; align-items: center; }
.sidebar-collapse {
  display: none;
  flex: none;
  place-items: center;
  width: var(--tap);
  height: var(--tap);
  padding: 0;
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--color-text);
  cursor: pointer;
}
.sidebar-collapse:hover { background: color-mix(in srgb, var(--color-accent) 10%, transparent); }
.sidebar-collapse:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }
.sidebar-collapse[hidden] { display: none; }
.sidebar-chevron { transition: transform 0.15s ease; transform-box: fill-box; transform-origin: center; }
body.shell[data-sidebar="expanded"] .sidebar-chevron { transform: scaleX(-1); }
@media (min-width: 52.01em) {
  body.shell:not(.shell-compact) .sidebar-collapse:not([hidden]) { display: inline-grid; }
  body.shell[data-sidebar="collapsed"]:not(.shell-compact) .sidebar { padding-inline: var(--space-2); align-items: center; }
  body.shell[data-sidebar="collapsed"]:not(.shell-compact) .sidebar-bar { flex-direction: column; gap: var(--space-3); }
  body.shell[data-sidebar="collapsed"]:not(.shell-compact) .nav-brand { gap: 0; font-size: 0; margin: 0; justify-content: center; min-width: var(--tap); min-height: var(--tap); }
  body.shell[data-sidebar="collapsed"]:not(.shell-compact) .sidebar-signout { justify-content: center; min-width: var(--tap); }
  body.shell[data-sidebar="collapsed"]:not(.shell-compact) .sidebar-nav a {
    justify-content: center;
    width: var(--tap);
    padding: 0;
  }
  body.shell[data-sidebar="collapsed"]:not(.shell-compact) .sidebar-nav ul { justify-items: center; }
  body.shell[data-sidebar="collapsed"]:not(.shell-compact) .sidebar-label {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
  }
  body.shell[data-sidebar="collapsed"]:not(.shell-compact) .sidebar-foot { justify-items: center; }
  body.shell[data-sidebar="collapsed"]:not(.shell-compact) .sidebar-who,
  body.shell[data-sidebar="collapsed"]:not(.shell-compact) .sidebar-links { display: none; }
}
/* The current page: a tint, a bar and weight, so it is never colour alone. */
.sidebar-nav a[aria-current="page"] {
  background: color-mix(in srgb, var(--color-accent) 16%, transparent);
  box-shadow: inset 2px 0 0 var(--color-accent);
  color: var(--color-accent-text);
  font-weight: 600;
}
.sidebar-foot {
  margin-top: auto;
  display: grid;
  justify-items: start;
  gap: var(--space-2);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-divider);
}
.sidebar-who {
  max-width: 100%;
  margin: 0;
  font-size: var(--text-xs);
  color: var(--color-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sidebar-foot .btn-ghost { padding-inline: 0; }
.sidebar-about { font-size: var(--text-xs); }
.sidebar-links { display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-4); margin: 0; }

@media (max-width: 52em) {
  body.shell { grid-template-columns: minmax(0, 1fr); }
  .sidebar {
    position: static;
    height: auto;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-4);
    border-right: 0;
    border-bottom: 1px solid var(--color-divider);
  }
  .sidebar[data-enhanced="true"] .sidebar-toggle { display: inline-flex; }
  .sidebar[data-enhanced="true"][data-open="false"] .sidebar-panel { display: none; }
  .sidebar-panel { padding-bottom: var(--space-2); }
}
/* The same fold at every width, for a screen that needs the room. */
body.shell-compact { grid-template-columns: minmax(0, 1fr); }
.shell-compact .sidebar {
  position: static;
  height: auto;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-4);
  border-right: 0;
  border-bottom: 1px solid var(--color-divider);
}
.shell-compact .sidebar[data-enhanced="true"] .sidebar-toggle { display: inline-flex; }
.shell-compact .sidebar[data-enhanced="true"][data-open="false"] .sidebar-panel { display: none; }
.shell-compact .sidebar-panel { padding-bottom: var(--space-2); }

/* `shell-fill`: the page is exactly one viewport tall and its content takes
   whatever the compact bar leaves - the viewer page, so a framed dashboard
   reaches the bottom of the window instead of stopping partway. The platform
   footer is dropped there: the customer's dashboard carries only the small
   platform bar (DESIGN-BAR line 12). */
body.shell-fill {
  display: flex;
  flex-direction: column;
  height: var(--viewport-height);
  min-height: 0;
  /* body.shell's `align-items: start` suits its grid, but in this column it
     shrinks every child to its content - and an iframe's content width is the
     browser's 300px default, so the dashboard rendered as a narrow strip. */
  align-items: stretch;
}
.shell-fill .sidebar { flex: 0 0 auto; }
.shell-fill .shell-main { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
.shell-fill main { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.shell-fill main > .viewer { flex: 1 1 auto; min-height: 0; }
.shell-fill .dash-frame { min-height: 0; }
.shell-fill .footer { display: none; }

/* Two page shells in a row (a state bloc's page, then the content's) read as
   one page, not two with a gap between. */
.page + .page { padding-top: 0; }

/* ── dashboard tiles: the Studio and "Your dashboards" ────────────────────
   One card per dashboard in a grid that drops columns as the screen narrows
   (never below one, never wider than the screen). The whole tile is a click
   target through ONE link - the title - whose ::after covers the card; the
   only other control, the secondary action, sits above that layer as its own
   focusable link. No nested interactive elements, one tab stop for the main
   action, and the link's name is the dashboard's name. */
.tile-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--tile-min)), 1fr));
}
.tile {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
  min-height: var(--tile-height);
  padding: var(--space-4);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  box-shadow: var(--shadow-sm);
  transition: box-shadow var(--motion-fast) var(--ease);
}
.tile:hover,
.tile:focus-within { box-shadow: var(--shadow-md); }
.tile-title { margin: 0; font-size: var(--text-lg); overflow-wrap: anywhere; }
/* The main link is empty and named by aria-label ("Open Headcount"): the
   heading beside it keeps the bare title, and the ::after takes the card. */
.tile-link { color: var(--color-text); text-decoration: none; }
.tile-link::after { content: ""; position: absolute; inset: 0; border-radius: inherit; }
.tile:has(.tile-link):hover .tile-title { text-decoration: underline; }
.tile-link:focus-visible { outline: none; }
.tile-link:focus-visible::after { outline: var(--focus-ring); outline-offset: 2px; }
.tile-desc {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--color-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tile-desc-none { color: var(--color-muted); font-style: italic; }
.tile-desc-wrap { white-space: normal; overflow: visible; }
/* Why a tile cannot do what its status suggests, and what to do about it. */
.tile-notice { margin: 0; font-size: var(--text-xs); color: var(--warn-text); line-height: var(--leading-snug); }
.tile-meta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin: 0; }
.tile-date { margin: 0; font-size: var(--text-xs); color: var(--color-muted); }
.tile-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin: auto 0 0;
  font-size: var(--text-sm);
}
.tile-cta { color: var(--color-accent-text); font-weight: 600; }
.tile-secondary {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  padding-inline: var(--space-3);
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-md);
  color: var(--color-link);
  text-decoration: none;
  background: var(--color-surface);
}
.tile-secondary:hover { background: color-mix(in srgb, var(--color-accent) 10%, var(--color-surface)); }
/* The owner's secondary links (Manage, Remove) together at the card's end; they
   wrap under the call to action on a narrow card rather than overflow it. */
.tile-actions { flex-wrap: wrap; }
.tile-actions-end { display: inline-flex; flex-wrap: wrap; gap: var(--space-2); margin-left: auto; }
.tile-remove { color: var(--bad-text); }
.tile-remove:hover { background: color-mix(in srgb, var(--bad) 12%, var(--color-surface)); }

/* ── Studio select mode (Ben: "like Photos on the iPhone") ─────────────────
   Each of the builder's own tiles becomes ONE checkbox: the round mark in the
   top corner is the checkbox itself, and its label's ::after covers the card,
   so a click or tap anywhere on the tile toggles it. A selected tile carries
   a filled mark and a double accent ring - no tinted wash, which would pull the
   status badges below contrast. The action bar sticks to the bottom of the
   screen; the page grows bottom padding so the bar never covers the last row. */
.page-selecting { padding-bottom: calc(var(--space-12) * 4); }
.tile-pick { cursor: pointer; padding-right: calc(var(--space-4) + var(--tap)); }
.tile-pick-label { position: static; }
.tile-pick-label::after { content: ""; position: absolute; inset: 0; border-radius: inherit; z-index: 1; }
.tile-check {
  appearance: none;
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
  z-index: 2;
  width: var(--space-6);
  height: var(--space-6);
  margin: 0;
  border: 2px solid var(--color-dim);
  border-radius: 50%;
  background: var(--color-surface);
  cursor: pointer;
}
.tile-check:checked {
  border-color: var(--color-accent);
  background: var(--color-accent);
}
/* The tick inside a selected circle - Photos' mark, not a radio dot. A mask,
   so its colour is a token (the shape carries no colour of its own). */
.tile-mark {
  display: none;
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
  z-index: 2;
  width: var(--space-6);
  height: var(--space-6);
  pointer-events: none;
  background: var(--color-surface);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M7 12.5l3.2 3.2L17 9' fill='none' stroke='black' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M7 12.5l3.2 3.2L17 9' fill='none' stroke='black' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
}
.tile-check:checked + .tile-mark { display: block; }
.tile-check:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }
.tile-pick:has(.tile-check:checked) {
  box-shadow: 0 0 0 2px var(--color-accent), var(--shadow-md);
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}
.tile-pick:has(.tile-check:focus-visible) { box-shadow: 0 0 0 2px var(--color-accent), var(--shadow-md); }
/* Pinned to the bottom of the VIEWPORT, like Photos' toolbar - not under the
   last row when there are only a few tiles. It spans the main column: right of
   the sidebar on a wide screen, the full width once the sidebar folds into the
   top bar (52em), and it clears the iPhone home indicator (safe-area inset). */
.select-bar {
  position: fixed;
  left: calc(var(--sidebar-now) + var(--space-6));
  right: var(--space-6);
  bottom: calc(var(--space-4) + env(safe-area-inset-bottom, 0));
  z-index: 3;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
  margin: 0;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  box-shadow: var(--shadow-lg);
}
.select-count { margin: 0; font-weight: 600; min-width: 7em; }
.select-bulk { display: flex; gap: var(--space-4); margin: 0; font-size: var(--text-sm); }
.select-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-left: auto; }
@media (max-width: 52em) {
  .select-bar { left: var(--space-4); right: var(--space-4); }
}
@media (max-width: 40em) {
  .select-bar { left: var(--space-2); right: var(--space-2); bottom: calc(var(--space-2) + env(safe-area-inset-bottom, 0)); }
  .select-actions { margin-left: 0; width: 100%; }
  .select-actions .btn { flex: 1 1 auto; }
}
.btn-danger-solid { color: var(--bad-text); border-color: var(--bad-text); font-weight: 600; }
.btn-danger-solid:hover { background: color-mix(in srgb, var(--bad) 14%, transparent); color: var(--bad-text); }

/* The confirmation sheet: a modal <dialog> over the Studio, or a card on a
   page of its own when script is off. */
.sheet {
  width: min(100% - var(--space-8), 40rem);
  max-height: calc(100% - var(--space-8));
  padding: 0;
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  color: var(--color-text);
  box-shadow: var(--shadow-lg);
}
.sheet::backdrop { background: color-mix(in srgb, var(--color-bg) 70%, transparent); }
.sheet-body { display: grid; gap: var(--space-3); padding: var(--space-6); }
.sheet-title { margin: 0; font-size: var(--text-lg); }
.sheet-page .sheet-body { padding: 0; }
.sheet-lede { margin: 0; color: var(--color-dim); line-height: var(--leading-body); }
.sheet-list-head { margin: 0; font-weight: 600; font-size: var(--text-sm); }
.sheet-list { max-height: 16rem; overflow: auto; }
.sheet-form { display: grid; gap: var(--space-3); }
.sheet-people { gap: var(--space-2); }
.choice-tight { padding: var(--space-2) var(--space-3); align-items: center; }
/* The "New dashboard" tile leads the Studio grid: an outline, not a card, so
   it reads as an action rather than one more dashboard. */
.tile-new {
  justify-content: center;
  background: transparent;
  box-shadow: none;
  border: 1px dashed var(--color-divider);
}
.tile-new .tile-title { color: var(--color-accent-text); }
/* A section's heading and its one-line explanation, stacked above its grid. */
.section-head { display: flex; flex-direction: column; gap: var(--space-1); margin-bottom: var(--space-4); }
.section-head h2, .section-head p { margin: 0; }

/* ── the landing page: the front door for someone signed out ──────────────
   Hero, the illustration, three steps, what you get, one way in. Centred and
   generous; the measure comes from the page tokens, the headline from the one
   display size. */
.landing {
  max-width: var(--measure-page);
  margin: 0 auto;
  padding: var(--space-12) var(--space-7);
  display: flex;
  flex-direction: column;
  gap: calc(var(--space-12) * 1.5);
}
.landing > * { min-width: 0; }
.landing-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  text-align: center;
}
.landing-hero .kicker { margin: 0; }
.landing-title {
  max-width: 22em;
  margin: 0;
  font-size: var(--text-display);
  line-height: var(--leading-tight);
  letter-spacing: -0.02em;
  text-wrap: balance;
}
.landing-sub {
  max-width: 34em;
  margin: 0;
  font-size: var(--text-lg);
  line-height: var(--leading-body);
  color: var(--color-dim);
  text-wrap: pretty;
}
.landing-ctas { justify-content: center; margin-top: var(--space-2); }
.landing-section { display: flex; flex-direction: column; gap: var(--space-8); scroll-margin-top: var(--space-6); }
.landing-section-head { display: flex; flex-direction: column; align-items: center; gap: var(--space-2); text-align: center; }
.landing-section-head > * { margin: 0; }
.landing-section-head h2 { font-size: var(--text-2xl); text-wrap: balance; }
.landing-section-lede { max-width: 40em; color: var(--color-dim); text-wrap: pretty; }

.landing-stage {
  padding: var(--space-8) var(--space-6) var(--space-4);
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--color-accent) 5%, var(--color-bg));
  box-shadow: var(--shadow-sm);
}

.landing-cards {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-4);
}
.landing-card h3 { margin: 0; font-size: var(--text-lg); }
.landing-card p { margin: 0; font-size: var(--text-sm); color: var(--color-dim); }
/* Four cards: two by two on a wide screen, never three and an orphan. */
.landing-cards { grid-template-columns: repeat(auto-fit, minmax(min(100%, calc(var(--tile-min) * 1.5)), 1fr)); }
.landing-card { gap: var(--space-2); }

.landing-band {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-12) var(--space-6);
  text-align: center;
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--color-accent) 9%, var(--color-surface));
  box-shadow: var(--shadow-sm);
}
.landing-band > * { margin: 0; max-width: 40em; }
.landing-band h2 { font-size: var(--text-2xl); }
.landing-band .note { margin-top: var(--space-2); }

@media (max-width: 40em) {
  .landing { padding: var(--space-8) var(--space-4); gap: var(--space-12); }
  .landing-stage { padding: var(--space-5) var(--space-3) var(--space-3); }
  .landing-band { padding: var(--space-8) var(--space-4); }
}

/* ── the access cell: one line per person, the editor opens in place ───────
   A <details> per person. The summary is a three-part line - who, what they
   hold now, Edit - that wraps to two lines on a phone. */
.access-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.access-person {
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}
.access-person[open] { border-color: var(--color-accent); }
.access-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
  padding: var(--space-3) var(--space-4);
  cursor: pointer;
  list-style: none;
}
.access-summary::-webkit-details-marker { display: none; }
.access-summary:focus-visible { outline: var(--focus-ring); outline-offset: 1px; border-radius: var(--radius-md); }
.access-who { display: flex; align-items: center; gap: var(--space-2); flex: 1 1 16em; min-width: 0; }
.access-email { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.access-has { flex: 2 1 14em; min-width: 0; font-size: var(--text-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.access-edit { font-size: var(--text-sm); color: var(--color-link); }
.access-person[open] .access-edit { visibility: hidden; }
.access-editor {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-4);
  border-top: 1px solid var(--color-divider);
}
.access-tools { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--space-3); }
.access-find { flex: 1 1 14em; }
.access-paste { resize: vertical; }
/* Forty cost centres stacked on a phone is a page of scrolling: the list scrolls
   in its own box, so Save stays near the find box. */
.access-editor .pick-grid { max-height: 50vh; overflow-y: auto; padding: 2px; }
.pick[hidden] { display: none; }
/* Save rides the bottom of the screen while a person's editor is open, so it is
   never below the fold of a long value list. */
.access-save {
  position: sticky;
  bottom: 0;
  z-index: 1;
  margin: 0 calc(-1 * var(--space-4)) calc(-1 * var(--space-4));
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface);
  border-top: 1px solid var(--color-divider);
  border-radius: 0 0 var(--radius-md) var(--radius-md);
}
.access-unsaved { font-size: var(--text-sm); font-weight: 600; color: var(--warn-text); }
.access-unsaved[hidden], .access-summary [data-role="picker-dirty"][hidden] { display: none; }

/* ── the pricing page: three tiers on the landing page's frame ─────────────
   A placeholder until prices are ruled; the note above the tiers says so. One
   column on a phone, three side by side once there is room. */
.pricing-placeholder {
  margin: 0 auto;
  max-width: 40em;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--color-accent) 9%, var(--color-surface));
  text-align: center;
  font-size: var(--text-sm);
}
.pricing-tiers {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--tile-min)), 1fr));
}
.pricing-tier { display: flex; flex-direction: column; gap: var(--space-3); }
.pricing-tier > * { margin: 0; }
.pricing-tier h3 { font-size: var(--text-lg); }
.pricing-tier-featured { box-shadow: 0 0 0 2px var(--color-accent), var(--shadow-md); }
.pricing-price { font-size: var(--text-2xl); font-weight: var(--font-heading-weight); line-height: var(--leading-tight); }
.pricing-for { font-size: var(--text-sm); color: var(--color-dim); }
.pricing-features {
  flex: 1;
  padding-left: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  font-size: var(--text-sm);
}
.pricing-tier .btn { align-self: flex-start; }
.pricing-cta-note { font-size: var(--text-sm); color: var(--color-dim); }

/* ── the landing illustration (web/templates/web/_landing_motion.html) ────
   Two inline SVG drawings of one storyboard: landscape on a wide screen,
   portrait on a narrow one, so the words stay readable at 360px. A drawing's
   coordinates are user units; text sizes come from the type ramp, so on the
   wide drawing at full width one unit is one pixel.

   Every colour is a class reading a token, so both themes come free. The
   static drawing IS the final, fully assembled frame; motion is layered on
   only under `prefers-reduced-motion: no-preference`, so with the OS setting
   on, nothing moves at all (animation-name stays none) and nothing is
   missing. */
.lm { display: grid; justify-items: center; gap: var(--space-3); }
.lm-svg { display: block; width: 100%; height: auto; overflow: visible; }
.lm-wide { max-width: var(--landing-art-wide); }
.lm-tall { display: none; max-width: var(--landing-art-tall); }
@media (max-width: 52em) {
  .lm-wide { display: none; }
  .lm-tall { display: block; }
}

.lm-svg text { font-family: var(--font-body); font-size: var(--text-sm); }
.lm-svg .lm-small { font-size: var(--text-xs); }
.lm-svg .lm-head { font-size: var(--text-md); font-weight: 600; }
.lm-svg .lm-figure { font-size: var(--text-xl); font-weight: 600; font-variant-numeric: tabular-nums; }
.lm-svg .lm-mono { font-family: var(--font-mono); font-size: var(--text-xs); }
.lm-svg .lm-kicker { font-size: var(--text-2xs); font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; }
.lm-strong { font-weight: 600; }
.lm-t { fill: var(--color-text); }
.lm-m { fill: var(--color-muted); }
.lm-a { fill: var(--color-accent-text); }

.lm-card, .lm-strip { fill: var(--color-surface); stroke: var(--color-divider); stroke-width: 1; }
.lm-well { fill: color-mix(in srgb, var(--color-accent) 9%, var(--color-surface)); }
.lm-area { fill: color-mix(in srgb, var(--color-accent) 24%, transparent); }
.lm-line { fill: none; stroke: var(--color-accent); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.lm-panel { fill: var(--color-surface); stroke: var(--color-accent); stroke-width: 1.5; }
.lm-slot { fill: none; stroke: var(--color-divider); stroke-width: 1.5; stroke-dasharray: 4 3; }
.lm-doc { fill: var(--color-accent-800); stroke: var(--color-accent); stroke-width: 1.5; }
.lm-doc-lines, .lm-doc-bars { fill: none; stroke: var(--color-accent-text); stroke-width: 2; stroke-linecap: round; }
.lm-brand-dot { fill: var(--color-accent); }
.lm-ring { fill: none; stroke: var(--color-divider); stroke-width: 1.5; }
.lm-tick-bg { fill: var(--ok); }
.lm-tick { fill: none; stroke: var(--color-surface); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.lm-draw {
  fill: none;
  stroke: color-mix(in srgb, var(--color-accent) 65%, transparent);
  stroke-width: 1.5;
  stroke-linecap: round;
  /* pathLength="1" on each path: one dash the path's whole length, drawn by
     taking the offset from 1 to 0. At rest it is simply drawn. */
  stroke-dasharray: 1;
  stroke-dashoffset: 0;
}
.lm-pill { fill: var(--color-accent-800); stroke: var(--color-accent); stroke-width: 1; }
.lm-chain { fill: none; stroke: var(--color-accent-text); stroke-width: 1.75; stroke-linecap: round; }
.lm-bar { fill: color-mix(in srgb, var(--color-accent) 50%, transparent); }
.lm-bar-hi { fill: var(--color-accent); }
.lm-dot-ok { fill: var(--ok); }
.lm-dot-bad { fill: var(--bad); }

.lm-cyl { fill: var(--color-accent-800); stroke: var(--color-accent); stroke-width: 1.5; }
.lm-cyl-band { fill: none; stroke: var(--color-accent); stroke-width: 1.25; }
.lm-flow { fill: var(--color-accent); }
.lm-rule-line { stroke: var(--color-divider); stroke-width: 1; }
.lm-lock-shackle { fill: none; stroke: var(--color-accent); stroke-width: 2.5; stroke-linecap: round; }
.lm-lock-body { fill: var(--color-accent-800); stroke: var(--color-accent); stroke-width: 1.5; }
.lm-lock-hole { fill: var(--color-accent-text); }
/* The caption row: an outline always, a soft fill while its scene plays. */
.lm-step { fill: none; stroke: var(--color-divider); stroke-width: 1; }
.lm-step-on { fill: var(--color-accent-800); stroke: var(--color-accent); stroke-width: 1; }

.lm-anim { transform-box: fill-box; transform-origin: center; }
.lm-bars { transform-origin: 50% 0; } /* the baseline: the bars hang from a flipped group */

/* WCAG 2.2.2: a native checkbox, so pausing needs no script. Nothing to pause
   with reduced motion, so it is not offered there. */
.lm-pause {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--tap);
  font-size: var(--text-xs);
  color: var(--color-muted);
  cursor: pointer;
}
.lm-pause-input { width: var(--space-4); height: var(--space-4); margin: 0; accent-color: var(--color-accent); }
@media (prefers-reduced-motion: reduce) { .lm-pause { display: none; } }

/* The storyboard: one 24-second loop (--landing-loop) that walks the four
   steps in order, each scene lighting its caption. Every part shares the loop
   and its own keyframes place it on the timeline, so nothing drifts out of
   step and only one or two things move at once:
     0-3.5s    bring your dashboard - the file slides in, glides into the panel
     3.5-7s    connect live data - the database, its line, data flowing in,
               "Live data connected"
     7-10s     control who sees what - the lock, the three per-person rules,
               "Row-level security on"
     10-13s    publish to your team - "Published" lights as the link appears,
               then the fan and three co-workers with their own numbers
     13.6-18s  the activity lines, one every 1.8s, each easing in over 0.9s
     18-23s    the whole picture holds, long enough to read the log
     23-23.6s  everything fades together; a breath, then again.
   Generated from a timeline written in seconds, so lengthening the loop never
   moves an earlier scene; the percentages are of the loop. */
@media (prefers-reduced-motion: no-preference) {
  .lm-anim { animation: var(--landing-loop) var(--ease-in-out) infinite both; }
  .lm-file { animation-name: lm-file; }
  .lm-db { animation-name: lm-db; }
  .lm-lock { animation-name: lm-lock; }
  .lm-rule-1 { animation-name: lm-rule-1; }
  .lm-rule-2 { animation-name: lm-rule-2; }
  .lm-rule-3 { animation-name: lm-rule-3; }
  .lm-check-1 { animation-name: lm-check-1; }
  .lm-check-2 { animation-name: lm-check-2; }
  .lm-check-3 { animation-name: lm-check-3; }
  .lm-flow-1 { animation-name: lm-flow-1; }
  .lm-flow-2 { animation-name: lm-flow-2; }
  .lm-flow-3 { animation-name: lm-flow-3; }
  .lm-link { animation-name: lm-link; }
  .lm-viewer-1 { animation-name: lm-viewer-1; }
  .lm-viewer-2 { animation-name: lm-viewer-2; }
  .lm-viewer-3 { animation-name: lm-viewer-3; }
  .lm-log-1 { animation-name: lm-log-1; }
  .lm-log-2 { animation-name: lm-log-2; }
  .lm-log-3 { animation-name: lm-log-3; }
  .lm-draw-1 { animation-name: lm-draw-1; }
  .lm-draw-db { animation-name: lm-draw-db; }
  .lm-draw-2 { animation-name: lm-draw-2; }
  .lm-fan { animation-name: lm-fan; }
  .lm-bars-1 { animation-name: lm-bars-1; }
  .lm-bars-2 { animation-name: lm-bars-2; }
  .lm-bars-3 { animation-name: lm-bars-3; }
  .lm-step-1 { animation-name: lm-step-1; }
  .lm-step-2 { animation-name: lm-step-2; }
  .lm-step-3 { animation-name: lm-step-3; }
  .lm-step-4 { animation-name: lm-step-4; }
  .lm-travel-wide { animation-name: lm-travel-wide; }
  .lm-travel-tall { animation-name: lm-travel-tall; }
  .lm:has(.lm-pause-input:checked) .lm-anim { animation-play-state: paused; }
}
@keyframes lm-file {
  0%, 1.42% { opacity: 0; transform: translateX(-24%); }
  5.67%, 95.83% { opacity: 1; transform: none; }
  98.33%, 100% { opacity: 0; transform: none; }
}
@keyframes lm-db {
  0%, 14.88% { opacity: 0; transform: translateX(-12%); }
  17.71%, 95.83% { opacity: 1; transform: none; }
  98.33%, 100% { opacity: 0; transform: none; }
}
@keyframes lm-lock {
  0%, 30.46% { opacity: 0; transform: scale(0.7); }
  33.29%, 95.83% { opacity: 1; transform: none; }
  98.33%, 100% { opacity: 0; transform: none; }
}
@keyframes lm-rule-1 {
  0%, 32.58% { opacity: 0; transform: translateX(-8%); }
  34.71%, 95.83% { opacity: 1; transform: none; }
  98.33%, 100% { opacity: 0; transform: none; }
}
@keyframes lm-rule-2 {
  0%, 34.71% { opacity: 0; transform: translateX(-8%); }
  36.83%, 95.83% { opacity: 1; transform: none; }
  98.33%, 100% { opacity: 0; transform: none; }
}
@keyframes lm-rule-3 {
  0%, 36.83% { opacity: 0; transform: translateX(-8%); }
  38.96%, 95.83% { opacity: 1; transform: none; }
  98.33%, 100% { opacity: 0; transform: none; }
}
@keyframes lm-check-1 {
  0%, 24.79% { opacity: 0; transform: scale(0.4); }
  26.92%, 95.83% { opacity: 1; transform: none; }
  98.33%, 100% { opacity: 0; transform: none; }
}
@keyframes lm-check-2 {
  0%, 39.67% { opacity: 0; transform: scale(0.4); }
  41.79%, 95.83% { opacity: 1; transform: none; }
  98.33%, 100% { opacity: 0; transform: none; }
}
@keyframes lm-check-3 {
  0%, 41.79% { opacity: 0; transform: scale(0.4); }
  43.92%, 95.83% { opacity: 1; transform: none; }
  98.33%, 100% { opacity: 0; transform: none; }
}
@keyframes lm-flow-1 {
  0%, 19.83% { opacity: 0; transform: scale(0.2); }
  21.25%, 95.83% { opacity: 1; transform: none; }
  98.33%, 100% { opacity: 0; transform: none; }
}
@keyframes lm-flow-2 {
  0%, 21.25% { opacity: 0; transform: scale(0.2); }
  22.67%, 95.83% { opacity: 1; transform: none; }
  98.33%, 100% { opacity: 0; transform: none; }
}
@keyframes lm-flow-3 {
  0%, 22.67% { opacity: 0; transform: scale(0.2); }
  24.08%, 95.83% { opacity: 1; transform: none; }
  98.33%, 100% { opacity: 0; transform: none; }
}
@keyframes lm-link {
  0%, 43.92% { opacity: 0; transform: scale(0.92); }
  46.75%, 95.83% { opacity: 1; transform: none; }
  98.33%, 100% { opacity: 0; transform: none; }
}
@keyframes lm-viewer-1 {
  0%, 48.88% { opacity: 0; transform: translateX(-6%); }
  51%, 95.83% { opacity: 1; transform: none; }
  98.33%, 100% { opacity: 0; transform: none; }
}
@keyframes lm-viewer-2 {
  0%, 50.29% { opacity: 0; transform: translateX(-6%); }
  52.42%, 95.83% { opacity: 1; transform: none; }
  98.33%, 100% { opacity: 0; transform: none; }
}
@keyframes lm-viewer-3 {
  0%, 51.71% { opacity: 0; transform: translateX(-6%); }
  53.83%, 95.83% { opacity: 1; transform: none; }
  98.33%, 100% { opacity: 0; transform: none; }
}
@keyframes lm-log-1 {
  0%, 56.67% { opacity: 0; transform: translateY(40%); }
  60.42%, 95.83% { opacity: 1; transform: none; }
  98.33%, 100% { opacity: 0; transform: none; }
}
@keyframes lm-log-2 {
  0%, 64.17% { opacity: 0; transform: translateY(40%); }
  67.92%, 95.83% { opacity: 1; transform: none; }
  98.33%, 100% { opacity: 0; transform: none; }
}
@keyframes lm-log-3 {
  0%, 71.67% { opacity: 0; transform: translateY(40%); }
  75.42%, 95.83% { opacity: 1; transform: none; }
  98.33%, 100% { opacity: 0; transform: none; }
}
@keyframes lm-draw-1 {
  0%, 5.67% { opacity: 0; stroke-dashoffset: 1; }
  6.08% { opacity: 1; stroke-dashoffset: 1; }
  7.79%, 95.83% { opacity: 1; stroke-dashoffset: 0; }
  98.33%, 100% { opacity: 0; stroke-dashoffset: 0; }
}
@keyframes lm-draw-db {
  0%, 17.71% { opacity: 0; stroke-dashoffset: 1; }
  18.12% { opacity: 1; stroke-dashoffset: 1; }
  19.83%, 95.83% { opacity: 1; stroke-dashoffset: 0; }
  98.33%, 100% { opacity: 0; stroke-dashoffset: 0; }
}
@keyframes lm-draw-2 {
  0%, 42.5% { opacity: 0; stroke-dashoffset: 1; }
  42.92% { opacity: 1; stroke-dashoffset: 1; }
  43.92%, 95.83% { opacity: 1; stroke-dashoffset: 0; }
  98.33%, 100% { opacity: 0; stroke-dashoffset: 0; }
}
@keyframes lm-fan {
  0%, 46.75% { opacity: 0; stroke-dashoffset: 1; }
  47.17% { opacity: 1; stroke-dashoffset: 1; }
  49.58%, 95.83% { opacity: 1; stroke-dashoffset: 0; }
  98.33%, 100% { opacity: 0; stroke-dashoffset: 0; }
}
@keyframes lm-bars-1 {
  0%, 49.58% { transform: scaleY(0); }
  52.42%, 100% { transform: none; }
}
@keyframes lm-bars-2 {
  0%, 51% { transform: scaleY(0); }
  53.83%, 100% { transform: none; }
}
@keyframes lm-bars-3 {
  0%, 52.42% { transform: scaleY(0); }
  55.25%, 100% { transform: none; }
}
@keyframes lm-step-1 {
  0% { opacity: 0; }
  1.42%, 13.46% { opacity: 1; }
  14.88%, 100% { opacity: 0; }
}
@keyframes lm-step-2 {
  0%, 14.17% { opacity: 0; }
  15.58%, 28.33% { opacity: 1; }
  29.75%, 100% { opacity: 0; }
}
@keyframes lm-step-3 {
  0%, 29.04% { opacity: 0; }
  30.46%, 41.08% { opacity: 1; }
  42.5%, 100% { opacity: 0; }
}
@keyframes lm-step-4 {
  0%, 41.79% { opacity: 0; }
  43.21%, 95.83% { opacity: 1; }
  98.33%, 100% { opacity: 0; }
}
@keyframes lm-travel-wide {
  0%, 8.5% { opacity: 0; transform: translateX(-194%); }
  9.58% { opacity: 1; transform: translateX(-194%); }
  13.46%, 95.83% { opacity: 1; transform: none; }
  98.33%, 100% { opacity: 0; transform: none; }
}
@keyframes lm-travel-tall {
  0%, 8.5% { opacity: 0; transform: translateY(-174%); }
  9.58% { opacity: 1; transform: translateY(-174%); }
  13.46%, 95.83% { opacity: 1; transform: none; }
  98.33%, 100% { opacity: 0; transform: none; }
}

/* "Connect your database": find it, choose how you sign in, fill in that   */
/* way's own fields. The secrets sit in their own marked box, never mixed   */
/* in with the details that are safe to show again.                         */
/* A required field's star (Jenna's UAT, 2026-10-29). Hidden from screen readers:
   the input carries aria-required instead. */
.req { color: var(--bad); font-weight: 700; }
.connect-step { display: flex; align-items: center; gap: var(--space-2); margin: 0 0 var(--space-2); font-size: var(--text-md); font-weight: 600; }
.connect-step-n {
  display: inline-grid;
  place-items: center;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 50%;
  font-size: var(--text-xs);
  background: var(--color-accent-800);
  color: var(--color-accent-100);
}
.connect-find { display: flex; gap: var(--space-2); margin-bottom: var(--space-3); }
.connect-find .input { flex: 1; min-width: 0; }
/* The database drop-down: a search box and a select, each with its button. */
.connect-choose { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-2); }
.connect-choose .field { margin: 0; min-width: 0; }
.connect-choose .connect-find { flex-wrap: wrap; }
.connect-choose select.input { width: 100%; max-width: 100%; flex-basis: 12rem; }
.connect-known { margin: var(--space-1) 0 0; padding-left: var(--space-4); }
.connect-kinds { list-style: none; margin: var(--space-2) 0; padding: 0; display: grid; gap: var(--space-1); font-size: var(--text-sm); }
.connect-facts { display: grid; grid-template-columns: max-content 1fr; gap: var(--space-1) var(--space-3); margin: var(--space-2) 0 0; font-size: var(--text-sm); }
.connect-facts dt { font-weight: 600; }
.connect-facts dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
@media (max-width: 40em) { .connect-facts { grid-template-columns: 1fr; } }
.method-tabs { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-3); }
.method-tab {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-height: var(--tap);
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  color: var(--color-text);
  text-decoration: none;
}
.method-tab.is-current { border: 2px solid var(--color-accent); font-weight: 600; }
.method-tab:focus-visible { outline: var(--focus-ring); outline-offset: 1px; }
.connect-notes { margin-top: var(--space-2); font-size: var(--text-sm); }
.connect-group { display: grid; gap: var(--space-3); margin: 0 0 var(--space-3); padding: var(--space-3); border: 1px solid var(--color-divider); border-radius: var(--radius-md); min-width: 0; }
.connect-group > legend { padding: 0 var(--space-1); }
.connect-secrets {
  border: 2px solid var(--warn);
  background: color-mix(in srgb, var(--warn) 7%, var(--color-surface));
}
.connect-secrets > legend { display: inline-flex; align-items: center; gap: var(--space-1); color: var(--warn-text); }
.connect-lock { width: 1rem; height: 1rem; }
.connect-group .input:disabled { opacity: 0.6; cursor: not-allowed; }
.input-short { max-width: 9rem; }

/* ── the preview tab: the dashboard, or one of its tables, as one person ────
   One panel at a time under a row of toggles (preview_tab.js). A table panel
   takes the frame's place and scrolls inside itself. */
.preview-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding: var(--space-2) 0;
}
.preview-tab {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: inherit;
  font: inherit;
  cursor: pointer;
}
.preview-tab[aria-pressed="true"] { border-color: var(--color-accent); border-bottom: 2px solid var(--color-accent); }
.preview-tab:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }
.preview-tab-count { font-size: var(--text-sm); color: var(--color-muted); }
.viewer-frame[hidden], .preview-table-panel[hidden] { display: none; }
.preview-table-panel {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.preview-table-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-3); }
.preview-table-box { flex: 1 1 auto; min-height: 0; overflow: auto; }
/* After publish: the way back out, at the foot of the run. */
.run-done { margin-top: var(--space-6); justify-content: center; }
/* ── the match step, several tables: one fold per table ─────────────────────
   A saved table folds to one line - its name, where it comes from, a check -
   with a green edge, so the tables still to do are the ones left open. */
.map-tables { display: flex; flex-direction: column; gap: var(--space-3); }
.map-table {
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  scroll-margin-top: var(--space-6);
}
.map-table[open] { border-color: var(--color-accent); }
.map-table[data-done] { border-color: var(--ok); border-left-width: var(--space-1); }
.map-table-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
  padding: var(--space-3) var(--space-4);
  cursor: pointer;
  list-style: none;
}
.map-table-summary::-webkit-details-marker { display: none; }
.map-table-summary::marker { content: ""; }
.map-table-summary:focus-visible { outline: var(--focus-ring); outline-offset: 1px; border-radius: var(--radius-md); }
.map-table-name { font-weight: 600; flex: 0 1 auto; }
.map-table-from { flex: 1 1 12em; min-width: 0; font-size: var(--text-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.map-table-edit { font-size: var(--text-sm); color: var(--color-link); }
.map-table[open] .map-table-edit { visibility: hidden; }
.map-table-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: 0 var(--space-4) var(--space-4);
  border-top: 1px solid var(--color-divider);
  padding-top: var(--space-4);
}

/* "Match your columns": find the source table by name. The list scrolls     */
/* inside itself, so a database with hundreds of tables never pushes the     */
/* matches off the page; table_pick.js narrows it as you type.               */
.table-find .connect-find { margin-bottom: var(--space-2); }
.table-list {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 16rem;
  overflow-y: auto;
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-sm);
}
.table-list > li + li { border-top: 1px solid var(--color-divider); }
.table-option {
  display: flex;
  align-items: center;
  min-height: var(--tap);
  padding: var(--space-1) var(--space-3);
  color: var(--color-text);
  text-decoration: none;
}
.table-option:hover { background: color-mix(in srgb, var(--color-accent) 14%, transparent); }
.table-option:focus-visible { outline: var(--focus-ring); outline-offset: 0; }
.table-option.is-current { color: var(--color-accent-text); font-weight: var(--font-heading-weight); }

/* "Ask us to add a database" in the connect step (Ben's ruling 2026-10-31). */
.source-request-form { display: flex; flex-direction: column; gap: var(--space-3); }
.source-request-list { display: grid; gap: var(--space-2); }
.source-request-items { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-1); font-size: var(--text-sm); }
.source-request-items > li { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }

/* ── "Check and match your columns": one fold per table ────────────────────
   Ben's ruling 2026-11-01. Each RAW_DATA table is a fold holding one grid -
   its columns' own settings, then the database column feeding each, then the
   file's sample rows. A saved table folds to one line with a green edge and
   the next one opens; tables waiting their turn are one quiet line each. */
.col-setup { display: flex; flex-direction: column; gap: var(--space-3); min-width: 0; }
.col-blocks { display: flex; flex-direction: column; gap: var(--space-3); min-width: 0; scroll-margin-top: var(--space-6); }
.col-block {
  min-width: 0;
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  scroll-margin-top: var(--space-6);
}
.col-block[open] { border-color: var(--color-accent); }
.col-block[data-done] { border-color: var(--ok); border-left-width: var(--space-1); }
.col-block-fix { border-color: var(--bad); border-left-width: var(--space-1); }
.col-block-waiting:not([open]) { background: transparent; }
.col-block-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
  min-height: var(--tap);
  padding: var(--space-3) var(--space-4);
  cursor: pointer;
  list-style: none;
}
.col-block-summary::-webkit-details-marker { display: none; }
.col-block-summary::marker { content: ""; }
.col-block-summary:hover { background: color-mix(in srgb, var(--color-accent) 6%, transparent); }
.col-block-summary:focus-visible { outline: var(--focus-ring); outline-offset: 1px; border-radius: var(--radius-md); }
.col-block-name { font-weight: 600; }
.col-block-from { flex: 1 1 12em; min-width: 0; font-size: var(--text-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.col-block-count { font-size: var(--text-sm); color: var(--color-dim); white-space: nowrap; }
.col-block-edit { font-size: var(--text-sm); color: var(--color-link); }
.col-block[open] .col-block-edit { visibility: hidden; }
.col-block-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
  padding: var(--space-4);
  border-top: 1px solid var(--color-divider);
}
.col-block-save { display: grid; gap: var(--space-2); }
.col-match .col-table-head,
.col-match .col-table-set { min-width: 9.5rem; }
.col-match thead .col-table-head,
.col-match thead .col-table-set { max-width: 13rem; }
/* "Kind of value": the type, a decimal's places beside it, a date's format under it. */
.kind { display: flex; align-items: center; gap: var(--space-2); }
.kind select.input { flex: 1 1 auto; min-width: 0; }
.kind-places { display: inline-flex; flex: none; align-items: center; gap: var(--space-1); font-size: var(--text-xs); white-space: nowrap; }
.col-table-grid .kind-places .input { width: 3.5em; padding-inline: var(--space-1); }
.kind-hint { display: block; margin-top: var(--space-1); font-size: var(--text-2xs); color: var(--color-muted); }
.kind-hint:empty { display: none; }
.col-renamed { display: block; margin-top: var(--space-1); font-size: var(--text-2xs); color: var(--warn-text); white-space: normal; }
/* Badges in the grid's head sit on its own background, not on transparency,
   so their contrast holds in both themes whatever scrolls under them. */
.col-table-grid .badge-pass { background: color-mix(in srgb, var(--ok) 14%, var(--color-surface)); }
.col-table-grid .badge-warn { background: color-mix(in srgb, var(--warn) 15%, var(--color-surface)); }
.col-table-grid .badge-fail { background: color-mix(in srgb, var(--bad) 16%, var(--color-surface)); }
@media (max-width: 40em) {
  .col-block-summary { padding-inline: var(--space-3); }
  .col-block-body { padding: var(--space-3) var(--space-2); }
}
/* The grid in two boxes (Ben, 2026-11-02 and 2026-11-07): the file's columns
   and sample rows on top, then the database - the picker, the picked column's
   type and match, and its live rows - so a file value and a database value for
   the same column sit one above the other. Each box shows about seven rows
   (grid_rows.js sets the height from the rows themselves; without script a
   fixed cap stands in), scrolls inside itself, never the page, and can be
   dragged taller or shorter at its corner to line the two up. The database
   box keeps the file box's column widths, and the two scroll sideways together. */
.col-match-bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-1) var(--space-4); }
.col-match-facts { display: flex; flex-wrap: wrap; gap: 0 var(--space-1); margin: 0; }
.col-match-facts > span + span::before { content: "· "; }
.col-rows-toggle { display: inline-flex; align-items: center; gap: var(--space-2); min-height: var(--tap); color: var(--color-text); font-family: var(--font-body); cursor: pointer; }
.col-rows-toggle input { margin: 0; }
.col-band-bar {
  margin: 0;
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-2xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-dim);
  border: 1px solid var(--color-divider);
  border-bottom: 0;
  background: var(--color-bg);
  overflow-wrap: anywhere;
}
.col-band-bar .mono { text-transform: none; letter-spacing: 0; }
.col-match .col-band-bar-db { margin-top: var(--space-4); border-radius: var(--radius-sm) var(--radius-sm) 0 0; }
.col-match .col-box {
  max-height: 24rem;
  min-height: 6rem;
  resize: vertical;
  overflow: auto;
}
.col-match .col-box[data-sized] { max-height: none; }
.col-match .col-table-grid thead { position: sticky; top: 0; z-index: 2; }
.col-db-grid { table-layout: fixed; }
.col-db-grid .col-db-name { font-family: var(--font-mono); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* "Show sample rows" off: only the columns, their kinds and the database's
   types and matches show - the data contract, with no rows under it. */
.col-match:has([data-rows-toggle]:not(:checked)) :is(.col-sample-row, .col-live-row, .col-live-note:not(:has(.col-live-problem))) { display: none; }
.col-match:has([data-rows-toggle]:not(:checked)) .col-box { height: auto !important; min-height: 0; max-height: none; resize: none; }
.col-db .col-table-set,
.col-live .col-table-set { background: var(--color-bg); font-family: var(--font-body); white-space: normal; }
.col-pick { font-weight: 600; color: var(--warn-text); }
.col-live-note > td { padding: var(--space-2) var(--space-3); font-family: var(--font-body); white-space: normal; }
.col-live-status { color: var(--color-dim); }
.col-live-problem { color: var(--bad-text); font-weight: 600; }
.col-live-note.visually-hidden-row > td { padding: 0; border: 0; }
[data-live-sample][aria-busy="true"] .col-live-row { opacity: 0.55; }
/* A table cell grows to its widest content (max-width does not bind it), so
   the long "column (type, never blank)" options are what set it: fix the
   picker's width instead, and four columns fit beside the labels at 1440. */
.col-match .col-db select.input { width: 100%; min-width: 9.5rem; }
.col-db .col-table-label,
.col-live .col-table-label { font-family: var(--font-body); }
/* At 1440 a four-column table fits its box whole (Ben, 2026-11-02): the row
   labels wrap to two short lines instead of setting a wide first column, and
   the places box takes only what two digits need. Wider tables still scroll
   inside the box. */
.col-match .col-table-label { width: 6.5rem; min-width: 6.5rem; white-space: normal; line-height: 1.25; }
.col-match .kind { gap: var(--space-1); }
/* The grid takes the box's width, not its widest content's: a column shrinks
   toward what it cannot do without (its picker, its values) and the longer
   words in it wrap. Only when that is still too wide does the box scroll. */
.col-match .col-table-grid { width: 100%; }

/* "2 of 3 · Table budget" on each table of a several-table file (Jenna's UAT). */
.col-block-n { font-weight: 600; }
.col-block-n::after { content: " \00b7"; }

/* The certify and publish moments (Jenna's UAT, 2026-10-29: "let's celebrate
   the publish more"). A big mark that draws itself in, a headline, and - for
   publish - the link to share. No confetti: a business tool, not a game.
   With reduced motion the mark is simply there. */
.celebrate {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-4);
  align-items: start;
  box-shadow: inset 0 0 0 2px var(--ok), var(--shadow-sm);
  background: var(--color-surface);
}
.run-cell-body .card.celebrate { box-shadow: inset 0 0 0 2px var(--ok); padding: var(--space-5); }
.celebrate-mark {
  position: relative;
  width: var(--space-12);
  height: var(--space-12);
  border-radius: 50%;
  background: var(--ok);
}
.celebrate-mark::after {
  content: "";
  position: absolute;
  inset: var(--space-2);
  background: var(--color-surface);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6.5 12.5l3.7 3.7L17.5 8.5' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6.5 12.5l3.7 3.7L17.5 8.5' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
}
.celebrate-certified .celebrate-mark { width: var(--space-8); height: var(--space-8); }
.celebrate-certified .celebrate-mark::after { inset: var(--space-1); }
.celebrate-body { display: grid; gap: var(--space-2); min-width: 0; }
.celebrate-head { margin: 0; font-size: var(--text-2xl); font-weight: 700; color: var(--ok-text); }
.celebrate-certified .celebrate-head { font-size: var(--text-xl); }
.celebrate .claim { margin: 0; }
.celebrate-link { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); margin: 0; overflow-wrap: anywhere; }
@media (prefers-reduced-motion: no-preference) {
  .celebrate-mark { animation: celebrate-pop var(--motion-slow) cubic-bezier(.2, 1.4, .4, 1) both; }
  .celebrate-mark::after { animation: celebrate-in var(--motion-slow) ease both; }
}
@keyframes celebrate-pop { from { transform: scale(0.4); opacity: 0; } to { transform: scale(1); opacity: 1; } }
@keyframes celebrate-in { 0%, 40% { opacity: 0; transform: scale(0.6); } to { opacity: 1; transform: scale(1); } }
