/* The Pixel Press theme — the dashboard's look, in one file.
 *
 * WHY THIS IS A SECOND FILE AND NOT AN EDIT TO ui.css
 * ---------------------------------------------------
 * `ui.css` is the legacy design system and every page that extends base.html loads it.
 * Re-skinning it in place would have been tidier, except that one page is deliberately
 * FROZEN: `/admin/metrics` (Editor pay) feeds invoicing decisions and was excluded from
 * the renovation by name. Layering the theme on top means that page can opt out by not
 * loading this file, and its rendering is then provably unchanged — byte-identical CSS,
 * not "we think we didn't touch it". base.html carries the opt-out
 * (`{% set legacy_theme = true %}`), and there is exactly one page using it.
 *
 * So: ui.css first, this second. Same selectors, same specificity, later file wins.
 *
 * WHAT IT DOES NOT REACH
 * ----------------------
 * The four editing surfaces are standalone documents that never extend base.html and
 * never load ui.css: the motion QC editors (`qc_editor.html` inline <style>,
 * `qc_editor2.html` -> /static/qc2/ui.css), the stills image-QC editor, and the gen
 * studio (`genstudio/static/studio.html`). They are excluded from the renovation and
 * this file cannot touch them. Same for `_prior_work.html` and `_srtcheck.html`, which
 * only those three include.
 *
 * TOKENS
 * ------
 * `--pp-*`, lifted from ~/work/pixelpress-site/site/styles.css, the same set cliplab.css
 * and work.css took. Those two now ALIAS these rather than carrying their own copies:
 * two copies of a palette drift, and thirty-one would be a guarantee.
 */

/* ---- ui.css's own tokens, repointed ------------------------------------------------
 * Anything still reading `var(--fg)` or `var(--line)` — the episode rail, the send-back
 * form — comes along without having to be found and rewritten. These stay HERE, not in
 * ui.css, because repointing them IS the re-skin: a page that opts out must keep the old
 * values. The `--pp-*` palette itself lives in ui.css, since the nav is global chrome and
 * needs it on every page including the opted-out one.
 */
:root {
  --bg: var(--pp-paper); --fg: var(--pp-ink); --muted: var(--pp-ink-soft);
  --line: var(--pp-line); --card: var(--pp-card);
  --ok: var(--pp-ok); --ok-bg: var(--pp-ok-bg);
  --warn: var(--pp-wait); --warn-bg: var(--pp-wait-bg);
  --bad: var(--pp-bad); --bad-bg: var(--pp-bad-bg);
  --idle: var(--pp-idle); --idle-bg: var(--pp-idle-bg);
  --r: var(--pp-r); --r-sm: var(--pp-r-sm);
}

/* ---- page -------------------------------------------------------------------------- */
body {
  font: 15px/1.55 var(--pp-sans);
  background: var(--pp-paper);
  color: var(--pp-ink);
}
a { color: var(--pp-accent); }
.wrap { max-width: 1180px; padding: 22px clamp(14px, 2.5vw, 26px) 44px; }

h1 {
  font-family: var(--pp-serif); font-weight: 500; font-size: 1.6rem;
  letter-spacing: -.02em; margin: 0 0 14px; line-height: 1.15; color: var(--pp-ink);
}
h2 { font-family: var(--pp-serif); font-weight: 500; letter-spacing: -.01em; }
h3 { font-size: 1rem; font-weight: 600; }

:focus-visible { outline: 2px solid var(--pp-accent); outline-offset: 2px; border-radius: 6px; }
::selection { background: color-mix(in srgb, var(--pp-accent) 24%, transparent); }

/* The brand ribbon under the nav. Absolutely positioned because the nav is a flex row and
   a pseudo-element in flow would become a flex item and push the links around. */
nav.nav { position: relative; }
nav.nav::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 3px;
  background: var(--pp-gradient);
}

