
:root { color-scheme: dark;
  --bg: #0b0d11; --panel: #141821; --panel-2: #1b2130; --line: #262d3c;
  --text: #e9edf5; --muted: #93a0b4; --gold: #f0c987; --amber: #d99a3d;
  --red: #d64545; --green: #3fb950; --blue: #6aa8ff; }
* { box-sizing: border-box; }
html, body { height: 100%; }
body { margin: 0; height: 100vh; height: 100dvh; display: flex; flex-direction: column;
  overflow: hidden; background:
    radial-gradient(1200px 600px at 15% -10%, #1d2434 0%, transparent 60%), var(--bg);
  color: var(--text); font: 14px/1.4 system-ui, -apple-system, Segoe UI, sans-serif; }
/* One compact chrome row: identity + counts, then filters on a single
   scrollable line — a wrapped filter bar is what made the old page tall. */
header.top { flex: 0 0 auto; display: flex; align-items: baseline; gap: 12px;
  padding: 7px 16px 5px; }
/* The webui's own mark beside the title (owner 2026-10-20): the brand emblem
   at 30 px - small enough to stay a mark, big enough to be recognised. */
header.top .mark { height: 30px; width: 30px; align-self: center;
  border-radius: 7px; flex: 0 0 auto; }
h1 { margin: 0; font-size: 15px; font-weight: 700; letter-spacing: .3px; white-space: nowrap; }
h1 span { color: var(--gold); }
header.top .sub { margin-left: auto; color: var(--muted); font-size: 11.5px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.filters { flex: 0 0 auto; display: flex; align-items: center; gap: 10px;
  padding: 0 16px 7px; overflow-x: auto; border-bottom: 1px solid var(--line);
  background: linear-gradient(180deg, #12161e, #0d1016); }
.chips, .tabs { display: flex; gap: 5px; flex: 0 0 auto; }
.chip, .pill { background: var(--panel); border: 1px solid var(--line); color: var(--muted);
  padding: 3px 9px; border-radius: 999px; font-size: 11.5px; text-decoration: none;
  white-space: nowrap; }
.chip.active { border-color: var(--gold); color: var(--gold); }
.pill.active { border-color: var(--blue); color: var(--blue); }
input.search { background: var(--panel); border: 1px solid var(--line); color: var(--text);
  border-radius: 7px; padding: 4px 9px; width: 170px; font-size: 12.5px; }
/* The filter bar, design A (owner 2026-10-29): one labelled group per kind
   of filter on a single line, the publish states first with their counts. */
.fgroup { display: inline-flex; align-items: stretch; flex: 0 0 auto;
  border: 1px solid var(--line); border-radius: 8px; overflow: hidden;
  background: var(--panel); }
.flabel { color: #6c788c; font-size: 10px; text-transform: uppercase; letter-spacing: .4px; }
.fgroup .flabel { display: flex; align-items: center; padding: 0 8px; background: #10141c;
  border-right: 1px solid var(--line); }
.fseg { display: flex; align-items: center; gap: 5px; padding: 3px 9px; color: var(--muted);
  font-size: 11.5px; text-decoration: none; white-space: nowrap;
  border-right: 1px solid var(--line); }
.fseg:last-child { border-right: 0; }
.fseg:hover { color: var(--text); background: var(--panel-2); }
.fseg.on { color: var(--blue); background: var(--panel-2);
  box-shadow: inset 0 -2px 0 var(--blue); }
.fn { padding: 0 5px; border-radius: 999px; background: #0f131b; color: var(--muted);
  font-size: 10px; }
.fseg.on .fn { color: var(--blue); }
.fn.hot { background: #3a1717; color: #ff8c8c; }
.fform { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; margin: 0; }
.fsel { display: inline-flex; align-items: center; gap: 6px; }
.fsel select { background: var(--panel); border: 1px solid var(--line); color: var(--text);
  border-radius: 7px; padding: 3px 6px; font-size: 11.5px; }
.fclear { color: var(--blue); font-size: 11.5px; white-space: nowrap; text-decoration: none; }
.fclear:hover { text-decoration: underline; }
.layout { flex: 1 1 auto; min-height: 0; display: grid; gap: 14px; padding: 12px 16px 16px;
  /* Rail fits the longest row fully (owner 2026-10-03): the sub-line runs
     36 chars ("2026-10-02 · match 109917178 · 34:46", ~220px at 11px) beside
     ~260px of fixed chrome (thumb, tags, delete button), so 360px clipped it
     and 480px does not. Narrow screens still fall back to one column below. */
  grid-template-columns: minmax(280px, 480px) minmax(0, 1fr); }
.rail { min-height: 0; display: flex; flex-direction: column; gap: 9px; }
.rows { flex: 1 1 auto; min-height: 0; overflow-y: auto; display: flex;
  flex-direction: column; gap: 3px; padding-right: 4px; }
.row { display: grid; grid-template-columns: 62px minmax(0, 1fr) auto; align-items: center;
  gap: 8px; padding: 4px 30px 4px 6px; border: 1px solid transparent; border-radius: 8px;
  color: inherit; text-decoration: none; }
.row:hover { background: var(--panel); }
.row.active { background: var(--panel-2); border-color: var(--gold); }
/* The row's delete control (issue #20): a sibling of the row link, parked in
   the link's right padding so it never covers the tags. Hidden until hover so
   a dozen-row rail stays a rail, not a button panel. */
.rowwrap { position: relative; }
.rowdel { position: absolute; right: 5px; top: 50%; transform: translateY(-50%);
  margin: 0; }
.rowdel button { display: none; width: 22px; height: 22px; padding: 0;
  border-radius: 6px; border: 1px solid var(--line); background: var(--panel-2);
  color: var(--muted); font-size: 12px; line-height: 1; cursor: pointer; }
.rowwrap:hover .rowdel button { display: block; }
.rowdel button:hover { color: var(--red); border-color: #5c2323; }
/* A failed run's retry (owner 2026-10-29): left of the ✕, the same look,
   shown on hover like it - the row's padding makes room for both. */
.rowwrap.can-retry .row { padding-right: 56px; }
.rowretry { position: absolute; right: 31px; top: 50%; transform: translateY(-50%);
  display: none; width: 22px; height: 22px; padding: 0; border-radius: 6px;
  border: 1px solid var(--line); background: var(--panel-2); color: var(--muted);
  font-size: 13px; line-height: 1; cursor: pointer; }
.rowwrap:hover .rowretry { display: block; }
.rowretry:hover { color: var(--green); border-color: #245c2c; }
.row-thumb { width: 62px; height: 35px; border-radius: 5px; overflow: hidden;
  background: #05070a; border: 1px solid var(--line); display: block; }
.row-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.row-placeholder { display: block; width: 100%; height: 100%;
  background: repeating-linear-gradient(135deg, #161b26 0 6px, #10141d 6px 12px); }
.row-main { min-width: 0; }
.row-main b { display: block; font-size: 12.5px; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.row-main em { display: block; font-style: normal; color: var(--muted); font-size: 11px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* The Needs attention view: what is missing, in the row itself. */
.row-main em.row-why { color: var(--amber); white-space: normal;
  overflow-wrap: anywhere; line-height: 1.4; }
.row-tags { display: flex; gap: 3px; }
.tag { font-size: 9.5px; letter-spacing: .5px; padding: 1px 4px; border-radius: 4px;
  border: 1px solid var(--line); color: var(--muted); white-space: nowrap; }
.tag-final { color: var(--green); border-color: #245c2c; }
.tag-branded { color: var(--gold); border-color: #5c4b23; }
.tag-raw { color: var(--amber); border-color: #5c4620; }
.tag-failed { color: var(--red); border-color: #5c2323; }
.tag-warn { color: var(--amber); border-color: #5c4620; }
.tag-published { color: var(--green); border-color: #245c2c; }
.tag-ready { color: var(--blue); border-color: #274067; }
/* Scheduler states (owner 2026-10-25): scheduled sits between ready and
   published - amber, like the queue's waiting rows. */
.tag-scheduled { color: var(--amber); border-color: #6b5417; }
.tag-mode-ranked { color: var(--blue); border-color: #274067; }
.tag-mode-streetbrawl { color: var(--purple, #b48ead); border-color: #4a3b52; }
.stage { min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 9px; }
/* L1: the pane is ~1900px of content in a ~750px window (measured live), so it
   gets a sticky index generated from the sections that are actually present. */
/* The scroll lives on the inner .secnav-track, never on .secnav itself: a
   sticky flex ITEM that is also a scroll container resolves its own auto
   height to zero in Blink, which crushed every chip to a 6px empty border
   (owner 2026-10-04). The track is a plain block child and lays out fine. */
/* A.1: the keyboard sheet. A plain overlay, dismissed with Esc or ?. */
.sheet { position: fixed; inset: 0; z-index: 40; display: flex;
  align-items: center; justify-content: center; background: #05070acc; }
.sheet[hidden] { display: none; }
.sc-card { min-width: 340px; max-width: 520px; padding: 16px 18px;
  border: 1px solid var(--line); border-radius: 12px; background: var(--panel);
  box-shadow: 0 24px 60px #000a; }
.sc-card h3 { margin: 0 0 10px; font-size: 14px; }
.sc-row { display: grid; grid-template-columns: 92px 1fr; gap: 10px;
  align-items: baseline; padding: 3px 0; font-size: 12.5px; }
.sc-row kbd { font: 600 11.5px/1.6 ui-monospace, monospace; text-align: center;
  padding: 1px 6px; border: 1px solid var(--line); border-radius: 5px;
  background: var(--panel-2); color: var(--gold); }
.sc-row span { color: var(--muted); }
.sc-foot { margin: 12px 0 0; font-size: 11.5px; color: var(--muted); }
/* C.2: the numbers a take is judged by, in one line above the sections. */
.statstrip { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 4px; }
.statstrip .stat { font-size: 11.5px; padding: 3px 9px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--panel-2); color: var(--text);
  white-space: nowrap; }
.statstrip .stat-hero { border-color: #7dd3fc; color: #7dd3fc; }
.statstrip .stat-ok { color: var(--muted); }
.statstrip .stat-warn { border-color: var(--amber); color: var(--amber); }
.statstrip .stat-bad { border-color: var(--red); color: var(--red); }
.statstrip .stat-file { color: var(--muted); }
.statstrip .stat-game { color: var(--muted); }
.secnav { position: sticky; top: 0; z-index: 5;
          padding: 4px 0 6px;
          background: linear-gradient(180deg, #0a0d13 78%, #0a0d1300); }
.secnav-track { display: flex; gap: 4px; overflow-x: auto; }
.secnav-link { font-size: 11.5px; text-decoration: none; color: var(--muted);
               border: 1px solid var(--line); border-radius: 999px;
               padding: 2px 10px; white-space: nowrap; }
.secnav-link:hover { color: var(--text); }
.secnav-link.active { color: var(--gold); border-color: var(--gold); }
.stage .sec { scroll-margin-top: 40px; }
/* L6: the tail of a dead round, on the pane that used to say only "no video". */
.logtail h4 { margin: 12px 0 4px; font-size: 13px; }
.loglines { background: #070a10; border: 1px solid var(--line); border-radius: 8px;
            padding: 8px 10px; font: 11.5px/1.5 ui-monospace, monospace;
            color: #9fb2c4; max-height: 260px; overflow: auto;
            overflow-wrap: anywhere; }
.logbtns { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0 0; }
/* L7: box health without opening the dialog. Filled from /api/health.
   One check per line, name then detail: the strip used to be a wrapping flex
   row of loose items (dot, name, detail, dot, name, detail...), so five checks
   ran together as a single ribbon that was impossible to scan (owner
   2026-10-04). Each check is now its own grid row. */
.statusbar { display: flex; flex-direction: column; gap: 6px;
             padding: 7px 2px 2px; font-size: 11px; color: var(--muted);
             border-top: 1px solid var(--line); }
.sb-row { display: grid; grid-template-columns: 10px 1fr; gap: 1px 6px;
          align-items: start; }
.sb-row .sb-dot { grid-column: 1; grid-row: 1; font-size: 9px; line-height: 1.7; }
.sb-row b { grid-column: 2; grid-row: 1; font-weight: 600; color: var(--text); }
.sb-row .sb-detail { grid-column: 2; grid-row: 2; overflow-wrap: anywhere; }
.sb-row.sb-more, .sb-row.sb-busy { display: block; opacity: .85; }
/* The health strip collapses (owner 2026-10-07): on a short window five
   rows plus the detail lines eat the rail, so the rows live behind a
   show/hide toggle. Collapsed by default on short screens, open on tall
   ones; the choice persists per browser. */
.statusbar .sb-head { display: flex; align-items: center; gap: 6px;
  font-size: 11px; color: var(--muted); }
.statusbar .sb-head b { font-weight: 600; }
/* The release the box runs, next to the health title (owner 2026-10-20). */
.statusbar .sb-version { color: var(--muted); font-size: 10.5px; }
.statusbar .sb-toggle { margin-left: auto; background: none;
  border: 1px solid var(--line); border-radius: 6px; color: var(--muted);
  font: inherit; font-size: 10.5px; padding: 0 7px; cursor: pointer; }
.statusbar .sb-toggle:hover { color: var(--text); }
.statusbar.sb-collapsed .sb-rows { display: none; }
.statusbar b { color: var(--text); font-weight: 600; }
.statusbar .dot-pass { color: var(--green); }
.statusbar .dot-warn { color: var(--amber); }
.statusbar .dot-fail { color: var(--red); }
/* X2: the page is dark on dark; a visible focus ring is the only affordance. */
:focus-visible { outline: 2px solid #7dd3fc; outline-offset: 2px; }
.rail .row:focus-visible { outline-offset: -2px; }
.stage-head { display: flex; align-items: baseline; gap: 10px; }
.stage-head h2 { margin: 0; font-size: 17px; }
.stage-day { color: var(--muted); font-size: 12px; }
.badges { display: flex; flex-wrap: wrap; gap: 6px; }
.badge { font-size: 10.5px; letter-spacing: .6px; text-transform: uppercase;
  padding: 2px 6px; border-radius: 5px; border: 1px solid var(--line); color: var(--muted); }
.badge-match, .badge-time { color: var(--blue); border-color: #274067; }
.badge-warn { color: var(--amber); border-color: #5c4620; background: #241c10; }
.badge-failed { color: var(--red); border-color: #5c2323; background: #241010; }
.badge-pair, .badge-branded { color: var(--gold); border-color: #5c4b23; }
.badge-pub { color: var(--green); border-color: #245c2c; }
.badge-ready { color: var(--blue); border-color: #274067; }
.badge-sched { color: var(--amber); border-color: #6b5417; }
.kind-pills { display: flex; gap: 6px; }
.player { width: 100%; max-height: 60vh; background: #000; border-radius: 10px;
  border: 1px solid var(--line); display: block; }
/* K1 (owner 2026-10-05): the video is a keyboard target of its own - a click
   focuses it, and a focused ring says so, or the focus is invisible. */
.player:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.player-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  color: var(--muted); font-size: 12px; }
.asset-name { color: var(--muted); font-size: 12px; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
/* The designed card (issue #37): full 16:9, capped so the pane still shows the
   facts below it, and click-through for the untouched JPEG. */
.card-shot { margin: 0; max-width: 520px; display: flex; flex-direction: column; gap: 5px; }
.card-shot a.shot { display: block; line-height: 0; background: #05070a;
  border: 1px solid var(--line); border-radius: 10px; overflow: hidden; }
.card-shot img { width: 100%; height: auto; display: block; }
.card-shot figcaption { display: flex; align-items: center; gap: 8px; font-size: 12px;
  /* wrap, or the download button + size note (a nowrap pair) push the card
     row ~20px past the pane and the whole stage grows a scrollbar. */
  flex-wrap: wrap; min-width: 0; }
.card-shot figcaption .muted { white-space: nowrap; }  /* 1280x720 · size, on one line */
.facts { display: grid; gap: 3px 16px; margin: 0;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); }
.facts div { display: flex; gap: 6px; font-size: 12px; min-width: 0; }
.facts dt { color: var(--muted); }
.facts dd { margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A long value (a ten-item build, a run of roster names) must WRAP, not run off
   the pane: the nowrap above is what made the build line unreachable on a
   narrow window (owner 2026-10-03, "text off screen horizontally"). Short
   values the owner scans (a KDA, an encoder name) keep the single line. */
.facts dd.wrap { white-space: normal; overflow-wrap: anywhere; }
.matchmeta h4 { margin: 12px 0 4px; font-size: 13px; }
.matchmeta .roster-team { margin-top: 6px; }
.matchmeta .roster-team .muted { font-size: 11.5px; }
.matchmeta .patron-hiddenking { color: var(--amber); font-size: 12.5px; font-weight: 600; }
.matchmeta .patron-archmother { color: var(--blue); font-size: 12.5px; font-weight: 600; }
.matchmeta .roster-lines { font-size: 12px; line-height: 1.55; }
.card-row { display: flex; gap: 10px; flex-wrap: wrap; min-width: 0; }
/* min-width:0 lets the shots shrink below their 280px basis: when the progress
   panel squeezes the pane (body.panel-open), a flex item's default
   min-content size kept the row at 430px and the whole stage grew a
   horizontal scrollbar (found live, 2026-10-04). */
.card-row .card-shot { flex: 1 1 280px; max-width: 430px; min-width: 0; }
/* L2: the A/B shortlist - up to three cards, numbered in pick order.
   These rules were written with doubled braces, and this block is substituted
   into the page with str.replace (never .format), so the browser saw a broken
   declaration and dropped every one of them: the pick button had no styling at
   all and a picked card had no frame (owner 2026-10-04). Single braces. */
.card-shot.picked img { outline: 3px solid var(--gold); outline-offset: 2px;
                       box-shadow: 0 0 0 6px rgba(240, 201, 135, .14), 0 10px 24px rgba(0, 0, 0, .55); }
.card-shot.picked a.shot { border-color: var(--gold); }
.card-shot.picked figcaption { color: var(--gold); }
.pickbtn { background: var(--panel-2); border: 1px solid var(--line); color: var(--muted);
           border-radius: 6px; font-size: 11px; cursor: pointer; padding: 2px 9px; }
.pickbtn:hover { color: var(--text); border-color: var(--gold); }
.pickbtn.on { color: #191307; border-color: var(--gold); background: var(--gold);
              font-weight: 600; }
.picknote { font-size: 11.5px; color: var(--muted); margin: 10px 0 0; }
.card-row-label { font-size: 11px; font-weight: 600; letter-spacing: .08em;
                  text-transform: uppercase; color: #7dd3fc; margin: 10px 0 4px; }
/* The regenerate button and its live note live on the row's label line. The
   label is uppercase and letter-spaced; the button and the note it writes into
   are neither, so they opt out of that styling rather than reading as part of
   the label (owner 2026-10-05). */
.card-row-label .btn-refresh { text-transform: none; letter-spacing: 0;
                               font-weight: 600; margin-left: 4px; }
button.btn-refresh[disabled] { cursor: wait; opacity: .7; }
.refresh-note { font-size: 11px; color: var(--muted); letter-spacing: 0;
                text-transform: none; font-weight: 400; }

.matchmeta .tagbox { width: 100%; box-sizing: border-box; background: var(--panel);
  border: 1px solid var(--line); color: var(--muted); font-size: 11px;
  padding: 4px 6px; border-radius: 4px; }
.youtubeblock h4 { margin: 12px 0 4px; font-size: 13px; }
.youtubeblock .promptbox, .youtubeblock .jsonbox { width: 100%; box-sizing: border-box;
  background: var(--panel); border: 1px solid var(--line); color: var(--text);
  font-size: 11px; padding: 6px 8px; border-radius: 4px; white-space: pre-wrap;
  overflow-wrap: anywhere; }
/* The description is prose: pre-wrap so it wraps inside the pane, plus its own
   scroll cap so a long package cannot push the file list out of the pane. */
.youtubeout .descbox { max-height: 420px; overflow-y: auto; white-space: pre-wrap;
  overflow-wrap: anywhere; }
/* The prompt fold (details/summary): the package is the headline, the prompt
   that made it is reference material - collapsed by default so the pane reads
   as output first. */
.promptfold { margin-top: 10px; }
.promptfold > summary { cursor: pointer; color: var(--muted); font-size: 12px;
  padding: 3px 0; }
.promptfold > summary code { color: var(--text); }
.promptfold .promptbox { margin-top: 6px; }
/* Generate/regenerate row for the package. */
.genrow { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 4px 0 8px; }
.genrow .genstatus { font-size: 11.5px; }
button.genbtn { background: var(--panel-2); cursor: pointer; font: inherit;
  padding: 5px 12px; font-size: 12px; font-weight: 600; border-radius: 7px; }
button.genbtn:hover { border-color: var(--gold); color: var(--gold); }
button.genbtn[disabled] { cursor: wait; opacity: .7; }
.youtubeblock .jsonbox { max-height: 260px; overflow-y: auto; }
.youtubeout h4 { margin: 12px 0 4px; font-size: 13px; }
.youtubeout div { font-size: 12px; margin: 2px 0; }
.youtubeout dt { color: var(--muted); display: inline; margin-right: 6px; }
.youtubeout dd { display: inline; margin: 0; }
/* Copy buttons (owner 2026-10-03): the .btn look on a real button element.
   The exemption is load-bearing (owner 2026-10-05): "Start recording" is a
   <button class="btn btn-download">, and this reset's element+class weight
   beat .btn-download's class-only gold background - only the dark ink
   survived, on the dark panel, in the only theme there is. The reset still
   inherits the page font and shows a pointer; it may not paint the gold CTAs. */
button.btn { font: inherit; cursor: pointer; }
button.btn:not(.btn-download) { background: var(--panel-2); }
/* The tags line: label + full-width input + copy button in one row. The input
   used to sit inside an inline <dd>, where width:100% resolved against nothing
   and it rendered ~170px wide; flex lets it fill the pane instead. Shared by
   the match-facts tags and the YouTube output tags. */
.tagrow { display: flex; gap: 8px; align-items: center; }
.tagrow input.tagbox { flex: 1 1 auto; min-width: 0;
  box-sizing: border-box; background: var(--panel); border: 1px solid var(--line);
  color: var(--muted); font-size: 11px; padding: 4px 6px; border-radius: 4px; }
/* The description's copy button sits right-aligned above its <pre>. */
.youtubeout .copybar { display: flex; justify-content: flex-end; margin: 6px 0 0; }
/* Section headers in the published description (owner 2026-10-03): the emoji
   are baked into the stored text by youtube.format_description, so the row
   below only makes them bold - copy-paste keeps emoji and text unchanged. */
.youtubeout .jsonbox .yt-h { font-weight: 700; color: var(--text); }
.matchmeta .tagbox { width: 100%; box-sizing: border-box; background: var(--panel);
  border: 1px solid var(--line); color: var(--muted); font-size: 11px;
  padding: 4px 6px; border-radius: 4px; }
.files { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column;
  gap: 3px; }
.files li { display: flex; align-items: center; gap: 8px; background: var(--panel);
  border: 1px solid var(--line); border-radius: 8px; padding: 4px 8px; font-size: 12px; }
.muted { color: var(--muted); }
.btn { text-decoration: none; border-radius: 7px; padding: 3px 9px; font-size: 11.5px;
  border: 1px solid var(--line); color: var(--text); white-space: nowrap; }
.btn-download { margin-left: auto; color: #0b0d11; background: var(--gold);
  border-color: var(--gold); font-weight: 600; }
.detail { color: var(--muted); font-size: 12.5px; word-break: break-word; }
/* A dead round's reason: one line, full text folded under it (owner
   2026-10-04). The error is written for debugging and ran to 600 characters
   of wall in a paragraph on a pane that also has the log tail below it. */
.rawerr { margin: 8px 0 0; color: var(--muted); font-size: 11.5px; }
.rawerr summary { cursor: pointer; width: max-content; }
.rawerr-body { margin-top: 6px; padding: 8px 10px; background: #070a10;
  border: 1px solid var(--line); border-radius: 8px; max-height: 260px;
  overflow: auto; overflow-wrap: anywhere; white-space: pre-wrap;
  font: 11.5px/1.5 ui-monospace, monospace; color: #9fb2c4; }
.empty { color: var(--muted); background: var(--panel); border: 1px dashed var(--line);
  border-radius: 10px; padding: 14px; font-size: 12.5px; }
/* --- round launcher + progress panel (owner 2026-10-04) -------------------
   Launching a recording is a task of its own, so it lives on its own surface:
   a dialog that posts the parameters, and a right-side panel that keeps
   reporting the round's steps while the library behind it stays browsable. */
.runbtn { font: inherit; font-size: 12px; font-weight: 600; padding: 4px 12px;
  border-radius: 999px; cursor: pointer; white-space: nowrap;
  background: var(--panel-2); border: 1px solid var(--gold); color: var(--gold); }
.runbtn:hover { background: #241c10; }
.runbtn.active { background: var(--gold); border-color: var(--gold); color: #0b0d11; }
/* A recording in progress (owner 2026-10-05): the flat dot becomes a ring with
   one quadrant open, turning. A solid circle rotated around its own centre
   would look static, so the gap is what makes the motion readable. */
.runbtn .rbdot { display: inline-block; }
.rbspin { display: none; width: 10px; height: 10px; box-sizing: border-box;
  border-radius: 50%; border: 1.5px solid currentColor;
  border-top-color: transparent; vertical-align: -1px; }
.runbtn.busy .rbdot { display: none; }
.runbtn.busy .rbspin { display: inline-block; animation: turn .8s linear infinite; }
@keyframes turn { to { transform: rotate(360deg); } }
/* The view tabs (owner 2026-11-08): Library, Schedule and Queue in the
   chrome row, right of Record. The Queue tab is the old reopen chip (owner
   2026-10-05) - gold while a round runs, red when the last one stopped. */
.views { display: inline-flex; align-items: center; gap: 2px; padding: 2px; align-self: center;
  background: var(--bg); border: 1px solid var(--line); border-radius: 10px; }
.viewtab { display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  background: none; border: 1px solid transparent; color: var(--muted);
  border-radius: 8px; padding: 3px 11px; font: inherit; font-size: 12px; white-space: nowrap; }
.viewtab:hover { color: var(--text); }
.viewtab[aria-current="page"] { background: var(--panel-2); color: var(--text); font-weight: 600; }
.rpreopen { color: var(--gold); }
.rpreopen:hover { background: #241c10; }
/* A stopped round's chip is the one that opens the log, so it is red - beside
   Record, in the header, not floating on the library (owner 2026-10-05). */
.rpreopen.failed { border-color: var(--red); color: var(--red); }
.rpreopen.failed:hover { background: #2a1414; }
.rpreopen.busy .rbspin { display: inline-block; animation: turn .8s linear infinite; }
/* The Queue page: the library and its filters step aside while it is open -
   from the first paint (:has), not after the page's load event. */
body.panel-open .layout, body.panel-open .filters,
body:has(#rpanel:not([hidden])) .layout, body:has(#rpanel:not([hidden])) .filters { display: none; }
dialog.rundlg { width: min(680px, 94vw); background: var(--panel); color: var(--text);
  border: 1px solid var(--line); border-radius: 12px; padding: 16px 18px;
  box-shadow: 0 24px 60px #000a; }
dialog.rundlg::backdrop { background: #05070acc; }
dialog.rundlg h2 { margin: 0 0 2px; font-size: 15px; }
dialog.rundlg .hint { color: var(--muted); font-size: 12px; margin: 0 0 12px; }
.rundlg label { display: block; font-size: 11.5px; color: var(--muted); margin-bottom: 3px; }
.rundlg input, .rundlg textarea, .rundlg select { width: 100%; box-sizing: border-box;
  background: var(--panel-2); border: 1px solid var(--line); color: var(--text);
  border-radius: 7px; padding: 5px 8px; font: inherit; font-size: 12.5px; }
.rundlg textarea { min-height: 108px; resize: vertical; font-family: ui-monospace, monospace;
  font-size: 12px; }
.rgrid { display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }
.rgrid .wide { grid-column: 1 / -1; }
/* The link field and its answer: the paste lands here and both ids fill. */
.urlhint { display: block; font-size: 11.5px; color: var(--muted); margin-top: 4px; }
.urlhint.ok { color: var(--gold); }
.urlhint.bad { color: var(--red); }
.rundlg input.ok, .rundlg textarea.ok { border-color: var(--gold); }
/* A laid-out element stays visible when it carries [hidden]: display wins over
   the UA rule. Every hidden pane in this page is a grid, so the reset is
   global rather than per-element (the Queue tab showed both panes at once). */
[hidden] { display: none !important; }
.rtabs { display: flex; gap: 6px; margin: 0 0 12px; }
.rundlg footer { display: flex; align-items: center; gap: 10px; margin-top: 14px; }
.rundlg .err { color: var(--red); font-size: 12px; }
.rundlg .spacer { margin-left: auto; }
.rpanel { flex: 1 1 auto; min-height: 0; display: grid; gap: 10px; padding: 12px 16px 14px;
  grid-template-rows: auto auto minmax(0, 1fr); border-top: 1px solid var(--line); }
.rpanel[hidden] { display: none; }
.rpanel-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; min-width: 0; }
.rpanel-head h2 { margin: 0; font-size: 15px; }
.rpanel-head .state { font-size: 10.5px; letter-spacing: .6px;
  text-transform: uppercase; color: var(--gold); }
.rpanel .who { font-size: 12px; color: var(--muted); margin: 0; min-width: 0; }
.rpanel .who b { color: var(--text); }
.rpanel .rp-acts { margin-left: auto; display: flex; gap: 8px; align-items: center; }
.rpanel .close { background: var(--panel-2); border: 1px solid var(--line); color: var(--muted);
  border-radius: 7px; cursor: pointer; font: inherit; font-size: 12px; padding: 4px 9px; }
.rpanel .close:hover { color: var(--text); }
/* Jobs and the live log side by side, each scrolling on its own: the log
   used to be crushed to a 14 px line at the bottom of the overlay (a scroll
   box in a column that overflowed), and the jobs list was capped at 5 rows. */
.rp-body { min-height: 0; display: grid; gap: 14px;
  grid-template-columns: minmax(320px, 0.9fr) minmax(0, 1.4fr); }
.rp-jobs { min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 8px;
  padding-right: 4px; overscroll-behavior: contain; }
.rp-logcol { min-height: 0; display: grid; grid-template-rows: auto minmax(0, 1fr) auto; gap: 6px; }
.rp-loghead { font-size: 12px; color: var(--text); font-weight: 600; }
.rp-loghead span { color: var(--muted); font-weight: 400; font-size: 11px; }
.steps { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 4px 14px; }
.steps li { display: inline-flex; gap: 5px; align-items: baseline; font-size: 12px; color: var(--muted); }
.steps li .what b { font-weight: 500; }
/* Only the step in progress (or the one that failed) says what it is doing. */
.steps li:not(.active):not(.failed) .what em { display: none; }
.steps li .mark { text-align: center; font-size: 12px; color: var(--muted); }
.steps li .what em { font-style: normal; color: var(--muted); font-size: 11.5px; margin-left: 4px; }
.steps li.done { color: var(--text); }
.steps li.done .mark { color: var(--green); }
.steps li.active { color: var(--gold); }
.steps li.active .mark { color: var(--gold); animation: pulse 1.4s ease-in-out infinite; }
.steps li.failed { color: var(--red); }
.steps li.failed .mark { color: var(--red); }
/* A step the config skipped: the round passed it, nothing was produced. */
.steps li.skipped { color: var(--muted); }
.steps li.skipped .mark { color: var(--muted); }
.steps li.skipped .what b { color: var(--muted); }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
/* R2: the last rounds, folded under the panel. */
.rpanel .recent { margin-top: 4px; }
.rpanel .recent > summary { cursor: pointer; color: var(--muted); font-size: 11px;
  padding: 3px 0; }
.rpanel .recent ul { list-style: none; margin: 4px 0 0; padding: 0; }
.rpanel .recent li { display: flex; gap: 6px; align-items: center; font-size: 11px;
  padding: 2px 0; color: var(--muted); }
.rpanel .recent li b { color: var(--text); overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; max-width: 130px; }
.rpanel .recent li .mark { width: 10px; }
.rpanel .recent li.item-ok .mark { color: var(--green); }
.rpanel .recent li.item-failed .mark { color: var(--red); }
.rpanel .recent li .btn:first-of-type { margin-left: auto; }
.rpanel .loglink, .logtail a { color: var(--gold); }
.rpanel .items { margin: 0; padding-left: 18px; color: var(--muted); font-size: 11.5px;
  list-style: none;
  display: flex; flex-direction: column; gap: 2px; }
.rpanel .items .item-ok { color: var(--green); }
.rpanel .items .item-degraded { color: var(--amber); }
.rpanel .items .item-failed { color: var(--red); }
/* --- the live queue board (owner 2026-10-05) -----------------------------
   The batch's own state file rendered: every job's state, a waiting job's
   retry ETA and its error, and the box that appends games to the running
   queue. All of it is read back from queue.py, never guessed from the log. */
.qboard { display: flex; flex-direction: column; gap: 6px; }
.qboard .qhead { display: flex; flex-direction: column; gap: 6px; font-size: 12.5px; }
.qboard .qline { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.qboard .qhead .qsum { color: var(--muted); font-size: 12px; }
.qboard .qprog { height: 6px; border-radius: 99px; background: var(--panel-2); overflow: hidden; }
.qboard .qprog i { display: block; height: 100%; background: var(--green); transition: width .4s; }
.qboard .qbtns { display: flex; gap: 6px; flex-wrap: wrap; }
.qboard .qbtns .btn { font-size: 11.5px; padding: 3px 9px; }
.qsettings summary { cursor: pointer; font-size: 11.5px; color: var(--muted); }
.qboard .qwait { margin: 0; font-size: 11.5px; color: var(--amber);
  border: 1px solid #6b5417; border-radius: 8px; padding: 6px 8px;
  background: #1c1708; overflow-wrap: anywhere; }
.qboard .qdead { margin: 0; font-size: 11.5px; color: var(--red); }
/* The rows no longer scroll on their own (owner 2026-10-07 capped them at
   ~5 so the steps, log and buttons stayed on the overlay): the jobs column
   scrolls instead, beside a log that keeps its own height. */
.qitems { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 3px; }
/* A row is a grid that never breaks apart (owner 2026-11-08): mark, who,
   meta and ✕ on one line, the error on its own line INSIDE the row - it used
   to push the ✕ onto a line of its own, right above the next row's ✕. */
.qitem { font-size: 12px; color: var(--muted); display: grid; align-items: center;
  grid-template-columns: 14px minmax(0, 1fr) auto auto; gap: 2px 8px;
  padding: 5px 7px; border-radius: 8px; border: 1px solid transparent; background: var(--panel); }
.qitem .qmark { text-align: center; grid-row: 1; grid-column: 1; }
.qitem .qwho { color: var(--text); min-width: 0; grid-row: 1; grid-column: 2; }
.qitem .qwho b { display: block; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qitem .qwho small { display: block; color: var(--muted); font-size: 11px; overflow-wrap: anywhere; }
.qitem .qmeta { color: var(--muted); font-size: 11px; white-space: nowrap; grid-row: 1; grid-column: 3; }
.qitem.item-ok .qmark, .qitem.item-ok .qwho { color: var(--green); }
.qitem.item-degraded .qmark { color: var(--amber); }
.qitem.item-failed .qmark, .qitem.item-failed .qwho { color: var(--red); }
.qitem.item-waiting { border-color: #6b5417; }
.qitem.item-waiting .qmark { color: var(--amber); }
.qitem.item-running { border-color: var(--gold); }
.qitem.item-running .qmark { color: var(--gold); animation: pulse 1.4s ease-in-out infinite; }
.qitem .qerr { grid-row: 2; grid-column: 2 / -1; color: var(--muted); font-size: 11px; }
.qitem .qerr summary { cursor: pointer; }
.qitem .qerr div { overflow-wrap: anywhere; }
/* The per-row remove button (owner 2026-10-20): quiet until pointed at, so
   the failed mark's own ✕ and the button's ✕ never read as one thing. */
.qitem .qrm { grid-row: 1; grid-column: 4; font: inherit; font-size: 11px; line-height: 1;
  min-width: 28px; min-height: 26px; padding: 2px 7px; background: none; border: 1px solid var(--line);
  border-radius: 6px; color: var(--muted); cursor: pointer; }
.qitem .qrm:hover:not(:disabled) { color: var(--red); border-color: var(--red); }
.qitem .qrm:disabled { opacity: .5; cursor: default; }
/* A removal the live batch has not applied yet: the row stays until the
   batch's own state write drops it, struck through so it reads as leaving. */
.qitem.qpending { opacity: .55; }
.qitem.qpending .qwho { text-decoration: line-through; }
.qadd { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.qadd label { flex-basis: 100%; font-size: 11px; color: var(--muted); }
.qadd textarea { flex: 1 1 180px; background: var(--panel-2); color: var(--text);
  border: 1px solid var(--line); border-radius: 7px; font: inherit; font-size: 11.5px;
  padding: 5px 7px; resize: vertical; }
.qadd .qaddnote { font-size: 11px; color: var(--muted); }
/* The way back for a queue with nothing alive behind it (owner 2026-10-06). */
.qretry { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.qretry .qretrybox { display: inline-flex; align-items: center; gap: 5px;
  font-size: 11px; color: var(--muted); }
.qretry input[type="checkbox"] { width: auto; margin: 0; }
.qretry .qretrynote { font-size: 11px; color: var(--muted); }
/* The demo client-build gate (owner 2026-10-24): a setting row, and the
   skipped mark for jobs the gate declined before any game launch. */
.qmin { display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  margin-top: 6px; }
.qmin .qretrybox { font-size: 11px; color: var(--muted); }
.qmin .qmininput { width: 90px; background: var(--panel-2); color: var(--text);
  border: 1px solid var(--line); border-radius: 7px; font: inherit;
  font-size: 11.5px; padding: 4px 7px; }
.qmin .qminnote { flex-basis: 100%; font-size: 11px; color: var(--muted); }
.qitem.item-skipped .qmark, .qitem.item-skipped .qwho { color: var(--muted); }
.qitem.item-skipped { border-color: var(--line); }
.qstrip { margin: 0 0 8px; font-size: 11.5px; color: var(--muted);
  border: 1px solid #6b5417; border-radius: 8px; padding: 6px 8px; background: #1c1708;
  display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.qstrip.qstrip-dead { border-color: #5c2323; }
.rpanel pre.tail { margin: 0; background: #05070a; border: 1px solid var(--line);
  border-radius: 8px; padding: 8px 10px; font-size: 11.5px; line-height: 1.45; color: #b9c3d3;
  white-space: pre-wrap; overflow-wrap: anywhere; min-height: 120px; overflow-y: auto; }
.rpanel .note { font-size: 11.5px; color: var(--muted); }
.rpanel .note.failed { color: var(--red); }
.rpanel footer { display: flex; gap: 8px; align-items: center; }
/* --- readiness strip + publish tracker (owner 2026-10-04) ----------------
   One chip per artifact a round writes (raw, final, branded, card, frames,
   facts, YouTube package): green when it is on disk, dim when it is not, and
   the Repair button only exists when a missing chip is rebuildable. */
.readiness { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; }
.rchip { font-size: 10px; letter-spacing: .5px; text-transform: uppercase;
  padding: 2px 6px; border-radius: 5px; border: 1px solid #245c2c; color: var(--green);
  white-space: nowrap; }
/* Artifact progress (owner 2026-10-05): the chips name the missing pieces,
   this bar carries the ratio - in the pane and in every rail row, so each
   video's progress is readable without opening it. */
.rbar { display: grid; grid-template-columns: minmax(60px, 1fr) auto; gap: 7px;
  align-items: center; flex: 1 1 100%; }
.rbar .track { display: block; height: 5px; border-radius: 999px; overflow: hidden;
  background: var(--panel-2); border: 1px solid var(--line); }
.rbar i { display: block; height: 100%; background: var(--gold); }
.rbar.full i { background: var(--green); }
.rbar b { font-size: 10px; letter-spacing: .4px; color: var(--muted);
  white-space: nowrap; }
.rbar.mini { display: block; flex: none; margin-top: 3px; }
.rbar.mini .track { height: 3px; }
.repairbtn { border-color: var(--amber); color: var(--amber); }
.repairbtn:hover { background: #241c10; }
.publishbox h4 { margin: 12px 0 4px; font-size: 13px; }
.pubrow { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.pubstate { font-size: 10.5px; letter-spacing: .6px; padding: 2px 6px;
  border-radius: 5px; border: 1px solid var(--line); white-space: nowrap; }
.pub-published { color: var(--green); border-color: #245c2c; }
.pub-ready { color: var(--blue); border-color: #274067; }
.pub-todo { color: var(--muted); }
.puburl { flex: 1 1 220px; min-width: 0; background: var(--panel);
  border: 1px solid var(--line); color: var(--text); border-radius: 7px;
  padding: 4px 8px; font-size: 12px; }
.pubstatus { font-size: 11.5px; }

/* --- box preflight in the launcher (owner 2026-10-04) -------------------- */
.preflight { border: 1px solid var(--line); border-radius: 9px; padding: 8px 10px;
  margin: 0 0 12px; background: #10141c; }
.pf-head { display: flex; align-items: center; gap: 8px; font-size: 12px; }
.pf-head .pf-state { margin-left: auto; color: var(--muted); font-size: 11px; }
.pf-src { margin: 2px 0 0; font-size: 11px; color: var(--muted); }
.pf-head button { padding: 1px 8px; }
/* One info per line: the check's name, then its detail underneath, indented to
   the name (owner 2026-10-04 - name and detail shared one line, so a long
   detail wrapped inside a narrow column and twenty rows read as one wall). The
   list scrolls on its own: stacked, it is taller than the dialog, and the form
   below it must stay reachable. */
.pf-rows { display: flex; flex-direction: column; gap: 6px; margin-top: 6px;
  max-height: 340px; overflow-y: auto; padding-right: 4px;
  overscroll-behavior: contain; }
.pf-row { display: grid; grid-template-columns: 9px 1fr; gap: 1px 8px;
  font-size: 11.5px; align-items: start; }
.pf-row .dot { grid-column: 1; grid-row: 1; width: 8px; height: 8px; margin-top: 4px;
  border-radius: 50%; background: var(--muted); }
/* Scoped to .pf-row on purpose (owner 2026-10-05): the base rule above sets
   the grey with TWO classes, so the one-class .dot-fail/.dot-warn/.dot-pass
   lost to it and every dot in the preflight was grey - the same specificity
   trap that painted "Start recording" dark on dark. Equal weight, later in
   the sheet, so the status finally wins. */
.pf-row .dot-pass { background: var(--green); }
.pf-row .dot-warn { background: var(--amber); }
.pf-row .dot-fail { background: var(--red); }
.pf-row b { grid-column: 2; grid-row: 1; font-weight: 600; }
/* GC session history (P4, owner 2026-10-04): the cooldown line leads when a
   throttle is live; the session lines are one-liners so eight of them read
   as a timeline, not a wall. */
.pf-gc { margin-top: 8px; border-top: 1px solid var(--line); padding-top: 6px; }
.pf-gc-throttle { margin: 0 0 4px; font-size: 11.5px; font-weight: 600;
  color: var(--amber); }
.pf-gc-head { margin: 0 0 3px; font-size: 11px; color: var(--muted); }
.pf-gc-row { font-size: 11px; color: var(--muted); line-height: 1.45; }
.pf-gc-row.bad { color: var(--red); }
/* The row that needs attention should be findable without reading 20 lines. */
.pf-row.warn b { color: var(--amber); }
.pf-row.fail b { color: var(--red); }
.pf-row em { grid-column: 2; grid-row: 2; font-style: normal; color: var(--muted);
  overflow-wrap: anywhere; }
/* A row that offers to stop something gets a third column, so the button
   sits beside the name instead of being pushed under the detail text. */
.pf-row:has(.pf-reap) { grid-template-columns: 9px 1fr auto; }
.pf-reap { grid-column: 3; grid-row: 1 / span 2; align-self: center;
  white-space: nowrap; }
.pf-note { font-size: 11.5px; color: var(--muted); margin: 6px 0 0; }
.pf-note.failed { color: var(--amber); }

/* The repair pane: one checkbox row per rebuildable step. */
/* Creators table (owner 2026-10-12): a real table - the row list hid the
   alts behind one link and had no header. Main account bold gold, alts
   smaller and muted, every one its own link. */
.ctable { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.ctable th, .ctable td { text-align: left; padding: 6px 8px;
  border-bottom: 1px solid var(--line); vertical-align: top; }
.ctable thead th { color: var(--muted); font-size: 11px;
  text-transform: uppercase; letter-spacing: .5px; }
.ctable tbody th { font-weight: 600; white-space: nowrap; }
.ctable td a { color: var(--gold); }
.ctable td .cmain { font-weight: 700; }
.ctable td .calt { color: var(--muted); font-size: 11.5px; margin-right: 6px; }
.ctable td .calt:hover { color: var(--gold); }
/* Owner 2026-11-09: the subscriber column - a right-aligned number so the
   most-popular-first sort reads as a ranking down the page. */
.ctable td.csubs { text-align: right; white-space: nowrap;
  font-variant-numeric: tabular-nums; color: var(--muted); }
.rundlg label.rep-item { display: flex; align-items: center; gap: 8px; font-size: 12.5px;
  color: var(--text); padding: 5px 7px; background: var(--panel-2);
  border: 1px solid var(--line); border-radius: 8px; margin-bottom: 5px; }
.rundlg label.notify { display: inline-flex; align-items: center; gap: 5px;
  font-size: 11.5px; color: var(--muted); margin: 0; white-space: nowrap; }
.rundlg input[type="checkbox"] { width: auto; margin: 0; }
.rundlg .retryrow { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.rundlg .retryrow .urlhint { margin: 0; }
.rep-title { font-size: 13px; }

/* Narrow screens fall back to a single column so the player stays usable. */
@media (max-width: 900px) {
  body { height: auto; overflow: auto; }
  .layout { grid-template-columns: 1fr; }
  .rows { max-height: 42vh; }
  .player { max-height: none; }
  /* The Queue page stacks: jobs, then a log that keeps a real height. */
  .rp-body { grid-template-columns: 1fr; }
  .rp-jobs { overflow: visible; }
  .rpanel pre.tail { height: 45vh; }
}
/* The phone-only controls (owner 2026-11-08), out of the way on a desktop. */
.hdr-more-btn, .fbtn, .fdone, .phoneback, .youtubeout .descmore { display: none; }
.hdr-more { display: contents; }
.fbadge { background: var(--gold); color: #0b0d11; border-radius: 99px; font-size: 11px; font-weight: 700;
  padding: 0 6px; }
/* Phone widths (owner 2026-10-04): the rail collapses first, then the facts
   grid, then the dialog becomes a full-height sheet - the page is checked
   from a phone in the living room, and a 680px dialog on a 390px screen hid
   its own footer buttons. */
@media (max-width: 640px) {
  header.top { flex-wrap: wrap; }
  header.top .sub { margin-left: 0; flex-basis: 100%; white-space: normal; }
  .filters { flex-wrap: wrap; }
  .filters form { flex: 1 1 100%; }
  input.search { width: 100%; }
  .layout { padding: 10px; gap: 10px; }
  .rows { max-height: 34vh; }
  .row { grid-template-columns: 52px minmax(0, 1fr); padding-right: 30px; }
  .row-thumb { width: 52px; height: 30px; }
  .row-tags { grid-column: 2; display: flex; flex-wrap: wrap; }
  .stage-head { flex-wrap: wrap; }
  .facts { grid-template-columns: 1fr; }
  .player { border-radius: 8px; }
  .puburl { flex-basis: 100%; }
  dialog.rundlg { width: 100vw; max-width: 100vw; height: 100dvh; max-height: 100dvh;
    border-radius: 0; padding: 14px; }
  .rgrid { grid-template-columns: 1fr; }
  .rundlg footer { flex-wrap: wrap; }
  .rundlg footer #r-go { flex: 1 1 100%; }
  .rpanel { padding: 10px; }
  .rpanel .rp-acts { margin-left: 0; width: 100%; }
  .views { order: 5; }
  .viewtab { padding: 6px 10px; }
  .qitem .qrm { min-width: 40px; min-height: 36px; }
  /* List, then detail (owner 2026-11-08): the list is the page, a row opens
     its recording full screen, and ‹ Library goes back with the filters. */
  body[data-view="list"] .stage, body[data-view="list"] .layout > .empty[role=status] { display: none; }
  body[data-view="detail"] .rail, body[data-view="detail"] .filters { display: none; }
  body[data-view="detail"] .phoneback { display: inline-flex; }
  .rows { max-height: none; }
  .phoneback { align-items: center; min-height: 40px; color: var(--blue); text-decoration: none;
    font-size: 15px; margin: -4px 0 -6px; }
  /* One compact bar: the title and the counts step aside, Manual and Creators
     wait behind ⋯. */
  header.top h1, header.top .sub { display: none; }
  .hdr-more-btn { display: inline-flex; }
  .hdr-more { display: none; }
  .hdr-more.open { display: flex; flex-direction: column; gap: 6px; position: absolute; top: 48px; right: 10px;
    z-index: 70; background: var(--panel-2); border: 1px solid var(--line); border-radius: 12px; padding: 8px;
    box-shadow: 0 16px 40px #000b; }
  header.top { position: relative; align-items: center; }
  /* Filters in a sheet: the search stays in sight, the rest opens on a tap. */
  .filters .fgroup, .filters .fsel, .filters .fclear, .filters .fdone { display: none; }
  .filters .fbtn { display: inline-flex; align-items: center; gap: 6px; }
  .filters form { flex: 1 1 0; min-width: 0; order: -1; }
  .filters.open { position: fixed; left: 0; right: 0; bottom: 0; z-index: 60; max-height: 85dvh; overflow-y: auto;
    flex-wrap: wrap; gap: 12px; padding: 16px 14px calc(14px + env(safe-area-inset-bottom));
    background: var(--panel); border-top: 1px solid var(--line); border-radius: 16px 16px 0 0;
    box-shadow: 0 -20px 50px #000c; }
  .filters.open .fgroup, .filters.open .fsel, .filters.open .fclear { display: flex; flex-wrap: wrap; }
  .filters.open .fgroup { flex: 1 1 100%; min-width: 0; }
  .filters.open .fform { flex-wrap: wrap; }
  .filters.open input.search { flex: 1 1 100%; }
  .filters.open .fgroup .fseg { min-height: 38px; }
  .filters.open .fsel { flex: 1 1 100%; }
  .filters.open .fsel select { flex: 1; min-height: 40px; }
  .filters.open .fdone { display: inline-flex; flex: 1 1 100%; justify-content: center; min-height: 44px; }
  .filters.open .fbtn { display: none; }
  /* The schedule card is one line: the forecast and the next slot (failed
     and held uploads stay listed - they want attention). */
  #pubcard .publast, #pubcard .pubrecent { display: none; }
  #pub-upcoming li:not(:first-child) { display: none; }
  /* The Record dialog's box checks fold into their head line. */
  .preflight:not(.open) .pf-rows, .preflight:not(.open) .pf-src, .preflight:not(.open) .pf-gc,
  .preflight:not(.open) .pf-note { display: none; }
  .preflight .pf-head { cursor: pointer; min-height: 36px; }
  .preflight:not(.open) .pf-head::after { content: "▸ tap for the checks"; margin-left: auto; color: var(--muted);
    font-size: 11.5px; }
  .rundlg .hint { display: none; }
  .rundlg footer { position: sticky; bottom: -14px; background: var(--panel); padding: 10px 0; margin-top: 10px; }
  /* Tap targets of 36 px and more. */
  .btn, .secnav-link { min-height: 36px; display: inline-flex; align-items: center; }
  /* The recording page: the cards of a design are a swipe row, one card at a
     time, the A/B pick and the download on the card shown (they were six
     full-width cards, about 1,900 px, one under the other). */
  .card-row { flex-wrap: nowrap; overflow-x: auto; scroll-snap-type: x mandatory; gap: 10px;
    padding: 0 0 6px; scrollbar-width: none; }
  .card-row::-webkit-scrollbar { display: none; }
  .card-row .card-shot { flex: 0 0 86%; max-width: none; scroll-snap-align: center; }
  .card-shot figcaption .asset-name, .card-shot figcaption .card-detail { display: none; }
  .card-shot figcaption { flex-wrap: wrap; }
  .card-shot .pickbtn, .card-shot .btn-download { min-height: 40px; flex: 1 1 40%; justify-content: center;
    display: inline-flex; align-items: center; }
  /* Each title is a row that copies on a tap. */
  .youtubeout .ytrow { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 2px 10px;
    min-height: 50px; padding: 6px 10px; margin: 0 0 6px; border: 1px solid var(--line); border-radius: 10px;
    background: var(--panel); cursor: pointer; font-size: 14px; }
  .youtubeout .ytrow dt { display: block; grid-column: 1; font-size: 11.5px; }
  .youtubeout .ytrow dd { display: block; grid-column: 1; overflow-wrap: anywhere; }
  .youtubeout .ytrow .copybtn { grid-column: 2; grid-row: 1 / span 2; }
  /* The description is folded to a few lines, with Show all beside Copy
     description. */
  .youtubeout .copybar { gap: 8px; }
  .youtubeout .descmore { display: inline-flex; }
  .youtubeout .descbox:not(.open) { max-height: 8.5em; overflow: hidden;
    -webkit-mask-image: linear-gradient(#000 60%, transparent); mask-image: linear-gradient(#000 60%, transparent); }
  .youtubeout .descbox.open { max-height: none; overflow: visible; }
  .youtubeout .descbox.fits { -webkit-mask-image: none; mask-image: none; }
  .youtubeout .copybar:has(+ .descbox.fits) .descmore { display: none; }
}
/* The recordings keep their room in the rail (owner 2026-11-08: at 650 px the
   list shrank to nothing under the schedule card and box health, at 768 px to
   two rows): the card gives way before the list does, and lists three slots
   on a window shorter than 860 px. */
@media (min-width: 901px) {
  /* A zero basis: the list grows into the room left, so a short rail
     shrinks the card, never both by their content heights. */
  .rows { flex: 1 1 0; min-height: 170px; }
  .pubcard { flex: 0 1 auto; min-height: 0; overflow-y: auto; }
}
@media (min-width: 901px) and (max-height: 860px) {
  #pub-upcoming li:nth-child(n+4) { display: none; }
}

/* --- YouTube schedule card (issue #39) --------------------------------- */
/* The rail's compact schedule: upcoming 14:00/20:00 slots, pending/failed
   stock, recently published. Server-rendered; app.js refreshes the header
   every 30 s. Hidden entirely until the publisher daemon writes its state
   file (the feature not being set up is not a warning worth screen). */
.pubcard { margin: 0 0 10px; padding: 10px 12px; border: 1px solid var(--line);
  border-radius: 10px; background: var(--panel-2); }
.pubcard h2 { font-size: 12px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted); display: flex; align-items: baseline; gap: 8px;
  margin: 0 0 4px; }
.pubstate { font-weight: 400; text-transform: none; letter-spacing: 0;
  color: var(--muted); font-size: 10.5px; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.publast { font-size: 10.5px; color: var(--muted); margin: 0 0 6px; }
.pubquota, .pubnote { font-size: 10.5px; color: var(--amber); margin: 0 0 4px; }
.pubrows { list-style: none; margin: 0 0 6px; padding: 0; }
.pubrow { display: flex; align-items: center; gap: 7px; font-size: 11.5px;
  padding: 3px 0; border-bottom: 1px solid color-mix(in srgb,
  var(--line) 55%, transparent); }
.pubrow:last-child { border-bottom: 0; }
.pubslot { color: var(--muted); font-variant-numeric: tabular-nums;
  white-space: nowrap; }
.pubwho { color: var(--text); flex: 1; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.pub-scheduled .pubslot, .pub-published .pubslot { color: var(--green); }
.pub-failed .pubwho { color: var(--red); }
.pub-held .pubwho { color: var(--muted); }
.publink { color: var(--muted); text-decoration: none; font-size: 10.5px; white-space: nowrap; }
.publink:hover { color: var(--text); }
.pubact { font: inherit; font-size: 10.5px; line-height: 1; padding: 2px 7px; min-height: 22px;
  background: none; border: 1px solid var(--line); border-radius: 6px;
  color: var(--muted); cursor: pointer; white-space: nowrap; }
.pubact:hover:not(:disabled) { color: var(--text); border-color: var(--text); }
.pubact:disabled { opacity: .6; cursor: default; }
/* The public publish sits apart from the YouTube link and asks first. */
.pubnow { margin-left: 6px; }
@media (max-width: 640px) {
  .pubact { min-height: 36px; padding: 6px 10px; font-size: 12px; }
  .publink { padding: 8px 4px; font-size: 12px; }
}
/* uiConfirm: the question before anything that cannot be taken back. */
dialog.confirmdlg { width: min(420px, 92vw); background: var(--panel); color: var(--text);
  border: 1px solid var(--line); border-radius: 14px; padding: 18px 20px; }
dialog.confirmdlg::backdrop { background: #05070acc; }
dialog.confirmdlg h3 { margin: 0 0 6px; font-size: 16px; }
dialog.confirmdlg p { margin: 0 0 16px; color: var(--muted); font-size: 13px; }
.confirmrow { display: flex; justify-content: flex-end; gap: 8px; }
.confirmrow .btn { min-height: 36px; }
.pubempty { font-size: 11px; color: var(--muted); padding: 3px 0; }
.pubrecent summary { font-size: 10.5px; color: var(--muted);
  cursor: pointer; margin: 2px 0; }

/* The schedule card's upcoming list (owner 2026-10-27): six rows visible,
   everything beyond scrolls - the week's 14 bookings stay one glance wide
   without eating the rail. Explicit scrollbar per the owner's standing
   scroll preference. */
#pub-upcoming { max-height: 178px; overflow-y: auto; scrollbar-gutter: stable; }
#pub-upcoming::-webkit-scrollbar { width: 8px; }
#pub-upcoming::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--line) 70%, var(--muted));
  border-radius: 4px; }
#pub-upcoming::-webkit-scrollbar-track { background: transparent; }

/* The coverage forecast line (owner 2026-10-27): scheduled vs stock vs how
   far the automation runs. Sits under the last-pass line, above the rows. */
.pubforecast { font-size: 11.5px; color: var(--text); margin: 0 0 6px;
  padding: 5px 8px; border: 1px solid var(--line); border-radius: 7px;
  background: color-mix(in srgb, var(--panel-2) 60%, transparent); }
.pubforecast b { color: var(--gold); }
/* Each segment stays whole; the line wraps between them so the red
   repeat warning is never cut off by an ellipsis on a narrow rail. */
.pubforecast > span { white-space: nowrap; }
.pubforecast .pubwarn { color: var(--red); }
/* The schedule editor (issue #146): 14 days x 2 slots, the stock beside. */
/* --- The schedule editor, design A (owner 2026-11-08) ----------------------
   A full-height dialog: the header (title, Arrange / Fill days, the
   publisher's activity, one ×), a line that says what to do next, then the
   calendar and the ready stock side by side, each scrolling on its own. The
   body row is sized (minmax(0, 1fr)): an auto row let both columns spill over
   the old footer instead of scrolling. On a phone the calendar is one column
   and the stock a bottom sheet. */
dialog.scheddlg { width: min(1180px, 96vw); height: calc(100dvh - 32px); max-height: none; padding: 0;
  background: var(--panel); color: var(--text); border: 1px solid var(--line); border-radius: 14px;
  box-shadow: 0 30px 80px #000c; overflow: hidden; }
dialog.scheddlg[open] { display: grid; grid-template-rows: auto auto minmax(0, 1fr) auto; }
dialog.scheddlg::backdrop { background: #05070acc; }
.sched-head { display: flex; align-items: center; gap: 12px; padding: 12px 14px 10px 18px;
  border-bottom: 1px solid var(--line); min-width: 0; position: relative; }
.sched-head h2 { margin: 0; font-size: 16px; white-space: nowrap; }
.sched-seg { display: inline-flex; background: var(--bg); border: 1px solid var(--line); border-radius: 10px; padding: 2px; }
.sched-seg button { background: none; border: 0; padding: 5px 14px; border-radius: 8px; color: var(--muted);
  cursor: pointer; font: inherit; font-size: 13px; white-space: nowrap; }
.sched-seg button[aria-pressed="true"] { background: var(--panel-2); color: var(--text); font-weight: 600; }
.sched-activity { margin-left: auto; display: inline-flex; align-items: center; gap: 8px; font: inherit; font-size: 12px;
  color: var(--muted); border: 1px solid var(--line); border-radius: 999px; padding: 4px 11px; background: var(--bg);
  cursor: pointer; min-width: 0; max-width: 46%; }
.sched-activity span:last-child { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sched-activity .rbspin { display: none; }
.sched-activity.busy { color: var(--gold); border-color: #6b5a2c; }
.sched-activity.busy .rbspin { display: inline-block; animation: turn .8s linear infinite; }
.sched-activity.bad { color: var(--red); border-color: #5c2323; }
.sched-x { font: inherit; font-size: 20px; line-height: 1; width: 34px; height: 34px; flex: none;
  border: 1px solid var(--line); border-radius: 9px; background: var(--panel-2); color: var(--text); cursor: pointer; }
.sched-x:hover { border-color: var(--text); }
.sched-acts { position: absolute; right: 60px; top: 54px; z-index: 5; width: min(440px, 90vw); max-height: 50vh;
  overflow-y: auto; background: var(--panel-2); border: 1px solid var(--line); border-radius: 12px; padding: 8px;
  box-shadow: 0 16px 40px #000b; font-size: 12.5px; }
.sched-acts div { display: flex; gap: 8px; align-items: center; padding: 5px 6px; }
.sched-acts .ok { color: var(--green); }
.sched-acts .bad { color: var(--red); }
.sched-acts .rbspin { display: inline-block; animation: turn .8s linear infinite; }
.sched-hint { display: flex; align-items: center; gap: 10px; padding: 8px 18px; min-height: 48px; font-size: 13px;
  border-bottom: 1px solid var(--line); background: #10141c; flex-wrap: wrap; }
.sched-hint .who { flex: 1; min-width: 0; }
.sched-hint .bad { color: var(--red); }
.sched-hint .sched-thumb { width: 44px; }
.sched-body { display: grid; grid-template-columns: minmax(0, 1.75fr) minmax(280px, 1fr); min-height: 0; }
.sched-cal { overflow-y: auto; padding: 10px 14px 18px 18px; min-height: 0; }
.sched-stockcol { border-left: 1px solid var(--line); display: grid; grid-template-rows: auto minmax(0, 1fr);
  min-height: 0; background: #11151d; }
.sched-tools { padding: 10px 12px; display: flex; flex-direction: column; gap: 8px; border-bottom: 1px solid var(--line); }
.sched-tools .r { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.sched-tools h3 { margin: 0; font-size: 12px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); flex: 1; }
.sched-tools .count { font-size: 11px; color: var(--muted); border: 1px solid var(--line); border-radius: 99px; padding: 0 7px; }
.sched-tools label { font-size: 12px; color: var(--muted); }
.sched-tools .btn.primary { flex: 1; }
.sched-in { background: var(--bg); color: var(--text); border: 1px solid var(--line); border-radius: 8px; padding: 6px 9px;
  font: inherit; font-size: 13px; min-width: 0; }
#sched-q { flex: 1; }
.sched-list { list-style: none; margin: 0; padding: 8px; overflow-y: auto; min-height: 0; display: flex;
  flex-direction: column; gap: 4px; }
.sched-item { display: grid; grid-template-columns: 64px minmax(0, 1fr) auto; gap: 9px; align-items: center; padding: 5px;
  border-radius: 9px; border: 1px solid transparent; cursor: pointer; background: none; text-align: left; width: 100%;
  font: inherit; color: var(--text); }
.sched-item:hover { background: var(--panel-2); }
.sched-item .w { min-width: 0; }
.sched-item b { display: block; font-size: 13px; }
.sched-item small { display: block; color: var(--muted); font-size: 11.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sched-item[aria-pressed="true"] { border-color: var(--gold); background: #1c1708; }
.sched-item .ord { width: 26px; height: 26px; border-radius: 50%; border: 1.5px solid var(--line); display: grid;
  place-items: center; font-size: 12px; color: var(--muted); }
.sched-item.ticked .ord { background: var(--gold); border-color: var(--gold); color: #1a1406; font-weight: 700; }
.sched-item.sending { opacity: .5; cursor: default; }
.sched-thumb { width: 64px; aspect-ratio: 16 / 9; border-radius: 5px; object-fit: cover; background: var(--panel-2);
  display: block; flex: none; }
.sched-day { display: grid; grid-template-columns: 86px minmax(0, 1fr) minmax(0, 1fr); gap: 8px; margin-bottom: 6px; }
.sched-day .d { font-size: 12px; color: var(--muted); align-self: center; line-height: 1.2; }
.sched-day .d b { display: block; color: var(--text); font-size: 13px; }
.sched-day.today .d b { color: var(--gold); }
.sched-cell { position: relative; display: grid; grid-template-columns: 48px minmax(0, 1fr) auto; gap: 8px;
  align-items: center; min-height: 46px; padding: 4px 8px 4px 4px; border-radius: 10px; border: 1px solid var(--line);
  background: var(--panel-2); cursor: pointer; text-align: left; width: 100%; color: var(--text); font: inherit; }
.sched-cell .sched-thumb { width: 48px; }
.sched-cell .hr { position: absolute; top: 2px; right: 6px; font-size: 10.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.sched-cell .w { min-width: 0; }
.sched-cell .w b { display: block; font-size: 12.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sched-cell .w small { display: block; font-size: 11.5px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sched-cell .bd { display: flex; flex-direction: column; gap: 2px; align-items: flex-end; padding-top: 10px; }
.sched-cell .pin { font-size: 9.5px; text-transform: uppercase; letter-spacing: .05em; color: var(--gold);
  border: 1px solid #6b5a2c; border-radius: 4px; padding: 0 4px; white-space: nowrap; }
.sched-cell .warn, .sched-cell .heldtag, .sched-cell .failtag { font-size: 10.5px; white-space: nowrap; }
.sched-cell .warn { color: var(--amber); }
.sched-cell .failtag { color: var(--red); }
.sched-cell .heldtag { color: var(--muted); }
.sched-cell .rbspin { display: inline-block; animation: turn .8s linear infinite; }
.sched-cell:hover { border-color: var(--muted); }
.sched-cell.st-empty, .sched-cell.st-studio, .sched-cell.st-locked:not(:has(.sched-thumb)) {
  grid-template-columns: 1fr; padding-left: 12px; }
.sched-cell.st-empty { border-style: dashed; background: none; color: var(--muted); }
.sched-cell.st-past { grid-template-columns: 1fr; padding-left: 12px; border-style: dashed; background: none;
  color: var(--muted); opacity: .4; cursor: default; }
@media (max-width: 760px) { .sched-cell.st-past { display: none; } }
.sched-cell.st-studio, .sched-cell.st-locked, .sched-cell.st-held { opacity: .55; cursor: not-allowed; }
.sched-cell.st-studio { background: repeating-linear-gradient(135deg, #1b2130, #1b2130 6px, #171c27 6px, #171c27 12px); }
.sched-cell.picked { border-color: var(--gold); box-shadow: 0 0 0 1px var(--gold) inset; background: #1c1708; }
.sched-cell.target { border-color: var(--blue); box-shadow: 0 0 0 1px var(--blue) inset; }
.sched-cell.sending { border-color: #6b5a2c; }
.sched-cell .ghost { position: absolute; inset: 0; border-radius: 10px; border: 2px dashed var(--gold); background: #1c1708e6;
  display: flex; align-items: center; gap: 8px; padding: 0 8px; font-size: 12px; pointer-events: none; overflow: hidden; }
.sched-cell .ghost b { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sched-cell .ghost.bad { border-color: var(--red); background: #1f0d0de6; color: #ff9b9b; }
.sched-cell .ghost .n { width: 22px; height: 22px; border-radius: 50%; background: var(--gold); color: #1a1406;
  display: grid; place-items: center; font-weight: 700; flex: none; }
.sched-choice { margin: 2px 0 8px 94px; padding: 10px 12px; background: #0f1a2b; border: 1px solid #28466e; border-radius: 12px;
  display: flex; flex-direction: column; gap: 8px; font-size: 13px; }
.sched-choice .btns { display: flex; gap: 8px; flex-wrap: wrap; }
.sched-choice p { margin: 0; color: var(--muted); font-size: 12px; }
.sched-choice .warn { color: var(--amber); }
.scheddlg .btn.primary { background: var(--gold); color: #1a1406; border-color: var(--gold); font-weight: 600; }
.scheddlg .btn.ghost { background: none; }
.scheddlg .btn.sm { font-size: 12px; padding: 3px 9px; }
.sched-sheetbar, .scheddlg .sheetonly { display: none; }
.pubedit { float: right; font: inherit; font-size: 10.5px; text-transform: none; letter-spacing: 0;
  padding: 2px 8px; border: 1px solid var(--line); border-radius: 6px; background: var(--panel-2);
  color: var(--text); cursor: pointer; }
@media (max-width: 760px) {
  dialog.scheddlg { width: 100vw; height: 100dvh; max-width: none; border-radius: 0; border: 0; }
  .sched-head { flex-wrap: wrap; padding: 10px 10px 8px 14px; }
  .sched-head h2 { flex: 1; font-size: 15px; }
  .sched-head .sched-seg { order: 3; flex: 1 1 100%; }
  .sched-head .sched-seg button { flex: 1; padding: 8px; }
  .sched-activity { order: 4; max-width: none; flex: 1 1 100%; margin: 0; }
  .sched-body { grid-template-columns: 1fr; }
  .sched-cal { padding: 8px 10px 20px; }
  .sched-day { grid-template-columns: 1fr; gap: 4px; margin-bottom: 12px; }
  .sched-day .d { display: flex; gap: 6px; align-items: baseline; }
  .sched-day .d b { display: inline; }
  .sched-cell { min-height: 54px; }
  .sched-choice { margin: 0 0 10px; }
  .sched-stockcol { position: fixed; left: 0; right: 0; bottom: 0; height: 78dvh; z-index: 10; border: 1px solid var(--line);
    border-radius: 16px 16px 0 0; transform: translateY(105%); transition: transform .2s; box-shadow: 0 -20px 50px #000c; }
  .sched-stockcol.open { transform: none; }
  .scheddlg .sheetonly { display: inline-block; }
  .sched-sheetbar { display: flex; gap: 8px; padding: 10px; border-top: 1px solid var(--line); background: var(--panel); }
  .sched-sheetbar .btn { flex: 1; padding: 10px; }
}
/* --- Channel posts (2026-11-08) ----------------------------------------------
   The schedule editor's dialog with two columns: the new post on the left
   (text, the picture strip in posting order, the Paris date and time), the
   posts the publisher knows on the right. One column on a phone. */
dialog.postsdlg[open] { grid-template-rows: auto auto minmax(0, 1fr); }
/* No band while there is nothing to say. */
.postsdlg .sched-hint:empty { min-height: 0; padding: 0; border-bottom: 0; }
.posts-who { color: var(--muted); font-size: 12.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  margin-right: auto; }
.posts-body { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(280px, 1fr); min-height: 0; }
.posts-new { overflow-y: auto; padding: 12px 18px 18px; display: flex; flex-direction: column; gap: 6px; min-height: 0; }
.posts-lab { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; margin-top: 8px; }
.posts-lab small { text-transform: none; letter-spacing: 0; }
.posts-text { min-height: 190px; resize: vertical; line-height: 1.45; font-size: 14px; }
.posts-count { align-self: flex-end; font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }
.posts-strip { display: flex; gap: 8px; flex-wrap: wrap; }
.posts-pic { position: relative; margin: 0; width: 120px; height: 120px; border-radius: 9px; overflow: hidden;
  border: 1px solid var(--line); background: var(--bg); }
.posts-pic img { width: 100%; height: 100%; object-fit: contain; display: block; }
.posts-pic .n { position: absolute; top: 4px; left: 4px; width: 22px; height: 22px; border-radius: 50%;
  background: var(--gold); color: #1a1406; font-weight: 700; font-size: 12px; display: grid; place-items: center; }
.posts-pic .acts { position: absolute; left: 0; right: 0; bottom: 0; display: flex; justify-content: space-between;
  background: #0b0d11d9; }
.posts-pic .acts button { flex: 1; background: none; border: 0; color: var(--text); font: inherit; font-size: 16px;
  padding: 3px 0; cursor: pointer; }
.posts-pic .acts button:disabled { color: #3a4253; cursor: default; }
.posts-pic .acts button:not(:disabled):hover { color: var(--gold); }
.posts-add { width: 120px; height: 120px; border-radius: 9px; border: 1px dashed var(--line); background: none;
  color: var(--muted); font: inherit; font-size: 13px; cursor: pointer; }
.posts-add:hover { border-color: var(--gold); color: var(--gold); }
.posts-when { display: flex; gap: 8px; flex-wrap: wrap; }
.posts-frame { display: flex; gap: 8px; flex-wrap: wrap; }
#posts-src { flex: 1 1 220px; min-width: 0; }
#posts-at:disabled { opacity: .45; }
.posts-go { display: flex; gap: 8px; justify-content: flex-end; margin-top: 14px; }
.posts-col { border-left: 1px solid var(--line); background: #11151d; display: grid; grid-template-rows: auto minmax(0, 1fr);
  min-height: 0; }
.posts-col h3 { margin: 0; padding: 12px 14px 8px; font-size: 12px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted); border-bottom: 1px solid var(--line); }
.posts-list { list-style: none; margin: 0; padding: 8px; overflow-y: auto; min-height: 0; display: flex;
  flex-direction: column; gap: 6px; }
.posts-row { border: 1px solid var(--line); border-radius: 10px; background: var(--panel-2); padding: 9px 11px; font-size: 12.5px; }
.posts-row b { display: block; font-size: 13px; }
.posts-row b small { color: var(--muted); font-weight: 400; }
.posts-row .st { display: inline-flex; gap: 6px; align-items: center; font-size: 11.5px; color: var(--muted); }
.posts-row .st .rbspin { display: inline-block; animation: turn .8s linear infinite; }
.posts-row.st-scheduled .st { color: var(--green); }
.posts-row.st-failed { border-color: #5c2323; }
.posts-row.st-failed .st, .posts-row .err { color: var(--red); }
.posts-row.st-queued .st, .posts-row.st-scheduling .st, .posts-row.st-sending .st { color: var(--gold); }
.posts-row .body { margin: 6px 0 0; white-space: pre-line; color: var(--text); display: -webkit-box; -webkit-line-clamp: 4;
  -webkit-box-orient: vertical; overflow: hidden; }
.posts-row .thumbs { display: flex; gap: 4px; margin-top: 6px; flex-wrap: wrap; }
.posts-row .thumbs img { width: 52px; height: 52px; object-fit: cover; border-radius: 5px; background: var(--bg); }
.posts-row .err { margin: 6px 0 0; font-size: 11.5px; }
.posts-row .acts { display: flex; gap: 6px; margin-top: 8px; }
.posts-empty { color: var(--muted); font-size: 12.5px; padding: 10px 6px; }
@media (max-width: 760px) {
  .posts-who { display: none; }
  /* One scrolling column; each part as tall as its content. */
  .posts-body { grid-template-columns: 1fr; grid-template-rows: auto auto; align-content: start; overflow-y: auto; }
  .posts-new, .posts-col { min-height: auto; }
  .posts-new, .posts-list { overflow: visible; }
  .posts-col { border-left: 0; border-top: 1px solid var(--line); }
  .posts-pic, .posts-add { width: calc(33.3% - 6px); height: auto; aspect-ratio: 1; }
}
