/* ==========================================================================
   BINMAN "Depot" design system (spec 6.2).

   Municipal waste-depot signage: a concrete ground, stencilled headings,
   sodium-amber wayfinding, one accent and everything else quiet.

   Deliberately differentiated from the marcdeller.com house brand, from
   GOBSMACKED's instrument panel and from KINFOLK's field guide. The house
   header and #1e73be palette do NOT apply here. They apply to the README.

   Every token is defined on bare :root first, then redefined under both
   @media (prefers-color-scheme: dark) and [data-theme="dark"], so no colour
   exists in only one theme (spec 4.4 acceptance criterion).
   ========================================================================== */

/* Fonts are linked from base.html rather than @import-ed: an @import is
   resolved serially after this file parses, and it was also producing a
   stray document-relative request for /vendor/fonts/fonts.css. */

:root {
  color-scheme: light;

  --bg: #E9EAE5;          /* concrete */
  --surface: #F7F7F4;
  --surface-2: #DFE1DA;
  --ink: #15171A;
  --line: #C9CBC3;
  --accent-soft: #F6E3D3;
  --good: #2C6D60;
  --bad: #A33A2A;

  /* These three were darkened from the spec 6.2 values after axe-core measured
     real contrast failures in the light theme: muted 4.25, accent 2.97 and warn
     3.05 against --surface-2, all under the 4.5 needed for body text. Each is
     now the deepest tone that still reads as the same colour, solved against the
     darkest light surface. The dark theme measured clean and is unchanged.
     Spec 6.2 values were --muted #6A6F68, --accent #D65B0A, --warn #9A7B10. */
  --muted: #61655F;       /* 4.50 on surface-2, 5.53 on surface */
  --accent: #A64708;      /* sodium amber, 4.51 on surface-2 */
  --warn: #79610D;        /* 4.51 on surface-2 */
  /* Text that sits ON the accent fill. White over the light dark-theme amber
     measured 2.35, so each theme names its own readable counterpart. */
  --on-accent: #FFFFFF;   /* 5.96 on the light-theme accent */

  --display: 'Archivo', 'Helvetica Neue', Arial, sans-serif;
  --body: 'Spline Sans', system-ui, -apple-system, sans-serif;
  --data: 'IBM Plex Mono', ui-monospace, Menlo, monospace;

  /* derived tokens, so a theme override of the base cascades */
  --shadow: 0 1px 2px rgb(21 23 26 / 8%), 0 4px 12px rgb(21 23 26 / 6%);
  --focus: var(--accent);
  --rail-w: 15.5rem;
  --radius: 3px;          /* signage is not rounded */
  --gutter: 1rem;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --bg: #131512;
    --surface: #1C1F1B;
    --surface-2: #252923;
    --ink: #E7E9E2;
    --muted: #8D938A;
    --line: #2F342D;
    --accent: #FF8A33;
    --accent-soft: #3A2415;
    --good: #5CBCA3;
    --warn: #D4AC3A;
    --bad: #E07A62;
    --on-accent: #15171A;   /* 8.4 on the dark-theme accent; white measured 2.35 */
    --shadow: 0 1px 2px rgb(0 0 0 / 40%), 0 4px 14px rgb(0 0 0 / 30%);
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #131512;
  --surface: #1C1F1B;
  --surface-2: #252923;
  --ink: #E7E9E2;
  --muted: #8D938A;
  --line: #2F342D;
  --accent: #FF8A33;
  --accent-soft: #3A2415;
  --good: #5CBCA3;
  --warn: #D4AC3A;
  --bad: #E07A62;
  --on-accent: #15171A;   /* 8.4 on the dark-theme accent; white measured 2.35 */
  --shadow: 0 1px 2px rgb(0 0 0 / 40%), 0 4px 14px rgb(0 0 0 / 30%);
}

/* -------------------------------------------------------------------------- */
/* reset and base                                                             */
/* -------------------------------------------------------------------------- */

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

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--body);
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 {
  font-family: var(--display);
  font-weight: 700;
  letter-spacing: 0.01em;
  margin: 0 0 0.5rem;
  line-height: 1.15;
}

