/* Hallmark · pre-emit critique: P4 H5 E5 S4 R5 V3
   Variety scores 3 by design: the visual system was specified (Themesberg
   Neumorphism UI), so theme selection was not Hallmark's to make. */
/* Cast Bridge — app layer.
   Everything here builds on Neumorphism UI's own tokens. No new palette,
   no new radii, no new shadows: the values below are lifted from the kit's
   _variables.scss so the app and the kit stay one system. */

:root {
  /* Kit tokens, verbatim from neumorphism/_variables.scss */
  --cb-surface: #e6e7ee;   /* $primary / $soft / $body-bg */
  --cb-accent: #2d4cc8;    /* $secondary */
  --cb-ink: #44476a;       /* $gray-900 / $body-color */
  --cb-ink-soft: #66799e;  /* $gray-700 */
  --cb-ink-2: #525480;     /* $gray-800 — 5.80:1 on the surface, AA for body text */
  --cb-mute: #93a5be;      /* $gray-600 — 2.04:1. DECORATION ONLY, never text. */
  --cb-line: #d1d9e6;      /* $gray-400 / $light */
  --cb-ok: #18634b;        /* $success */
  --cb-warn: #f0b400;      /* $warning — 1.52:1 on the surface. FILLS ONLY, never text. */
  --cb-warn-ink: #7a5200;  /* the same warning, dark enough to read: 5.61:1 */
  --cb-bad: #a91e2c;       /* $danger */

  --cb-shadow-soft: 6px 6px 12px #b8b9be, -6px -6px 12px #ffffff;
  --cb-shadow-light: 3px 3px 6px #b8b9be, -3px -3px 6px #ffffff;
  --cb-shadow-inset: inset 2px 2px 5px #b8b9be, inset -3px -3px 7px #ffffff;

  --cb-radius: 0.55rem;    /* $border-radius */
  --cb-radius-xl: 0.95rem; /* $border-radius-xl */

  /* One eyebrow, one spec.
     Six places were drawing the same small-caps label — the gate eyebrow,
     the field labels, the history scope, the member tag, the danger-zone
     heading and now the table header — at six different sizes and five
     different letter-spacings (.04em to .18em). Same role, six looks, and
     the eye reads that as words that will not sit still. */
  --cb-label-size: 0.6875rem;
  --cb-label-weight: 700;
  --cb-label-track: 0.08em;

  /* The on-air panel is the app's one dark surface. The light-surface
     accent reads at 3.1:1 on #0d0f14; this one clears 7:1. */
  --cb-accent-dark: #7d93f2;
}

body {
  /* The bottom padding clears the fixed menu bar: 3.5rem of bar, the
     safe-area inset, and a gap. Without it the last row of every list on
     every screen sits under the bar. */
  padding: max(1rem, env(safe-area-inset-top)) 0
           calc(5.25rem + env(safe-area-inset-bottom));
  overflow-x: hidden;
}

.cb-shell {
  max-width: 620px;
  margin: 0 auto;
  padding: 0 1rem;
}

.btn {
  transition: color 0.15s ease, background-color 0.15s ease,
              border-color 0.15s ease, box-shadow 0.15s ease;
  /* The kit only positions .btn inside an .input-group. The busy spinner is
     laid out in flow now rather than absolutely, but a .btn is still the
     containing block anything else in it should measure against. */
  position: relative;
}

/* ---------- Focus ---------- *
   The kit sets $input-btn-focus-box-shadow and $input-focus-box-shadow to
   none, which leaves keyboard users with no visible focus at all — a WCAG
   2.4.7 failure. Correctness outranks the look, so the ring goes back on,
   scoped to :focus-visible so a mouse click stays clean. The accent clears
   3:1 against the #e6e7ee surface. */
.btn:focus-visible,
.form-control:focus-visible,
.nav-link:focus-visible,
a:focus-visible,
[tabindex]:focus-visible,
summary:focus-visible {
  outline: 3px solid var(--cb-accent);
  outline-offset: 2px;
  border-radius: var(--cb-radius);
}
.btn:focus:not(:focus-visible) { outline: none; }

.cb-skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--cb-surface);
  color: var(--cb-accent);
  padding: 0.65rem 1rem;
  border-radius: var(--cb-radius);
  box-shadow: var(--cb-shadow-soft);
  z-index: 60;
  font-weight: 600;
}
.cb-skip:focus { left: 1rem; top: 1rem; }

/* ---------- Masthead ---------- */
.cb-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 1rem;
}
/* A flex item's floor is its content, so without this the wordmark block
   refuses to shrink and pushes the pill off-screen instead. */
.cb-head > div { min-width: 0; }
.cb-wordmark {
  margin: 0;
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.1;
  color: var(--cb-ink);
}
.cb-wordmark span { color: var(--cb-accent); }
.cb-tagline {
  margin: 0.15rem 0 0;
  font-size: 0.8125rem;
  color: var(--cb-ink-2);
  font-weight: 400;
}

/* Device pill — quietest notification surface (badge). */
.cb-devicepill {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  /* `flex: none` was written for "no devices". A real TV announces itself as
     "Living Room Samsung QLED (Chromecast built-in)", which ran 77px past the
     right edge of a 390px phone. body{overflow-x:hidden} swallowed that in
     Chrome, so it measured clean here while the phone panned the whole page
     sideways. The pill gives way instead, and the name truncates. */
  flex: 0 1 auto;
  min-width: 0;
  max-width: 60%;
  padding: 0.4rem 0.7rem;
  border-radius: 2rem;
  background: var(--cb-surface);
  box-shadow: var(--cb-shadow-light);
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--cb-ink-2);
  white-space: nowrap;
}

/* The dot is fixed; the name is what gives. */
.cb-devicepill > span:last-child {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.cb-dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--cb-mute);
  flex: none;
}
.cb-dot.is-ready { background: var(--cb-ok); }
.cb-dot.is-live {
  background: var(--cb-accent);
  box-shadow: 0 0 0 0.25rem rgba(45, 76, 200, 0.18);
}
.cb-dot.is-bad { background: var(--cb-bad); }
@media (prefers-reduced-motion: no-preference) {
  .cb-dot.is-live { animation: cb-pulse 1.8s ease-in-out infinite; }
}
@keyframes cb-pulse {
  50% { box-shadow: 0 0 0 0.55rem rgba(45, 76, 200, 0); }
}

/* ---------- Player ---------- *
   A screen is the one thing neumorphism has a real idiom for: press it into
   the surface. The video is the only dark element in the app. */
.cb-screen {
  border-radius: var(--cb-radius-xl);
  background: var(--cb-surface);
  box-shadow: var(--cb-shadow-inset);
  padding: 0.5rem;
  margin-bottom: 0.75rem;
}
.cb-screen-inner {
  position: relative;
  border-radius: var(--cb-radius);
  overflow: hidden;
  background: #0d0f14;
}
.cb-screen.is-idle .cb-screen-inner {
  background: transparent;
  min-height: 4.5rem;
}
.cb-screen.is-live .cb-screen-inner { aspect-ratio: 16 / 9; }
.cb-screen video {
  display: block;
  width: 100%;
  height: 100%;
  background: #0d0f14;
}
.cb-screen.is-idle video { display: none; }

.cb-idle {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem 0.5rem;
  color: var(--cb-ink-2);
  font-size: 0.875rem;
  line-height: 1.45;
}
.cb-screen.is-live .cb-idle { display: none; }
.cb-idle svg { flex: none; color: var(--cb-line); }

/* Casting takeover — the phone stops being the screen and becomes the
   remote. The 16/9 box is released here on purpose: a transport bar
   squeezed into 206px of letterbox is a row of targets too small to hit. */
/* ---------- The session you left running ----------
   Sits above the player, in the app's own raised-surface idiom rather than
   as a coloured alert strip: this is not a warning, it is a thing you can
   pick back up, and painting it amber would say something went wrong.

   The button is on its own row, always. Inline beside the text it took
   114px, and on a 390px phone that left "Bigg Boss 20 — Epi…" — which is
   to say it truncated away the episode number and the position, the two
   facts the banner exists to carry, in order to protect a button whose
   label is guessable from context. The text wins the width.

   Colours are taken from the tokens, not chosen: --cb-mute is documented
   there as decoration only (2.04:1, fails as text) so the subtitle uses
   --cb-ink-2, which is 5.80:1 and passes AA. */
.cb-session {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  margin: 0 0 0.9rem;
  padding: 0.75rem 0.8rem;
  border-radius: var(--cb-radius-xl);
  background: var(--cb-surface);
  box-shadow: var(--cb-shadow-light);
}
.cb-session[hidden] { display: none; }
.cb-session-ico { flex: none; color: var(--cb-accent); }

.cb-session-text {
  display: flex;
  flex-direction: column;
  /* min-width:0 lets it shrink; the ZERO BASIS is what stops it demanding
     its max-content width in the first place. With `flex: 1 1 auto` the
     base size is the full title, which does not fit beside the icon, so the
     row wrapped before it ever shrank — leaving the icon alone on one line
     and the dismiss alone on another. */
  min-width: 0;
  flex: 1 1 0;
}
.cb-session-text strong {
  font-weight: 700;
  font-size: 0.875rem;
  line-height: 1.3;
  color: var(--cb-ink);
}
.cb-session-text span {
  font-size: 0.75rem;
  line-height: 1.35;
  color: var(--cb-ink-2);
  /* Two lines, then ellipsis. One line loses the position on any real title;
     unbounded lets a long title push the player down the page. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Its own row. Ordered by the DOM, not by `order` — a CSS-reordered row
   teleports keyboard focus around the banner. */
.cb-session-go {
  flex: 1 0 100%;
  /* 44px: the touch-target floor. This is the one thing on the banner
     anybody actually wants to hit. */
  min-height: 44px;
  padding: 0 0.85rem;
  border: 0;
  border-radius: var(--cb-radius);
  background: var(--cb-accent);
  color: #fff;
  font-size: 0.8125rem;
  font-weight: 700;
  cursor: pointer;
}
.cb-session-go:active { transform: translateY(1px); }

.cb-session-x {
  flex: none;
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--cb-radius);
  background: transparent;
  color: var(--cb-ink-2);
  cursor: pointer;
}
.cb-session-x:hover { color: var(--cb-ink); }

/* Click leaves no ring, Tab shows one. Removing the ring outright is a
   WCAG 2.4.7 failure, so it is replaced rather than deleted. */
.cb-session-go:focus-visible,
.cb-session-x:focus-visible {
  outline: 2px solid var(--cb-accent);
  outline-offset: 2px;
}

.cb-onair {
  display: none;
  flex-direction: column;
  gap: 0.9rem;
  background: #0d0f14;
  color: #e6e7ee;
  padding: 1rem 0.9rem 0.9rem;
}
.cb-screen.is-onair .cb-onair { display: flex; }
.cb-screen.is-onair .cb-screen-inner { aspect-ratio: auto; }
.cb-screen.is-onair video,
.cb-screen.is-onair .cb-idle { display: none; }

