/*
  Bacdum dashboard.

  Written fresh. The previous file had been patched repeatedly as the website design changed, and the
  patches stopped agreeing with each other: stream blocks separated by a hairline so faint it was
  invisible on near black, controls that had lost their edges, and states that no longer read as states.
  Rewriting was cheaper and safer than another round of corrections.

  ── What this shares with the website ────────────────────────────────────────────

  The same near black surface, the same two self hosted typefaces, the same small box language with one
  fill and one border, the same working sized buttons, a visible hover on everything, and YouTube's dark
  red for text selection.

  ── Where it deliberately differs, and why ───────────────────────────────────────

  The marketing site is flat: no containers, separation by space alone. That is right for reading and
  wrong for operating. A tool has dense, interactive controls that have to be found and grouped at a
  glance, so here each stream is a real bordered panel with a header strip, and every control has an
  edge. Space alone cannot group a stream key, a status, an upload target and a duration picker.

  Two other differences, both flagged rather than slipped in:

  STATUS CARRIES COLOUR. Three low chroma tints for live, warning and failed. An operator scanning ten
  streams has to find the broken one instantly, and lightness alone cannot separate "live" from
  "failed" fast enough to be safe. Each is always paired with a text label, so colour is never the only
  carrier. Ratios are measured, listed at the token, and none is used on layout or on a button.

  MOTION EXISTS WHERE IT REPORTS SOMETHING. A spinner means work is happening, a bar means bytes are
  moving, the offline dot pulses because a dropped connection must be noticed. Nothing animates on
  hover or on load, and all of it stops under prefers-reduced-motion.
*/

@font-face {
  font-family: "Inter";
  src: url("/fonts/inter.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "JetBrains Mono";
  src: url("/fonts/mono.woff2") format("woff2");
  font-weight: 100 800;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "InterFallback";
  src: local("Helvetica Neue"), local("Arial"), local("Liberation Sans");
  size-adjust: 107.12%;
  ascent-override: 90.44%;
  descent-override: 22.52%;
  line-gap-override: 0%;
}

:root {
  --page: #08090b;
  --panel: #0c0e12;
  --fill: #12151a;
  --border: #2a2f38;
  --line: #1e232a;

  --ink: #ffffff;
  --body: #aeb3bd;
  /*
    Brighter than the website's equivalent grey on purpose. Here it labels controls rather than
    decorating prose, and a control label that is hard to read is a usability problem rather than a
    stylistic one. Measured: 6.09 on the panel, 5.77 on the control fill.
  */
  --muted: #8b919b;

  /*
    State tints, lit for a dark surface. Measured against the panel they sit on: live 8.35,
    warn 7.60, bad 6.80. Against their own soft fills: live 7.59, warn 6.92, bad 6.32.
  */
  --live: #5cbd86;
  --live-soft: #0e1c16;
  --warn: #ce9942;
  --warn-soft: #1e1810;
  --bad: #e8796f;
  --bad-soft: #221312;

  --sans: "Inter", "InterFallback", -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif;
  --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

* { box-sizing: border-box; }
/*
  hidden must actually hide. This one line is not defensive tidiness, it is the fix for a bug that shipped:
  the sign-in dialog and all four of its forms were visible at once, stacked on top of each other and on top
  of the dashboard, and signing in did not make them go away.

  Why: the hidden attribute works by applying display:none from the browser's own stylesheet, and ANY author
  rule that sets display outranks a browser default. `.gate { display: grid }`, `.gate-step { display: grid }`
  and `.account { display: flex }` therefore each quietly cancelled hidden on their own element.

  !important rather than relying on where this sits in the file. `[hidden]` and `.gate` have identical
  specificity, so without it the winner would be whichever appears later, and this would break again the
  next time somebody adds a rule below. The whole point is that it cannot be outranked by accident.
*/
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--page);
  color: var(--body);
  color-scheme: dark;
  font-family: var(--sans);
  font-size: 14.5px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  padding-bottom: 56px;
}

h2 { font-size: 15px; margin: 0; color: var(--ink); font-weight: 660; letter-spacing: -0.015em; }
h3 {
  font-family: var(--mono); font-size: 10.5px; font-weight: 500;
  text-transform: uppercase; letter-spacing: 0.14em; color: var(--muted); margin: 22px 0 9px;
}
small { font-size: 12.5px; color: var(--muted); }

:focus-visible { outline: 1px solid var(--ink); outline-offset: 2px; }