/* Stencilled headings: the depot signage cue. */
h1 { font-size: 1.5rem; text-transform: uppercase; letter-spacing: 0.06em; }
h2 { font-size: 1.1rem; text-transform: uppercase; letter-spacing: 0.05em; }
h3 { font-size: 0.95rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); }

a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { text-decoration-thickness: 2px; }

code, kbd, samp, .mono { font-family: var(--data); font-size: 0.86em; }

:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

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

.skip-link {
  position: absolute; left: var(--gutter); top: -3rem; z-index: 100;
  background: var(--accent); color: #fff; padding: 0.5rem 0.75rem;
  border-radius: var(--radius); transition: top 0.15s;
}
.skip-link:focus { top: var(--gutter); }

/* -------------------------------------------------------------------------- */
/* header                                                                     */
/* -------------------------------------------------------------------------- */

.app-header {
  display: flex; align-items: center; gap: 1rem;
  padding: 0.6rem var(--gutter);
  background: var(--surface);
  border-bottom: 2px solid var(--line);
  position: sticky; top: 0; z-index: 20;
  flex-wrap: wrap;
}

.brand { display: flex; align-items: baseline; gap: 0.55rem; text-decoration: none; color: var(--ink); }
/* 32 by 50 viewBox: the height follows the width so the bin is not squashed
   back into the crate it used to be. */
.brand__mark { width: 26px; height: 41px; flex: none; align-self: center; }
.brand__name {
  font-family: var(--display); font-weight: 700; font-size: 1.05rem;
  text-transform: uppercase; letter-spacing: 0.1em;
}
.brand__sub { color: var(--muted); font-size: 0.68rem; letter-spacing: 0.02em; }
/* The project's links, sitting under the backronym.

   .brand-block is only a column: the brand anchor keeps its own baseline row,
   and the pills go beneath it rather than beside it. The indent lines them up
   with the backronym's left edge rather than with the bin, and is the mark's
   26px plus the brand's own 0.55rem gap.

   Geometry, border and background are .pill's, so the header reads as the same
   system as the verdict pills in the tables, one step smaller. The colour
   coding lives in the border and the text, as it does there. */
.brand-block { display: flex; flex-direction: column; gap: 0.3rem; min-width: 0; }
.brand__links {
  display: flex; flex-wrap: wrap; gap: 0.25rem;
  padding-left: calc(26px + 0.55rem);
}
.brand__links a {
  display: inline-block; padding: 0.05rem 0.35rem; border-radius: 2px;
  border: 1px solid var(--line); background: var(--surface-2);
  color: var(--muted); text-decoration: none; white-space: nowrap;
  font-family: var(--data); font-size: 0.62rem; letter-spacing: 0.01em;
}
.brand__links a:hover, .brand__links a:focus-visible {
  border-color: var(--accent); color: var(--accent);
}

/* The six letters that spell BINMAN, lit in sequence left to right.

   A long rest between sweeps on purpose. The header is sticky, so this is on
   screen the whole time someone is working; something that pulsed continuously
   would be an animated advert sitting over the data. One pass every seven
   seconds reads as the name assembling itself and then getting out of the way.

   The sweep is colour and weight only, with no layout property animated, so it
   cannot reflow the header mid-pass. The reduced-motion rule at the bottom of
   this file collapses the duration, which leaves the letters in their resting
   accent colour and nothing moving. */
.brand__key {
  color: var(--accent); font-weight: 700;
  animation: brand-key 7s ease-in-out infinite;
}
.brand__key:nth-of-type(2) { animation-delay: 0.16s; }
.brand__key:nth-of-type(3) { animation-delay: 0.32s; }
.brand__key:nth-of-type(4) { animation-delay: 0.48s; }
.brand__key:nth-of-type(5) { animation-delay: 0.64s; }

@keyframes brand-key {
  0%, 9%, 100% { color: var(--accent); text-shadow: none; }
  3% { color: var(--ink); text-shadow: 0 0 9px var(--accent); }
}