.cb-onair-head { display: flex; align-items: center; gap: 0.7rem; }
.cb-onair-head svg { flex: none; color: var(--cb-accent-dark); }
.cb-onair-text { display: flex; flex-direction: column; min-width: 0; }
.cb-onair strong {
  font-weight: 700; font-size: 0.9375rem; line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cb-onair-text > span {
  font-size: 0.8125rem; color: #a7b4cc; line-height: 1.35;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ---------- The remote ----------
   Every control clears 44px. This is a thumb on a sofa, in the dark, at
   arm's length from a television — the one place where a 32px icon button
   is not a style choice but a missed tap. */
.cb-remote { display: flex; flex-direction: column; gap: 0.85rem; }

.cb-range {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: 34px; margin: 0;
  background: transparent; cursor: pointer;
}
.cb-range::-webkit-slider-runnable-track {
  height: 6px; border-radius: 3px;
  background: linear-gradient(to right,
    var(--cb-accent-dark) 0 var(--p, 0%), #333a4d var(--p, 0%) 100%);
}
.cb-range::-moz-range-track { height: 6px; border-radius: 3px; background: #333a4d; }
.cb-range::-moz-range-progress { height: 6px; border-radius: 3px; background: var(--cb-accent-dark); }
.cb-range::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 18px; height: 18px; margin-top: -6px;
  border-radius: 50%; border: 0;
  background: #e6e7ee; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.55);
}
.cb-range::-moz-range-thumb {
  width: 18px; height: 18px; border-radius: 50%; border: 0;
  background: #e6e7ee; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.55);
}
.cb-range:disabled { cursor: default; opacity: 0.45; }

.cb-seek { display: flex; flex-direction: column; }
.cb-times {
  display: flex; align-items: baseline; gap: 0.5rem;
  margin: -0.15rem 0 0; font-size: 0.75rem; color: #a7b4cc;
  /* Elapsed time redraws once a second; proportional digits make the row
     twitch sideways under a thumb that is trying to hold still. */
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}
.cb-times-state { flex: 1; text-align: center; color: #8e9db8; }
.cb-times-state.is-live { color: var(--cb-accent-dark); font-weight: 600; }

.cb-transport { display: flex; align-items: center; justify-content: center; gap: 1.1rem; }
.cb-rbtn {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px; padding: 0;
  border: 0; border-radius: 50%;
  background: #1b2030; color: #e6e7ee;
  cursor: pointer;
  transition: background 0.15s ease, transform 0.1s ease;
}
.cb-rbtn:hover:not(:disabled) { background: #262d42; }
.cb-rbtn:active:not(:disabled) { transform: scale(0.94); }
.cb-rbtn:disabled { opacity: 0.4; cursor: default; }
.cb-rbtn-lg {
  min-width: 60px; min-height: 60px;
  background: var(--cb-accent-dark); color: #0d0f14;
}
.cb-rbtn-lg:hover:not(:disabled) { background: #96a9f7; }
.cb-rbtn-sm { min-width: 44px; min-height: 44px; background: transparent; }
.cb-rbtn-sm:hover:not(:disabled) { background: #1b2030; }
/* The skip amount rides inside the arrow rather than beside it, which
   means it has to sit on the arc's centre — not the button's — and knock
   the stroke out behind itself. Drawn without both, the arc runs straight
   through the digits and "10" reads as a smudge. */
.cb-rbtn-num {
  position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  padding: 0 1.5px; border-radius: 2px;
  background: inherit;
  font-size: 0.625rem; font-weight: 700; line-height: 1;
  letter-spacing: -0.02em;
}
#rBack .cb-rbtn-num { left: 46%; }
#rFwd .cb-rbtn-num { left: 54%; }

.cb-vol { display: flex; align-items: center; gap: 0.6rem; }
.cb-vol .cb-range { flex: 1; }
.cb-vol-num {
  min-width: 2.1ch; text-align: right;
  font-size: 0.75rem; color: #a7b4cc;
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}

/* Stopping the cast is reversible in one tap, so it gets geography rather
   than a dialog — and no red. Red is spent on things that go wrong. */
.cb-stop {
  align-self: center;
  min-height: 44px; padding: 0 1.1rem;
  border: 1px solid #333a4d; border-radius: var(--cb-radius);
  background: transparent; color: #a7b4cc;
  font-size: 0.8125rem; font-weight: 600;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.cb-stop:hover { color: #e6e7ee; border-color: #4a5570; }

/* The same control, sitting in the head row rather than under the remote,
   so it is reachable while the TV is still only "connecting" — which is
   exactly when someone wants it. */
.cb-stop-inline {
  align-self: center;
  margin-left: auto;
  flex: none;
  min-height: 38px;
  padding: 0 0.85rem;
  font-size: 0.75rem;
}
@media (max-width: 380px) {
  .cb-onair-head { flex-wrap: wrap; }
  .cb-stop-inline { margin-left: 0; width: 100%; }
}

.cb-onair :focus-visible { outline: 3px solid var(--cb-accent-dark); outline-offset: 2px; }

/* ---------- Cast log ----------
   Deliberately quiet: it is diagnostics, not a feature, and it must not
   compete with the status line above it. Closed it is one line; open it is
   a monospaced transcript that can be selected and copied whole. */
.cb-log {
  margin-bottom: 0.75rem;
  border-radius: var(--cb-radius-xl);
  background: var(--cb-surface);
  box-shadow: var(--cb-shadow-light);
  padding: 0.35rem 0.85rem;
}
.cb-log > summary {
  display: flex; align-items: center; gap: 0.55rem;
  min-height: 44px;
  font-size: 0.875rem; font-weight: 600; color: var(--cb-ink-2);
  cursor: pointer; list-style: none;
}
.cb-log > summary::-webkit-details-marker { display: none; }
.cb-log > summary svg { flex: none; color: var(--cb-ink-soft); }
.cb-log > summary::after {
  content: ''; margin-left: auto;
  width: 8px; height: 8px;
  border-right: 2px solid var(--cb-ink-soft);
  border-bottom: 2px solid var(--cb-ink-soft);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform 0.18s ease;
}
.cb-log[open] > summary::after { transform: rotate(-135deg) translate(-2px, -2px); }
/* Not red. The status strip above is already carrying the failure, and red
   spent in three places at once stops meaning anything — the panel opening
   itself is the signal that it has something to say. */
.cb-log.is-bad > summary { color: var(--cb-ink); }
.cb-log-lines {
  margin: 0 0 0.55rem;
  max-height: 15rem;
  overflow: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: 0.6rem 0.7rem;
  border-radius: var(--cb-radius);
  background: #22262e;
  color: #c8d0dd;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.6875rem;
  line-height: 1.55;
  white-space: pre-wrap;
  word-break: break-all;
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}
.cb-log-lines:focus-visible { outline: 3px solid var(--cb-accent-dark); outline-offset: 2px; }
.cb-log-actions { display: flex; gap: 0.5rem; padding-bottom: 0.6rem; }
.cb-log-btn {
  min-height: 44px; padding: 0 1rem;
  border: 1px solid var(--cb-line); border-radius: var(--cb-radius);
  background: transparent; color: var(--cb-ink-2);
  font-size: 0.8125rem; font-weight: 600; cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.cb-log-btn:hover { color: var(--cb-ink); border-color: var(--cb-ink-soft); }
.cb-log-btn:focus-visible { outline: 3px solid var(--cb-accent-dark); outline-offset: 2px; }

/* ---------- Subtitles ---------- */
.cb-subs {
  margin-bottom: 0.75rem;
  border-radius: var(--cb-radius-xl);
  background: var(--cb-surface);
  box-shadow: var(--cb-shadow-light);
  padding: 0.35rem 0.85rem;
}
.cb-subs > summary {
  display: flex; align-items: center; gap: 0.55rem;
  min-height: 44px;
  font-size: 0.875rem; font-weight: 600; color: var(--cb-ink-2);
  cursor: pointer; list-style: none;
}
.cb-subs > summary::-webkit-details-marker { display: none; }
.cb-subs > summary svg { flex: none; color: var(--cb-ink-soft); }
.cb-subs > summary::after {
  content: ''; margin-left: auto;
  width: 8px; height: 8px;
  border-right: 2px solid var(--cb-ink-soft);
  border-bottom: 2px solid var(--cb-ink-soft);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform 0.18s ease;
}
.cb-subs[open] > summary::after { transform: rotate(-135deg) translate(-2px, -2px); }
.cb-subs.is-on > summary { color: var(--cb-accent); }
.cb-subs form { padding-bottom: 0.25rem; }
.cb-subs-note {
  margin: 0.35rem 0 0.6rem;
  font-size: 0.75rem; line-height: 1.45; color: var(--cb-ink-2);
}
/* The divider between the two ways in. A bare second button under a form
   reads as a second submit for that form; the word is what makes it an
   alternative to the field rather than an extra step after it. */
.cb-subs-or {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin: 0.15rem 0 0.55rem;
  font-size: var(--cb-label-size);
  font-weight: var(--cb-label-weight);
  letter-spacing: var(--cb-label-track);
  text-transform: uppercase;
  color: var(--cb-ink-2);
}
.cb-subs-or::before,
.cb-subs-or::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--cb-line);
}
/* A retention and reach note, not an error — it carries the weight of the
   warning ink without red, which is reserved for destruction. */
.cb-subs-warn { color: var(--cb-warn-ink); }

.cb-subs-off {
  margin-bottom: 0.65rem;
  min-height: 44px; padding: 0 1rem;
  border: 1px solid var(--cb-line); border-radius: var(--cb-radius);
  background: transparent; color: var(--cb-ink-2);
  font-size: 0.8125rem; font-weight: 600; cursor: pointer;
}
.cb-subs-off:hover { color: var(--cb-ink); border-color: var(--cb-ink-soft); }

/* ---------- Status strip ---------- */
.cb-status {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  padding: 0.7rem 0.85rem;
  margin-bottom: 0.75rem;
  font-size: 0.875rem;
  color: var(--cb-ink);
  line-height: 1.4;
}
.cb-status .cb-status-text { flex: 1; min-width: 0; }
.cb-status .custom-select,
.cb-status select {
  flex: none;
  width: auto;
  font-size: 0.8125rem;
  padding: 0.3rem 1.6rem 0.3rem 0.6rem;
  height: auto;
}
.cb-status.is-bad { color: var(--cb-bad); }

/* ---------- Source strip ----------
   The other addresses the same scan found. It sits under the status line
   because that is where the player says what it is doing, and switching
   source is the answer to the thing it most often has to say.

   Below 26rem of container the button drops to its own line rather than
   squeezing the picker down to a stub — a select showing "480p — H…" is a
   choice you cannot read. */
.cb-sources {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding: 0.55rem 0.85rem 0.7rem;
  margin: -0.35rem 0 0.75rem;
  font-size: 0.8125rem;
  color: var(--cb-ink-soft);
}
.cb-sources-label {
  flex: none;
  margin: 0;
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.cb-sources .cb-sources-pick {
  flex: 1 1 9rem;
  min-width: 0;
  width: auto;
  font-size: 0.8125rem;
  padding: 0.3rem 1.6rem 0.3rem 0.6rem;
  height: auto;
}
.cb-sources-next { flex: none; }
.cb-sources.is-switching .cb-sources-pick { opacity: 0.6; }

/* On a phone these two are the recovery action for a film that would not
   play, and 37px of button is a miss away from a second tap on something
   else. Raised only where the pointer is a finger, so the desktop strip
   stays the compact one line it is at 414px and above. */
@media (pointer: coarse) {
  .cb-sources .cb-sources-pick,
  .cb-sources .cb-sources-next { min-height: 44px; }
}

/* Once the button has been pushed onto its own line it should own that
   line. A half-width button left-aligned under a full-width select is the
   ragged edge you get from letting a flex row wrap and then not saying what
   the wrapped item should do. */
@media (max-width: 420px) {
  .cb-sources-next { flex: 1 1 100%; }
}

/* ---------- Action grid ---------- */
.cb-actions {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.5rem;
  /* Two reasons, and the second one is the one that was reported.
   *
   * Proximity: everything above this acts on the film that is ALREADY
   * loaded; the paste box below it starts a new one. Those are different
   * groups, so the gap between them has to beat the 0.5rem gap inside this
   * one — otherwise the four buttons and the form read as one block of
   * five controls. Every other child of this section carries 0.75rem;
   * these two carried nothing at all and touched at exactly 434px.
   *
   * Shadow: the Play button in the form below is neumorphic — 3px offsets
   * under a 6px blur — so its highlight reaches 9px ABOVE its own box and
   * was landing on the Cast button. A shadow crossing into a neighbouring
   * control makes the two read as one raised surface and puts soft edges
   * inside somebody else's 46px touch target.
   *
   * 1.75rem rather than a new number: that is the step this sheet already
   * uses for a group boundary (.cb-help), it is more than double the 12px
   * rhythm every other sibling carries, it is 3.5x the 0.5rem gap inside
   * this grid — so the boundary is unambiguous — and it clears the
   * shadow's 9px reach several times over. */
  margin-bottom: 1.75rem;
}
.cb-actions .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: 2.875rem; /* 46px — over the 44px touch floor */
  padding-left: 0.6rem;
  padding-right: 0.6rem;
  text-transform: none;
  font-weight: 600;
  font-size: 0.875rem;
}
.cb-actions .btn:disabled,
.cb-actions .btn.disabled {
  opacity: 1;                       /* the outline carries the state, not a fade */
  box-shadow: none;
  background-color: var(--cb-surface);
  border: 1px solid var(--cb-line);
  color: var(--cb-ink-soft);
}

.cb-actions .btn svg { flex: none; }

/* Nothing is loaded: three of these four can do nothing, and a 2x2 grid of
   dead buttons is the first thing the eye lands on — it also pushes the
   paste box, the one control that matters at this moment, past the fold on
   a 390px phone. Idle shows Cast alone (you may well want to reach the TV
   before you pick anything); the rest arrive when there is something to
   act on. */
#main:has(#screen.is-idle) .cb-actions { grid-template-columns: 1fr; }
#main:has(#screen.is-idle) .cb-actions .btn:not(#btnCast) { display: none; }

.cb-actions .btn span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

google-cast-launcher {
  --connected-color: #2d4cc8;
  --disconnected-color: #44476a;
  width: 1.25rem;
  height: 1.25rem;
  display: inline-block;
  flex: none;
}

/* Buttons: lock the width so an in-place spinner moves nothing.
 *
 * The spinner used to be drawn in --cb-accent (#2d4cc8) on a track of
 * rgba(68,71,106,.25). That is fine on the light kit buttons and a lie on
 * the filled ones: .btn-secondary's background IS #2d4cc8, so the arc came
 * out at 1.00:1 against the button it was spinning on. Measured, not
 * guessed. Since .btn.is-busy also hides the label, Scan and Play spent
 * their entire run as a blank blue slab with no sign of life in it — the
 * single most-reported "is this thing working" in the app.
 *
 * currentColor is the fix, because it is the one value that is already
 * correct on every button variant: near-white on the filled ones, ink on
 * the light ones. Never name a colour here again. */
/* A busy button still has to say what it is busy WITH.
 *
 * This used to hide the label and centre a 17px ring in the space it left.
 * Measured, that ring is where it should be and clears 6.15:1 on the filled
 * buttons — it is not invisible. It is just not enough: what the eye reads
 * on a 390px screen is a label that vanished and a solid blue slab, and a
 * ring that small inside a 65x53 button does not register as an answer to
 * "did my tap do anything". Reported, twice, as "no loader, and the scan
 * button turns full blue".
 *
 * So the label stays and changes tense — Scan becomes Scanning… — with the
 * ring beside it rather than instead of it. Two signals, one of them made
 * of words. The width is locked in JS before the swap so the longer word
 * cannot shove the field next to it. */
.btn.is-busy { pointer-events: none; }
.cb-spin {
  display: none;
  flex: none;
  width: 0.85rem;
  height: 0.85rem;
  margin-right: 0.4rem;
  vertical-align: -0.1em;
  order: -1;                        /* markup puts it after the label */
  /* Fallback: a ring with one side cut out. Legible anywhere, no alpha. */
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
}
/* The spinner leads the label, so the button has to lay its children out
   in a row — the kit leaves .btn as inline-block. */
.btn.is-busy {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
/* Preferred: faint full track, solid leading arc — the kit's original look,
   just in a colour that can actually be seen on the button underneath. */
@supports (border-color: color-mix(in srgb, currentColor 25%, transparent)) {
  .cb-spin {
    border-color: color-mix(in srgb, currentColor 25%, transparent);
    border-top-color: currentColor;
  }
}
.btn.is-busy .cb-spin { display: inline-block; animation: cb-rotate 0.7s linear infinite; }
@keyframes cb-rotate { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .btn.is-busy .cb-spin { animation-duration: 2s; }
}

/* ---------- Screens and the bottom bar ---------- *
   Nine screens, five in the bar. RJ-Design-Skill navigation-and-feedback
   §2.3: under five entries a tab bar, over five a Sections sheet — so the
   fifth entry IS the sheet, and the four screens behind it are routes with a
   Menu button rather than a cramped nine-item strip nobody can hit. Targets
   are 56px tall because a thumb is not a mouse. */
.cb-nav {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 40;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 0;
  padding: 0.35rem 0.35rem max(0.35rem, env(safe-area-inset-bottom));
  background: var(--cb-surface);
  /* Upward only: a bottom bar casting a shadow downward casts it off the
     screen, and the edge it has to separate from is the one above it. */
  box-shadow: 0 -6px 16px rgba(184, 185, 190, 0.55);
}
body.is-gated .cb-nav { display: none; }

.cb-navbtn {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.2rem;
  position: relative;
  min-height: 3.5rem;
  padding: 0.4rem 0.15rem;
  border: 0;
  border-radius: var(--cb-radius);
  background: transparent;
  color: var(--cb-ink-2);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  cursor: pointer;
  transition: color 0.16s ease, box-shadow 0.16s ease;
}
.cb-navbtn svg { width: 21px; height: 21px; }
.cb-navbtn.is-on {
  color: var(--cb-accent);
  background: var(--cb-surface);
  box-shadow: var(--cb-shadow-light);
}
.cb-navbtn:focus-visible { outline: 2px solid var(--cb-accent); outline-offset: -2px; }
.cb-navcount {
  position: absolute;
  top: 0.3rem;
  left: 55%;
  min-width: 1.05rem;
  padding: 0.05rem 0.3rem;
  border-radius: 2rem;
  font-size: 0.625rem;
  font-weight: 700;
  line-height: 1.35;
  color: #fff;
  background: var(--cb-accent);
}

.cb-view[hidden] { display: none; }
@media (prefers-reduced-motion: no-preference) {
  .cb-view.is-on { animation: cb-fade 0.18s ease-out both; }
}
.cb-view:focus { outline: none; }

.cb-viewhead { margin: 0.75rem 0 1.1rem; }
.cb-viewtitle {
  margin: 0;
  font-size: 1.35rem;
  font-weight: 700;
  color: var(--cb-ink);
}
.cb-viewsub,
.cb-viewnote {
  margin: 0.3rem 0 0;
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--cb-ink-2);
}
.cb-viewnote { margin-bottom: 1rem; }

.cb-back {
  display: inline-flex;
  align-items: center;
  gap: 0.15rem;
  min-height: 2.75rem;
  margin: 0 0 0.35rem -0.5rem;
  padding: 0 0.5rem;
  border: 0;
  background: none;
  color: var(--cb-ink-2);
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
}
.cb-back:focus-visible { outline: 2px solid var(--cb-accent); outline-offset: 2px; }

/* ---------- The More menu ---------- */
.cb-menu {
  display: grid;
  gap: 0.6rem;
}
.cb-menurow {
  display: block;
  width: 100%;
  min-height: 3.5rem;
  padding: 0.85rem 1rem;
  text-align: left;
  border: 0;
  border-radius: var(--cb-radius-xl);
  background: var(--cb-surface);
  box-shadow: var(--cb-shadow-light);
  cursor: pointer;
}
.cb-menurow:focus-visible { outline: 2px solid var(--cb-accent); outline-offset: 2px; }
.cb-menurow:active { box-shadow: var(--cb-shadow-inset); }
.cb-menuk {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--cb-ink);
}
.cb-menuv {
  display: block;
  margin-top: 0.15rem;
  font-size: 0.8rem;
  line-height: 1.45;
  color: var(--cb-ink-2);
}

/* The mode switch sits above the field it governs, so it needs the gap
   underneath rather than the one the scope row carries by default. */
.cb-findrow { margin-bottom: 0.6rem; }

/* ---------- Episodes and remembered series ---------- */
.cb-ephead {
  margin: 1rem 0 0.5rem;
  font-size: var(--cb-label-size);
  font-weight: var(--cb-label-weight);
  letter-spacing: var(--cb-label-track);
  text-transform: uppercase;
  color: var(--cb-ink-2);
}
.cb-epnote {
  margin-top: 0.35rem;
  font-size: 0.78rem;
  line-height: 1.45;
  color: var(--cb-warn-ink);
}
.cb-epfilter { margin-bottom: 0.5rem; }
.cb-epcount {
  margin: 0.35rem 0 0;
  font-size: 0.78rem;
  color: var(--cb-ink-2);
}

/* A row is the whole target — 56px, comfortably past the 44px minimum, with
   the number in a fixed gutter so a column of 1 · 9 · 10 · 100 lines up
   instead of drifting right as the numbers grow. */
.cb-eprow {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  min-height: 3.5rem;
  padding: 0.7rem 0.9rem;
  text-align: left;
  border: 0;
  border-radius: var(--cb-radius-xl);
  background: var(--cb-surface);
  box-shadow: var(--cb-shadow-light);
  cursor: pointer;
}
.cb-eprow + .cb-eprow { margin-top: 0.45rem; }
.cb-eprow:focus-visible { outline: 2px solid var(--cb-accent); outline-offset: 2px; }
.cb-eprow:active { box-shadow: var(--cb-shadow-inset); }
.cb-epnum {
  flex: 0 0 2.25rem;
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  font-size: 1rem;
  font-weight: 700;
  color: var(--cb-accent);
}
.cb-eprow.is-season .cb-epname { color: var(--cb-accent); }
.cb-epbody { min-width: 0; display: block; }
/* Two lines, then an ellipsis. A full episode title can be a sentence, and a
   row that grows to four lines breaks the rhythm of the whole list. */
.cb-epname {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 0.9rem;
  font-weight: 700;
  line-height: 1.35;
  color: var(--cb-ink);
}
.cb-epsub {
  display: block;
  margin-top: 0.1rem;
  font-size: 0.78rem;
  line-height: 1.4;
  color: var(--cb-ink-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cb-serieshist { margin-top: 1.25rem; }
.cb-serieshead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.5rem;
}
.cb-seriestitle {
  margin: 0;
  font-size: var(--cb-label-size);
  font-weight: var(--cb-label-weight);
  letter-spacing: var(--cb-label-track);
  text-transform: uppercase;
  color: var(--cb-ink-2);
}
.cb-seriesrow {
  display: flex;
  align-items: stretch;
  gap: 0.4rem;
  border-radius: var(--cb-radius-xl);
  background: var(--cb-surface);
  box-shadow: var(--cb-shadow-light);
}
.cb-seriesrow + .cb-seriesrow { margin-top: 0.45rem; }
.cb-seriesopen {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 3.5rem;
  padding: 0.7rem 0.2rem 0.7rem 0.9rem;
  text-align: left;
  border: 0;
  border-radius: var(--cb-radius-xl);
  background: none;
  cursor: pointer;
}
.cb-seriesopen:focus-visible { outline: 2px solid var(--cb-accent); outline-offset: -2px; }
.cb-seriesname {
  display: block;
  font-size: 0.9rem;
  font-weight: 700;
  line-height: 1.35;
  color: var(--cb-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cb-seriessub {
  display: block;
  margin-top: 0.1rem;
  font-size: 0.78rem;
  color: var(--cb-ink-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Removal is one tap with an undo behind it, so it stays quiet: this is not
   destruction, it is forgetting, and red is a budget spent elsewhere. */
.cb-seriesforget {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 2.75rem;
  border: 0;
  border-radius: var(--cb-radius-xl);
  background: none;
  color: var(--cb-ink-soft);
  cursor: pointer;
}
.cb-seriesforget:hover { color: var(--cb-ink); }
.cb-seriesforget:focus-visible { outline: 2px solid var(--cb-accent); outline-offset: -2px; }

/* ---------- Settings rows ----------
   A light preference: it applies on the tap and confirms inline. The switch
   is a real `role="switch"` button rather than a styled checkbox, so the
   pressed state is announced and the focus ring is the same one every other
   control here uses. */
.cb-settings {
  margin-bottom: 0.9rem;
  padding: 0.85rem 1rem;
  border-radius: var(--cb-radius-xl);
  background: var(--cb-surface);
  box-shadow: var(--cb-shadow-light);
}
.cb-setrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  min-height: 2.75rem;
}
.cb-setk {
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--cb-ink);
}
.cb-setv {
  margin: 0.2rem 0 0;
  font-size: 0.8rem;
  line-height: 1.45;
  color: var(--cb-ink-2);
}
.cb-setsaved {
  margin: 0.45rem 0 0;
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--cb-ok);
}
.cb-setsaved.is-bad { color: var(--cb-bad); }

/* A report about what happened, not more description of the setting — so it
   sits across a boundary rather than tucked under the note at the 0.2rem
   step the other lines share. */
.cb-settap {
  margin: 0.85rem 0 0;
  padding-top: 0.7rem;
  border-top: 1px solid var(--cb-line);
  font-size: 0.78rem;
  line-height: 1.45;
  color: var(--cb-ink-2);
}

/* 44px of touch target, which is the whole button — the track inside it is
   smaller on purpose so the control reads as a switch and still takes a
   thumb anywhere near it. */
.cb-switch {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 3.6rem;
  height: 2.75rem;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}
.cb-switch:focus-visible { outline: 2px solid var(--cb-accent); outline-offset: 2px; border-radius: var(--cb-radius); }
.cb-switch[disabled] { cursor: not-allowed; opacity: 0.55; }
.cb-switch-track {
  position: relative;
  display: block;
  width: 3.1rem;
  height: 1.7rem;
  border-radius: 999px;
  background: var(--cb-surface);
  box-shadow: var(--cb-shadow-inset);
  transition: background 180ms ease-out;
}
.cb-switch-thumb {
  position: absolute;
  top: 0.2rem;
  left: 0.2rem;
  width: 1.3rem;
  height: 1.3rem;
  border-radius: 50%;
  background: var(--cb-mute);
  box-shadow: var(--cb-shadow-light);
  transition: transform 180ms ease-out, background 180ms ease-out;
}
.cb-switch[aria-checked="true"] .cb-switch-track { background: color-mix(in srgb, var(--cb-accent) 18%, var(--cb-surface)); }
.cb-switch[aria-checked="true"] .cb-switch-thumb {
  transform: translateX(1.4rem);
  background: var(--cb-accent);
}
@media (prefers-reduced-motion: reduce) {
  .cb-switch-track, .cb-switch-thumb { transition: none; }
}

/* ---------- Library ---------- */
.cb-libtools {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 0.5rem;
}
.cb-librow {
  padding: 0.85rem 0.95rem;
  border-radius: var(--cb-radius-xl);
  background: var(--cb-surface);
  box-shadow: var(--cb-shadow-light);
}
.cb-librow + .cb-librow { margin-top: 0.6rem; }
.cb-librow.is-armed { box-shadow: var(--cb-shadow-light), 0 0 0 2px var(--cb-bad); }
.cb-librow.is-missing { opacity: 0.72; }

.cb-librow-head {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
}
.cb-librow-name {
  flex: 1;
  min-width: 0;
  margin: 0;
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--cb-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Its own right-aligned slot with tabular figures, so a column of sizes
   compares on the last digit instead of wandering. */
.cb-librow-size {
  flex: none;
  font-size: 0.8rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--cb-ink-2);
}
.cb-librow-meta {
  margin: 0.2rem 0 0;
  font-size: 0.78rem;
  color: var(--cb-ink-2);
}
.cb-librow-link {
  margin: 0.45rem 0 0;
  padding: 0.4rem 0.55rem;
  border-radius: var(--cb-radius);
  background: var(--cb-surface);
  box-shadow: var(--cb-shadow-inset);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.7rem;
  line-height: 1.5;
  color: var(--cb-ink-2);
  overflow-wrap: anywhere;
}
.cb-librow-warn {
  margin: 0.55rem 0 0;
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--cb-bad);
}
.cb-librow-acts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.75rem;
  margin-top: 0.7rem;
}
.cb-librow-keep {
  width: auto;
  min-height: 2.25rem;
  padding: 0.25rem 1.7rem 0.25rem 0.6rem;
  font-size: 0.78rem;
}

/* ---------- How long to keep an upload ---------- */
.cb-keep { margin: 0.85rem 0 0.35rem; }
.cb-keep-label {
  margin: 0 0 0.4rem;
  font-size: var(--cb-label-size);
  font-weight: var(--cb-label-weight);
  letter-spacing: var(--cb-label-track);
  text-transform: uppercase;
  color: var(--cb-ink-2);
}
/* Four choices, one of them a sentence — a pill row would either overflow a
   390px screen or shrink the labels past reading. Two by two, full width,
   44px tall. */
.cb-keep .cb-segment {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 3px;
  width: 100%;
  border-radius: var(--cb-radius-xl);
}
.cb-keep .cb-segment-btn {
  min-height: 2.75rem;
  padding: 0.4rem 0.5rem;
  border-radius: var(--cb-radius);
}
.cb-keep-note {
  margin: 0.45rem 0 0;
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--cb-ink-2);
}

/* ---------- The Bilibili sign-in code ---------- */
.cb-qr {
  width: min(15rem, 68vw);
  margin: 0.9rem auto;
  padding: 0.6rem;
  border-radius: var(--cb-radius-xl);
  /* White, not the surface: a reader wants the quiet zone to be the same
     white as the code's own background, and #e6e7ee is not it. */
  background: #fff;
  box-shadow: var(--cb-shadow-light);
}
.cb-qr svg { display: block; width: 100%; height: auto; }

.cb-pick-share {
  margin: 0.5rem 0 0.6rem;
  padding: 0.4rem 0.55rem;
  border-radius: var(--cb-radius);
  background: var(--cb-surface);
  box-shadow: var(--cb-shadow-inset);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.7rem;
  line-height: 1.5;
  color: var(--cb-ink-2);
  overflow-wrap: anywhere;
}

.cb-rec { margin-top: 1.1rem; }
.cb-rec .btn.is-rec { color: var(--cb-bad); }

@keyframes cb-fade {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: none; }
}

/* ---------- Lists ---------- */
.cb-list { margin: 0; }
.cb-list .list-group-item {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.7rem 0.85rem;
  margin-bottom: 0.5rem;
  border: 0;
  border-radius: var(--cb-radius);
  background: var(--cb-surface);
  box-shadow: var(--cb-shadow-light);
}
.cb-list .list-group-item:last-child { margin-bottom: 0; }

.cb-item-body { flex: 1; min-width: 0; }
.cb-item-title {
  display: block;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--cb-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cb-item-meta {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-wrap: wrap;
  margin-top: 0.15rem;
  font-size: 0.75rem;
  color: var(--cb-ink-2);
}
.cb-item-meta .badge { font-size: 0.625rem; letter-spacing: 0.04em; }
.cb-item-actions { display: flex; align-items: center; gap: 0.35rem; flex: none; }
.cb-item-actions .btn {
  min-width: 2.25rem;
  min-height: 2.25rem;
  padding: 0.3rem 0.55rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-transform: none;
  font-size: 0.8125rem;
  font-weight: 600;
}

/* Resume marker on a history row. */
.cb-resume {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  color: var(--cb-accent);
  font-weight: 600;
}
.cb-progress {
  height: 0.1875rem;
  border-radius: 2rem;
  background: rgba(68, 71, 106, 0.12);
  overflow: hidden;
  margin-top: 0.4rem;
}
.cb-progress i {
  display: block;
  height: 100%;
  border-radius: 2rem;
  background: var(--cb-accent);
}

.cb-star { color: var(--cb-mute); }
.cb-star.is-on { color: var(--cb-warn); }

/* ---------- Empty states — never a dead end ---------- */
.cb-empty {
  text-align: center;
  padding: 1.75rem 1.25rem;
  border-radius: var(--cb-radius-xl);
  box-shadow: var(--cb-shadow-inset);
  color: var(--cb-ink-2);
}
.cb-empty h3 {
  font-size: 0.9375rem;
  font-weight: 700;
  color: var(--cb-ink);
  margin: 0 0 0.35rem;
}
.cb-empty p { font-size: 0.8125rem; margin: 0 auto 0.9rem; max-width: 46ch; line-height: 1.5; }
.cb-empty .btn { text-transform: none; font-weight: 600; font-size: 0.8125rem; }

/* A wall, stated. Same inset well as any empty state — this is an outcome,
   not a failure — but the left rule marks it as "this will never work"
   rather than "try again". Warning, not danger: nothing broke. */
.cb-walled {
  text-align: left;
  border-left: 3px solid var(--cb-warn);
  margin-top: 0.75rem;
}
.cb-walled p { margin-left: 0; margin-right: 0; }
.cb-walled-note {
  font-size: 0.75rem;
  color: var(--cb-ink-soft);
  font-style: italic;
}

/* The site's own refusal, quoted. Set apart from our explanation of it so
   the two are not read as one voice — the claim is checkable precisely
   because these words are theirs and the paragraph under it is ours. */
.cb-walled-quote {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.8125rem;
  color: var(--cb-ink);
  background: var(--cb-surface);
  border: 1px solid var(--cb-line);
  border-radius: var(--cb-radius);
  padding: 0.5rem 0.625rem;
}

/* ---------- Browser pane ---------- */
.cb-frame-wrap {
  position: relative;
  border-radius: var(--cb-radius-xl);
  box-shadow: var(--cb-shadow-inset);
  padding: 0.4rem;
  margin-top: 0.75rem;
}
.cb-frame {
  display: block;
  width: 100%;
  height: 20rem;
  border: 0;
  border-radius: var(--cb-radius);
  background: #f3f7fa;   /* $gray-100, not pure white */
}
.cb-frame-note {
  padding: 1rem 0.85rem;
  font-size: 0.8125rem;
  color: var(--cb-ink-2);
  line-height: 1.5;
  text-align: center;
}
.cb-frame-note b { color: var(--cb-ink); font-weight: 600; }

.cb-pagecard { margin-bottom: 0.75rem; }
.cb-pagecard .card-body { padding: 0.85rem; }
.cb-pagetitle {
  font-size: 0.9375rem;
  font-weight: 700;
  color: var(--cb-ink);
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cb-pagehost { font-size: 0.75rem; color: var(--cb-ink-2); }

/* ---------- Search / filter ---------- */
.cb-filterbar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.75rem;
}
.cb-filterbar .form-control { flex: 1; min-width: 0; }
.cb-chip {
  flex: none;
  padding: 0.45rem 0.75rem;
  border: 0;
  border-radius: 2rem;
  background: var(--cb-surface);
  box-shadow: var(--cb-shadow-light);
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--cb-ink-2);
  cursor: pointer;
  min-height: 2.25rem;
}
.cb-chip[aria-pressed="true"] {
  box-shadow: var(--cb-shadow-inset);
  color: var(--cb-accent);
}

/* ---------- Inline validation ---------- */
.cb-fielderror {
  display: none;
  align-items: flex-start;
  gap: 0.35rem;
  margin: 0.5rem 0 0;
  font-size: 0.8125rem;
  color: var(--cb-bad);
  line-height: 1.45;
}
.cb-fielderror.is-shown { display: flex; }
.cb-fielderror button {
  border: 0;
  background: none;
  padding: 0;
  color: var(--cb-accent);
  font: inherit;
  font-weight: 600;
  text-decoration: underline;
  cursor: pointer;
}
.form-control.is-invalid,
.form-control[aria-invalid="true"] { border-color: var(--cb-bad); }

/* ---------- Toasts — auto-dismiss, carrying an undo ---------- */
.cb-toasts {
  position: fixed;
  left: 0;
  right: 0;
  bottom: max(1rem, env(safe-area-inset-bottom));
  z-index: 50;
  display: flex;
  flex-direction: column-reverse;
  align-items: center;
  gap: 0.5rem;
  padding: 0 1rem;
  pointer-events: none;
}
.cb-toast {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  max-width: 34rem;
  padding: 0.7rem 0.85rem;
  border-radius: var(--cb-radius);
  background: var(--cb-surface);
  box-shadow: var(--cb-shadow-soft);
  font-size: 0.8125rem;
  color: var(--cb-ink);
}
.cb-toast .cb-toast-text { flex: 1; min-width: 0; }
.cb-toast .btn { flex: none; text-transform: none; font-weight: 700; font-size: 0.8125rem; padding: 0.3rem 0.7rem; }
@media (prefers-reduced-motion: no-preference) {
  .cb-toast { animation: cb-toast-in 0.22s cubic-bezier(0.22, 1, 0.36, 1) both; }
}
@keyframes cb-toast-in {
  from { opacity: 0; transform: translateY(0.75rem); }
  to { opacity: 1; transform: none; }
}
.cb-toast.is-leaving { opacity: 0; transition: opacity 0.18s ease-in; }

/* ---------- Help ---------- */
.cb-help { margin-top: 1.75rem; }
.cb-help summary {
  cursor: pointer;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--cb-ink-2);
  padding: 0.5rem 0;
  list-style: none;
}
.cb-help summary::-webkit-details-marker { display: none; }
.cb-help summary::after { content: " ›"; }
.cb-help[open] summary::after { content: " ⌄"; }
.cb-help dl { margin: 0.5rem 0 0; font-size: 0.8125rem; line-height: 1.55; color: var(--cb-ink-2); }
.cb-help dt { font-weight: 700; color: var(--cb-ink); margin-top: 0.75rem; }
.cb-help dt:first-child { margin-top: 0; }
.cb-help dd { margin: 0.15rem 0 0; }

/* ---------- Danger zone — bordered, labelled, last ---------- */
.cb-danger {
  margin-top: 1.25rem;
  padding: 0.85rem;
  border: 1px solid rgba(169, 30, 44, 0.3);
  border-radius: var(--cb-radius);
}
.cb-danger h3 {
  font-size: var(--cb-label-size);
  font-weight: var(--cb-label-weight);
  letter-spacing: var(--cb-label-track);
  text-transform: uppercase;
  color: var(--cb-bad);
  margin: 0 0 0.15rem;
}
.cb-danger p { font-size: 0.75rem; color: var(--cb-ink-2); margin: 0 0 0.6rem; line-height: 1.45; }
.cb-danger .btn { text-transform: none; font-weight: 600; font-size: 0.8125rem; }

.cb-linkbtn {
  border: 0;
  background: none;
  padding: 0.35rem 0;
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--cb-ink-2);
  text-decoration: underline;
  cursor: pointer;
}
.cb-linkbtn.is-danger { color: var(--cb-bad); }

.cb-sronly {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Narrow phones: the action grid gets cramped at 4 across, never let it. */
@media (max-width: 22.5rem) {
  .cb-actions .btn { font-size: 0.8125rem; }
  .cb-head { flex-wrap: wrap; }
}

/* ---------- Interaction states for the app's own controls ----------
   Gate: default + hover + focus-visible + active + disabled, all present. */
/* On a wide screen the bar stops being an edge-to-edge phone chrome and
   becomes the shell's own footer: same width as the content it belongs to,
   sitting on it rather than spanning a monitor. */
@media (min-width: 660px) {
  .cb-nav {
    left: 50%;
    right: auto;
    transform: translateX(-50%);
    width: min(620px, 100%);
    border-radius: var(--cb-radius-xl) var(--cb-radius-xl) 0 0;
  }
}

.cb-navbtn:hover { color: var(--cb-ink); }
.cb-navbtn:active { transform: translateY(0.5px); }
.cb-navbtn.is-on:hover { color: var(--cb-accent); }

.cb-chip:hover { color: var(--cb-ink); }
.cb-chip:active { box-shadow: var(--cb-shadow-inset); }

.cb-linkbtn:hover { color: var(--cb-bad); }
.cb-linkbtn:active { opacity: 0.75; }
.cb-linkbtn:disabled { opacity: 0.5; cursor: default; text-decoration: none; }

.cb-star:hover { color: var(--cb-warn); }

.cb-skip:active { box-shadow: var(--cb-shadow-inset); }

/* ------------------------------------------------------------------ *
 * The gate
 *
 * The same system as the app, not a second one. Every value below is a
 * token off :root — the Neumorphism surface, the same inset field, the
 * same accent, the same radii, the same focus ring — so signing in and
 * using the thing read as one product. It was a dark ember screen for a
 * day; that made the app look like it started somewhere else.
 *
 * What keeps it from being a grey card in a void is the composition, not
 * the palette: left-anchored, bottom-weighted, with the signal meter
 * holding the top half.
 * ------------------------------------------------------------------ */

.cb-gate {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: flex;
  /* Bottom-weighted so the field lands under a thumb rather than under the
     notch, and left-anchored so the screen has a direction. */
  align-items: flex-end;
  justify-content: flex-start;
  padding: max(24px, env(safe-area-inset-top)) 22px max(30px, env(safe-area-inset-bottom));
  overflow: hidden;
  isolation: isolate;
  background: var(--cb-surface);
}

.cb-gate[hidden] { display: none; }

body.is-gated { overflow: hidden; }

/* ---- the signal field ------------------------------------------------ */

/* Eighteen bars, bottom-aligned, heights hand-set so they read as a meter
   rather than a pattern. Not ornament: transmission strength is what this
   app is about. It breathes slowly and out of phase; nothing here loops
   fast enough to ask for attention. */
.cb-gate-field {
  position: absolute;
  left: 0;
  right: 0;
  /* Top half, ending on the line the form starts at: the upper third stops
     being dead space, and no bar sits behind an input. */
  top: 0;
  height: 46%;
  z-index: -1;
  display: flex;
  align-items: flex-end;
  /* Right-anchored, so the meter leans away from the left-hand form column
     instead of sitting symmetrically over it. */
  justify-content: flex-end;
  gap: clamp(7px, 1.4vw, 20px);
  padding: 0 22px;
  pointer-events: none;
  opacity: .5;
  /* Fades upward into the surface and never reaches the form below. */
  mask-image: linear-gradient(to top, #000 0%, rgba(0,0,0,.35) 52%, transparent 94%);
  -webkit-mask-image: linear-gradient(to top, #000 0%, rgba(0,0,0,.35) 52%, transparent 94%);
}

.cb-gate-field .cb-bar {
  flex: 1 1 0;
  /* Capped, or an 18-bar row across 1440px renders as a bar CHART — fat
     blocks that read as data. A meter is thin strokes at any width. */
  max-width: 11px;
  border-radius: 2px 2px 0 0;
  /* The app's one accent, same as the tab count chip and the live dot. */
  background: linear-gradient(to top,
    rgba(45, 76, 200, .62) 0%, rgba(45, 76, 200, 0) 100%);
  transform-origin: bottom;
  animation: cb-meter 5.2s ease-in-out infinite;
}

/* A hand-set curve. An even ramp reads as a chart; this reads as signal. */
.cb-gate-field .cb-bar:nth-child(1)  { height: 18%; animation-delay: -.2s; }
.cb-gate-field .cb-bar:nth-child(2)  { height: 34%; animation-delay: -1.4s; }
.cb-gate-field .cb-bar:nth-child(3)  { height: 26%; animation-delay: -.7s; }
.cb-gate-field .cb-bar:nth-child(4)  { height: 52%; animation-delay: -2.1s; }
.cb-gate-field .cb-bar:nth-child(5)  { height: 41%; animation-delay: -.5s; }
.cb-gate-field .cb-bar:nth-child(6)  { height: 68%; animation-delay: -3.0s; }
.cb-gate-field .cb-bar:nth-child(7)  { height: 47%; animation-delay: -1.1s; }
.cb-gate-field .cb-bar:nth-child(8)  { height: 82%; animation-delay: -2.6s; }
.cb-gate-field .cb-bar:nth-child(9)  { height: 58%; animation-delay: -.9s; }
.cb-gate-field .cb-bar:nth-child(10) { height: 94%; animation-delay: -3.7s; }
.cb-gate-field .cb-bar:nth-child(11) { height: 63%; animation-delay: -1.8s; }
.cb-gate-field .cb-bar:nth-child(12) { height: 77%; animation-delay: -4.2s; }
.cb-gate-field .cb-bar:nth-child(13) { height: 44%; animation-delay: -2.4s; }
.cb-gate-field .cb-bar:nth-child(14) { height: 61%; animation-delay: -.3s; }
.cb-gate-field .cb-bar:nth-child(15) { height: 33%; animation-delay: -3.3s; }
.cb-gate-field .cb-bar:nth-child(16) { height: 49%; animation-delay: -1.6s; }
.cb-gate-field .cb-bar:nth-child(17) { height: 24%; animation-delay: -2.9s; }
.cb-gate-field .cb-bar:nth-child(18) { height: 15%; animation-delay: -.8s; }

@keyframes cb-meter {
  0%, 100% { transform: scaleY(.72); opacity: .55; }
  50%      { transform: scaleY(1);   opacity: .95; }
}

/* Motion here is decorative and carries no information, so it stops entirely
   rather than merely slowing down. The bar heights alone still read. */
@media (prefers-reduced-motion: reduce) {
  .cb-gate-field .cb-bar { animation: none; transform: none; }
}

/* ---- the column ------------------------------------------------------ */

.cb-gate-inner {
  position: relative;
  width: 100%;
  max-width: 380px;
}

.cb-gate-head { margin-bottom: 26px; }

.cb-gate-eyebrow {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 14px;
  font-size: var(--cb-label-size);
  font-weight: var(--cb-label-weight);
  letter-spacing: var(--cb-label-track);
  text-transform: uppercase;
  color: var(--cb-ink-2);
}

/* The standby lamp. Same dot, same pulse as the live indicator in the app
   header — it is the one thing on the screen that moves near text, which is
   why it is small and slow. */
.cb-standby {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--cb-accent);
  box-shadow: 0 0 0 0 rgba(45, 76, 200, .5);
  animation: cb-standby 3.4s ease-in-out infinite;
}

@keyframes cb-standby {
  0%, 100% { box-shadow: 0 0 0 0 rgba(45, 76, 200, .45); opacity: .85; }
  50%      { box-shadow: 0 0 0 6px rgba(45, 76, 200, 0); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .cb-standby { animation: none; }
}

/* The masthead wordmark, scaled up. Same weight, same tracking, same accent
   on the second word as `.cb-wordmark` in the app — it is the same lockup,
   sized for a screen that holds nothing else. */
.cb-gate-mark {
  margin: 0;
  font-size: clamp(2.9rem, 15vw, 3.9rem);
  font-weight: 700;
  line-height: .92;
  letter-spacing: -.045em;
  color: var(--cb-ink);
}
.cb-gate-mark em {
  font-style: normal;
  color: var(--cb-accent);
}

/* ---- the form -------------------------------------------------------- */

.cb-gate-form { position: relative; }

.cb-field { margin-bottom: 14px; }

.cb-gate-label {
  display: block;
  margin-bottom: 7px;
  font-size: var(--cb-label-size);
  font-weight: var(--cb-label-weight);
  letter-spacing: var(--cb-label-track);
  text-transform: uppercase;
  color: var(--cb-ink-2);
}

.cb-input-wrap { position: relative; }

/* The app's own field: pressed into the surface, no border, kit radius.
   Matched against the computed style of `#url` on the Link tab, so the two
   are the same control at two sizes rather than two designs. */
.cb-input {
  width: 100%;
  /* 16px min, or iOS Safari zooms the viewport on focus and the layout jumps. */
  font-size: 1rem;
  font-weight: 300;
  line-height: 1.4;
  padding: 14px 16px;
  color: var(--cb-ink);
  background: var(--cb-surface);
  border: 0;
  border-radius: var(--cb-radius);
  box-shadow: var(--cb-shadow-inset);
  transition: box-shadow .16s ease;
  -webkit-appearance: none;
  appearance: none;
}
.cb-input-wrap .cb-input { padding-right: 48px; }

.cb-input::placeholder { color: var(--cb-ink-soft); }

/* Same ring the app puts on every focusable thing, for the same reason: the
   kit ships none at all. */
.cb-input:focus-visible {
  outline: 3px solid var(--cb-accent);
  outline-offset: 2px;
}
.cb-input:focus { outline: none; }
.cb-input:focus:not(:focus-visible) { box-shadow: var(--cb-shadow-inset); }

/* A field that has already errored turns live (JS switches it to on-input),
   so the mark must read as "still wrong" until the fix lands. There is no
   border to recolour on a pressed field, so the ring is drawn as a second
   shadow layer on top of the kit's. */
.cb-input.is-bad,
.cb-input.is-bad:focus {
  box-shadow: var(--cb-shadow-inset), 0 0 0 2px var(--cb-bad);
}
.cb-input.is-good {
  box-shadow: var(--cb-shadow-inset), 0 0 0 2px rgba(24, 99, 75, .55);
}

.cb-reveal {
  position: absolute;
  right: 6px;
  top: 50%;
  transform: translateY(-50%);
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  padding: 0;
  border: 0;
  border-radius: var(--cb-radius);
  background: none;
  color: var(--cb-ink-2);
  cursor: pointer;
}
.cb-reveal:hover { color: var(--cb-ink); }
.cb-reveal:focus-visible {
  outline: 3px solid var(--cb-accent);
  outline-offset: 2px;
}
/* Pressed = password is visible = the slash is off. */
.cb-reveal .cb-eye-slash { display: none; }
.cb-reveal[aria-pressed="false"] .cb-eye-slash { display: block; }

.cb-gate-form .cb-fielderror {
  margin: -2px 0 0;
  color: var(--cb-bad);
}

/* The app's primary action, at full width. Same fill, same ink, same raised
   shadow and radius as the Play button on the Link tab. */
.cb-gate-btn {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  width: 100%;
  margin-top: 18px;
  /* A thumb target, not a mouse target. */
  min-height: 50px;
  padding: 14px 18px;
  font-size: .95rem;
  font-weight: 700;
  letter-spacing: .01em;
  color: #ecf0f3;
  background: var(--cb-accent);
  border: 0;
  border-radius: var(--cb-radius);
  box-shadow: var(--cb-shadow-light);
  cursor: pointer;
  transition: transform .12s ease, box-shadow .16s ease, filter .16s ease;
}
.cb-gate-btn:hover { filter: brightness(1.08); }
/* Pressed, the way every raised control in this kit presses. */
.cb-gate-btn:active {
  transform: translateY(1px);
  box-shadow: var(--cb-shadow-inset);
}
.cb-gate-btn:focus-visible {
  outline: 3px solid var(--cb-accent);
  outline-offset: 2px;
}
.cb-gate-btn[disabled] {
  cursor: default;
  filter: saturate(.55) brightness(1.05);
  box-shadow: none;
}

.cb-gate-btn-arrow { transition: transform .16s ease; }
.cb-gate-btn:hover .cb-gate-btn-arrow { transform: translateX(3px); }
/* While it is working the arrow would say "go" twice. */
.cb-gate-btn.is-busy .cb-gate-btn-arrow { display: none; }

.cb-gate-spinner {
  display: none;
  width: 16px;
  height: 16px;
  border: 2px solid rgba(236, 240, 243, .35);
  border-top-color: #ecf0f3;
  border-radius: 50%;
  animation: cb-spin .7s linear infinite;
}
.cb-gate-btn.is-busy .cb-gate-spinner { display: block; }

@keyframes cb-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .cb-gate-spinner { animation-duration: 1.6s; }
}

.cb-gate-foot {
  margin: 16px 0 0;
  font-size: .75rem;
  line-height: 1.5;
  color: var(--cb-ink-2);
}

/* Short landscape phone: the meter is the first thing worth losing, and the
   mark stops being the largest thing on a 380px-tall screen. */
@media (max-height: 560px) {
  .cb-gate-field { display: none; }
  .cb-gate-mark { font-size: 2.2rem; }
  .cb-gate-head { margin-bottom: 18px; }
}

/* Desktop: the column stays left. A wide screen is not a reason to centre a
   380px form in 1400px of nothing. */
@media (min-width: 720px) {
  .cb-gate { padding-left: clamp(40px, 8vw, 120px); }
  .cb-gate-mark { font-size: 4.6rem; }
}

/* Build stamp — quiet, but present, so "did my change ship" is a glance
   rather than an argument. --cb-mute is 2.04:1 and banned for text, so this
   uses --cb-ink-2 even though it is secondary information. */
.cb-build {
  display: flex;
  gap: 14px;
  align-items: center;
  justify-content: center;
  margin: 20px 0 4px;
  font-size: .72rem;
  font-variant-numeric: tabular-nums;
  color: var(--cb-ink-2);
}

.cb-linkbtn {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}

.cb-linkbtn:hover { color: var(--cb-ink); }
.cb-linkbtn[hidden] { display: none; }

/* The deep-scan offer is a next step, not a failure — it reads as an
   invitation, not as the red the field errors use. */
.cb-deepnote {
  margin-top: 6px;
  font-size: .8rem;
  line-height: 1.55;
  color: var(--cb-ink-2);
}

/* ------------------------------------------------------------------ *
 * Pull to refresh
 *
 * The ring is drawn with a conic gradient masked to an annulus, so the
 * same element that stretches with the finger is the one that spins. A
 * separate spinner swapped in at release is the tell of a fake gesture.
 * ------------------------------------------------------------------ */

.cb-ptr {
  position: fixed;
  top: max(6px, env(safe-area-inset-top));
  left: 50%;
  /* Above the gate (90). At 60 the gesture fired on the login screen and the
     ring was painted underneath it, which reads as nothing happening. */
  z-index: 95;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  opacity: 0;
  transform: translate3d(-50%, 0, 0);
  border-radius: 50%;
  background: var(--cb-surface);
  box-shadow: var(--cb-shadow-light);
  pointer-events: none;
}

.cb-ptr-ring {
  --cb-ptr-sweep: 0deg;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: conic-gradient(var(--cb-accent) var(--cb-ptr-sweep), transparent 0);
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 2.5px), #000 calc(100% - 2.5px));
          mask: radial-gradient(farthest-side, transparent calc(100% - 2.5px), #000 calc(100% - 2.5px));
}

/* Past the threshold: the ring is full and the plate lifts slightly, so
   "let go now and it fires" is legible before the finger leaves. */
.cb-ptr.is-armed { box-shadow: var(--cb-shadow-soft); }
.cb-ptr.is-armed .cb-ptr-ring { --cb-ptr-sweep: 360deg; }

.cb-ptr.is-running .cb-ptr-ring {
  --cb-ptr-sweep: 300deg;
  animation: cb-spin .8s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .cb-ptr.is-running .cb-ptr-ring { animation-duration: 1.8s; }
}

/* ------------------------------------------------------------------ *
 * Owner surfaces — scope switch and the People list
 * ------------------------------------------------------------------ */

.cb-whoami {
  margin-left: .5rem;
  font-weight: 600;
  color: var(--cb-ink-2);
}
.cb-whoami::before { content: '· '; color: var(--cb-ink-2); }

.cb-scoperow {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 14px;
}

.cb-scoperow-label {
  font-size: var(--cb-label-size);
  font-weight: var(--cb-label-weight);
  letter-spacing: var(--cb-label-track);
  text-transform: uppercase;
  color: var(--cb-ink-2);
}

.cb-segment {
  display: inline-flex;
  padding: 3px;
  border-radius: 999px;
  background: var(--cb-surface);
  box-shadow: var(--cb-shadow-inset);
}

.cb-segment-btn {
  min-height: 34px;
  padding: 6px 15px;
  font-size: .8rem;
  font-weight: 600;
  color: var(--cb-ink-2);
  background: none;
  border: 0;
  border-radius: 999px;
  cursor: pointer;
  transition: color .16s ease, box-shadow .16s ease;
}
.cb-segment-btn.is-on {
  color: var(--cb-accent);
  background: var(--cb-surface);
  box-shadow: var(--cb-shadow-light);
}
.cb-segment-btn:focus-visible {
  outline: 2px solid var(--cb-accent);
  outline-offset: 2px;
}

.cb-sub {
  margin: 22px 0 4px;
  font-size: .95rem;
  font-weight: 600;
  color: var(--cb-ink);
}
.cb-adduser .cb-sub { margin-top: 0; }

.cb-hint {
  margin: 0 0 16px;
  font-size: .82rem;
  line-height: 1.5;
  color: var(--cb-ink-2);
}

/* Live checklist. Unmet is stated plainly rather than coloured red — it is
   not an error yet, it is a rule not reached. */
.cb-rules {
  margin: 8px 0 0;
  padding: 0;
  list-style: none;
  font-size: .78rem;
  line-height: 1.7;
  color: var(--cb-ink-2);
}
.cb-rules li { display: flex; gap: 7px; align-items: baseline; }
.cb-rules li::before {
  content: '○';
  color: var(--cb-ink-2);
}
.cb-rules li.is-met { color: var(--cb-ok); }
.cb-rules li.is-met::before { content: '✓'; color: var(--cb-ok); }

.cb-userrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding: 13px 15px;
  margin-bottom: 9px;
  border-radius: var(--cb-radius);
  background: var(--cb-surface);
  box-shadow: var(--cb-shadow-light);
}
.cb-userrow.is-off { opacity: .62; }

.cb-userrow-main { display: flex; align-items: baseline; gap: 9px; min-width: 0; }

.cb-userrow-name {
  font-weight: 600;
  color: var(--cb-ink);
  overflow-wrap: anywhere;
}

.cb-userrow-tag {
  font-size: var(--cb-label-size);
  font-weight: var(--cb-label-weight);
  letter-spacing: var(--cb-label-track);
  text-transform: uppercase;
  color: var(--cb-ink-2);
}
.cb-userrow-tag.is-owner { color: var(--cb-accent); }

.cb-userrow-you {
  font-size: .72rem;
  font-weight: 600;
  color: var(--cb-ink-2);
}

.cb-userrow-acts { display: flex; gap: 14px; align-items: center; }

/* ------------------------------------------------------------------ *
 * The stream table
 *
 * Six decisions, made on purpose rather than defaulted:
 *
 *   ALIGN    Length and Size are right-aligned on tabular figures, so the
 *            last digit lines up and two sizes can be compared by eye.
 *   ROWS     Hairline divider, no zebra. Six columns is nowhere near the
 *            ~12 where striping starts earning its noise.
 *   DENSITY  48px — this is an everyday read, not an ops console.
 *   STICKY   Header sticks. The identity column does not need to, because
 *            the table never scrolls sideways: below 640px it stacks.
 *   CELLS    Loading is a shimmer, missing is an em-dash carrying its
 *            reason, a value is a value. One line per row, ellipsis and a
 *            tooltip for a long name — numbers never truncate.
 *   ACTIONS  Play is always visible, because on a phone there is no hover
 *            to reveal it with.
 *
 * The breakpoint is a container query, not a viewport one: this table has
 * to hold up inside the browse panel as well as full width, and a media
 * query pinned to a phone size would lay out a desktop table in a 380px
 * column the moment the panel narrowed.
 * ------------------------------------------------------------------ */

.cb-tablewrap {
  container-type: inline-size;
  border-radius: var(--cb-radius);
  background: var(--cb-surface);
  box-shadow: var(--cb-shadow-light);
  padding: 2px 10px 6px;
}

.cb-table {
  width: 100%;
  border-collapse: collapse;
  font-size: .8125rem;
  color: var(--cb-ink);
}

.cb-table thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--cb-surface);
  padding: 10px 8px 8px;
  font-size: var(--cb-label-size);
  font-weight: var(--cb-label-weight);
  letter-spacing: var(--cb-label-track);
  text-transform: uppercase;
  color: var(--cb-ink-2);
  text-align: left;
  border-bottom: 1px solid var(--cb-line);
  white-space: nowrap;
}

.cb-table td {
  padding: 9px 8px;
  vertical-align: middle;
  border-bottom: 1px solid var(--cb-line);
}
.cb-table tbody tr:last-of-type td { border-bottom: 0; }

/* Numbers: right, tabular, never wrapped, never truncated. */
.cb-table th.cb-t-num,
.cb-table td.cb-t-num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  width: 1%;
}
.cb-table th.cb-t-type,
.cb-table td.cb-t-type,
.cb-table th.cb-t-q,
.cb-table td.cb-t-q,
.cb-table th.cb-t-act,
.cb-table td.cb-t-act { width: 1%; white-space: nowrap; }
.cb-table td.cb-t-q { font-variant-numeric: tabular-nums; }
.cb-table td.cb-t-act { text-align: right; }

/* Identity: as wide as is left over, one line, ellipsis + tooltip. */
.cb-table td.cb-t-name { width: 100%; min-width: 0; max-width: 0; }

.cb-t-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  text-align: left;
  color: inherit;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
.cb-t-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.cb-t-chev {
  flex: none;
  width: 6px;
  height: 6px;
  border-right: 2px solid var(--cb-ink-soft);
  border-bottom: 2px solid var(--cb-ink-soft);
  transform: rotate(45deg) translate(-2px, -2px);
}
.cb-t-row.is-open .cb-t-chev { transform: rotate(-135deg) translate(-2px, -2px); }
@media (prefers-reduced-motion: no-preference) {
  .cb-t-chev { transition: transform .18s ease; }
}

.cb-t-sub {
  display: block;
  margin-top: 1px;
  font-size: .6875rem;
  color: var(--cb-ink-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cb-table .badge { font-size: .625rem; letter-spacing: .04em; }

/* An em-dash is a state, so it is drawn as one — quieter than a value,
   and never mistakable for a cell that failed to render. */
.cb-table td.is-missing { color: var(--cb-ink-2); }

.cb-skel {
  display: inline-block;
  width: 3.2em;
  height: .7em;
  border-radius: 3px;
  background: var(--cb-line);
  vertical-align: middle;
}
@media (prefers-reduced-motion: no-preference) {
  .cb-skel {
    background: linear-gradient(90deg, var(--cb-line) 25%, #e9edf5 50%, var(--cb-line) 75%);
    background-size: 240% 100%;
    animation: cb-shimmer 1.1s linear infinite;
  }
  @keyframes cb-shimmer {
    from { background-position: 120% 0; }
    to   { background-position: -120% 0; }
  }
}

.cb-table td.cb-t-act .btn {
  min-height: 2rem;
  padding: .25rem .7rem;
  text-transform: none;
  font-size: .78125rem;
  font-weight: 600;
}

/* Row expand — the third-priority fields live here rather than nowhere. */
.cb-t-detailrow td { padding: 0 8px 12px; border-bottom: 1px solid var(--cb-line); }
.cb-t-defs {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 4px 14px;
  margin: 0 0 10px;
  font-size: .75rem;
}
.cb-t-defs dt { color: var(--cb-ink-2); font-weight: 600; }
.cb-t-defs dd {
  margin: 0;
  color: var(--cb-ink);
  overflow-wrap: anywhere;
  font-variant-numeric: tabular-nums;
}
.cb-t-detail .btn { text-transform: none; font-size: .75rem; font-weight: 600; }

.cb-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---------- Stacked, below 640px of the table's own container ----------
 *
 * Identity and the value the person is judging on stay on line one;
 * everything else drops to line two with its label. Nothing is hidden and
 * nothing scrolls sideways. */
@container (max-width: 640px) {
  .cb-table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }

  /* Four columns, two rows. Line one is the name and the number the
     person is judging on; line two is the state, in its own slots rather
     than stacked on top of each other. */
  .cb-table tbody tr.cb-t-row {
    display: grid;
    grid-template-columns: max-content max-content minmax(0, 1fr) max-content;
    align-items: center;
    gap: 4px 10px;
    padding: 10px 0;
    border-bottom: 1px solid var(--cb-line);
  }
  .cb-table tbody tr.cb-t-row td {
    border: 0;
    padding: 0;
    width: auto;
    max-width: none;
  }

  .cb-table td.cb-t-name { grid-column: 1 / 4; grid-row: 1; min-width: 0; }
  .cb-table td.cb-t-num[data-label="Size"] {
    grid-column: 4; grid-row: 1;
    font-weight: 600;
  }
  .cb-table td.cb-t-type { grid-column: 1; grid-row: 2; }
  .cb-table td.cb-t-q { grid-column: 2; grid-row: 2; }
  .cb-table td.cb-t-num[data-label="Length"] {
    grid-column: 3; grid-row: 2;
    text-align: left;
  }
  .cb-table td.cb-t-act { grid-column: 4; grid-row: 2; }

  .cb-table td.cb-t-q,
  .cb-table td.cb-t-num[data-label="Length"] {
    font-size: .75rem;
    color: var(--cb-ink-2);
  }

  /* Label the ambiguous only. "720p" and "2.1 GB" say what they are;
     "1:04:12" on its own could be a length or a timestamp. */
  .cb-table td.cb-t-num[data-label="Length"]::before {
    content: "Length ";
    color: var(--cb-ink-2);
  }
  .cb-table td.cb-t-num[data-label="Length"].is-loading::before { content: "Length "; }

  .cb-table .cb-t-detailrow td { padding: 0 0 12px; }
}

/* ------------------------------------------------------------------ *
 * Scan progress
 * ------------------------------------------------------------------ */

.cb-scan {
  border-radius: var(--cb-radius);
  background: var(--cb-surface);
  box-shadow: var(--cb-shadow-light);
  padding: 14px 16px 16px;
  /* The panel scrolls itself into view when a scan starts (see
     startScanProgress). `block: 'nearest'` does the minimum, which parked it
     flush on the bottom edge of a 664px phone — in view, but reading as cut
     off, and its soft shadow clipped. This buys the gap. */
  scroll-margin: 16px;
}

.cb-scan-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 10px;
}
.cb-scan-stage {
  font-size: .875rem;
  font-weight: 600;
  color: var(--cb-ink);
}
.cb-scan-clock {
  font-size: .75rem;
  font-variant-numeric: tabular-nums;
  color: var(--cb-ink-2);
  white-space: nowrap;
}

.cb-scan-track {
  height: 6px;
  border-radius: 3px;
  background: var(--cb-line);
  box-shadow: var(--cb-shadow-inset);
  overflow: hidden;
}
.cb-scan-fill {
  height: 100%;
  width: 0;
  border-radius: 3px;
  background: var(--cb-accent);
}
@media (prefers-reduced-motion: no-preference) {
  .cb-scan-fill { transition: width .25s linear; }
}

/* Past the estimate the bar stops claiming to know how far along it is. */
.cb-scan-track.is-waiting .cb-scan-fill {
  background: linear-gradient(90deg,
    var(--cb-line) 0%, var(--cb-accent) 40%, var(--cb-accent) 60%, var(--cb-line) 100%);
  background-size: 220% 100%;
}
@media (prefers-reduced-motion: no-preference) {
  .cb-scan-track.is-waiting .cb-scan-fill {
    animation: cb-scan-wait 1.4s ease-in-out infinite;
  }
  @keyframes cb-scan-wait {
    from { background-position: 120% 0; }
    to   { background-position: -120% 0; }
  }
}

.cb-scan-note {
  margin: 10px 0 0;
  font-size: .75rem;
  line-height: 1.5;
  color: var(--cb-ink-2);
}

/* ---------- A file on this phone ----------
   The picker sits under the address field because it answers the same
   question the field does — "what do you want to play" — and splitting the
   two into separate tabs would have made five tabs on a 390px strip. */
.cb-device {
  margin-top: 0.75rem;
}
.cb-device .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  width: 100%;
  min-height: 2.875rem;
  text-transform: none;
  font-weight: 600;
  font-size: 0.875rem;
}
.cb-device-note {
  margin: 0.5rem 0 0;
  font-size: 0.75rem;
  line-height: 1.5;
  color: var(--cb-ink-2);
}

.cb-pick {
  margin-top: 0.75rem;
  border-radius: var(--cb-radius);
  background: var(--cb-surface);
  box-shadow: var(--cb-shadow-light);
  padding: 14px 16px 16px;
  scroll-margin: 16px;
}
.cb-pick-name {
  margin: 0 0 0.25rem;
  font-size: 0.9375rem;
  font-weight: 700;
  color: var(--cb-ink);
  /* A phone camera's filename is 40 characters of nothing; the panel gives
     way rather than the layout. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cb-pick-sub,
.cb-pick-note,
.cb-pick-clock {
  margin: 0 0 0.75rem;
  font-size: 0.8125rem;
  line-height: 1.55;
  color: var(--cb-ink-2);
}
.cb-pick-clock {
  /* The figures change four times a second. Proportional digits jitter the
     whole line as they do it. */
  font-variant-numeric: tabular-nums;
  margin: 0.55rem 0 0.75rem;
}
.cb-pick-note { margin-bottom: 0; }
.cb-pick-error {
  margin: 0.75rem 0 0;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--cb-bad);
}
.cb-pick .btn {
  text-transform: none;
  font-weight: 600;
  font-size: 0.8125rem;
  min-height: 2.5rem;
  padding-left: 0.9rem;
  padding-right: 0.9rem;
}