/* ---- surfaces ---------------------------------------------------------------------- */
.card {
  background: var(--pp-card); border: 1px solid var(--pp-line);
  border-radius: var(--pp-r); padding: 16px 18px; margin-bottom: 16px;
}
.empty {
  padding: 34px; text-align: center; color: var(--pp-ink-soft); font-size: .9rem;
  background: var(--pp-card); border: 1px dashed var(--pp-line);
  border-radius: var(--pp-r);
}

/* No `overflow` here, deliberately — ui.css owns it, in BOTH states. It clips the table
 * to the radius on desktop and turns it into its own horizontal scroller under 760px.
 * This rule used to re-declare `overflow: hidden`; it is the later sheet, the selector is
 * the same `table`, so the shorthand reset `overflow-x` and beat the phone rule at equal
 * specificity — a media query adds none. Every data table then dropped its right-hand
 * columns with no way to scroll to them: 809px lost on /ops, 1092px on /admin/users.
 * /admin/metrics was the tell. It sets `legacy_theme`, so it never loads this file, and
 * its tables were the only ones on the dashboard that scrolled correctly. */
table {
  background: var(--pp-card); border: 1px solid var(--pp-line);
  border-radius: var(--pp-r);
}
th {
  background: var(--pp-paper-alt); color: var(--pp-ink-soft);
  font-size: .67rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  border-bottom: 1px solid var(--pp-line); padding: 9px 12px;
}
td { border-bottom: 1px solid var(--pp-line-soft); padding: 10px 12px; font-size: .88rem; }
tbody tr:hover td { background: color-mix(in srgb, var(--pp-paper) 55%, var(--pp-card)); }

.muted { color: var(--pp-ink-soft); }
.mono { font-variant-numeric: tabular-nums; }