/*
  Selection in YouTube's dark red, matching the site. #cc0000 not #ff0000: white on the pure red measures
  4.00 to 1 and fails, on this it is 5.89.

  The second rule is the WebKit selection gap fix, same as the site. WebKit fills the space between
  selected line boxes out to each block's edges using that block's selection background, so a selection
  spanning blocks paints filled rectangles over empty space. Blink paints only the text. Suppressing the
  fill on elements that hold no text of their own makes both behave the same.
*/
::selection { background: #cc0000; color: #ffffff; }
::-moz-selection { background: #cc0000; color: #ffffff; }

html::selection, body::selection, main::selection, section::selection, div::selection,
header::selection, footer::selection, nav::selection, ul::selection, ol::selection,
form::selection, table::selection, thead::selection, tbody::selection, tr::selection,
label::selection, fieldset::selection {
  background: transparent;
}

/* ── Top bar ─────────────────────────────────────────────────────────────────── */

.topbar { background: var(--page); border-bottom: 1px solid var(--line); position: sticky; top: 0; z-index: 20; }
.topbar-in {
  max-width: 940px; margin: 0 auto; padding: 0 18px;
  height: 54px; display: flex; align-items: center; gap: 14px;
}
.brand {
  display: inline-flex; align-items: center; gap: 8px;
  color: var(--ink); font-size: 15px; font-weight: 740; letter-spacing: -0.018em;
}
/* The inline logo. Needs the block so it does not sit on the text baseline and shift the wordmark. */
.brand .mark { display: block; }
.topbar-label {
  margin-left: auto;
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--muted);
}

.app { max-width: 940px; margin: 0 auto; padding: 20px 18px 40px; }

/* ── Stream panel ────────────────────────────────────────────────────────────────
   A real bordered box with a header strip. This is the change that makes the dashboard legible: a
   stream is one object, and everything belonging to it is visibly inside it. */

.card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  margin-bottom: 16px;
  padding: 16px;
}
/* Live is marked with a brighter edge as well as the status pill, so a running stream is findable
   without reading anything. */