/* ---------- Stream host ---------- */
.cb-host-body { padding: 0.25rem 0 0.75rem; }
.cb-host-empty {
  margin: 0.5rem 0;
  font-size: 0.8125rem;
  color: var(--cb-ink-2);
}
.cb-host-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.4rem 0;
  border-bottom: 1px solid var(--cb-line);
  font-size: 0.8125rem;
}
.cb-host-row:last-of-type { border-bottom: 0; }
.cb-host-k { color: var(--cb-ink-2); }
.cb-host-v {
  font-weight: 700;
  color: var(--cb-ink);
  /* Every value in this panel is a quantity, and they are read down the
     column against each other. */
  font-variant-numeric: tabular-nums;
  text-align: right;
}
/* A group label inside the host panel. The panel was one flat run of a
   dozen numbers; what the box is doing and what is on its disk are two
   different questions and read faster with a line between them. */
.cb-host-head {
  margin: 1rem 0 0.1rem;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--cb-ink-2);
}
.cb-host-body > .cb-host-head:first-child { margin-top: 0; }
/* Load over 1.0 per core, or memory over 90%. Amber, but the readable
   amber — the token one is a fill colour and fails as text. */
.cb-host-row.is-warn .cb-host-v { color: var(--cb-warn-ink); }

.cb-host-path {
  margin: 0.6rem 0 0;
  font-family: var(--font-family-monospace, monospace);
  font-size: 0.6875rem;
  color: var(--cb-ink-2);
  word-break: break-all;
}
.cb-host-files { margin-top: 0.9rem; }
.cb-host-file {
  display: grid;
  /* The size gets a fixed slot rather than an auto one. Auto sizes the
     column to the widest value and then leaves each number left-aligned
     inside it, so 1.2 GB and 10 MB start together and end in different
     places — measured, both ended at x=313 by accident of the grid and
     neither lined up on its last digit. A receipt column lines up on the
     right or it is not a column. */
  grid-template-columns: minmax(0, 1fr) 4.5rem auto;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0;
  border-top: 1px solid var(--cb-line);
  font-size: 0.8125rem;
}
.cb-host-fname {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--cb-ink);
}
.cb-host-fsize {
  color: var(--cb-ink-2);
  font-variant-numeric: tabular-nums;
  text-align: right;
}
/* A record whose file has gone reads as an em-dash, and the dash is not a
   quantity — it does not get the numeric treatment. */