/* A one-line caveat under a page blurb. A full notice box at the top of every
   page turns the caveat into furniture people stop reading; this keeps the
   warning adjacent to the data and sends the detail to the About tab. */
.caveat {
  max-width: 72ch; margin: 0 0 1rem; color: var(--muted);
  font-size: 0.84rem; line-height: 1.45;
  border-left: 2px solid var(--line); padding-left: 0.7rem;
}
.caveat code { font-size: 0.95em; }

.app-nav { display: flex; gap: 0.15rem; flex-wrap: wrap; }
.app-nav a {
  padding: 0.35rem 0.65rem; border-radius: var(--radius);
  color: var(--muted); text-decoration: none;
  font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.05em;
  font-family: var(--display); font-weight: 600;
  border: 1px solid transparent;
}
.app-nav a:hover { background: var(--surface-2); color: var(--ink); }
.app-nav a[aria-current="page"] {
  background: var(--accent-soft); color: var(--accent); border-color: var(--accent);
}

/* The tabs and the theme toggle, held on one line.

   nowrap on the container is the point: the header itself wraps, and when the
   toggle was a sibling of the nav it was the item that got pushed to a row of
   its own at almost every window width. The nav inside still wraps, so a narrow
   window breaks the tabs over two lines with the toggle beside them rather than
   below. */
.header-end {
  margin-left: auto; display: flex; align-items: center;
  gap: 0.6rem; flex-wrap: nowrap; min-width: 0;
}

.theme-toggle {
  background: var(--surface-2); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 0.3rem 0.5rem; cursor: pointer; font-size: 0.95rem; line-height: 1;
}
.theme-toggle:hover { border-color: var(--accent); }

/* -------------------------------------------------------------------------- */
/* ternary triangle (spec 6.3): always visible, also a control                */
/* -------------------------------------------------------------------------- */

.triangle { width: 80px; height: 70px; flex: none; cursor: pointer; }
/* Round caps and a heavier stroke so the edges read as drawn lines rather than
   as hairlines, and so the joins under each node look solid. */
.triangle .tri-edge {
  stroke: var(--line); stroke-width: 4; fill: none;
  stroke-linecap: round; transition: stroke 0.2s, stroke-width 0.2s;
}
.triangle .tri-edge.is-live { stroke: var(--accent); stroke-width: 5; }
.triangle .tri-corner {
  fill: var(--surface-2); stroke: var(--ink); stroke-width: 2.4;
  transition: fill 0.2s, stroke 0.2s;
}
.triangle .tri-corner.is-pinned { fill: var(--accent); stroke: var(--ink); }
.triangle .tri-corner:hover { stroke: var(--accent); }
.triangle .tri-corner:focus-visible { outline: none; stroke: var(--accent); stroke-width: 3; }
/* Nothing is drawn until the readout resolves. The dashed ring it used to show
   was a fourth node that could not be clicked. */
.triangle .tri-centre { fill: none; stroke: none; pointer-events: none; }
.triangle .tri-centre.is-resolved {
  fill: var(--good); stroke: var(--ink); stroke-width: 2.4; pointer-events: auto;
}
.triangle .tri-label {
  font-family: var(--display); font-size: 8.5px; font-weight: 700;
  letter-spacing: 0.06em; fill: var(--muted);
  text-anchor: middle; dominant-baseline: middle; pointer-events: none;
}

/* -------------------------------------------------------------------------- */
/* split ledger layout (spec 6.3)                                             */
/* -------------------------------------------------------------------------- */

.ledger {
  display: grid;
  grid-template-columns: var(--rail-w) minmax(0, 1fr);
  gap: 0;
  min-height: calc(100vh - 58px);
}

.rail {
  background: var(--surface);
  border-right: 2px solid var(--line);
  padding: 0.9rem var(--gutter);
  display: flex; flex-direction: column; gap: 1.1rem;
  overflow-y: auto;
}