/* ---- controls ---------------------------------------------------------------------- */
.btn {
  font-family: var(--pp-sans); font-size: .82rem; font-weight: 600;
  padding: 7px 15px; border-radius: 999px; white-space: nowrap;
  border: 1px solid var(--pp-line); background: var(--pp-card); color: var(--pp-ink);
}
.btn:hover { background: var(--pp-paper); border-color: var(--pp-ink-soft); }
.btn.primary { background: var(--pp-ink); border-color: var(--pp-ink); color: #fff; }
.btn.primary:hover { background: #1e2635; border-color: #1e2635; }
.btn.sm { padding: 5px 13px; font-size: .78rem; }
.btn.danger { color: var(--pp-bad); border-color: color-mix(in srgb, var(--pp-bad) 35%, transparent); }
.btn.danger:hover { background: var(--pp-bad-bg); border-color: var(--pp-bad); }
.btn:disabled, .btn[disabled] {
  background: var(--pp-paper-alt); border-color: var(--pp-line);
  color: var(--pp-ink-soft); cursor: not-allowed;
}
.linkbtn { color: var(--pp-accent); }

/* A bare <button> is a button. Most of this dashboard's small forms — confirm the delivery
   columns, load a chapter map, pick a source sheet — never named a class, so they were
   still rendering the browser's own grey box next to `.btn` pills three inches away. This
   is deliberately the same shape as `.btn`; anything wanting the SOLID treatment still
   asks with `.primary`. Safe to do globally here because theme.css reaches only the pages
   that extend base.html — the four editing surfaces never load it. */
button, input[type="submit"] {
  font-family: var(--pp-sans); font-size: .82rem; font-weight: 600;
  padding: 7px 15px; border-radius: 999px; cursor: pointer; white-space: nowrap;
  border: 1px solid var(--pp-line); background: var(--pp-card); color: var(--pp-ink);
}
button:hover, input[type="submit"]:hover {
  background: var(--pp-paper); border-color: var(--pp-ink-soft);
}
button:disabled, input[type="submit"]:disabled {
  background: var(--pp-paper-alt); border-color: var(--pp-line);
  color: var(--pp-ink-soft); cursor: not-allowed;
}
button.primary, input[type="submit"].primary {
  background: var(--pp-ink); border-color: var(--pp-ink); color: #fff;
}
button.primary:hover { background: #1e2635; border-color: #1e2635; }
/* `.linkbtn` is a button that must read as a link — it undoes the above on purpose. */
.linkbtn {
  border: 0; background: none; padding: 0; border-radius: 0;
  font-weight: 600; color: var(--pp-accent);
}
.linkbtn:hover { background: none; text-decoration: underline; }

input, select, textarea {
  font: inherit; font-size: .88rem; color: var(--pp-ink);
  background: var(--pp-card); border: 1px solid var(--pp-line);
  border-radius: var(--pp-r-sm); padding: 6px 9px;
}
input[type="checkbox"], input[type="radio"] { padding: 0; accent-color: var(--pp-accent); }
input[type="submit"], button { font-family: var(--pp-sans); }
label { color: var(--pp-ink); }

/* ---- status pills ------------------------------------------------------------------ */
.pill {
  font-size: .7rem; font-weight: 700; padding: 3px 9px; border-radius: 999px;
  letter-spacing: .01em;
  /* A DEFAULT, so a status with no `.s-*` rule still reads as a pill instead of as bare
     bold text that looks like the page failed. `generating_srt` was exactly that: the
     status list here predates it, and on the season board it rendered naked next to four
     coloured neighbours. The list below is a refinement of this, never the only thing
     making a pill look like one. */
  background: var(--pp-paper-alt); color: var(--pp-ink-soft);
}
.s-needs_review, .s-ready_to_match, .s-awaiting_image_qc, .s-awaiting_video_qc {
  background: var(--pp-wait-bg); color: var(--pp-wait);
}
.s-done, .s-delivered, .s-ready_to_deliver, .s-client_approved { background: var(--pp-ok-bg); color: var(--pp-ok); }
.s-error, .s-errored, .s-client_comments { background: var(--pp-bad-bg); color: var(--pp-bad); }
.s-queued, .s-matched, .s-approved, .s-built,
.s-matching, .s-rendering, .s-delivering, .s-running, .s-generating_srt,
.s-checking, .s-awaiting_qc {
  background: var(--pp-idle-bg); color: var(--pp-idle);
}
.pill.s-sync-ok { background: var(--pp-ok-bg); color: var(--pp-ok); }
.pill.s-sync-warn { background: var(--pp-wait-bg); color: var(--pp-wait); }
.pill.s-sync-bad { background: var(--pp-bad-bg); color: var(--pp-bad); }

/* ---- episode trail / send-back (still on the shared sheet) -------------------------- */
.state { font-weight: 600; }
.t-ok { color: var(--pp-ok); } .t-warn { color: var(--pp-wait); }
.t-wait { color: var(--pp-wait); } .t-bad { color: var(--pp-bad); }
.t-idle { color: var(--pp-ink-soft); }
/* The episode Activity table: WHO ACTS on each row (app/timeline_present.py lanes), and
   the header row that opens each round. */
.lane { display: inline-block; font-size: .74rem; font-weight: 600; padding: 2px 9px;
        border-radius: 999px; white-space: nowrap; background: var(--pp-idle-bg);
        color: var(--pp-ink-soft); }
.lane-editor { background: var(--pp-ok-bg); color: var(--pp-ok); }
.lane-qc { background: color-mix(in srgb, var(--pp-accent) 14%, var(--pp-card));
           color: var(--pp-accent); }
.lane-admin { background: var(--pp-ink); color: var(--pp-card); }
.lane-client { background: var(--pp-wait-bg); color: var(--pp-wait); }
tr.roundhead td { background: var(--pp-paper-alt); font-weight: 600; color: var(--pp-ink);
                  border-top: 2px solid var(--pp-line); }
tr.roundhead .muted { font-weight: 400; }
.told { display: block; font-size: .8rem; color: var(--pp-ink-soft); }
.rawdetail summary { cursor: pointer; font-size: .8rem; color: var(--pp-ink-soft); }
.rawdetail div { white-space: pre-wrap; font-size: .8rem; }
.stack textarea, .stack select { border-color: var(--pp-line); border-radius: var(--pp-r-sm); }

/* ---- narrow ------------------------------------------------------------------------ */
@media (max-width: 760px) {
  .wrap { padding: 14px 12px 32px; }
  h1 { font-size: 1.3rem; }
}

/* ======================================================================================
 * SHARED COMPONENTS (2026-09-17)
 *
 * Every one of these was being re-invented inline, page by page, in slightly different
 * pixels: the season board wrote `style="margin:12px 0;padding:9px 12px;border:1px solid
 * #f0c0c0;border-radius:10px;background:#fff5f5;color:#b00;font-size:13px"` three times
 * for three warnings, and /ops, the episode trail and the admin pages each had their own
 * near-miss of the same box. Forty-four inline `style=` attributes on one admin page is
 * not styling, it is a design system nobody wrote down.
 *
 * These are named for the JOB, not the look, so a page asks for "a standing warning" and
 * gets whatever that currently is.
 * ==================================================================================== */

/* ---- banner: a standing message above the work ------------------------------------- */
.banner {
  display: flex; align-items: flex-start; gap: 9px;
  margin: 0 0 14px; padding: 11px 14px;
  border: 1px solid var(--pp-line); border-radius: var(--pp-r);
  background: var(--pp-card); font-size: .86rem; line-height: 1.5;
}
/* The bold run leads the message; it is not always a short label in front of prose. On
 * the season board the WHOLE banner is bold ("Set the episode mapping (local → US)
 * before matching."), and `flex: none` refused to shrink it: 369px of text inside a
 * 336px panel, clipped by the panel's own `overflow: hidden`. Shrinking is still floored
 * at min-content, so a real label like "Stuck:" cannot be broken mid-word. */
.banner > b:first-child { flex: 0 1 auto; }
.banner a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.banner.bad  { background: var(--pp-bad-bg);  color: var(--pp-bad);
               border-color: color-mix(in srgb, var(--pp-bad) 30%, transparent); }
.banner.warn { background: var(--pp-wait-bg); color: var(--pp-wait);
               border-color: color-mix(in srgb, var(--pp-wait) 30%, transparent); }
.banner.ok   { background: var(--pp-ok-bg);   color: var(--pp-ok);
               border-color: color-mix(in srgb, var(--pp-ok) 30%, transparent); }
/* The icon column. A glyph rather than an image: these render inside a live region that
   the poll swaps, and a request per banner per swap is not worth a picture. */
.banner::before { content: "●"; flex: none; font-size: .7rem; line-height: 1.7; opacity: .8; }
.banner.bad::before { content: "✕"; }
.banner.warn::before { content: "!"; font-weight: 800; }
.banner.ok::before { content: "✓"; }

/* ---- pagehead: the title, and the things you do to the whole page ------------------- */
.pagehead { display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px 12px; margin: 0 0 6px; }
.pagehead h1 { margin: 0; }
.pagehead .sub { font-size: .85rem; color: var(--pp-ink-soft); }
.pagehead .sp { flex: 1; }
/* A row of page-level actions: links and single-button forms, aligned on one line and
   separated by rules rather than by the pipe characters these pages used to type. */
.pageacts { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin: 0 0 16px; }
.pageacts form { margin: 0; }
.pageacts > a {
  font-size: .82rem; font-weight: 600; color: var(--pp-accent); text-decoration: none;
  padding: 5px 2px;
}
.pageacts > a:hover { text-decoration: underline; }
.pageacts .sep { width: 1px; align-self: stretch; background: var(--pp-line); margin: 0 3px; }

/* ---- toolbar: the things you do to a SELECTION ------------------------------------- */
.toolbar {
  display: flex; align-items: center; flex-wrap: wrap; gap: 7px;
  margin: 0 0 14px; padding: 10px 12px;
  background: var(--pp-card); border: 1px solid var(--pp-line); border-radius: var(--pp-r);
}
.toolbar form { margin: 0; }
.toolbar > b, .toolbar .lede {
  font-size: .67rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--pp-ink-soft); margin-right: 2px;
}
.toolbar .sep { width: 1px; align-self: stretch; background: var(--pp-line); margin: 0 5px; }
.toolbar .sp { flex: 1; }
/* A bare <button> inside a toolbar is a .btn — these pages have dozens and naming each
   one would be the same list, kept by hand. */
.toolbar button, .toolbar input[type="submit"], .toolbar > a.act {
  font-family: var(--pp-sans); font-size: .78rem; font-weight: 600;
  padding: 5px 13px; border-radius: 999px; white-space: nowrap; cursor: pointer;
  border: 1px solid var(--pp-line); background: var(--pp-card); color: var(--pp-ink);
  text-decoration: none; display: inline-block;
}
.toolbar button:hover, .toolbar > a.act:hover {
  background: var(--pp-paper); border-color: var(--pp-ink-soft);
}
.toolbar button.primary { background: var(--pp-ink); border-color: var(--pp-ink); color: #fff; }
.toolbar button.primary:hover { background: #1e2635; }
.toolbar button.danger { color: var(--pp-bad); border-color: color-mix(in srgb, var(--pp-bad) 35%, transparent); }
.toolbar button:disabled { background: var(--pp-paper-alt); color: var(--pp-ink-soft); cursor: not-allowed; }
.toolbar select { font-size: .8rem; padding: 5px 8px; }

/* ---- panel: a collapsible section of setup ----------------------------------------- */
.panel {
  margin: 0 0 16px; border: 1px solid var(--pp-line); border-radius: var(--pp-r);
  background: var(--pp-card); overflow: hidden;
}
.panel > summary {
  cursor: pointer; list-style: none; padding: 11px 14px;
  font-size: .82rem; font-weight: 700; color: var(--pp-ink);
  display: flex; align-items: center; gap: 8px;
}
.panel > summary::-webkit-details-marker { display: none; }
.panel > summary::before {
  content: ""; flex: none; width: 0; height: 0;
  border: 4px solid transparent; border-left-color: currentColor;
  transition: transform .15s ease; transform-origin: 2px 4px;
}
.panel[open] > summary::before { transform: rotate(90deg); }
.panel > summary:hover { background: var(--pp-paper); }
.panel .panel-body { padding: 4px 14px 16px; border-top: 1px solid var(--pp-line-soft); }
.panel fieldset {
  border: 1px solid var(--pp-line); border-radius: var(--pp-r-sm);
  padding: 12px 14px; margin: 14px 0 0; max-width: 680px;
}
.panel legend { font-size: .78rem; font-weight: 700; padding: 0 6px; }

/* ---- field: a labelled input ------------------------------------------------------- */
.field { display: flex; flex-direction: column; gap: 4px; }
.field > span, .field > label {
  font-size: .67rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--pp-ink-soft);
}
.fieldrow {
  display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end;
  padding: 12px 14px; margin: 0 0 16px;
  background: var(--pp-card); border: 1px solid var(--pp-line); border-radius: var(--pp-r);
}
/* A label in one of these owns its control: caption over input, whatever the markup does
   about line breaks. Without it a `<label>Person<br><input></label>` lays out as an
   inline box and the captions drift beside their neighbours' inputs. */
.fieldrow > label {
  display: flex; flex-direction: column; gap: 4px;
  font-size: .67rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--pp-ink-soft);
}
.fieldrow > label > input, .fieldrow > label > select, .fieldrow > label > textarea {
  font-size: .88rem; font-weight: 400; letter-spacing: 0; text-transform: none;
  color: var(--pp-ink);
}
.fieldrow > label br { display: none; }

/* ---- a column that should take exactly what it needs --------------------------------
 * `width:1%` + nowrap is the table idiom for it: the column shrinks to its content and
 * every other column splits what is left. The board and the live monitor each had their
 * own page-local copy of this, and the preflight page then used the class and got nothing
 * — a shared class whose rule is page-scoped is worse than no class, because the markup
 * says it is handled.
 */
th.tight, td.tight { width: 1%; white-space: nowrap; }

/* ---- a table cell holding buttons --------------------------------------------------- */
td.act, .act {
  display: flex; flex-wrap: wrap; gap: 5px; align-items: center;
}
/* BUTTONS get button chrome. A bare <a> in here does not: `▶ video`, `EN` and `script`
   are links to somewhere else, and dressing them as pills both lied about what they do
   and cost the row ~180px — which is what pushed a delivered row's six controls onto a
   second line. An anchor that wants to look like a button says so with `.btn`. */
td.act button, td.act > a.btn, .act button, .act > a.btn, .act > a.act-link {
  font-family: var(--pp-sans); font-size: .75rem; font-weight: 600;
  padding: 4px 11px; border-radius: 999px; white-space: nowrap; cursor: pointer;
  border: 1px solid var(--pp-line); background: var(--pp-card); color: var(--pp-ink);
  text-decoration: none; margin: 0;
}
td.act button:hover, td.act > a.btn:hover, .act button:hover, .act > a.btn:hover {
  background: var(--pp-paper); border-color: var(--pp-ink-soft);
}
/* A plain link in an action cell: readable, compact, and obviously not a control. */
td.act > a:not(.btn), .act > a:not(.btn) {
  font-size: .76rem; font-weight: 600; color: var(--pp-accent); text-decoration: none;
  white-space: nowrap;
}
td.act > a:not(.btn):hover, .act > a:not(.btn):hover { text-decoration: underline; }
td.act .lock, .act .lock {
  flex: 0 0 100%; font-size: .72rem; color: var(--pp-ink-soft); margin: 0 0 2px;
}

/* ---- small print -------------------------------------------------------------------- */
.note { font-size: .8rem; color: var(--pp-ink-soft); line-height: 1.55; }
.note code { background: var(--pp-paper-alt); border-radius: 4px; padding: 1px 5px; font-size: .95em; }

/* ---- the episode trail --------------------------------------------------------------
 * A `.card` in a tone, a video at the card's width, and the two narrow bits of type this
 * page had been setting inline at every call site. `h3` losing its top margin inside a
 * card is a rule about cards, not a decision to re-make per heading.
 */
.card > h3:first-child, .card > h2:first-child { margin-top: 0; }
.card.bad  { border-color: color-mix(in srgb, var(--pp-bad) 30%, transparent);  background: var(--pp-bad-bg); }
.card.warn { border-color: color-mix(in srgb, var(--pp-wait) 30%, transparent); background: var(--pp-wait-bg); }
.card.ok   { border-color: color-mix(in srgb, var(--pp-ok) 30%, transparent);   background: var(--pp-ok-bg); }
.errtext {
  margin: 7px 0 0; font-size: .84rem; line-height: 1.5;
  overflow-wrap: anywhere; white-space: pre-wrap;
}
.qcvideo { max-width: 100%; border-radius: var(--pp-r-sm); display: block; }
.epmeta { margin: 4px 0 16px; font-size: .86rem; }
.pad-t { margin-top: 10px; }
h3 .sub { font-weight: 400; font-size: .84rem; }

/* ---- the filter chip ----------------------------------------------------------------
 * A toggle that narrows what is on screen, with its count beside it. It started life in
 * work.css as `.wk-chip`, and when /ops wanted the same control the choice was to copy it
 * or to share it. It is here under a shared name, and work.css no longer defines it —
 * /ops loads ui.css and theme.css but NOT work.css, so borrowing the console's private
 * class rendered its filter row as six bare underlined links.
 */
.chiprow { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin: 0 0 14px; }
.chips { display: flex; gap: 6px; flex-wrap: wrap; }
.chip {
  display: inline-flex; align-items: baseline; gap: 6px;
  font-size: .78rem; font-weight: 600; padding: 6px 12px;
  border: 1px solid var(--pp-line); border-radius: 999px;
  background: var(--pp-card); color: var(--pp-ink-soft); text-decoration: none;
  white-space: nowrap;
}
.chip b { color: var(--pp-ink); font-variant-numeric: tabular-nums; }
.chip:hover { border-color: var(--pp-accent); color: var(--pp-ink); }
.chip.on { background: var(--pp-ink); border-color: var(--pp-ink); color: #fff; }
.chip.on b { color: #fff; }

/* A table row that IS the problem — an errored task on the live monitor. Tinted rather
   than only pilled, because on a 200-row monitor the pill is one cell and the row is what
   you are scanning for. */
tr.row-bad td { background: var(--pp-bad-bg); }
tbody tr.row-bad:hover td { background: color-mix(in srgb, var(--pp-bad-bg) 80%, #fff); }

/* A cell holding a worker error: readable, clamped, never the thing setting the column's
   width. */
.errcell {
  font-size: .76rem; color: var(--pp-bad); max-width: 26rem; overflow-wrap: anywhere;
}

/* ---- the season list's live-monitor strip -------------------------------------------- */
.opsbar { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.opsbar .sp { flex: 1; }
.opsbar-link { font-weight: 700; text-decoration: none; }
.opsbar-link::before { content: "◉ "; }
.tight-b { margin-bottom: 2px; }

/* ---- a health strip: one line of numbers with a verdict down its edge ---------------- */
.healthbar {
  display: flex; gap: 18px; flex-wrap: wrap; align-items: baseline;
  padding: 11px 14px; margin: 0 0 14px;
  background: var(--pp-card); border: 1px solid var(--pp-line);
  border-radius: var(--pp-r); border-left-width: 4px;
  font-size: .86rem;
}
.healthbar.ok  { border-left-color: var(--pp-ok); }
.healthbar.bad { border-left-color: var(--pp-bad); }
.strong { font-weight: 700; }
.lede { max-width: 62rem; }

/* ---- the last few one-word utilities -------------------------------------------------
 * Named for what they mean, and deliberately few: every one of these replaced the same
 * inline declaration written out on six different pages. Anything needing more than one
 * property gets a real component above, not another utility.
 */
.nowrap { white-space: nowrap; }
.right { text-align: right; }
.wide { width: 100%; }
.narrow { max-width: 30rem; }
.stack-sm { display: flex; flex-direction: column; gap: 6px; }
.row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
/* A select and the button that applies it. NOWRAP on purpose: these live in a narrow
   table cell, and wrapping put "Set" on its own line under every role dropdown, which
   reads as a second control rather than as this one's confirm. */
.row.tight { gap: 6px; flex-wrap: nowrap; }
.row form { margin: 0; }
.sp { flex: 1; }
.sp-left { margin-left: auto; }
.clickable { cursor: pointer; }
.left { text-align: left; }
.m0 { margin: 0; }
.pad-b { margin-bottom: 12px; }
.grow { flex: 1; min-width: 14rem; }
.lg { font-size: 1.05rem; }
.xl { font-size: 2.6rem; line-height: 1; }
.narrow-lg { max-width: 56rem; }
.inline-b { display: inline-block; margin-right: 12px; }
.hairline-t { border-top: 1px solid var(--pp-line); }
.card-accent { border-color: color-mix(in srgb, var(--pp-accent) 40%, transparent); }
/* A label with its control under it, stacked and spaced — the shape every small form on
   this dashboard uses. */
.fieldblock { display: block; margin-bottom: 12px; }
.fieldblock > input, .fieldblock > select, .fieldblock > textarea { width: 100%; margin-top: 4px; }
/* The one button that destroys something. Solid, so it can never be mistaken for the
   cancel beside it. */
.btn.danger-solid { background: var(--pp-bad); border-color: var(--pp-bad); color: #fff; }
.btn.danger-solid:hover { background: #963a34; border-color: #963a34; }
.w-num { width: 9rem; }

/* ---- a page that is only a message ---------------------------------------------------
 * "This episode is locked", and anything else that is a dead end rather than a surface.
 * Centred, narrow, and on the same stylesheets as the rest — a page you meet when you are
 * already confused should not also look like a different product.
 */
.standalone {
  max-width: 34rem; margin: 64px auto; padding: 0 20px; text-align: center;
}
.standalone h1 { font-size: 1.5rem; }
.standalone .xl { margin-bottom: 10px; }

@media (max-width: 760px) {
  /* Under 760px ui.css turns a table into its own horizontal scroller and caps every cell
     at 62vw. `td.act` is a FLEX container, and a flex box inside that regime resolves its
     height against the cap rather than its content — the live monitor's errored rows grew
     to ~190px of mostly empty space. It goes back to being a table cell here; the buttons
     are inline-block and wrap on their own. */
  td.act { display: table-cell; white-space: normal; }
  td.act button, td.act > a { display: inline-block; margin: 2px 3px 2px 0; }
  /* …and they are the controls a phone operator reaches for first. `▶ video`, `EN` and
     `clean` are plain links in an action cell, so they carried no padding at all and
     measured 19px tall. Padding rather than a height, so they still wrap as text. */
  td.act button, td.act > a, .act button, .act > a, .pageacts > a {
    padding-top: 10px; padding-bottom: 10px;
  }
  /* ui.css floors every non-empty cell at 12ch inside the phone's table scroller, which
     is a sensible default and far too narrow for a worker error: at ~116px the message
     wrapped to fifteen lines and made a 228px row of mostly empty space in every OTHER
     column. Inside a horizontal scroller a wide column costs nothing but scroll.

     `.notecell` is the same job on a table that is not the monitor: the trail's Note and
     Detail, the generation log's request, preflight's finding. Those tables declare
     PERCENTAGE widths for their other columns, and a percentage of a table already at
     its minimum width is that minimum — every column came out 131px and the trail's note
     rendered as a 522px-tall ribbon. Naming the prose column is what tells the layout
     which one is allowed to be wide.

     Both are written `td.` and not as bare classes, and that is load-bearing: the floor
     above is `td:not(:empty)`, specificity (0,1,1), and a bare `.errcell` is (0,1,0). It
     LOST, silently, from the day the floor was added — theme.css being the later sheet
     buys nothing against a more specific selector — so the monitor's error column had
     been sitting at 12ch all along, which is the very thing this rule was written to
     stop. Qualifying the class ties the specificity and lets source order decide. */
  td.errcell, td.notecell { min-width: 22rem; max-width: none; }

  /* A filter chip on /ops carries a SEASON NAME, and `nowrap` made a long one 389px
     wide — the last thing still pushing the document sideways on a 320px phone, which
     this stylesheet's own rule says must never happen. A chip wraps here; a filter you
     cannot read beats a pill that stays on one line. This override lives in THIS file
     and not in ui.css's phone block for the reason the table bug above exists: `.chip`
     is declared here, unconditionally, and a media query in the earlier sheet does not
     outrank it. */
  .chip { white-space: normal; }
  /* A banner is three flex columns on a desktop — glyph, bold lead, prose — and on a phone
     that squeezed the prose into a third of the width and set a trailing "Fix it in Setup"
     link as a one-word-per-line column of its own. Here it is one paragraph that wraps. */
  .banner { display: block; }
  .banner::before { display: inline-block; margin-right: 7px; }
  /* The toolbar's group rules are vertical hairlines between buttons on ONE line; once the
     buttons wrap they land mid-row and read as stray marks. */
  .toolbar .sep { display: none; }
  .toolbar button { padding: 8px 13px; }
  /* The board's floating "N selected" bar (board.html): on a phone the bulk buttons are a
     screen-and-a-half above the episode you just ticked. */
  .selbar {
    position: fixed; left: 10px; right: 10px; bottom: 10px; z-index: 30;
    display: flex; align-items: center; gap: 10px; padding: 9px 10px 9px 16px;
    background: var(--pp-ink); color: #fff; border-radius: 999px;
    box-shadow: 0 10px 30px -10px rgba(43, 52, 70, .6); font-size: .88rem; font-weight: 600;
  }
  .selbar[hidden] { display: none; }
  body:has(.selbar:not([hidden])) .wrap { padding-bottom: 84px; }
  .selbar .sp { flex: 1; }
  .selbar button {
    font: inherit; font-size: .82rem; border-radius: 999px; padding: 8px 14px; cursor: pointer;
    border: 1px solid rgba(255, 255, 255, .35); background: none; color: #fff;
  }
  .selbar button.go { background: #fff; color: var(--pp-ink); border-color: #fff; }
}
/* Never on a desktop, where the toolbar is on screen beside the rows. */
@media (min-width: 761px) { .selbar { display: none; } }