.cb-host-fsize.is-missing { color: var(--cb-ink-soft); font-variant-numeric: normal; }
.cb-host-acts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1.25rem;
  margin-top: 0.9rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--cb-line);
}

/* ---------- Put this screen on the TV ---------- */
.cb-screen-body { padding: 0.25rem 0 0.9rem; }
.cb-screen-lead,
.cb-screen-note,
.cb-screen-alt {
  margin: 0 0 0.6rem;
  font-size: 0.8125rem;
  line-height: 1.6;
  color: var(--cb-ink-2);
}
.cb-screen-alt { color: var(--cb-ink); font-weight: 600; }
.cb-screen-steps {
  margin: 0 0 0.6rem;
  padding-left: 1.15rem;
  font-size: 0.8125rem;
  line-height: 1.65;
  color: var(--cb-ink);
}
.cb-screen-steps li { margin-bottom: 0.25rem; }
.cb-screen-rule {
  border: 0;
  border-top: 1px solid var(--cb-line);
  margin: 0.9rem 0;
}
.cb-screen-live {
  margin: 0 0 0.6rem;
  font-size: 0.8125rem;
  font-weight: 700;
  color: var(--cb-ink);
  font-variant-numeric: tabular-nums;
}
.cb-screen-body .btn {
  text-transform: none;
  font-weight: 600;
  font-size: 0.8125rem;
  min-height: 2.5rem;
  padding-left: 0.9rem;
  padding-right: 0.9rem;
}