.rail__modules { display: flex; flex-direction: column; gap: 0.2rem; }
.rail__modules a {
  display: block; padding: 0.45rem 0.6rem; border-radius: var(--radius);
  text-decoration: none; color: var(--ink); font-size: 0.85rem;
  border-left: 3px solid transparent;
}
.rail__modules a:hover { background: var(--surface-2); }
.rail__modules a[aria-current="page"] {
  background: var(--accent-soft); color: var(--accent);
  border-left-color: var(--accent); font-weight: 600;
}
.rail__modules .count { color: var(--muted); font-family: var(--data); font-size: 0.75rem; float: right; }

/* the query stack: the app's audit trail, pastes into a methods section */
.stack { border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg); }
.stack__head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 0.4rem 0.55rem; border-bottom: 1px solid var(--line);
}
.stack__head h3 { margin: 0; font-size: 0.72rem; }
.stack__list { list-style: none; margin: 0; padding: 0.3rem; font-family: var(--data); font-size: 0.74rem; }
.stack__list li {
  display: flex; justify-content: space-between; gap: 0.4rem; align-items: baseline;
  padding: 0.25rem 0.3rem; border-radius: 2px;
}
.stack__list li + li { border-top: 1px dashed var(--line); }
.stack__list .drop {
  background: none; border: 0; color: var(--muted); cursor: pointer;
  font-family: var(--data); padding: 0 0.2rem;
}
.stack__list .drop:hover { color: var(--bad); }
.stack__empty { color: var(--muted); padding: 0.45rem; font-size: 0.76rem; font-family: var(--body); }

.btn {
  background: var(--surface-2); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 0.3rem 0.55rem; cursor: pointer;
  font-family: var(--display); font-size: 0.72rem;
  text-transform: uppercase; letter-spacing: 0.04em;
}
.btn:hover { border-color: var(--accent); color: var(--accent); }
.btn--accent { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.btn--accent:hover { color: var(--on-accent); filter: brightness(1.08); }

/* Worked questions under the natural-language box.
   Sentence case and left-aligned rather than uppercase chips: these are
   questions to read, not labels to scan, and the rail is too narrow to set
   them on one line. */
.examples { display: flex; flex-direction: column; align-items: flex-start;
            gap: 0.25rem; margin: 0.45rem 0 0.2rem; }
.examples__label { font-size: 0.72rem; color: var(--muted);
                   text-transform: uppercase; letter-spacing: 0.04em; }
.chip {
  background: none; color: var(--muted);
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 0.2rem 0.4rem; cursor: pointer; text-align: left;
  font-family: var(--data); font-size: 0.74rem; line-height: 1.3;
  max-width: 100%;
}
.chip:hover { border-color: var(--accent); color: var(--accent); }
.chip[disabled] { opacity: 0.5; cursor: progress; }

.field { display: flex; flex-direction: column; gap: 0.2rem; margin-bottom: 0.5rem; }
.field label { font-size: 0.72rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; }
.field select, .field input {
  background: var(--bg); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 0.3rem 0.4rem; font-family: var(--data); font-size: 0.8rem;
  min-width: 0;
}
.field select:focus, .field input:focus { border-color: var(--accent); }
/* The rail is 15.5rem wide, which cannot hold three columns without clipping
   the operator label. Field and operator share a row, value gets its own. */
.builder__row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
                gap: 0.3rem 0.4rem; }
.builder__row .field:last-child { grid-column: 1 / -1; }

/* -------------------------------------------------------------------------- */
/* main pane                                                                  */
/* -------------------------------------------------------------------------- */

.pane { padding: 0.9rem var(--gutter) 2rem; min-width: 0; }

.pane__head {
  display: flex; align-items: baseline; gap: 0.7rem;
  flex-wrap: wrap; margin-bottom: 0.7rem;
}
.pane__head .meta { color: var(--muted); font-family: var(--data); font-size: 0.78rem; }

/* One card. Every panel on the site is this box with an h3 title as its first
   child, and nothing sits loose between panels: content that floated outside
   one looked like it belonged to whatever panel happened to be above it. */