.card.is-live { border-color: #3c4450; }

/* The header strip bleeds to the panel edges by cancelling the panel padding. */
.card-top {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: -16px -16px 16px;
  padding: 11px 14px;
  background: var(--fill);
  border-bottom: 1px solid var(--border);
  border-radius: 7px 7px 0 0;
}

.name-input {
  font-family: var(--sans); font-size: 16px; font-weight: 660; letter-spacing: -0.02em;
  border: 1px solid transparent; border-radius: 5px; background: none;
  padding: 4px 7px; margin-left: -7px; color: var(--ink); flex: 0 1 auto; width: 168px; min-width: 0;
}
.name-input:hover:not(:disabled) { border-color: var(--border); background: rgba(255, 255, 255, 0.04); }
.name-input:focus { border-color: var(--ink); background: var(--page); outline: none; }

/* ── Stream key ──────────────────────────────────────────────────────────────── */

.keybar {
  display: inline-flex; align-items: center; gap: 7px; margin-left: auto;
  height: 30px; padding: 0 4px 0 9px;
  background: var(--page); border: 1px solid var(--border); border-radius: 6px;
  flex: 0 0 auto;
}
.keybar.set { border-color: #3a424e; }
.keybar.editing { flex: 0 1 320px; border-color: var(--ink); }
.keybar-ico { font-size: 11px; color: var(--muted); flex: none; }
.keybar-val {
  font-family: var(--mono); font-size: 12px; font-weight: 500; letter-spacing: 0.02em;
  color: var(--ink); white-space: nowrap;
}
.keybar-val.empty { color: var(--muted); font-family: var(--sans); font-size: 13px; font-style: normal; }
.keybar-in {
  flex: 1; min-width: 0; height: 100%;
  border: none !important; background: transparent !important; padding: 0 !important;
  font-family: var(--mono); font-size: 12.5px; color: var(--ink);
}
.keybar-in:focus { outline: none; }

.keysave {
  flex: none; border: 1px solid var(--ink); border-radius: 4px;
  background: var(--ink); color: var(--page);
  font-family: var(--sans); font-size: 11.5px; font-weight: 620;
  padding: 0 11px; height: 22px; cursor: pointer;
}
.keysave:hover:not(:disabled) { background: #d8dbe0; border-color: #d8dbe0; }
.keysave:disabled { background: none; border-color: var(--border); color: var(--muted); cursor: not-allowed; }

/* Confirmation after a save. Monochrome, and it settles rather than flashing. */
.keybar.flash { animation: flashok 1s ease-out; }
@keyframes flashok {
  0%, 55% { border-color: var(--ink); box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.12); }
  100% { border-color: #3a424e; box-shadow: none; }
}

.pencil {
  flex: none; display: inline-grid; place-items: center; width: 22px; height: 22px;
  border: none; border-radius: 4px; background: none; color: var(--muted); cursor: pointer; padding: 0;
}
.pencil:hover:not(:disabled) { color: var(--ink); background: rgba(255, 255, 255, 0.1); }
.pencil:disabled { opacity: 0.4; cursor: not-allowed; }

.icon-btn {
  flex: none; display: inline-grid; place-items: center; width: 28px; height: 28px;
  border: 1px solid var(--border); border-radius: 5px; background: var(--page);
  color: var(--muted); cursor: pointer;
}
.icon-btn:hover { border-color: var(--bad); color: var(--bad); background: var(--bad-soft); }

/* ── Status ──────────────────────────────────────────────────────────────────── */

.status {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--mono); font-size: 10px; font-weight: 500;
  padding: 4px 9px; border-radius: 5px; border: 1px solid transparent;
  text-transform: uppercase; letter-spacing: 0.12em; white-space: nowrap;
}
.status::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.status.live { color: var(--live); background: var(--live-soft); border-color: #1e3a2c; }
.status.offline { color: var(--muted); background: var(--page); border-color: var(--border); }
.status.needs_setup { color: var(--body); background: var(--page); border-color: var(--border); }
.status.stalled, .status.behind { color: var(--warn); background: var(--warn-soft); border-color: #3a2f18; }
.status.failed { color: var(--bad); background: var(--bad-soft); border-color: #3d2320; }

/* ── Notices ─────────────────────────────────────────────────────────────────── */

.note {
  padding: 11px 14px; font-size: 13.5px; margin-bottom: 14px; border-radius: 6px;
  border: 1px solid var(--border); border-left: 2px solid var(--border);
  background: var(--fill);
}
.note.info { border-left-color: var(--ink); color: var(--body); }
.note.bad { border-color: #3d2320; border-left-color: var(--bad); background: var(--bad-soft); color: var(--bad); }

/* ── Upload ──────────────────────────────────────────────────────────────────── */

.drop {
  display: block; cursor: pointer; margin-bottom: 14px; border-radius: 6px;
  border: 1px dashed var(--border); background: var(--page);
}
.drop:hover, .drop.over { border-color: var(--ink); background: rgba(255, 255, 255, 0.03); }
.drop.over { border-style: solid; }
.drop-inner { padding: 30px 20px; text-align: center; display: grid; gap: 7px; justify-items: center; }
.drop-icon {
  width: 32px; height: 32px; display: grid; place-items: center; border-radius: 5px;
  background: var(--ink); color: var(--page); font-size: 14px;
}
.drop-inner b { font-size: 14.5px; color: var(--ink); font-weight: 620; }

/* ── Video ───────────────────────────────────────────────────────────────────── */

.vid {
  display: flex; margin-bottom: 12px; overflow: hidden; border-radius: 6px;
  border: 1px solid var(--border); background: var(--page);
}
.vid.busy { border-color: #3a3524; }
.vid-thumb {
  flex: none; width: 158px; align-self: stretch; min-height: 106px;
  background: #050607 center / cover no-repeat; position: relative;
}
.vid-thumb.empty { display: grid; place-items: center; color: var(--border); font-size: 22px; }
.vid-thumb .dur {
  position: absolute; bottom: 6px; right: 6px; border-radius: 4px;
  background: rgba(0, 0, 0, 0.75); color: var(--ink);
  font-family: var(--mono); font-size: 10px; padding: 2px 6px;
}
.vid-body { flex: 1; padding: 13px 15px; min-width: 0; }
.vid-top { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.vid-name { font-weight: 620; font-size: 13.5px; color: var(--ink); word-break: break-word; }
.vid-actions { display: flex; align-items: center; gap: 8px; flex: none; }
.vid-actions > * { height: 26px; display: inline-flex; align-items: center; }

.badge {
  font-family: var(--mono); font-size: 10px; font-weight: 500; padding: 0 8px; border-radius: 5px;
  border: 1px solid transparent; text-transform: uppercase; letter-spacing: 0.1em;
}
.badge.ready { color: var(--live); background: var(--live-soft); border-color: #1e3a2c; }
.badge.rejected, .badge.failed { color: var(--bad); background: var(--bad-soft); border-color: #3d2320; }
.badge.checking, .badge.preparing, .badge.converting { color: var(--warn); background: var(--warn-soft); border-color: #3a2f18; }

/* Source facts. Mono, because each one is a measured value. */
.chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 11px; }
.chip {
  font-family: var(--mono); font-size: 10.5px; padding: 4px 8px; border-radius: 5px;
  background: var(--fill); border: 1px solid var(--border); color: var(--body);
  font-variant-numeric: tabular-nums;
}
.chip b { color: var(--ink); font-weight: 500; }
.chip.warn { background: var(--warn-soft); border-color: #3a2f18; color: var(--warn); }
/* What we send, set apart from what arrived. */
.chip.out { background: var(--ink); border-color: var(--ink); color: var(--page); }
.chip.out b { color: var(--page); }

/* ── Codec choice ────────────────────────────────────────────────────────────── */

.pick {
  display: flex; align-items: center; flex-wrap: wrap; gap: 12px;
  margin-top: 12px; padding: 11px 13px; border-radius: 6px;
  background: var(--fill); border: 1px solid var(--border); border-left: 2px solid var(--ink);
}
.pick-label {
  font-family: var(--mono); font-size: 10px; font-weight: 500;
  text-transform: uppercase; letter-spacing: 0.14em; color: var(--muted);
}
.pick-opt { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; cursor: pointer; color: var(--ink); }
.pick-opt em { font-style: normal; color: var(--muted); font-size: 11.5px; }
.pick button { margin-left: auto; }

.outsel {
  font-family: var(--mono); font-size: 10.5px; padding: 4px 7px; border-radius: 5px;
  border: 1px solid var(--border); background: var(--fill); color: var(--ink); cursor: pointer;
}
.outsel:hover:not(:disabled) { border-color: var(--ink); background: rgba(255, 255, 255, 0.07); }
.outsel:disabled { opacity: 0.5; cursor: not-allowed; }

/*
  The encoder picker sits beside the resolution picker in the video's action row. It carries a wider
  value than a resolution does, so it gets room rather than being allowed to squeeze the row.
*/
.outsel.codec { min-width: 84px; }

/* ── Work in progress ────────────────────────────────────────────────────────── */

.working {
  display: flex; gap: 12px; align-items: flex-start; margin-top: 12px; padding: 11px 13px;
  border-radius: 6px; background: var(--warn-soft); border: 1px solid #3a2f18; border-left: 2px solid var(--warn);
}
.working-body { flex: 1; min-width: 0; }
.working-label { font-size: 12.5px; font-weight: 620; color: var(--warn); margin-bottom: 7px; }
.working .track { background: #2c2517; }
.working .track span { background: var(--warn); }
.working small { display: block; margin-top: 6px; font-variant-numeric: tabular-nums; }

.uploading {
  display: flex; gap: 13px; align-items: flex-start; margin-bottom: 14px; padding: 16px;
  border-radius: 6px; background: var(--fill); border: 1px solid var(--border);
}
.uploading-body { flex: 1; min-width: 0; }
.uploading-label { font-size: 13.5px; font-weight: 620; color: var(--ink); margin-bottom: 9px; }
.uploading .track { height: 6px; background: var(--border); }
.uploading .track span { background: var(--ink); }
.uploading small { display: block; margin-top: 8px; font-family: var(--mono); font-variant-numeric: tabular-nums; }
.uploading .spinner {
  width: 20px; height: 20px; border-width: 2px;
  border-color: rgba(255, 255, 255, 0.2); border-top-color: var(--ink); margin-top: 2px;
}

.track { height: 5px; background: var(--border); overflow: hidden; border-radius: 3px; }
.track span { display: block; height: 100%; width: 0; background: var(--ink); transition: width 0.3s; }
.track.indet { position: relative; }
.track.indet span { position: absolute; width: 40% !important; animation: indet 1.2s ease-in-out infinite; }
@keyframes indet { 0% { left: -40%; } 100% { left: 100%; } }

.spinner {
  flex: none; width: 15px; height: 15px; margin-top: 2px;
  border: 2px solid rgba(206, 153, 66, 0.25); border-top-color: var(--warn);
  border-radius: 50%; animation: spin 0.7s linear infinite;
}
.spinner.big {
  width: 28px; height: 28px; border-width: 3px; margin: 0;
  border-color: rgba(255, 255, 255, 0.25); border-top-color: var(--ink);
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ── Controls ────────────────────────────────────────────────────────────────── */

.controls { display: grid; gap: 14px; padding-top: 2px; }
.field { display: grid; gap: 7px; }
.field.tight { gap: 6px; }
.field label {
  font-family: var(--mono); font-size: 10px; text-transform: uppercase; letter-spacing: 0.14em;
  color: var(--muted); font-weight: 500;
}

input[type="password"], input[type="text"], input[type="email"] {
  width: 100%; padding: 9px 12px; border-radius: 5px;
  font-family: var(--mono); font-size: 12.5px;
  border: 1px solid var(--border); background: var(--page); color: var(--ink);
}
input:focus { outline: none; border-color: var(--ink); }

/* ── Segmented control ───────────────────────────────────────────────────────────
   The selected option is a solid white slab with dark text and heavier weight. Three signals rather
   than one, because on a dark interface a single subtle fill is easy to miss and this control decides
   whether a stream loops forever or stops. */

.segmented {
  display: flex; width: fit-content; overflow: hidden;
  border: 1px solid var(--border); border-radius: 6px; background: var(--page);
}
.segmented button {
  border: none; border-right: 1px solid var(--border); background: none;
  padding: 7px 14px; font-family: var(--sans); font-size: 12.5px; font-weight: 560;
  color: var(--muted); cursor: pointer;
}
.segmented button:last-child { border-right: none; }
.segmented button:hover:not(.on):not(:disabled) { background: rgba(255, 255, 255, 0.08); color: var(--ink); }
.segmented button.on { background: var(--ink); color: var(--page); font-weight: 660; }
.segmented button:disabled { opacity: 0.45; cursor: not-allowed; }
/* Disabled but selected must still read as selected, so it keeps its fill. */
.segmented button.on:disabled { opacity: 0.65; }
.segmented.tiny button { padding: 5px 11px; font-size: 11.5px; }

/* ── Buttons ─────────────────────────────────────────────────────────────────── */

.go { margin-top: 4px; }

button.big, button.thin {
  width: 100%; font-family: var(--sans); font-weight: 620;
  border: 1px solid var(--border); border-radius: 5px; cursor: pointer;
  background: var(--fill); color: var(--ink);
}
button.big { padding: 11px; font-size: 14px; }
button.thin { padding: 8px; font-size: 13px; }
button.big:hover:not(:disabled), button.thin:hover:not(:disabled) { background: rgba(255, 255, 255, 0.09); border-color: var(--ink); }

button.primary { background: var(--ink); border-color: var(--ink); color: var(--page); }
button.primary:hover:not(:disabled) { background: #d8dbe0; border-color: #d8dbe0; }
/*
  A disabled primary action becomes an outline rather than a dimmed slab. The dimmed slab was the worst
  thing on the previous version: a mid grey bar reads as a button you are allowed to press, so the
  clearest thing an unavailable action can do is stop looking filled at all.
*/
button.primary:disabled {
  background: none; border-color: var(--border); color: var(--muted); cursor: not-allowed; opacity: 1;
}

button.danger { background: var(--fill); border-color: var(--border); color: var(--bad); }
button.danger:hover:not(:disabled) { background: var(--bad-soft); border-color: var(--bad); }

button.sm {
  font-family: var(--sans); font-size: 12px; font-weight: 600; padding: 5px 11px;
  border: 1px solid var(--border); border-radius: 5px; background: var(--fill);
  color: var(--ink); cursor: pointer;
}
button.sm:hover:not(:disabled) { background: rgba(255, 255, 255, 0.09); border-color: #3a424e; }
button.sm.danger { color: var(--bad); }
button.sm.primary, button.primary.sm { background: var(--ink); border-color: var(--ink); color: var(--page); }
button.sm.primary:hover:not(:disabled), button.primary.sm:hover:not(:disabled) { background: #d8dbe0; border-color: #d8dbe0; }
button.sm.primary:disabled { background: none; border-color: var(--border); color: var(--muted); cursor: not-allowed; }

/* ── Scheduling ──────────────────────────────────────────────────────────────── */

.sched-open {
  display: inline-flex; align-items: center; gap: 6px;
  margin-top: 9px; padding: 5px 9px; border-radius: 5px;
  background: none; border: 1px solid transparent;
  font-family: var(--sans); font-size: 12.5px; font-weight: 600;
  color: var(--muted); cursor: pointer;
}
.sched-open:hover { color: var(--ink); border-color: var(--border); background: rgba(255, 255, 255, 0.06); }
.sched-open:focus-visible { outline: 1px solid var(--ink); outline-offset: 1px; }

.sched-edit {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin-top: 10px; padding: 11px 13px; border-radius: 6px;
  border: 1px solid var(--border); background: var(--fill);
}
.sched-edit input[type="datetime-local"] {
  flex: 1 1 210px; min-width: 0; padding: 8px 10px; border-radius: 5px;
  font-family: var(--mono); font-size: 12.5px;
  border: 1px solid var(--border); background: var(--page); color: var(--ink);
}
.sched-edit input[type="datetime-local"]:focus { outline: none; border-color: var(--ink); }
.sched-hint { flex-basis: 100%; }

.sched-banner {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin-top: 10px; padding: 11px 13px; border-radius: 6px;
  border: 1px solid var(--border); background: var(--fill); color: var(--body); font-size: 13px;
}
.sched-banner b { font-weight: 620; color: var(--ink); }

/* ── Metrics ─────────────────────────────────────────────────────────────────────
   The densest part of the tool, so mono at tabular width matters most here. */

.metrics {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(112px, 1fr));
  border: 1px solid var(--border); border-radius: 6px; margin-bottom: 13px; overflow: hidden;
  background: var(--fill);
}
.metrics .m { padding: 12px 15px; border-right: 1px solid var(--border); }
.metrics .m:last-child { border-right: none; }
.metrics .m span {
  display: block; font-family: var(--mono); font-size: 9.5px; color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.14em; font-weight: 500;
}
.metrics .m b {
  display: block; margin-top: 6px;
  font-family: var(--mono); font-size: 16px; font-weight: 500; color: var(--ink);
  letter-spacing: -0.02em; font-variant-numeric: tabular-nums;
}
.metrics .m.good b { color: var(--live); }
.metrics .m.bad b { color: var(--bad); }
.metrics .m small { display: block; }

/* ── Adding and empty states ─────────────────────────────────────────────────── */

.add-stream {
  width: 100%; padding: 13px; border-radius: 6px;
  font-family: var(--sans); font-size: 13.5px; font-weight: 620;
  border: 1px dashed var(--border); background: none; color: var(--ink); cursor: pointer;
}
.add-stream:hover { border-color: var(--ink); background: rgba(255, 255, 255, 0.05); }

.plan-line {
  text-align: center; color: var(--muted); font-family: var(--mono);
  font-size: 10.5px; letter-spacing: 0.06em; margin: 16px 0 0;
}
.empty-first {
  text-align: center; color: var(--muted); padding: 44px; border-radius: 8px;
  border: 1px dashed var(--border); background: var(--panel);
}
.del-stream { color: var(--bad); }

/* ── Connection lost banner ──────────────────────────────────────────────────────
   The [hidden] rule is not optional: hidden works by applying display:none and the explicit
   display:flex below would outrank it, so the banner could be shown once and never hidden again. */

.offline {
  position: fixed; top: 0; left: 0; right: 0; z-index: 70;
  display: flex; align-items: center; justify-content: center; gap: 9px;
  padding: 10px 16px; font-size: 13px; font-weight: 600;
  background: var(--bad-soft); color: var(--bad); border-bottom: 1px solid var(--bad);
}
.offline-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--bad); animation: pulse 1.2s ease-in-out infinite; }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }
body:has(.offline:not([hidden])) .app { padding-top: 44px; }

/* ── Operator overlay and logs ───────────────────────────────────────────────── */

#op-body { max-width: 940px; margin: 0 auto; }
pre.log {
  font-family: var(--mono); font-size: 11px; line-height: 1.6;
  background: var(--page); color: var(--body);
  border: 1px solid var(--border); border-radius: 6px; padding: 12px;
  max-height: 200px; overflow: auto; margin: 8px 0 0;
  white-space: pre-wrap; word-break: break-all;
}

/* ── Toast, errors only ──────────────────────────────────────────────────────── */

.toast {
  position: fixed; bottom: 52px; left: 50%; transform: translateX(-50%);
  background: var(--fill); color: var(--ink);
  border: 1px solid var(--border); border-left: 2px solid var(--bad); border-radius: 6px;
  padding: 12px 18px; font-size: 13.5px; z-index: 60; max-width: 90vw;
  box-shadow: 0 14px 40px rgba(0, 0, 0, 0.55);
}

/* ── Reduced motion ─────────────────────────────────────────────────────────────
   Everything that moves here is reporting progress, so the elements stay and only the movement stops.
   A spinner that cannot spin still marks where work is happening. */

@media (prefers-reduced-motion: reduce) {
  .spinner, .offline-dot, .track.indet span, .keybar.flash { animation: none; }
  .track span { transition: none; }
  .track.indet span { width: 100% !important; position: static; opacity: 0.35; }
}

/* ── Narrow ──────────────────────────────────────────────────────────────────── */

@media (max-width: 620px) {
  .app { padding: 14px 12px 32px; }
  .topbar-in { padding: 0 12px; }
  .card { padding: 14px; }
  .card-top { margin: -14px -14px 14px; }
  .vid { flex-direction: column; }
  .vid-thumb { width: 100%; min-height: 150px; }
  .keybar { margin-left: 0; flex: 1 1 100%; }
  .name-input { width: 100%; flex: 1 1 100%; }
  .segmented { width: 100%; }
  .segmented button { flex: 1; padding: 8px 6px; }
}
/* ── The account gate ────────────────────────────────────────────────────────────
   A single dialog, centred in the window.
   It used to be a bare column of fields floating at the top left of an empty page, which read as an
   unfinished form rather than as a thing to complete: nothing framed it, nothing said where it ended, and
   with three steps in sequence and no numbering, each one arrived as a surprise.
   So: a real panel with an edge, centred both ways, the head centred inside it and the fields left aligned
   because a label above a box belongs on the left. Progress dots at the top of the sign-up path.
   Centred with place-items rather than flex so the dialog stays put as steps change height. */
.gate {
  min-height: calc(100vh - 54px);
  display: grid;
  place-items: center;
  padding: 28px 18px 44px;
}
.gate-in {
  width: 100%; max-width: 372px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 30px 28px 26px;
  /* One soft shadow, no glow and no gradient. It lifts the panel off the page enough to read as a
     foreground object, which is the whole job. */
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5), 0 16px 40px -12px rgba(0, 0, 0, 0.7);
}
.gate-mark { display: block; color: var(--ink); margin: 0 auto 16px; }
.gate-title {
  margin: 0 0 6px; text-align: center;
  font-size: 20px; font-weight: 700; letter-spacing: -0.02em; color: var(--ink); line-height: 1.3;
}
.gate-sub {
  margin: 0 0 20px; text-align: center;
  font-size: 13px; color: var(--body); line-height: 1.55;
}
.gate-sub:empty { margin-bottom: 12px; }
/* ── Progress ─────────────────────────────────────────────────────────────────
   Three dots and a count. The count carries the meaning on its own, so the dots are reinforcement rather
   than the only signal, which keeps this readable without colour vision. */
.gate-steps {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  margin: 0 0 20px;
}
.gate-steps .dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--border);
}
.gate-steps .dot.done { background: var(--muted); }
/* The current step is a short bar rather than a bigger dot: a length difference is easier to spot at 6px
   than a diameter difference. */
.gate-steps .dot.now { width: 18px; border-radius: 3px; background: var(--ink); }
.gate-steps-label {
  margin-left: 6px;
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--muted);
}
.gate-step { display: grid; gap: 14px; }
.gate-hint { margin: -4px 0 0; font-size: 12.5px; color: var(--muted); line-height: 1.55; }
/* Errors carry the failed tint and a label, never colour alone. Kept in flow rather than absolutely
   positioned so a two line message pushes the button down instead of covering it. */
.gate-error {
  margin: -4px 0 0; padding: 9px 11px; border-radius: 5px;
  background: var(--bad-soft); border: 1px solid var(--bad); color: var(--bad);
  font-size: 12.5px; line-height: 1.5;
}
/* An operational notice, not a user error, so it uses the warning tint rather than the failure one. */
.gate-notice {
  margin: 0 0 18px; padding: 10px 12px; border-radius: 5px;
  background: var(--warn-soft); border: 1px solid var(--warn); color: var(--warn);
  font-size: 12.5px; line-height: 1.55;
}
.gate-alt { margin: 2px 0 0; font-size: 12.5px; color: var(--muted); text-align: center; }
.gate-dot { padding: 0 5px; color: var(--border); }
.gate-skip { margin-top: 22px; padding-top: 14px; border-top: 1px solid var(--line); }
/* A button that has to read as a link, because it acts on this page rather than navigating. Underlined
   rather than coloured: on a dark surface an unadorned tinted word is easy to read as ordinary text. */
.linkish {
  border: none; background: none; padding: 0; cursor: pointer;
  font-family: var(--sans); font-size: inherit; font-weight: 560;
  color: var(--ink); text-decoration: underline; text-underline-offset: 2px;
  text-decoration-color: var(--border);
}
.linkish:hover:not(:disabled) { text-decoration-color: var(--ink); }
.linkish:disabled { color: var(--muted); cursor: not-allowed; text-decoration-color: var(--line); }
/* The code field. Monospace and widely tracked so four digits can be checked against a message at a
   glance, which is the whole job of this screen. */
.code-input {
  font-size: 21px !important; letter-spacing: 0.42em; text-align: center;
  padding: 11px 12px !important; text-indent: 0.42em;
}
.password-row { display: flex; gap: 8px; align-items: stretch; }
.password-row input { flex: 1; min-width: 0; }
/* The reveal toggle. Fixed width so the label swapping between Show and Hide does not move the field. */
.reveal {
  flex: 0 0 auto; width: 62px;
  border: 1px solid var(--border); border-radius: 5px; background: var(--fill); color: var(--ink);
  font-family: var(--sans); font-size: 12px; font-weight: 600; cursor: pointer;
}
.reveal:hover:not(:disabled) { background: rgba(255, 255, 255, 0.09); border-color: var(--ink); }
.reveal[aria-pressed="true"] { background: var(--ink); border-color: var(--ink); color: var(--page); }
/* ── Who is signed in ────────────────────────────────────────────────────────────
   Sits at the right of the topbar. The address is the useful part: on a product where one account can
   own several channels, knowing which account you are looking at is the difference between checking a
   stream and breaking somebody else's. */
.account { display: flex; align-items: center; gap: 10px; font-size: 12px; }
.topbar-label + .account { margin-left: 0; }
.account-email {
  font-family: var(--mono); font-size: 11px; color: var(--muted);
  max-width: 30ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
@media (max-width: 620px) {
  /* The address is the first thing to go: on a narrow screen the sign out control matters more than
     being reminded of an address you just typed. */
  .account-email { display: none; }
  /* The dialog loses its frame on a phone and becomes the page. At this width a bordered panel with 18px
     of gutter either side is a border for the sake of having one, and the space is better spent on the
     fields. Top aligned as well, so the keyboard opening does not shove the form off screen. */
  .gate { padding: 18px 16px 32px; place-items: start center; }
  .gate-in {
    max-width: none;
    background: none; border: 0; box-shadow: none;
    padding: 8px 0 0;
  }
}
/* ── Sign in with Google ─────────────────────────────────────────────────────────
   The mark is Google's own, inline and unaltered, on its own light chip. Their branding terms require the
   official G on a white or light surface, so recolouring it to sit flat on this near black panel is not an
   option that was available. The chip is the compromise that keeps both sides right: their mark on white,
   our button in the interface's own language. */
.google-row { display: grid; gap: 0; margin-bottom: 4px; }
.google-btn {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  padding: 10px 14px; border: 1px solid var(--border); border-radius: 5px;
  background: var(--fill); color: var(--ink);
  font-family: var(--sans); font-size: 14px; font-weight: 620; text-decoration: none;
}
.google-btn:hover { background: rgba(255, 255, 255, 0.09); border-color: var(--ink); }
.google-chip {
  display: grid; place-items: center; width: 24px; height: 24px;
  background: #fff; border-radius: 3px; flex: 0 0 auto;
}
.google-chip svg { display: block; }
/* A rule with the word "or" sitting in it. Two lines and a label rather than a bare divider, because a
   plain line between two controls reads as a section break instead of a choice between them. */
.gate-or {
  display: flex; align-items: center; gap: 12px;
  margin: 18px 0; font-family: var(--mono); font-size: 10px; letter-spacing: 0.2em;
  text-transform: uppercase; color: var(--muted);
}
.gate-or::before, .gate-or::after {
  content: ""; flex: 1; height: 1px; background: var(--line);
}