/* ---------- Bilibili ---------- */
.cb-bili-body { padding: 0.25rem 0 0.9rem; }
.cb-bili-note,
.cb-bili-small {
  margin: 0 0 0.7rem;
  font-size: 0.8125rem;
  line-height: 1.6;
  color: var(--cb-ink-2);
}
.cb-bili-small { font-size: 0.75rem; }

/* Somewhere to paste a sign-in. Folded shut, because the QR is the ordinary
   way in and this is the way in when there is only one phone in the room —
   an alternative offered, not a form to fill in first. */
.cb-bili-paste { margin-top: 1rem; }
.cb-bili-paste > summary {
  cursor: pointer;
  list-style: none;
  padding: 0.5rem 0;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--cb-ink-2);
}
.cb-bili-paste > summary::-webkit-details-marker { display: none; }
.cb-bili-paste > summary::after { content: " ›"; }
.cb-bili-paste[open] > summary::after { content: " ⌄"; }

/* Monospace and wrapped: a SESSDATA is sixty characters of percent-encoded
   noise, and it has to be readable enough to see whether it arrived whole. */
.cb-bili-cookie {
  margin-bottom: 0.7rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.8125rem;
  line-height: 1.5;
  resize: vertical;
  overflow-wrap: anywhere;
}
.cb-bili-body .btn,
.cb-bili-body a.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  text-transform: none;
  font-weight: 600;
  font-size: 0.8125rem;
  min-height: 2.5rem;
  padding-left: 0.9rem;
  padding-right: 0.9rem;
  margin-bottom: 0.7rem;
}
/* Cancel and Copy sit under the button they qualify, on their own line each
   so neither is mistaken for the primary action. */