.panel {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 0.8rem; margin-bottom: 0.9rem;
}
/* One panel title, whatever its level.

   A panel's title was h2 in some sections and h3 in others, and the two were
   styled differently: the h2 carried a rule under it and the h3 did not, so
   identical cards looked like different kinds of thing depending on which tag
   happened to open them. The step panels had a third style again, small and
   accent-coloured. All of them are this now, and the heading level goes back
   to being about document structure rather than appearance. */
.panel > h2, .panel > h3 {
  font-size: 0.95rem; color: var(--ink);
  text-transform: uppercase; letter-spacing: 0.04em;
  margin: 0 0 0.7rem;
  border-bottom: 1px solid var(--line); padding-bottom: 0.4rem;
}
.panel > :last-child { margin-bottom: 0; }

/* A panel whose body is a full-bleed table or canvas. The padding moves from
   the box onto the title and any prose, so the table still meets the border.
   This was `style="padding:0"` repeated inline in four places, which meant the
   title and the blurb had nowhere to live and ended up outside the panel. */
.panel--flush { padding: 0; }
.panel--flush > h2, .panel--flush > h3 {
  margin: 0; padding: 0.8rem 0.8rem 0.5rem;
}
.panel--flush > p { margin: 0; padding: 0 0.8rem 0.8rem; }
.panel--flush > .panel__foot { padding: 0.7rem 0.8rem; border-top: 1px solid var(--line); }

/* A numbered step in the About walkthrough. A panel like any other, keeping the
   accent edge that marked it out when it was a bare left-border block. */
/* The accent edge is what marks a step out. Its title is the same as every
   other panel title, so the walkthrough reads as four cards in a sequence
   rather than as four cards in a different typeface. */
.panel--step { border-left: 3px solid var(--accent); }
.panel__lead { font-size: 1.05rem; font-weight: 600; margin: 0 0 0.2rem; }
.panel__note { color: var(--muted); font-size: 0.86rem; max-width: 68ch; }
.panel__table { max-width: 34rem; }
/* A row of controls inside a panel, under its title. Was an inline flex
   declaration on the panel itself, which left that panel the only one on the
   site with no heading. */
.panel__controls {
  display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: flex-end;
}
.panel__controls .field { margin: 0; }

.split { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 0.9rem; }

/* The table and the viewer are the two halves of the workbench, so they should
 * end on the same line. The grid already stretches both cells to the taller of
 * the two; the viewer stopped at its own min-height instead of filling the cell
 * it was given, which left it floating short of the table beside it. Scoped to
 * the workbench so the About page's example viewers keep their own size. */
.split--workbench > * { display: flex; flex-direction: column; min-height: 0; }
.split--workbench .viewer { flex: 1 1 auto; }
/* The table's panel hugs the table rather than stretching, so a taller viewer
 * column leaves page background below it instead of an empty white panel. */
.split--workbench .panel { flex: 0 0 auto; }
/* The selected-row detail shares the viewer's column. Capped and scrollable so
 * a long row cannot squeeze the viewer down to its min-height, which is what
 * happened as soon as the first row started being pinned automatically.
 * Matched by its position after the viewer, because an unqualified `.panel`
 * here also caught the table's wrapper and capped the table at 34% of the
 * column. */
.split--workbench .viewer ~ .panel { flex: 0 0 auto; max-height: 34%; overflow: auto; }

.notice {
  border-left: 3px solid var(--accent); background: var(--accent-soft);
  color: var(--ink); padding: 0.6rem 0.75rem; border-radius: var(--radius);
  font-size: 0.85rem; margin-bottom: 0.9rem;
}
.notice--good { border-left-color: var(--good); background: color-mix(in srgb, var(--good) 8%, var(--surface)); }
.notice--bad { border-left-color: var(--bad); background: color-mix(in srgb, var(--bad) 8%, var(--surface)); }

/* stat tiles */
.stats { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 0.9rem; }
.stat {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 0.5rem 0.7rem; min-width: 7.5rem;
}
.stat__value { font-family: var(--data); font-size: 1.15rem; font-weight: 600; }
.stat__label { color: var(--muted); font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em; }
.stat--good .stat__value { color: var(--good); }
.stat--warn .stat__value { color: var(--warn); }
.stat--bad .stat__value { color: var(--bad); }

/* The two classes of headline card.

   A figure reports and does nothing; an action filters the table and the
   viewer. They used to share one row and one box, so there was no way to tell
   which was which until you clicked. Now each class gets its own labelled
   group, and the action cards carry a left accent bar and a pointer so the
   difference reads before the click rather than after it. */
.stats-group { margin-bottom: 0.9rem; }
.stats-group .stats { margin-bottom: 0; }
.stats-group__label {
  font-family: var(--display); font-size: 0.68rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--muted); margin: 0 0 0.35rem;
}
.stats-group--actions .stats-group__label { color: var(--accent); }

.stat--action {
  cursor: pointer; text-align: left; font: inherit;
  display: flex; flex-direction: column; gap: 0.1rem;
  border-left: 3px solid var(--accent);
  transition: border-color 0.12s ease, background 0.12s ease;
}
.stat--action:hover, .stat--action:focus-visible {
  border-color: var(--accent); background: var(--accent-soft);
}
.stat--action[aria-pressed="true"] {
  border-color: var(--accent); background: var(--accent-soft);
}
/* A tone colours the value on a figure card. On an action card the left bar is
   the accent and stays the accent, so the tone is carried by the value alone
   and the two cues do not compete. */
.stat--action.stat--good, .stat--action.stat--warn, .stat--action.stat--bad {
  border-left-color: var(--accent);
}

/* verdict and class pills */
.pill {
  display: inline-block; padding: 0.05rem 0.4rem; border-radius: 2px;
  font-family: var(--data); font-size: 0.72rem; border: 1px solid var(--line);
  background: var(--surface-2); color: var(--muted); white-space: nowrap;
}
/* Pills sit on alternating table-row backgrounds, so a translucent tint of the
   token colour lands on a different base in odd and even rows and fails contrast
   on the darker one. An opaque --surface background makes the ratio the same
   wherever the pill appears: measured 5.5 to 6.1 for every token. The colour
   coding lives in the border and the text. */
.pill--good { color: var(--good); border-color: var(--good); background: var(--surface); }
.pill--warn { color: var(--warn); border-color: var(--warn); background: var(--surface); }
.pill--bad { color: var(--bad); border-color: var(--bad); background: var(--surface); }
.pill--glue { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }

/* -------------------------------------------------------------------------- */
/* Mol* viewer frame (spec 6.4)                                               */
/* -------------------------------------------------------------------------- */

.viewer {
  position: relative; background: var(--surface-2);
  border: 1px solid var(--line); border-radius: var(--radius);
  min-height: 340px; overflow: hidden;
}
.viewer__canvas { position: absolute; inset: 0; }
.viewer__bar {
  position: absolute; top: 0; left: 0; right: 0; z-index: 5;
  display: flex; gap: 0.25rem; align-items: center; flex-wrap: wrap;
  padding: 0.3rem; background: color-mix(in srgb, var(--surface) 88%, transparent);
  border-bottom: 1px solid var(--line);
}
.viewer__bar .btn { font-size: 0.66rem; padding: 0.2rem 0.4rem; }
.viewer__id { margin-left: auto; font-family: var(--data); font-size: 0.72rem; padding-right: 0.3rem; }
.viewer__empty {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  text-align: center; padding: 2rem 1.2rem; color: var(--muted); font-size: 0.88rem;
}
/* An explicit `display` beats the user-agent rule that `[hidden]` relies on, so
   hideEmpty() set hidden=true and the overlay stayed on screen over a viewer
   that had in fact loaded. That is the whole "viewers do not load" symptom: the
   structure was there, behind this. */
.viewer__empty[hidden] { display: none; }
.viewer__overlay {
  position: absolute; bottom: 0.4rem; left: 0.4rem; z-index: 5;
  background: color-mix(in srgb, var(--surface) 90%, transparent);
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 0.3rem 0.45rem; font-family: var(--data); font-size: 0.72rem;
  max-width: calc(100% - 0.8rem);
}
.viewer--tall { min-height: 460px; }