.cb-bili-body .cb-linkbtn { display: block; margin-top: 0.35rem; }

/* The "no TV found" explainer. Borrows the log's disclosure chrome; the body
   is prose, so it gets reading measure and the UI font rather than the log's
   monospace column. */
.cb-help > summary { color: var(--cb-ink); }
.cb-help-body { padding: 0 0 0.9rem; max-width: 62ch; }
.cb-help-body p { margin: 0 0 0.7rem; font-size: 0.9rem; line-height: 1.55; color: var(--cb-ink-soft); }
.cb-help-body ol { margin: 0 0 0.7rem; padding-left: 1.2rem; }
.cb-help-body li { margin-bottom: 0.55rem; font-size: 0.9rem; line-height: 1.5; color: var(--cb-ink-soft); }
.cb-help-body b { color: var(--cb-ink); font-weight: 600; }
.cb-help-body p:last-child { margin-bottom: 0; }

/* ---------- TV mode ----------
   The phone as a remote for a TV that plays the film in its own browser.
   Same panel as the cast log and subtitles so it reads as one more tool on
   the loaded film, not a second app. */
.cb-tv-body { padding: 0 0 0.9rem; }
.cb-tv-lead { margin: 0 0 0.6rem; font-size: 0.9rem; line-height: 1.55; color: var(--cb-ink-soft); max-width: 62ch; }
.cb-tv-steps { margin: 0 0 0.9rem; padding-left: 1.2rem; font-size: 0.875rem; line-height: 1.55; color: var(--cb-ink-2); }
.cb-tv-steps li + li { margin-top: 0.3rem; }
.cb-tv-steps b { color: var(--cb-ink); font-weight: 700; overflow-wrap: anywhere; }