/* -------------------------------------------------------------------------- */
/* tables (Tabulator overrides, themed from tokens only)                      */
/* -------------------------------------------------------------------------- */

.tabulator {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); font-size: 0.8rem; color: var(--ink);
}
.tabulator .tabulator-header {
  background: var(--surface-2); border-bottom: 2px solid var(--line); color: var(--ink);
}
.tabulator .tabulator-header .tabulator-col {
  background: var(--surface-2); border-right: 1px solid var(--line);
}
.tabulator .tabulator-header .tabulator-col-title {
  font-family: var(--display); font-size: 0.7rem;
  text-transform: uppercase; letter-spacing: 0.04em;
  /* The columns are divided out of the panel width, so a header that will not
     wrap forces a horizontal scrollbar on the whole table. Two short lines are
     better than one long one here. */
  white-space: normal; line-height: 1.15; overflow: visible; text-overflow: clip;
}
/* The unit rides under the name in the header: small, muted and not uppercased,
   so it reads as a unit rather than as part of the column's name. The full
   label and the field description are in the header tooltip. */
.col-name { display: block; }
.col-unit {
  display: block; font-family: var(--data); font-size: 0.62rem;
  text-transform: none; letter-spacing: 0; color: var(--muted); font-weight: 400;
}
/* Numbers are set with tabular figures so the two decimal places line up down
   the column, which is the point of fixing them at two. */
.tabulator .tabulator-cell { font-variant-numeric: tabular-nums; }
.tabulator .tabulator-row { background: var(--surface); border-bottom: 1px solid var(--line);
  color: var(--ink); }
.tabulator .tabulator-row .tabulator-cell { color: var(--ink); }
.tabulator .tabulator-row.tabulator-selectable:hover { color: var(--ink); }
.tabulator .tabulator-row.tabulator-row-even { background: var(--bg); }
.tabulator .tabulator-row:hover { background: var(--accent-soft) !important; }
.tabulator .tabulator-row.tabulator-selected { background: var(--accent-soft) !important; }
.tabulator .tabulator-cell { border-right: 1px solid var(--line); font-family: var(--data); }
.tabulator .tabulator-footer {
  background: var(--surface-2); border-top: 1px solid var(--line); color: var(--muted);
}
.tabulator .tabulator-footer .tabulator-page {
  background: var(--surface); border: 1px solid var(--line); color: var(--ink);
}
.tabulator .tabulator-footer .tabulator-page.active { background: var(--accent); color: #fff; }
.tabulator-col-sorter-element .tabulator-arrow { border-bottom-color: var(--muted) !important; }

/* a plain table for the About tab and small summaries */
.grid-table { width: 100%; border-collapse: collapse; font-size: 0.82rem; }
.grid-table th, .grid-table td {
  text-align: left; padding: 0.35rem 0.5rem; border-bottom: 1px solid var(--line);
  vertical-align: top;
}
.grid-table th {
  font-family: var(--display); font-size: 0.7rem; color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.04em; border-bottom: 2px solid var(--line);
}
.grid-table td.num, .grid-table th.num { text-align: right; font-family: var(--data); }
.grid-table tbody tr:hover { background: var(--surface-2); }

/* Two table shapes, and every table on the site is one of them.
   `--pairs` is a label and a value: the label reads as a caption, the value is
   right-aligned in the data font. `--stack` is a table with a header row, and
   it becomes a list of blocks on a phone (see the responsive section). */
.grid-table--pairs td:first-child { color: var(--muted); }
.grid-table--pairs td:last-child { text-align: right; font-family: var(--data); }
.grid-table--pairs td:last-child a { font-family: var(--display); }
.grid-table--stack .stack__title { min-width: 14rem; }
.series__plus { color: var(--muted); }
.series__lens { margin-left: 0.5rem; }

/* -------------------------------------------------------------------------- */
/* About tab                                                                  */
/* -------------------------------------------------------------------------- */

.schematic { width: 100%; height: auto; display: block; }
.schematic text { font-family: var(--data); }

/* -------------------------------------------------------------------------- */
/* lens graph                                                                 */
/* -------------------------------------------------------------------------- */

.lens { display: grid; grid-template-columns: minmax(0, 1fr) 20rem; gap: 0.9rem; }
.lens__canvas {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); min-height: 560px;
  /* An inline SVG with no width is 300px wide by the replaced-element default,
   * whatever its container does. The graph measured itself at 300 and laid 344
   * nodes out in a 300-wide box, so they flew apart and the fit scaled the
   * whole thing down to 0.05. The background filled the column, which is what
   * made it look like a sizing problem somewhere else. */
  display: block; width: 100%;
}
.lens__canvas .node { stroke: var(--surface); stroke-width: 1.5; cursor: pointer; }
.lens__canvas .node.is-selected {
  stroke: var(--accent); stroke-width: 3;
  paint-order: stroke;
}
.lens__canvas .link { stroke: var(--line); stroke-opacity: 0.75; }
.lens__canvas .node-label { font-family: var(--data); font-size: 9px; fill: var(--muted); pointer-events: none; }