.cb-tv-pair { display: flex; gap: 0.5rem; }
/* One field, six digits: a paste lands whole and backspace is the
   keyboard's own. Tabular figures and tracking give it the boxes' rhythm
   without six inputs pretending to be one value. */
.cb-tv-code.form-control {
  flex: 1; min-width: 0; min-height: 2.875rem;
  font-size: 1.35rem; font-weight: 700; letter-spacing: 0.32em;
  font-variant-numeric: tabular-nums; text-align: center;
}
.cb-tv-code[aria-invalid="true"] { box-shadow: var(--cb-shadow-inset), 0 0 0 2px var(--cb-bad); }
.cb-tv-pair .btn { min-height: 2.875rem; min-width: 5.5rem; }
.cb-tv-error { margin: 0.5rem 0 0; font-size: 0.8125rem; line-height: 1.45; color: var(--cb-bad); }

@keyframes cb-tv-shake {
  0%, 100% { transform: translateX(0); }
  20%, 60% { transform: translateX(-6px); }
  40%, 80% { transform: translateX(6px); }
}
.cb-tv-code.is-shake { animation: cb-tv-shake 0.32s ease-in-out; }

.cb-tv-where { display: flex; align-items: center; gap: 0.5rem; margin: 0 0 0.5rem; font-size: 0.8125rem; color: var(--cb-ink-2); font-variant-numeric: tabular-nums; }
.cb-tv-dot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--cb-mute); }
.cb-tv-dot.is-live { background: var(--cb-ok); }
.cb-tv-title { margin: 0 0 0.15rem; font-size: 1rem; font-weight: 700; color: var(--cb-ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cb-tv-state { margin: 0 0 0.75rem; font-size: 0.875rem; line-height: 1.45; color: var(--cb-ink-soft); }
.cb-tv-state.is-bad { color: var(--cb-warn-ink); }

.cb-tv-scrub { display: flex; align-items: center; gap: 0.6rem; font-size: 0.8125rem; color: var(--cb-ink-2); font-variant-numeric: tabular-nums; }
.cb-tv-scrub input { flex: 1; min-width: 0; min-height: 44px; accent-color: var(--cb-accent); }
.cb-tv-buttons { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr) minmax(0, 1fr); gap: 0.6rem; margin: 0.35rem 0 0.9rem; }
.cb-tv-btn.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.2rem;
  min-height: 3.25rem; padding: 0 0.4rem; font-weight: 700; font-size: 0.8125rem;
  font-variant-numeric: tabular-nums;
}
.cb-tv-btn svg[hidden] { display: none; }