/* -------------------------------------------------------------------------- */
/* responsive: phone width, 16px gutter, no horizontal page scroll            */
/* -------------------------------------------------------------------------- */

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

@media (max-width: 720px) {
  .ledger { grid-template-columns: minmax(0, 1fr); }
  .rail {
    border-right: 0; border-bottom: 2px solid var(--line);
    max-height: none;
  }
  .app-header { gap: 0.6rem; }
  .brand__sub { display: none; }
  .app-nav a { font-size: 0.72rem; padding: 0.3rem 0.45rem; }
  h1 { font-size: 1.2rem; }
  .builder__row { grid-template-columns: minmax(0, 1fr); }
  .viewer { min-height: 280px; }

  /* Every table: tighter, and a long value wraps rather than widening the
     page. A table that scrolls sideways inside a page that does not is the
     thing being avoided here. */
  .grid-table { font-size: 0.78rem; }
  .grid-table th, .grid-table td { padding: 0.3rem 0.4rem; overflow-wrap: anywhere; }
  .grid-table--pairs td:first-child { width: 45%; }

  /* A table with a header row cannot keep its columns at 360px: six of them,
     or four with two 48-character protein names, will not fit. Each row
     becomes a block, the header row goes, and every figure announces itself
     from its `data-label`. */
  .grid-table--stack thead { display: none; }
  .grid-table--stack tr { display: block; padding: 0.55rem 0.8rem; }
  .grid-table--stack tbody tr:not(:last-child) { border-bottom: 1px solid var(--line); }
  .grid-table--stack td {
    display: block; border: 0; padding: 0; text-align: left;
  }
  .grid-table--stack .stack__title {
    min-width: 0; font-weight: 600; margin-bottom: 0.3rem;
  }
  /* The figures share one wrapped line rather than taking one line each. */
  .grid-table--stack td.num {
    display: inline-block; margin: 0 0.9rem 0.2rem 0; text-align: left;
  }
  .grid-table--stack td[data-label]::before {
    content: attr(data-label) " ";
    font-family: var(--display); font-size: 0.62rem; color: var(--muted);
    text-transform: uppercase; letter-spacing: 0.04em;
  }
  .grid-table--stack .stack__links { margin-top: 0.25rem; }
  /* An empty cell in a stacked row would otherwise print its label alone. */
  .grid-table--stack td:empty { display: none; }
}

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

@media print {
  .rail, .app-nav, .theme-toggle, .viewer__bar { display: none; }
  body { background: #fff; color: #000; }
}

/* Defensive, and last so it wins on equal specificity: any element carrying the
   `hidden` attribute stays hidden. A class that sets an explicit `display`
   silently overrides the user-agent rule behind `[hidden]`, which is how the
   viewer's empty overlay stayed on top of a structure that had loaded. Anything
   toggled with `hidden` in JS is exposed to the same trap. */
[hidden] { display: none !important; }