@media (prefers-reduced-motion: reduce) {
  .cb-tv-code.is-shake { animation: none; }
}


/* ==================================================================== *
 * Cast as two screens (2026-09-12)
 *
 * Rj: "everything on one page looks very complicated, flow doesn't make
 * sense from page 2 to page 1". Screen 1 picks a film, screen 2 watches it.
 * Both carry a step marker so the pair reads as a sequence, and each links
 * to the other in words: "Pick another" going back, the Now playing bar
 * going forward. Same clay kit as every other screen, no new colours.
 * ==================================================================== */

.cb-step {
  display: inline-flex; align-items: center; gap: 0.45rem;
  margin: 0 0 0.35rem;
  font-size: var(--cb-label-size); font-weight: var(--cb-label-weight);
  letter-spacing: var(--cb-label-track); text-transform: uppercase;
  color: var(--cb-ink-2);
}
.cb-step b {
  display: inline-grid; place-items: center;
  width: 1.35rem; height: 1.35rem; border-radius: 50%;
  background: var(--cb-surface); box-shadow: var(--cb-shadow-inset);
  color: var(--cb-accent); font-size: 0.6875rem; letter-spacing: 0;
  font-variant-numeric: tabular-nums;
}

/* ---------- Screen 1: pick ---------- */
.cb-pickhead { margin: 0.5rem 0 1rem; }
.cb-picktitle {
  margin: 0; font-size: clamp(1.6rem, 7vw, 2.1rem); line-height: 1.1;
  font-weight: 700; letter-spacing: -0.01em; color: var(--cb-ink);
  overflow-wrap: anywhere;
}
#view-cast #linkForm .form-control-lg { min-height: 3.5rem; font-size: 1rem; }
#view-cast #linkForm .btn { min-width: 5.5rem; }

.cb-nowbar {
  display: flex; align-items: center; gap: 0.8rem;
  width: 100%; min-height: 3.5rem; margin: 0.25rem 0 0.9rem; padding: 0.6rem 0.9rem;
  border: 0; border-radius: var(--cb-radius-xl);
  background: var(--cb-surface); box-shadow: var(--cb-shadow-soft);
  color: var(--cb-ink); text-align: left; font: inherit; cursor: pointer;
}
.cb-nowbar[hidden] { display: none; }
.cb-nowbar:active { box-shadow: var(--cb-shadow-inset); }
.cb-nowbar:focus-visible { outline: 2px solid var(--cb-accent); outline-offset: 3px; }
.cb-nowbar > svg { flex: none; color: var(--cb-ink-soft); }
.cb-nowbar-text { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.cb-nowbar-k {
  font-size: var(--cb-label-size); font-weight: var(--cb-label-weight);
  letter-spacing: var(--cb-label-track); text-transform: uppercase; color: var(--cb-accent);
}
.cb-nowbar strong { font-size: 0.9375rem; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cb-nowbar-eq { flex: none; display: inline-flex; align-items: flex-end; gap: 2px; width: 1.1rem; height: 1rem; }
.cb-nowbar-eq i { flex: 1; height: 40%; border-radius: 1px; background: var(--cb-accent); }
.cb-nowbar-eq i:nth-child(2) { height: 100%; }
.cb-nowbar-eq i:nth-child(3) { height: 65%; }
@media (prefers-reduced-motion: no-preference) {
  .cb-nowbar-eq i { animation: cb-eq 1.1s ease-in-out infinite alternate; transform-origin: bottom; }
  .cb-nowbar-eq i:nth-child(2) { animation-delay: -0.4s; }
  .cb-nowbar-eq i:nth-child(3) { animation-delay: -0.8s; }
}
@keyframes cb-eq { from { transform: scaleY(0.35); } to { transform: scaleY(1); } }

.cb-recent { margin-top: 1.75rem; }
.cb-recenthead { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; margin-bottom: 0.6rem; }
.cb-recenttitle {
  margin: 0; font-size: var(--cb-label-size); font-weight: var(--cb-label-weight);
  letter-spacing: var(--cb-label-track); text-transform: uppercase; color: var(--cb-ink-2);
}
#view-cast:has(#recentBox:not([hidden])) #linkHint { display: none; }

/* ---------- Screen 2: now playing ---------- */
.cb-playhead { margin: 0.25rem 0 0.9rem; }
.cb-playhead .cb-back { margin-bottom: 0.15rem; }
.cb-playhead .cb-step { display: flex; }
.cb-playtitle {
  margin: 0; font-size: 1.35rem; line-height: 1.2; font-weight: 700; color: var(--cb-ink);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  overflow-wrap: anywhere;
}
.cb-playfrom { margin: 0.3rem 0 0; font-size: 0.8125rem; color: var(--cb-ink-2); }
.cb-playfrom[hidden] { display: none; }
.cb-pagelink { color: var(--cb-accent); font-weight: 600; text-decoration: underline; text-underline-offset: 2px; }
.cb-pagelink:focus-visible { outline: 2px solid var(--cb-accent); outline-offset: 2px; border-radius: 2px; }

.cb-watchon {
  display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
  margin: 0 0 0.9rem;
}
.cb-watchon-seg { flex: none; }
.cb-watchon-seg .cb-segment-btn { min-height: 40px; padding: 6px 16px; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* On the TV, the phone is the remote: its own paused picture and the
   Chromecast status line would be two more things describing nothing. */
#view-playing.is-on-tv #screen,
#view-playing.is-on-tv #status { display: none; }

.cb-tvremote {
  margin: 0 0 1.75rem; padding: 1rem 1rem 0.9rem;
  border-radius: var(--cb-radius-xl);
  background: var(--cb-surface); box-shadow: var(--cb-shadow-soft);
}
.cb-tvremote[hidden] { display: none; }
.cb-tvcard-head { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.35rem; }
.cb-tvremote .cb-tv-where { margin: 0; font-weight: 600; }
.cb-tvremote .cb-tv-state { font-size: 1rem; font-weight: 600; color: var(--cb-ink); margin-bottom: 0.9rem; }
.cb-tvremote .cb-tv-scrub input { min-height: 44px; }
.cb-tvremote .cb-tv-btn.btn { min-height: 3.5rem; font-size: 0.875rem; }
.cb-tvremote #tvSend { min-height: 2.875rem; margin-bottom: 0.9rem; text-transform: none; font-weight: 600; }
.cb-tvcard-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
  padding-top: 0.75rem; border-top: 1px solid var(--cb-line);
}
.cb-tvcard-foot .btn {
  display: inline-flex; align-items: center; gap: 0.45rem;
  min-height: 2.75rem; text-transform: none; font-weight: 600; font-size: 0.875rem;
}
.cb-tvcard-foot .btn:disabled { opacity: 0.55; }
.cb-tvcard-foot .cb-linkbtn { min-height: 2.75rem; }

.cb-groupk {
  margin: 0 0 0.6rem;
  font-size: var(--cb-label-size); font-weight: var(--cb-label-weight);
  letter-spacing: var(--cb-label-track); text-transform: uppercase; color: var(--cb-ink-2);
}
#view-playing .cb-actions { margin-bottom: 1.75rem; }

/* ---------- TV screen ---------- */
.cb-tvsteps { list-style: none; margin: 0 0 1.25rem; padding: 0; display: grid; gap: 0.9rem; }
.cb-tvsteps li { display: flex; gap: 0.75rem; font-size: 0.9375rem; line-height: 1.5; color: var(--cb-ink-2); }
.cb-tvsteps-n {
  flex: none; display: grid; place-items: center; width: 1.75rem; height: 1.75rem; border-radius: 50%;
  background: var(--cb-surface); box-shadow: var(--cb-shadow-inset);
  color: var(--cb-accent); font-weight: 700; font-size: 0.8125rem; font-variant-numeric: tabular-nums;
}
.cb-tvaddr { color: var(--cb-ink); font-weight: 700; overflow-wrap: anywhere; }
#view-tv .cb-tv-pair { display: block; }
#view-tv .cb-tv-code.form-control { width: 100%; min-height: 4rem; font-size: 2rem; margin: 0.35rem 0 0.75rem; }
#view-tv .cb-tv-pair .btn { min-height: 3rem; text-transform: none; font-weight: 600; }
#view-tv .cb-tv-error { margin: -0.25rem 0 0.75rem; }
.cb-tvnote { margin: 1rem 0 0; font-size: 0.8125rem; line-height: 1.55; color: var(--cb-ink-2); max-width: 60ch; }
.cb-tvnote b { color: var(--cb-ink); }

.cb-tvlinked {
  display: flex; align-items: center; gap: 0.75rem;
  padding: 1rem; margin: 0 0 1rem;
  border-radius: var(--cb-radius-xl); background: var(--cb-surface); box-shadow: var(--cb-shadow-inset);
}
.cb-tvlinked .cb-tv-dot { width: 10px; height: 10px; }
.cb-tvlinked-text { display: flex; flex-direction: column; min-width: 0; }
.cb-tvlinked strong { font-size: 1.05rem; color: var(--cb-ink); font-variant-numeric: tabular-nums; }
.cb-tvlinked-text span { font-size: 0.8125rem; line-height: 1.45; color: var(--cb-ink-2); }
#view-tv #tvPaired .btn { min-height: 2.875rem; text-transform: none; font-weight: 600; margin-bottom: 0.6rem; }
#view-tv #tvPaired .btn[hidden] { display: none; }
/* Red is spent on the one action here that ends something. */
#view-tv .cb-tvdisconnect.btn { color: var(--cb-bad); margin-top: 0.75rem; }

/* ---------- History: ways out of a row ---------- */
.cb-histrow { align-items: flex-start; }
.cb-histrow .cb-item-actions { padding-top: 0.1rem; }
.cb-item-links { display: flex; flex-wrap: wrap; align-items: center; gap: 0 0.35rem; margin: 0.2rem 0 -0.6rem -0.4rem; }
.cb-itemlink {
  display: inline-flex; align-items: center; gap: 0.2rem;
  min-height: 2.75rem; padding: 0 0.4rem;
  border: 0; border-radius: var(--cb-radius); background: none;
  font: inherit; font-size: 0.8125rem; font-weight: 600; color: var(--cb-accent);
  text-decoration: none; white-space: nowrap; cursor: pointer;
}
a.cb-itemlink:first-child { text-decoration: underline; text-underline-offset: 2px; }
.cb-itemlink:active { box-shadow: var(--cb-shadow-inset); }
.cb-itemlink:focus-visible { outline: 2px solid var(--cb-accent); outline-offset: 1px; }
.cb-histrow.is-compact .cb-item-links { margin-bottom: -0.7rem; }
