/* ── Reset & Base ─────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

:root {
  --header-h: 62px;
  /* The nav is fixed, so this width has to be reproduced as an inset on
     `.app-main`. Kept as a token for the same reason --header-h is: it was
     hardcoded in two places once and drifted the moment one changed. */
  --sidebar-w:      232px;
  --sidebar-rail-w: 64px;
  --bg:         #080c14;
  --surface:    #0e1420;
  --surface2:   #151d2e;
  --border:     rgba(255,255,255,.07);
  --border2:    rgba(255,255,255,.12);
  --text:       #e2e8f0;
  --text-muted: #64748b;
  --text-dim:   #334155;
  --accent:     #3b82f6;
  --accent-dim: rgba(59,130,246,.15);
  --green:      #22c55e;
  --red:        #ef4444;
  --yellow:     #f59e0b;
  --radius:     12px;
  --radius-sm:  8px;
}

html, body {
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  font-size: 14px;
  line-height: 1.5;
  min-height: 100vh;
}

/* ── App shell & sidebar ──────────────────────────────────── */
/* The nav is `position: fixed` rather than a flex sibling so that `.header`
   can stay `position: sticky` inside the scrolling column. As a flex child of
   a full-height row, sticky has no scroll container to stick to and the header
   scrolled away with the content. */
.appnav {
  position: fixed;
  top: 0; left: 0; bottom: 0;
  width: var(--sidebar-w);
  z-index: 200;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border-right: 1px solid var(--border);
  transition: width .16s ease;
}

.app-main {
  margin-left: var(--sidebar-w);
  transition: margin-left .16s ease;
  min-height: 100vh;
}

.app-shell.rail .appnav   { width: var(--sidebar-rail-w); }
.app-shell.rail .app-main { margin-left: var(--sidebar-rail-w); }

/* Brand */
.appnav-brand {
  height: var(--header-h);
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  padding: 0 18px;
  cursor: pointer;
  border-bottom: 1px solid var(--border);
  overflow: hidden;
}
.appnav-logo-full { height: 24px; display: block; }
.appnav-logo-mark {
  display: none;
  font-size: 15px; font-weight: 800; letter-spacing: -.03em;
  color: var(--accent);
}
.app-shell.rail .appnav-brand    { padding: 0; justify-content: center; }
.app-shell.rail .appnav-logo-full { display: none; }
.app-shell.rail .appnav-logo-mark { display: block; }

/* Items */
.appnav-items {
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 18px 12px;
  display: flex;
  flex-direction: column;
  /* Roomy on purpose. At 2px the items read as one continuous block and the
     eye has to work to separate them; the reference sits nearer 6px with
     taller rows, which is what makes it scan as a list of destinations. */
  gap: 6px;
}
.app-shell.rail .appnav-items { padding: 18px 8px; }

.appnav-item {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 11px 12px;
  background: transparent;
  border: 0;
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  font-family: inherit;
  font-size: 13px;
  font-weight: 500;
  text-align: left;
  cursor: pointer;
  white-space: nowrap;
  transition: background .13s, color .13s;
}
.appnav-item svg { flex: 0 0 auto; }
.appnav-item:hover:not(:disabled) { background: var(--surface2); color: var(--text); }

/* Active item carries a left bar as well as a fill. Fill alone was hard to
   pick out against --surface2, which the hover state also uses. */
.appnav-item.active {
  background: var(--accent-dim);
  color: #fff;
  position: relative;
}
.appnav-item.active::before {
  content: '';
  position: absolute;
  left: -12px; top: 50%;
  transform: translateY(-50%);
  width: 3px; height: 20px;
  border-radius: 0 3px 3px 0;
  background: var(--accent);
}
.app-shell.rail .appnav-item.active::before { left: -8px; }

/* Disabled entries are shown rather than hidden so the product's shape is
   legible, but they must never look merely unselected — hence the reduced
   contrast, the cursor, and the "Soon" tag. */
.appnav-item:disabled {
  color: var(--text-dim);
  cursor: not-allowed;
}
.appnav-soon {
  margin-left: auto;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-dim);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 1px 5px;
}

.appnav-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }

.appnav-sep {
  height: 1px;
  background: var(--border);
  margin: 14px 12px;
}
.app-shell.rail .appnav-sep { margin: 14px 6px; }

/* In the rail the label and the Soon tag are gone, so the icon has to centre
   itself rather than sit at the start of a row it no longer fills. */
.app-shell.rail .appnav-item { justify-content: center; padding: 12px 0; gap: 0; }
.app-shell.rail .appnav-label,
.app-shell.rail .appnav-soon { display: none; }

/* Foot */
/* No top border: the plan card has its own, and two lines a few pixels apart
   read as a mistake. More padding below than at the sides, so the card clears
   the bottom edge rather than sitting flush against it. */
.appnav-foot { flex: 0 0 auto; padding: 12px 12px 32px; }

/* Plan card. Entirely placeholder — see planCard() in app.js. */
.appnav-plan {
  padding: 12px;
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.appnav-plan-head { display: flex; align-items: center; gap: 10px; }
.appnav-plan-icon {
  display: flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; flex: 0 0 auto;
  background: var(--accent); color: #fff; border-radius: 50%;
}
.appnav-plan-text { min-width: 0; font-size: 11px; color: var(--text-muted); line-height: 1.35; }
.appnav-plan-text strong {
  display: block; font-size: 12px; font-weight: 600; color: var(--text);
}
.appnav-plan-bar {
  height: 5px; margin: 11px 0 11px;
  background: var(--bg); border-radius: 3px; overflow: hidden;
}
.appnav-plan-bar i { display: block; height: 100%; background: var(--accent); border-radius: 3px; }
.appnav-plan-btn {
  width: 100%; padding: 8px;
  background: transparent; color: var(--accent);
  border: 1px solid rgba(59,130,246,.4); border-radius: var(--radius-sm);
  font-family: inherit; font-size: 12px; font-weight: 600;
  cursor: pointer;
}
.appnav-plan-btn:hover:not(:disabled) { background: var(--accent-dim); }
/* Not wired to anything, and says so rather than looking pressable. */
.appnav-plan-btn:disabled { opacity: .55; cursor: not-allowed; }

/* No room for it in the rail, and a progress bar 40px wide says nothing. */
.app-shell.rail .appnav-plan { display: none; }
.app-shell.rail .appnav-foot { padding: 10px 6px; }

/* Mobile: the nav goes off-canvas entirely rather than eating half a phone
   screen as a rail. The hamburger then means open/close, not expand/collapse. */
@media (max-width: 720px) {
  .appnav { transform: translateX(-100%); transition: transform .16s ease; width: var(--sidebar-w); }
  .app-main { margin-left: 0; }
  .app-shell.nav-open .appnav { transform: none; }
  .app-shell.rail .appnav { width: var(--sidebar-w); }
  .app-shell.rail .app-main { margin-left: 0; }
  .app-shell.rail .appnav-logo-full { display: block; }
  .app-shell.rail .appnav-logo-mark { display: none; }
  .app-shell.rail .appnav-label { display: block; }
  .app-shell.rail .appnav-item { justify-content: flex-start; padding: 11px 12px; gap: 12px; }
}

/* ── Header ───────────────────────────────────────────────── */
/* The editor sizes itself against this, so it must match the header's real
   height. It was hardcoded as 53px in one place and drifted the moment the
   avatar made the header taller — the editor then overflowed by the
   difference and the page grew a stray scrollbar. */
.header {
  height: var(--header-h);
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 28px;
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  background: var(--bg);
  z-index: 100;
}

.header-logo {
  font-size: 15px;
  font-weight: 700;
  letter-spacing: -0.01em;
}

.header-logo span { color: var(--accent); }

/* The logo now lives in the sidebar, so the header's left slot is the rail
   toggle. `justify-content: space-between` on .header still does the work. */
.header-menu-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px; height: 30px;
  background: transparent;
  border: 0;
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  cursor: pointer;
  transition: background .13s, color .13s;
}
.header-menu-btn:hover { background: var(--surface2); color: var(--text); }

.header-back {
  display: none;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  font-size: 13px;
  cursor: pointer;
  transition: color .15s, border-color .15s;
}
.header-back:hover { color: var(--text); border-color: var(--border2); }
.header-back.visible { display: flex; }

/* ── Layout ───────────────────────────────────────────────── */
.view { display: none; }
.view.active { display: block; }

/* ── Form View ────────────────────────────────────────────── */
.form-view {
  max-width: 680px;
  margin: 0 auto;
  padding: 56px 24px;
}

.form-eyebrow {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 12px;
}

.form-title {
  font-size: 28px;
  font-weight: 700;
  letter-spacing: -.02em;
  margin-bottom: 6px;
}

.form-subtitle {
  color: var(--text-muted);
  margin-bottom: 36px;
  font-size: 14px;
}

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 28px;
}

.form-group { margin-bottom: 18px; }
.form-group:last-child { margin-bottom: 0; }

.form-label {
  display: block;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 7px;
}

.form-input {
  width: 100%;
  padding: 11px 14px;
  background: rgba(255,255,255,.04);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  font-size: 13px;
  outline: none;
  transition: border-color .15s;
}
.form-input:focus { border-color: var(--accent); }
.form-input::placeholder { color: var(--text-dim); }

.form-hint {
  font-size: 11px;
  color: var(--text-dim);
  margin-top: 5px;
}

.form-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}

.form-divider {
  border: none;
  border-top: 1px solid var(--border);
  margin: 20px 0;
}

.btn-primary {
  /* inline-flex so the leading SVG centres against the label instead of sitting
     on the text baseline */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  padding: 14px;
  background: var(--accent);
  color: #fff;
  border: none;
  border-radius: var(--radius-sm);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  transition: background .15s, opacity .15s;
}
.btn-primary:hover { background: #2563eb; }
.btn-primary:disabled { opacity: .4; cursor: not-allowed; }

.btn-ghost {
  padding: 9px 18px;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  font-size: 13px;
  cursor: pointer;
  transition: color .15s, border-color .15s;
}
.btn-ghost:hover { color: var(--text); border-color: var(--border2); }

/* ── Progress View ────────────────────────────────────────── */
.progress-view {
  max-width: 600px;
  margin: 0 auto;
  padding: 80px 24px;
}

.progress-label {
  font-size: 11px;
  color: var(--text-muted);
  margin-bottom: 24px;
  word-break: break-all;
}

.progress-phase {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 8px;
}

.progress-message {
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -.01em;
  margin-bottom: 20px;
}

.progress-track {
  height: 3px;
  background: var(--surface2);
  border-radius: 2px;
  margin-bottom: 8px;
  overflow: hidden;
}

.progress-fill {
  height: 100%;
  background: var(--accent);
  border-radius: 2px;
  transition: width .4s ease;
}

.progress-pct {
  font-size: 12px;
  color: var(--text-muted);
}

.progress-error { color: var(--red); }

/* ── Clips View ───────────────────────────────────────────── */
.clips-view { padding: 28px; }

.clips-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 24px;
  flex-wrap: wrap;
  gap: 12px;
}

.clips-toolbar-left h2 {
  font-size: 20px;
  font-weight: 700;
  letter-spacing: -.01em;
}

.clips-toolbar-left p {
  font-size: 13px;
  color: var(--text-muted);
  margin-top: 2px;
}

.clips-toolbar-right {
  display: flex;
  gap: 8px;
}

.btn-accent {
  padding: 10px 20px;
  background: var(--accent);
  color: #fff;
  border: none;
  border-radius: var(--radius-sm);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background .15s;
}
.btn-accent:hover { background: #2563eb; }
.btn-accent:disabled { opacity: .4; cursor: not-allowed; }

.clips-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 14px;
}

.clip-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  transition: border-color .15s;
}
.clip-card:hover { border-color: var(--border2); }
.clip-card.selected { border-color: var(--accent); }

.clip-thumb {
  width: 100%;
  aspect-ratio: 9/16;
  object-fit: cover;
  background: var(--surface2);
  display: block;
  max-height: 180px;
}

.clip-body { padding: 12px 14px; }

.clip-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 8px;
}

.clip-index {
  font-size: 11px;
  font-weight: 700;
  color: var(--accent);
}

.clip-time {
  font-size: 11px;
  color: var(--text-muted);
}

.clip-duration {
  font-size: 11px;
  color: var(--text-dim);
}

.clip-score {
  font-size: 11px;
  color: var(--text-muted);
  margin-bottom: 8px;
}

.clip-reason {
  font-size: 11px;
  color: var(--text-muted);
  line-height: 1.5;
  margin-bottom: 10px;
  font-style: italic;
}

.clip-actions {
  display: flex;
  gap: 8px;
}

.clip-select-btn {
  flex: 1;
  padding: 7px;
  border-radius: 6px;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text-muted);
  transition: all .15s;
}
.clip-select-btn:hover { border-color: var(--border2); color: var(--text); }
.clip-select-btn.active {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}

.clip-reexport-btn {
  padding: 7px 12px;
  border-radius: 6px;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  border: 1px solid rgba(59,130,246,.25);
  background: var(--accent-dim);
  color: #60a5fa;
  transition: all .15s;
  white-space: nowrap;
}
.clip-reexport-btn:hover { background: rgba(59,130,246,.25); }
.clip-reexport-btn:disabled { opacity: .4; cursor: not-allowed; }

/* ── Editor View ──────────────────────────────────────────── */
.editor-view {
  display: none;
  height: calc(100vh - var(--header-h));
  flex-direction: column;
  overflow: hidden;
}
.editor-view.active { display: flex; }

/* Three columns. The player takes what the two fixed panels leave, because it
   is the only one whose useful size is "as big as possible". */
.editor-body {
  display: flex;
  flex: 1;
  overflow: hidden;
}
.editor-col {
  display: flex; flex-direction: column;
  min-height: 0;
  border-left: 1px solid var(--border);
  background: var(--surface);
}
.editor-highlights { flex: 0 0 300px; }
.editor-topics     { flex: 0 0 290px; }

.col-head {
  flex: 0 0 auto;
  display: flex; align-items: baseline; gap: 8px;
  padding: 13px 14px;
  border-bottom: 1px solid var(--border);
}
.col-title { font-size: 13px; font-weight: 700; }
.col-sub { font-size: 11px; color: var(--text-muted); }
.col-body { flex: 1; min-height: 0; overflow-y: auto; padding: 12px; }

/* Below this the three columns cannot all be read. The panels go under the
   player rather than being dropped — both are the point of the page. */
@media (max-width: 1240px) {
  .editor-view { height: auto; overflow: visible; }
  .editor-body { flex-direction: column; }
  .editor-col { flex: 0 0 auto; border-left: 0; border-top: 1px solid var(--border); }
  .col-body { max-height: 420px; }
}

/* Video panel */
.editor-player {
  flex: 1;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: #000;
}

.editor-video-wrap {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #000;
  overflow: hidden;
}

#editor-video {
  max-width: 100%;
  max-height: 100%;
  display: block;
}

/* Controls bar */
.editor-controls {
  padding: 10px 16px;
  background: var(--surface);
  border-top: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: 14px;
}

.ctrl-btn {
  background: none;
  border: none;
  color: var(--text);
  font-size: 18px;
  cursor: pointer;
  padding: 4px;
  line-height: 1;
  opacity: .8;
  transition: opacity .15s;
}
.ctrl-btn:hover { opacity: 1; }

.ctrl-time {
  font-size: 12px;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.ctrl-speed {
  font-size: 12px;
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text);
  padding: 3px 8px;
  cursor: pointer;
}

/* Timeline */
.editor-timeline {
  padding: 12px 16px;
  background: var(--surface);
  border-top: 1px solid var(--border);
  user-select: none;
}

/* Taller, because the blocks carry a name now — at the old height the text had
   nowhere to sit. */
.timeline-track {
  position: relative;
  height: 88px;
  background: var(--surface2);
  border-radius: 6px;
  overflow: hidden;
  cursor: pointer;
}

/* Makes the scrub strip look like one. It was invisible before, which is how it
   came to be mistaken for padding and removed.
   A quarter-width tick rather than a hatch: at 12px intervals it read as
   texture, and the strip is a place to click, not a ruler — the ruler with the
   actual times sits directly above it. The tone shift and the hairline do most
   of the work; the ticks just say "this is a scale". */
.timeline-track::before {
  content: '';
  position: absolute; top: 0; left: 0; right: 0; height: 26px;
  background:
    repeating-linear-gradient(90deg,
      rgba(255,255,255,.07) 0 1px, transparent 1px 25%),
    rgba(0,0,0,.18);
  border-bottom: 1px solid var(--border);
  pointer-events: none;
}

.timeline-progress {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  background: rgba(59,130,246,.15);
  pointer-events: none;
  transition: width .1s linear;
}

.timeline-playhead {
  position: absolute;
  top: 0;
  width: 2px;
  height: 100%;
  background: #fff;
  pointer-events: none;
  transform: translateX(-1px);
  z-index: 30;  /* always on top */
}

/* Inset from the top, leaving a strip of bare track above.
   That strip is not padding — it is the ONLY place you can click to seek. The
   blocks stopPropagation on mousedown so that clicking one selects a topic
   rather than scrubbing, so without the strip the track has no clickable area
   left at all. Removing it once broke scrubbing entirely.
   The track is taller than it was to pay for the strip AND give the blocks more
   room than they had. */
.timeline-marker {
  position: absolute;
  top: 26px;
  height: calc(100% - 32px);
  border-radius: 5px;
  cursor: pointer;
  transition: filter .15s, outline-color .15s;
  min-width: 2px;
  z-index: 10;
  /* Full strength, not 60%. The blocks carry white text now, and dimming the
     fill dimmed the contrast behind it — which is what made the labels look
     washed out and needed a shadow to rescue them. Selection is shown with an
     outline instead. */
  outline: 2px solid transparent;
}
.timeline-marker:hover { filter: brightness(1.12); }
.timeline-marker.active { outline-color: #fff; z-index: 15; }





/* Audience model for highlight ranking. Set once per show — the audience is a
   property of the broadcast, not of an individual clip. */


.sidebar-clips {
  flex: 1;
  overflow-y: auto;
  padding: 8px;
}

.sidebar-clip {
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background .12s;
  border: 1px solid transparent;
  margin-bottom: 6px;
}
.sidebar-clip:hover { background: var(--surface2); }
.sidebar-clip.active {
  background: var(--accent-dim);
  border-color: rgba(59,130,246,.3);
}

.sidebar-clip-rank {
  font-size: 10px;
  font-weight: 700;
  color: var(--accent);
  width: 20px;
  text-align: center;
  flex-shrink: 0;
}


.sidebar-clip-time {
  display: flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
  font-size: 12px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
/* The in/out range yields first — it is the less useful half when scanning. */
.clip-range {
  min-width: 0;
  flex: 0 1 auto;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Duration never shrinks and never truncates. */
.clip-dur {
  flex: 0 0 auto;
  white-space: nowrap;
  color: var(--text-muted);
  font-weight: 600;
}
.clip-dur::before { content: '· '; }

.sidebar-clip-score {
  font-size: 11px;
  color: var(--text-muted);
  margin-top: 1px;
}

.sidebar-clip-topics {
  font-size: 10px;
  color: var(--text-dim);
  margin-top: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Selected clip detail panel */
.sidebar-detail {
  border-top: 1px solid var(--border);
  padding: 24px 16px;
}

.sidebar-detail-title {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 12px;
}

.detail-times {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-bottom: 12px;
}

.detail-time-group label {
  display: block;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin-bottom: 4px;
}

.detail-time-input {
  width: 100%;
  padding: 6px 8px;
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: 5px;
  color: var(--text);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  outline: none;
  transition: border-color .15s;
}
.detail-time-input:focus { border-color: var(--accent); }

.detail-duration {
  font-size: 11px;
  color: var(--text-muted);
}

/* Duration on the left, step on the right — one row rather than two. */
.detail-meta {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 12px;
}



/* Export status */
.export-status {
  display: none;
  margin-top: 10px;
  padding: 10px 12px;
  background: var(--accent-dim);
  border: 1px solid rgba(59,130,246,.2);
  border-radius: var(--radius-sm);
  font-size: 12px;
  color: #93c5fd;
}
.export-status.visible { display: block; }
.export-status.success { background: rgba(34,197,94,.1); border-color: rgba(34,197,94,.2); color: #86efac; }
.export-status.error { background: rgba(239,68,68,.1); border-color: rgba(239,68,68,.2); color: #fca5a5; }

/* Scrollbar */
::-webkit-scrollbar { width: 4px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border2); border-radius: 2px; }

/* ── Responsive ───────────────────────────────────────────── */
@media (max-width: 768px) {
  .form-row { grid-template-columns: 1fr; }
  .clips-grid { grid-template-columns: 1fr 1fr; }
}

/* ── Jobs List View ───────────────────────────────────────── */
/* Full bleed, matching the templates page: the sidebar already constrains the
   content column, so a second max-width inside it just adds gutters. */
.jobs-view {
  max-width: none;
  margin: 0;
  padding: 16px 32px 56px;
}

.jobs-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  margin-bottom: 16px;
  gap: 16px;
}

.jobs-title {
  font-size: 26px;
  font-weight: 700;
  letter-spacing: -.02em;
}

.jobs-subtitle {
  font-size: 13px;
  color: var(--text-muted);
  margin-top: 4px;
}

/* ── Jobs grid ────────────────────────────────────────────── */
.jobs-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 16px;
}

.jobs-empty {
  text-align: center;
  padding: 64px 24px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
  grid-column: 1 / -1;
}

.jobs-empty-icon { font-size: 40px; margin-bottom: 16px; }
.jobs-empty-title { font-size: 16px; font-weight: 600; margin-bottom: 6px; }
.jobs-empty-sub { font-size: 13px; color: var(--text-muted); }

.job-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  cursor: default;
  display: flex;
  flex-direction: column;
  transition: border-color .15s, transform .15s;
}
/* Only a finished job opens, so only a finished job lifts. Offering the
   affordance on one still analyzing invites a click that does nothing. */
.job-card.done:hover { border-color: var(--border2); transform: translateY(-2px); }

.job-card-thumb {
  position: relative;
  width: 100%;
  aspect-ratio: 16/9;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: var(--bg);
}
.job-card-thumb img { width: 100%; height: 100%; object-fit: cover; }
.job-card-thumb-icon { font-size: 32px; opacity: 0.5; }

/* Status sits top-left, length bottom-right — opposite corners so neither can
   cover the other on a short title or a long one. */
/* Pinned left only. A duplicate of this rule once set `right` as well, and a
   box with both offsets stretches to the full width of the thumbnail — which
   is what it looked like, rather than a stray width. */
.job-card-status-pill {
  position: absolute; top: 8px; left: 8px; right: auto;
  width: fit-content; max-width: calc(100% - 16px);
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 8px;
  font-size: 10.5px; font-weight: 600;
  border-radius: 5px;
  background: rgba(0,0,0,.55);
  backdrop-filter: blur(4px);
  color: var(--text);
}
.job-card-status-pill.done       { color: #4ade80; }
.job-card-status-pill.processing { color: #93c5fd; }
.job-card-status-pill.error      { color: #fca5a5; }

.job-card-duration {
  position: absolute; right: 8px; bottom: 8px;
  padding: 2px 7px;
  font-size: 11px; font-weight: 600;
  color: #fff;
  background: rgba(0,0,0,.72);
  border-radius: 4px;
}

.job-card-body { padding: 14px; display: flex; flex-direction: column; flex: 1; }

/* Two lines, then ellipsis. Titles are show names with a date appended and run
   long; letting them wrap freely made cards in a row different heights. */
.job-card-title {
  font-size: 13.5px; font-weight: 600; line-height: 1.4;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
  min-height: calc(2 * 1.4 * 13.5px);
}
.job-card-when { font-size: 11px; color: var(--text-muted); margin-top: 5px; }
.job-card-counts {
  display: flex; align-items: center; gap: 10px;
  margin-top: 7px;
  font-size: 11px; color: var(--text-muted);
}
.job-card-count { display: inline-flex; align-items: center; gap: 4px; }
.job-card-count svg { opacity: .7; }

.job-card-actions { display: flex; gap: 8px; margin-top: auto; padding-top: 13px; }
.job-card-btn {
  flex: 1;
  display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  padding: 7px 10px;
  font-family: inherit; font-size: 11.5px; font-weight: 600;
  border-radius: var(--radius-sm);
  cursor: pointer;
  border: 1px solid var(--border2);
  background: rgba(255,255,255,.05);
  color: var(--text);
  transition: background .13s;
}
.job-card-btn:hover:not(:disabled) { background: rgba(255,255,255,.1); }
.job-card-btn.primary { color: var(--accent); border-color: rgba(59,130,246,.35); }
.job-card-btn:disabled { opacity: .4; cursor: not-allowed; }

.job-card-progress-bar {
  height: 3px;
  background: var(--surface2);
  border-radius: 2px;
  margin: 10px 0 5px;
  overflow: hidden;
}
.job-card-progress-fill {
  height: 100%;
  background: var(--accent);
  border-radius: 2px;
  transition: width .4s ease;
}
.job-card-progress-msg {
  font-size: 11px;
  color: var(--text-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.job-card-error {
  font-size: 11px;
  color: var(--red);
  margin-top: 8px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50%       { opacity: .4; }
}

.job-error {
  font-size: 11px;
  color: var(--red);
  margin-top: 3px;
}

/* ── Sidebar clip export status ───────────────────────────── */
/* No top margin. This used to sit on its own row BELOW the actions, where the
   margin separated the two; it is a flex sibling of the Reframe and Find
   highlights buttons now, so the same margin just pushed it a few pixels lower
   than everything beside it. */
.sidebar-clip-export { display: flex; align-items: center; }



.clip-export-done {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;      /* three items now — never stack them */
  gap: 6px;
  min-width: 0;
}

/* Every item stays on one line. Without this the row overflowed the sidebar and
   each button wrapped its own icon onto a second line, which is what broke the
   layout when the re-export control was added. */
.clip-export-done > * {
  white-space: nowrap;
  flex: 0 0 auto;
}

.clip-export-check {
  font-size: 11px;
  font-weight: 600;
  color: var(--green);
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
  flex: 0 1 auto;         /* the label yields first if space runs out */
}

.clip-export-dl {
  font-size: 11px;
  font-weight: 600;
  padding: 4px 9px;
  line-height: 1.4;
  white-space: nowrap;
  background: rgba(34,197,94,.1);
  border: 1px solid rgba(34,197,94,.2);
  border-radius: 5px;
  color: #86efac;
  text-decoration: none;
  transition: background .15s;
}
.clip-export-dl:hover { background: rgba(34,197,94,.2); }

.clip-export-progress {
  font-size: 11px;
  color: var(--text-muted);
  font-style: italic;
}

/* ── Queue bar ────────────────────────────────────────────── */
/* Two rows rather than one: the count, a clear button, the template picker and
   Export could not sit on one line in a narrow sidebar without the count
   wrapping to two lines and everything feeling cramped. */
/* [hidden] must be declared or the author `display:grid` below wins the
   specificity tie and the bar shows when it should not. */
.queue-bar[hidden] { display: none; }

.queue-bar {
  padding: 12px 14px 14px;
  border-top: 1px solid var(--border);
  background: var(--surface2);
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas:
    "count  clear"
    "tpl    export";
  align-items: center;
  gap: 10px 8px;
}

.queue-count {
  grid-area: count;
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
}

.queue-clear {
  grid-area: clear;
  font-size: 11px; font-weight: 600;
  padding: 4px 10px;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 5px;
  color: var(--text-muted);
  cursor: pointer;
  white-space: nowrap;
}
.queue-clear:hover { color: var(--text); border-color: var(--border2); }

.queue-export-btn {
  grid-area: export;
  padding: 8px 18px;
  background: var(--accent);
  border: none;
  border-radius: var(--radius-sm);
  color: #fff;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background .15s;
  white-space: nowrap;
}
.queue-export-btn:hover { background: #2563eb; }
.queue-export-btn:disabled { opacity: .4; cursor: not-allowed; }

/* Queue button in sidebar */
/* Icon and label share a centre line. Without a flex context the SVG sits on
   the TEXT BASELINE — a couple of pixels low, which reads as a crooked button.
   The same applies to every button in this file that renders an icon beside a
   label; see the test that checks them as a set. */
.clip-queue-btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 11px;
  font-weight: 600;
  /* Matches .btn-reframe and .btn-analyze-topic exactly — a button a pixel
     taller than the two beside it reads as misaligned even when it is centred. */
  padding: 5px 9px;
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: 5px;
  color: var(--text-muted);
  cursor: pointer;
  transition: all .15s;
}
.clip-queue-btn:hover { border-color: var(--accent); color: var(--accent); }
.clip-queue-btn.queued {
  background: var(--accent-dim);
  border-color: rgba(59,130,246,.4);
  color: #60a5fa;
}



/* Prevent text selection while dragging */
.editor-timeline.dragging { user-select: none; cursor: ew-resize; }

/* ── Sidebar clip header ──────────────────────────────────── */
.sidebar-clip-header {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  cursor: pointer;
}

.sidebar-clip-meta { flex: 1; min-width: 0; }

.sidebar-clip-summary {
  font-size: 11px;
  color: var(--text-muted);
  line-height: 1.5;
  margin: 6px 0 4px 0;
  font-style: italic;
}





/* Two-line grid: the time range gets a full line to itself, meta and the action
   share the second. A single row could not fit label + full time + duration +
   score + button in a 280px sidebar, so the time was ellipsing to "11:...". */
.highlight-item {
  display: grid;
  grid-template-columns: auto 1fr auto;
  /* The rating and note rows were added without extending this template, so
     they landed in implicit rows and overlapped the meta row — the score badge
     and the export button drew on top of each other. Every child needs a named
     area here. */
  grid-template-areas:
    "label time   time"
    "label desc   desc"
    "label meta   action"
    "label rating rating"
    "label note   note";
  /* Start, not centre: the label column now spans five rows, and centring it
     floats "H2" into the middle of a tall row instead of aligning with the
     time it belongs to. */
  align-items: start;
  column-gap: 8px;
  row-gap: 3px;
  padding: 7px 9px;
  background: var(--surface2);
  border: 1px solid transparent;
  border-radius: 5px;
  margin-bottom: 4px;
  cursor: pointer;
  transition: background .12s;
}
.highlight-item:hover { background: rgba(59,130,246,.1); }

.highlight-item-label {
  grid-area: label;
  align-self: start;      /* three rows now — keep the badge at the top */
  padding-top: 1px;
  font-size: 11px;
  font-weight: 700;
  color: var(--text-muted);
  padding-right: 2px;
}

.highlight-item-time {
  grid-area: time;
  font-size: 11px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

/* What the moment actually is — GPT's own label, e.g. "caller says the trade
   was a mistake". This is the fastest way for a user to find the clip they
   want; time and score alone say nothing about content. */
.highlight-item-desc {
  grid-area: desc;
  font-size: 10px;
  line-height: 1.35;
  color: var(--text-muted);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  min-width: 0;
}
.highlight-item.active .highlight-item-desc { color: var(--text); }

.highlight-item-meta {
  grid-area: meta;
  display: flex;
  align-items: center;
  align-self: center;
  gap: 8px;
  min-width: 0;
}

.highlight-item-dur,
.highlight-item-score {
  font-size: 10px;
  color: var(--text-muted);
  white-space: nowrap;
  flex: 0 0 auto;
}

.highlight-item-score {
  display: inline-flex;
  align-items: center;
  gap: 3px;
}

/* Action sits bottom-right and never squeezes the meta cells */
/* Inside .highlight-item-actions now, so it no longer claims the grid cell. */
.highlight-action-slot {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.highlight-item .btn-queue-highlight,
.highlight-item .clip-export-dl {
  flex: 0 0 auto;
  white-space: nowrap;
}

/* Icon and label on a shared centre line. Without a flex context the SVG sits
   on the TEXT BASELINE, which puts it a couple of pixels low and makes the
   button look misaligned — the same for every icon button in this file. */
.btn-queue-highlight {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 10px;
  font-weight: 600;
  padding: 4px 8px;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text-muted);
  cursor: pointer;
  white-space: nowrap;
  transition: all .12s;
  flex-shrink: 0;
}
.btn-queue-highlight:hover { border-color: var(--accent); color: var(--accent); }
/* The markup has a literal space between icon and label; the gap above already
   provides the spacing, so the icon must not shrink and add to it. */
.btn-queue-highlight svg { flex: 0 0 auto; }
.btn-queue-highlight:disabled { opacity: .4; cursor: not-allowed; }

.highlights-loading,
.highlights-empty,
.highlights-error {
  font-size: 11px;
  color: var(--text-muted);
  padding: 6px 0;
  font-style: italic;
}
.highlights-error { color: var(--red); }

/* The loading row now contains an inline SVG spinner rather than a text glyph,
   so it needs flex alignment or the icon sits on the text baseline. */
.highlights-loading {
  display: flex;
  align-items: center;
  gap: 6px;
  font-style: normal;
}
.highlights-loading svg { flex: 0 0 auto; width: 13px; height: 13px; }

.clip-export-progress { display: flex; align-items: center; gap: 6px; }
.clip-export-progress svg { flex: 0 0 auto; width: 13px; height: 13px; }

/* ── +/- time adjustment ──────────────────────────────────── */
.time-adjust {
  display: flex;
  align-items: center;
  gap: 4px;
}

.time-adj-btn {
  width: 28px;
  height: 28px;
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: 5px;
  color: var(--text);
  font-size: 16px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background .12s;
  flex-shrink: 0;
}
.time-adj-btn:hover { background: var(--border2); }

.detail-time-input {
  flex: 1;
  min-width: 0;
}

/* ── Sidebar clip actions ──────────────────────────────────── */
/* ── Sidebar clip layout fixes ────────────────────────────── */
.sidebar-clip-header {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin-bottom: 4px;
}

.sidebar-clip-rank {
  font-size: 11px;
  font-weight: 700;
  width: 24px;
  flex-shrink: 0;
  padding-top: 1px;
}

.sidebar-clip-meta { flex: 1; min-width: 0; }

.sidebar-clip-time {
  display: flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
  font-size: 12px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
/* The in/out range yields first — it is the less useful half when scanning. */
.clip-range {
  min-width: 0;
  flex: 0 1 auto;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Duration never shrinks and never truncates. */
.clip-dur {
  flex: 0 0 auto;
  white-space: nowrap;
  color: var(--text-muted);
  font-weight: 600;
}
.clip-dur::before { content: '· '; }

.sidebar-clip-score {
  font-size: 11px;
  color: var(--text-muted);
  margin-top: 1px;
}

.sidebar-clip-summary {
  font-size: 11px;
  color: var(--text-muted);
  line-height: 1.4;
  margin: 5px 0 5px 32px;
  font-style: italic;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.sidebar-clip-topics {
  font-size: 10px;
  color: var(--text-dim);
  margin: 2px 0 6px 32px;
}





/* Indented to line up with the clip's text, not its number. */
.sidebar-clip-actions { margin: 6px 0 0 32px; }

/* Timeline marker minimum width */
.timeline-marker {
  min-width: 2px !important;
}



.highlight-item.active {
  background: rgba(59,130,246,.15);
  border: 1px solid rgba(59,130,246,.3);
  border-radius: 5px;
}

.btn-queue-highlight.queued {
  background: rgba(59,130,246,.15);
  border-color: rgba(59,130,246,.4);
  color: #60a5fa;
}

/* Export in flight — locked out so the same highlight can't be queued twice */
.btn-queue-highlight.exporting,
.btn-queue-highlight.exporting:hover {
  background: rgba(148,163,184,.12);
  border-color: rgba(148,163,184,.35);
  color: var(--text-muted);
  cursor: not-allowed;
  opacity: 1;
  pointer-events: none;
}

/* ── Icon styles ──────────────────────────────────────────── */
.icon, [class^="icon-"] {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  vertical-align: middle;
  flex-shrink: 0;
}
.icon svg, [class^="icon-"] svg { display: block; }

/* Spinning animation for spinner icon */
@keyframes spin { to { transform: rotate(360deg); } }
.spin { animation: spin 0.8s linear infinite; display: inline-block; }


/* ── Re-export ────────────────────────────────────────────── */
/* A clip can always be exported again. Watching the result is exactly when a
   user notices the trim is wrong, so the action must not collapse to a
   download-only state. */
.clip-reexport-btn {
  font-size: 10px;
  font-weight: 600;
  padding: 3px 7px;
  line-height: 1.4;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text-muted);
  cursor: pointer;
  white-space: nowrap;
  transition: all .12s;
}
.clip-reexport-btn:hover { border-color: var(--accent); color: var(--accent); }

/* The trim moved after the file was made — the download is out of date. */
.clip-reexport-btn.urgent,
.btn-queue-highlight.urgent {
  background: rgba(245,158,11,.15);
  border-color: rgba(245,158,11,.45);
  color: #fbbf24;
}
.clip-export-done.stale .clip-export-check { color: #fbbf24; }

.highlight-actions {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.highlight-actions .clip-export-dl { padding: 3px 6px; }


/* ── Caption editor modal ─────────────────────────────────── */
.modal-backdrop {
  position: fixed; inset: 0; z-index: 200;
  background: rgba(0,0,0,.6);
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
}
.modal-backdrop[hidden] { display: none; }

.modal {
  width: min(680px, 100%);
  max-height: 82vh;
  display: flex; flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 20px 60px rgba(0,0,0,.5);
  overflow: hidden;
}
.modal-header {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
  padding: 14px 16px; border-bottom: 1px solid var(--border);
}
.modal-title { font-size: 13px; font-weight: 700; }
.modal-sub   { font-size: 11px; color: var(--text-muted); margin-top: 2px; }
.modal-close {
  background: transparent; border: none; color: var(--text-muted);
  font-size: 15px; cursor: pointer; padding: 2px 6px; border-radius: 4px;
}
.modal-close:hover { color: var(--text); background: var(--surface2); }

.modal-body { padding: 12px 16px; overflow-y: auto; flex: 1; }
.modal-footer {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 16px; border-top: 1px solid var(--border);
}
.modal-note { font-size: 10px; color: var(--text-muted); }
.modal-actions { display: flex; gap: 8px; }

/* Words grouped the way they appear on screen, so the row the user is looking
   for matches what they saw in the burned-in caption. */
.cc-phrase {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px;
  padding: 6px 8px; border-radius: 6px; margin-bottom: 4px;
  background: var(--surface2);
}
.cc-time {
  font-size: 10px; color: var(--text-muted);
  font-variant-numeric: tabular-nums; min-width: 44px; flex: 0 0 auto;
}
.cc-word {
  font-size: 12px; padding: 3px 6px;
  background: transparent; border: 1px solid transparent; border-radius: 4px;
  color: var(--text); font-family: inherit; min-width: 28px;
}
.cc-word:hover  { border-color: var(--border2); }
.cc-word:focus  { outline: none; border-color: var(--accent); background: var(--surface); }
.cc-word.edited { border-color: #fbbf24; background: rgba(245,158,11,.12); color: #fbbf24; }

.btn-ghost {
  font-size: 11px; font-weight: 600; padding: 6px 12px;
  background: transparent; border: 1px solid var(--border);
  border-radius: 5px; color: var(--text-muted); cursor: pointer;
}
.btn-ghost:hover { color: var(--text); border-color: var(--border2); }
/* Scoped to dialogs. As a bare .btn-primary this clobbered the app's own
   button style — the analyze button lost its padding and its icon alignment,
   because two rules with equal specificity resolve by source order. */
.dlg .btn-primary,
.modal .btn-primary {
  width: auto;
  font-size: 11px; font-weight: 600; padding: 6px 14px;
  border: 1px solid var(--accent);
  border-radius: 5px;
}

.btn-cc {
  font-size: 10px; font-weight: 600; padding: 3px 8px;
  background: transparent; border: 1px solid var(--border);
  border-radius: 4px; color: var(--text-muted); cursor: pointer; white-space: nowrap;
}
.btn-cc:hover { border-color: var(--accent); color: var(--accent); }
.btn-cc:disabled { opacity: .4; cursor: not-allowed; }

.sidebar-clip-meta { display: flex; align-items: center; gap: 6px; flex-wrap: nowrap; }
.sidebar-clip-meta .btn-cc { margin-left: auto; flex: 0 0 auto; }


/* ── Sign in ──────────────────────────────────────────────── */
/* Two panels: brand on the left, form on the right. The form panel is the one
   that must always work, so it is the one that survives at every width. */
.login-body { min-height: 100vh; display: flex; flex-direction: column; }

.login-split {
  flex: 1;
  display: grid;
  grid-template-columns: 1.15fr 1fr;
  min-height: 0;
}
/* Both panels stretch to the taller of the two, so the artwork always fills its
   side even when the card is the thing setting the height. */
.login-split > * { min-height: 0; }

/* ── Brand panel ───────────────────────────────────────────── */
.login-brand {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 36px 48px;
  overflow: hidden;
  border-right: 1px solid var(--border);
  background-image: url('/img/bg-login.jpg');
  background-size: cover;
  background-repeat: no-repeat;
  background-color: var(--bg);
}

/* Sits above the background so nothing in the artwork can render over the
   text. The scrim is on this rather than on the panel so it travels with the
   copy when the copy moves. */
.login-brand::before {
  content: '';
  position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(8,12,20,.55) 0%, rgba(8,12,20,.20) 38%, transparent 62%);
  pointer-events: none;
}
.login-brand > * { position: relative; }

.login-brand-head { flex: 0 0 auto; }
.login-brand-logo { height: 30px; display: block; }

/* Auto margins top and bottom: the logo holds the top, and the copy sits in the
   optical centre of what is left. */
.login-brand-copy {
  flex: 0 0 auto;
  max-width: 560px;
  margin: auto 0;
  padding: 32px 0;
}

.login-pill {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 13px; margin-bottom: 22px;
  font-size: 11px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase;
  color: #93c5fd;
  background: rgba(59,130,246,.12);
  border: 1px solid rgba(59,130,246,.28);
  border-radius: 999px;
}
/* Every inline SVG on this page is sized here, because none of them carry
   width/height attributes — an unsized SVG has no intrinsic size and expands to
   fill its container, which turned the pill's star into a full-panel graphic. */
.login-pill svg { width: 13px; height: 13px; display: block; flex: 0 0 auto; }

.login-headline {
  font-size: clamp(30px, 3.1vw, 44px);
  font-weight: 700; line-height: 1.14; letter-spacing: -.025em;
  color: #fff;
}
.login-headline span { color: var(--accent); }

.login-lede {
  margin-top: 16px;
  font-size: 14px; line-height: 1.65; color: var(--text-muted);
}

.login-features {
  display: flex; flex-wrap: wrap; gap: 10px;
  margin-top: 30px;
}
.login-feature {
  display: flex; align-items: center; gap: 10px;
  padding: 11px 15px;
  background: rgba(255,255,255,.045);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  backdrop-filter: blur(6px);
}
.login-feature-icon { display: flex; color: var(--accent); flex: 0 0 auto; }
.login-feature-icon svg { width: 17px; height: 17px; display: block; }
.login-feature-name { font-size: 12.5px; font-weight: 600; color: var(--text); }
.login-feature-sub  { font-size: 11px; color: var(--text-muted); margin-top: 1px; }

/* ── Form panel ────────────────────────────────────────────── */
.login-panel {
  display: flex; align-items: center; justify-content: center;
  padding: 40px 32px;
}

.login-card {
  width: min(400px, 100%);
  display: flex; flex-direction: column;
  padding: 36px 32px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
}

.login-card-head { text-align: center; margin-bottom: 26px; }
/* Only shown when the brand panel is not — otherwise the logo appears twice. */
.login-card-logo { height: 28px; display: none; margin: 0 auto 18px; }
.login-title { font-size: 23px; font-weight: 700; letter-spacing: -.02em; color: #fff; }
.login-sub   { font-size: 13px; color: var(--text-muted); margin-top: 6px; }

.login-fields { display: flex; flex-direction: column; }
/* An author `display` beats the UA stylesheet's [hidden] { display: none } on a
   specificity tie, so the login fields stayed visible after switching to the
   set-password step. */
.login-fields[hidden] { display: none; }

.login-label {
  font-size: 12.5px; font-weight: 600; color: var(--text);
  margin-bottom: 7px;
}
.login-label + .login-input-wrap { margin-bottom: 16px; }

.login-input-wrap { position: relative; display: flex; align-items: center; }
.login-input-icon {
  position: absolute; left: 13px;
  display: flex; color: var(--text-muted); pointer-events: none;
}
.login-input-icon svg { width: 16px; height: 16px; display: block; }

.login-input {
  width: 100%;
  padding: 12px 14px 12px 40px;
  background: var(--bg); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  font-family: inherit; font-size: 13.5px;
}
.login-input::placeholder { color: var(--text-dim); }
.login-input:focus { outline: none; border-color: var(--accent); }
/* Room for the reveal button, so a long password does not run underneath it. */
.login-input-wrap:has(.login-reveal) .login-input { padding-right: 42px; }

.login-reveal {
  position: absolute; right: 6px;
  display: flex; align-items: center; justify-content: center;
  width: 30px; height: 30px;
  background: none; border: 0; border-radius: 6px;
  color: var(--text-muted); cursor: pointer;
}
.login-reveal:hover { color: var(--text); }
.login-reveal svg { width: 16px; height: 16px; display: block; }

.login-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-top: 2px;
}
.login-check {
  display: flex; align-items: center; gap: 8px;
  font-size: 13px; color: var(--text); cursor: pointer;
}
.login-check input { cursor: pointer; }

.login-link {
  background: none; border: 0; padding: 0;
  font-family: inherit; font-size: 13px; color: var(--accent); cursor: pointer;
}
.login-link:hover { text-decoration: underline; }
.login-link-static { color: var(--accent); }

.login-note {
  margin-top: 12px; padding: 10px 12px;
  font-size: 12px; line-height: 1.5; color: var(--text-muted);
  background: rgba(255,255,255,.03);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.login-note[hidden] { display: none; }

.login-error {
  margin-top: 16px; padding: 10px 12px;
  font-size: 12.5px; color: #fca5a5;
  background: rgba(239,68,68,.1);
  border: 1px solid rgba(239,68,68,.3); border-radius: var(--radius-sm);
}
.login-error[hidden] { display: none; }

.login-submit {
  margin-top: 22px; padding: 12px;
  background: var(--accent); color: #fff;
  border: 1px solid var(--accent); border-radius: var(--radius-sm);
  font-family: inherit; font-size: 14px; font-weight: 600; cursor: pointer;
  transition: filter .13s;
}
.login-submit:hover:not(:disabled) { filter: brightness(1.1); }
.login-submit:disabled { opacity: .6; cursor: not-allowed; }

.login-foot-note {
  margin-top: 22px; text-align: center;
  font-size: 12.5px; color: var(--text-muted);
}

.login-footer {
  flex: 0 0 auto;
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px;
  padding: 16px 32px;
  border-top: 1px solid var(--border);
  font-size: 12px; color: var(--text-muted);
}
.login-footer-links { display: flex; gap: 22px; }
.login-footer-links a { color: var(--text-muted); text-decoration: none; }
.login-footer-links a:hover { color: var(--text); }

/* Below this the two panels cannot both be read, and the form is the one people
   came for — the brand panel goes rather than stacking above it and pushing the
   fields off the first screen. */
@media (max-width: 980px) {
  .login-split { grid-template-columns: 1fr; }
  .login-brand { display: none; }
  .login-card-logo { display: block; }
  .login-footer { flex-direction: column; text-align: center; gap: 10px; }
}


/* ── Account menu ─────────────────────────────────────────── */
/* Back and the avatar share this group, so Back is no longer pinned beside the
   logo where it read as part of the branding. */
.header-right { display: flex; align-items: center; gap: 12px; }

.header-user { position: relative; display: flex; align-items: center; }
/* Author `display` beats the UA stylesheet's [hidden] on a specificity tie. */
.header-user[hidden] { display: none; }

.header-avatar {
  width: 30px; height: 30px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 700; letter-spacing: .02em;
  background: var(--accent); border: none; color: #fff;
  cursor: pointer; padding: 0;
}
.header-avatar:hover { filter: brightness(1.12); }

.header-menu {
  position: absolute; top: calc(100% + 8px); right: 0;
  min-width: 190px; padding: 6px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 8px; box-shadow: 0 12px 32px rgba(0,0,0,.45);
  z-index: 300;
}
.header-menu[hidden] { display: none; }

.header-menu-email {
  font-size: 11px; font-weight: 600; color: var(--text);
  padding: 6px 8px 2px; word-break: break-all;
}
.header-menu-client {
  font-size: 10px; color: var(--text-muted);
  padding: 0 8px 8px; border-bottom: 1px solid var(--border); margin-bottom: 4px;
}
.header-menu-item {
  width: 100%; text-align: left;
  font-size: 12px; font-weight: 600; padding: 10px;
  background: transparent; border: none; border-radius: 5px;
  color: var(--text-muted); cursor: pointer;
}
.header-menu-item:hover { background: var(--surface2); color: var(--text); }


/* ── Dialogs (alert / confirm replacements) ───────────────── */
/* No backdrop-click close, same as the caption editor: a misclick outside must
   not dismiss a message unread or silently answer a question. */
.dlg-backdrop {
  position: fixed; inset: 0; z-index: 400;
  background: rgba(0,0,0,.6);
  display: flex; align-items: center; justify-content: center; padding: 24px;
}
.dlg-backdrop[hidden] { display: none; }

.dlg {
  width: min(400px, 100%);
  padding: 20px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 20px 60px rgba(0,0,0,.5);
}
.dlg-title { font-size: 13px; font-weight: 700; margin-bottom: 6px; }
.dlg-title[hidden] { display: none; }
.dlg-message { font-size: 12px; line-height: 1.5; color: var(--text-muted); }
.dlg-actions {
  display: flex; justify-content: flex-end; gap: 8px; margin-top: 18px;
}
.dlg-actions .btn-ghost[hidden] { display: none; }


/* Caption template picker in the export bar. */
.queue-template {
  grid-area: tpl;
  font-size: 11px; font-weight: 600;
  padding: 6px 8px; min-width: 0; width: 100%;
  background: var(--surface2); border: 1px solid var(--border);
  border-radius: 5px; color: var(--text); cursor: pointer;
}
.queue-template:hover { border-color: var(--accent); }


/* ── Templates ─────────────────────────────────────────────── */
/* Full bleed. The sidebar already constrains the content column, so a second
   max-width inside it left a wide screen with empty gutters on both sides of a
   grid that has more cards to show. Padding, not centring, sets the margins. */
.tpl-view { max-width: none; margin: 0; padding: 16px 32px 56px; }
.tpl-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 24px; margin-bottom: 16px; }
/* The one primary action for a page, sitting in its heading row. Shared by
   Videos and Templates so the two agree; sized down from the full-width button
   the form views use, and never allowed to stretch. */
.head-action { width: auto; padding: 10px 18px; font-size: 12px; flex: 0 0 auto; }

/* ── Page back link (shared component) ─────────────────────── */
/* Sits above the page heading on every view that has one. Names its
   DESTINATION rather than the current page: a chevron already says "back", and
   labelling it with where you are makes the two disagree.

   Always calls goBack() rather than showView() directly, so the reframe view's
   unsaved-changes guard still runs — routing around it here would discard work
   silently. */
.page-back {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 0; margin-bottom: 14px;
  background: none; border: 0;
  color: var(--accent); font-family: inherit;
  font-size: 11px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  cursor: pointer;
}
.page-back:hover { filter: brightness(1.25); }
.page-back svg { display: block; }

/* ── Toolbar (shared component) ─────────────────────────────── */
/* A band that holds a view's controls. Written for the templates page and
   reused by the videos list; the BAND and the CONTROLS are shared, what goes
   inside is not — templates wants search/sort/layout, videos wants
   search/analyze/filter/sort. Compose per view, do not add view-specific rules
   here.

   The controls invert to --bg: against a --surface band a --surface control is
   invisible, so they read as inset rather than floating. */
.toolbar {
  display: flex; align-items: center; gap: 10px;
  margin-bottom: 20px;
  padding: 10px 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  flex-wrap: wrap;
}
/* Pushes everything after it to the far end of the band. */
.toolbar-spacer { flex: 1 1 auto; }

/* Input and its button share a row and read as one control: the button is
   square-cornered on the side that meets the input, and the two overlap by a
   border so there is no seam. */
.toolbar-search { position: relative; display: flex; flex: 1 1 260px; max-width: 380px; }
.toolbar-search-icon {
  position: absolute; left: 11px; top: 50%; transform: translateY(-50%);
  color: var(--text-muted); display: flex; pointer-events: none;
}
.toolbar-search-input {
  width: 100%; min-width: 0; padding: 9px 12px 9px 34px;
  background: var(--bg); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  font-family: inherit; font-size: 13px;
}
.toolbar-search.has-btn .toolbar-search-input {
  border-top-right-radius: 0; border-bottom-right-radius: 0;
}
.toolbar-search-btn {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0 13px;
  margin-left: -1px;             /* collapse the doubled border */
  background: var(--surface2); color: var(--text);
  border: 1px solid var(--border);
  border-top-right-radius: var(--radius-sm);
  border-bottom-right-radius: var(--radius-sm);
  cursor: pointer;
  transition: background .13s, color .13s;
}
.toolbar-search-btn:hover { background: var(--accent); border-color: var(--accent); color: #fff; }
/* Focus must beat the button's negative margin or the ring is clipped. */
.toolbar-search-input:focus { position: relative; z-index: 1; }
.toolbar-search-input::placeholder { color: var(--text-muted); }
.toolbar-search-input:focus { outline: none; border-color: var(--accent); }
/* WebKit's own clear button sits on a light background and is invisible here. */
.toolbar-search-input::-webkit-search-cancel-button { -webkit-appearance: none; }

.toolbar-select {
  padding: 9px 12px;
  background: var(--bg); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  font-family: inherit; font-size: 13px; cursor: pointer;
}
.toolbar-select:focus { outline: none; border-color: var(--accent); }

/* Filter popover. Anchored to its button rather than centred, so it opens
   under the control it belongs to and does not cover the results. */
.toolbar-filter { position: relative; flex: 0 0 auto; }
.toolbar-filter-btn {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 9px 13px;
  background: var(--bg); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  font-family: inherit; font-size: 13px; cursor: pointer;
  transition: border-color .13s, color .13s;
}
.toolbar-filter-btn:hover { border-color: var(--border2); }
/* An active filter has to be visible while the panel is shut, or the list
   looks wrong for no apparent reason. */
.toolbar-filter-btn.active { border-color: var(--accent); color: #fff; }
.toolbar-filter-count {
  min-width: 17px; height: 17px; padding: 0 5px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--accent); color: #fff;
  border-radius: 9px; font-size: 10px; font-weight: 700;
}
.toolbar-filter-count[hidden] { display: none; }

.toolbar-filter-panel {
  position: absolute; top: calc(100% + 8px); left: 0;
  z-index: 60;
  min-width: 208px;
  padding: 14px;
  background: var(--surface2);
  border: 1px solid var(--border2);
  border-radius: var(--radius);
  box-shadow: 0 12px 32px rgba(0,0,0,.5);
}
.toolbar-filter-panel[hidden] { display: none; }
.toolbar-filter-group { margin-bottom: 14px; }
.toolbar-filter-group:last-of-type { margin-bottom: 10px; }
.toolbar-filter-label {
  font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-muted); margin-bottom: 8px;
}
.toolbar-filter-group label {
  display: flex; align-items: center; gap: 8px;
  padding: 4px 0;
  font-size: 13px; color: var(--text); cursor: pointer;
}
.toolbar-filter-group .toolbar-select { width: 100%; }
.toolbar-filter-actions {
  display: flex; gap: 8px; align-items: center;
  padding-top: 12px; border-top: 1px solid var(--border);
}
.toolbar-filter-actions .btn-primary {
  width: auto; margin-left: auto; padding: 7px 16px; font-size: 12px;
}
.toolbar-filter-clear {
  background: none; border: 0; padding: 0;
  color: var(--text-muted); font-family: inherit; font-size: 12px; cursor: pointer;
}
.toolbar-filter-clear:hover { color: var(--text); }

/* Segmented control. */
.toolbar-seg {
  display: flex;
  background: var(--bg); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 2px; gap: 2px;
}
/* Sized for its content, with a floor rather than a fixed width.
   This was `width: 30px`, built for the icon-only grid/rows toggle on Videos.
   The calendar's Week / Month / Agenda are text, and a 30px box made all three
   overflow and print on top of each other. */
.toolbar-seg-btn {
  display: flex; align-items: center; justify-content: center;
  min-width: 30px; height: 28px; padding: 0 11px;
  background: transparent; border: 0; border-radius: 6px;
  font-family: inherit; font-size: 12.5px; font-weight: 600; white-space: nowrap;
  color: var(--text-muted); cursor: pointer; transition: background .12s, color .12s;
}
/* The icon-only variant keeps the square it was drawn for. */
.toolbar-seg-btn:has(svg) { padding: 0; width: 30px; }
.toolbar-seg-btn:hover { color: var(--text); }
/* Reads against --bg, so the selection lifts rather than recedes.
   Both class names: Videos marks the selected layout `.active`, the calendar
   marks the selected mode `.on`. One rule rather than two, so a third user of
   this component cannot pick a name that quietly styles nothing. */
.toolbar-seg-btn.active,
.toolbar-seg-btn.on { background: var(--surface2); color: var(--text); }

/* Grid */
.tpl-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 16px; }
.tpl-list.rows { grid-template-columns: 1fr; gap: 10px; }
.tpl-empty {
  grid-column: 1 / -1;
  font-size: 13px; color: var(--text-muted);
  text-align: center; padding: 56px 24px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 16px;
}

.tpl-card {
  position: relative;
  display: flex; flex-direction: column;
  padding: 18px;
  cursor: pointer;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  transition: border-color .13s, transform .13s;
}
.tpl-card:hover { border-color: var(--border2); transform: translateY(-2px); }

/* The wash is tinted from the template's own spoken-word colour, so the card
   previews the style rather than decorating it. Behind the content and
   non-interactive, or it would swallow the click that opens the builder. */
.tpl-card-wash {
  position: absolute; inset: 0;
  pointer-events: none;
  opacity: .5;
}
.tpl-card > *:not(.tpl-card-wash) { position: relative; }

.tpl-card-brand {
  width: 40px; height: 40px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 8px; overflow: hidden;
  margin-bottom: 14px;
}
.tpl-card-brand img { max-width: 100%; max-height: 100%; object-fit: contain; display: block; }
/* Fallback when no logo is attached — most templates have none. Uses the
   template's own two colours so the monogram still says something. */
.tpl-card-mono { font-size: 15px; font-weight: 800; letter-spacing: -.02em; }
/* `hidden` alone loses to the flex container's display, so the monogram would
   sit visible underneath the logo. */
.tpl-card-brand [hidden] { display: none; }

.tpl-card-name { font-size: 15px; font-weight: 700; letter-spacing: -.01em; }
.tpl-card-date { font-size: 11px; color: var(--text-muted); margin-top: 3px; }

.tpl-card-colors-label {
  font-size: 10px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase;
  color: var(--text-muted); margin: 16px 0 7px;
}
.tpl-card-swatches { display: flex; gap: 7px; }
.tpl-card-swatch {
  width: 26px; height: 26px; border-radius: 6px;
  border: 1px solid var(--border2);
}

/* One line, always. Wrapping made cards in the same grid row different heights,
   which is the most visible kind of misalignment in a grid. */
.tpl-card-tags { display: flex; flex-wrap: nowrap; gap: 6px; margin-top: 16px; min-width: 0; }
.tpl-card-tag {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 8px;
  font-size: 10.5px; color: var(--text-muted);
  background: rgba(255,255,255,.04);
  border: 1px solid var(--border);
  border-radius: 5px;
  white-space: nowrap;
  /* Stated rather than inherited: the shrinking tag has to be `display: block`
     for text-overflow to apply, and a block box and an inline-flex box only
     agree on height if the line box is the same. Without this the font tag sat
     a pixel off its neighbours. */
  line-height: 1.5;
  /* The fixed tags state a constant and a number — both are short and both
     become meaningless if clipped, so only `.shrink` gives way. */
  flex: 0 0 auto;
}
.tpl-card-tag.shrink {
  flex: 0 1 auto;
  min-width: 0;          /* without this a flex item refuses to shrink past its content */
  overflow: hidden;
  text-overflow: ellipsis;
  display: block;        /* text-overflow does not apply to a flex container */
}

.tpl-card-open {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  margin-top: 16px; padding: 9px;
  font-size: 12px; font-weight: 600; color: var(--text);
  background: rgba(255,255,255,.05);
  border: 1px solid var(--border2);
  border-radius: var(--radius-sm);
}
.tpl-card:hover .tpl-card-open { background: rgba(255,255,255,.1); }

/* Row layout: same card, laid out horizontally. The wash and the Colors label
   are dropped rather than restyled — at this height they crowd the row. */
.tpl-list.rows .tpl-card { flex-direction: row; align-items: center; gap: 16px; padding: 14px 18px; }
.tpl-list.rows .tpl-card:hover { transform: none; }
.tpl-list.rows .tpl-card-wash,
.tpl-list.rows .tpl-card-colors-label { display: none; }
.tpl-list.rows .tpl-card-brand { margin-bottom: 0; flex: 0 0 auto; }
.tpl-list.rows .tpl-card-main { flex: 1; min-width: 0; }
.tpl-list.rows .tpl-card-colors,
.tpl-list.rows .tpl-card-tags,
.tpl-list.rows .tpl-card-open { margin-top: 0; flex: 0 0 auto; }
.tpl-list.rows .tpl-card-open { padding: 8px 14px; }
@media (max-width: 760px) {
  .tpl-list.rows .tpl-card-colors, .tpl-list.rows .tpl-card-tags { display: none; }
}


/* Editor: controls on the left, live 9:16 preview on the right. */
/* ── Template editor ───────────────────────────────────────── */
.tpl-edit-view { max-width: none; margin: 0; padding: 16px 32px 56px; }
.tpl-edit-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 24px; margin-bottom: 16px;
}
.tpl-edit-actions { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }
.tpl-edit-actions .btn-ghost { width: auto; padding: 9px 14px; font-size: 12px; }

/* Controls | preview | info. The preview column is fixed at the width of the
   frame it draws; the other two take what is left. */
/* Controls | preview | info. The preview column is the widest: it is the thing
   being judged, and the controls are compact rows that do not need the space. */
.tpl-edit {
  display: grid;
  grid-template-columns: minmax(0, 400px) minmax(0, 1fr) minmax(0, 310px);
  gap: 20px;
  align-items: start;
}

.tpl-fields {
  position: relative;
  min-width: 0;
  padding: 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
/* Name field with its counter */
.tpl-name-wrap { position: relative; }
.tpl-name-wrap .form-input { padding-right: 62px; }
.tpl-name-count {
  position: absolute; right: 11px; top: 50%; transform: translateY(-50%);
  font-size: 11px; color: var(--text-dim); pointer-events: none;
}
.tpl-name-count.full { color: var(--yellow); }

/* Accordion.
   Each section is a bordered block with a filled header bar, so the header
   reads as a header rather than as a bold line in a long form. */
.tpl-acc {
  margin-top: 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.tpl-acc-head {
  display: flex; align-items: center; gap: 10px;
  width: 100%; padding: 13px 14px;
  background: var(--bg);
  border: 0;
  color: var(--text); font-family: inherit; font-size: 13px; font-weight: 600;
  text-align: left; cursor: pointer;
  transition: background .13s;
}
.tpl-acc-head:hover { background: var(--surface2); }
/* An open section's header sits against its own body, so it takes a rule to
   separate them and the accent bar to mark which one is open. */
.tpl-acc.open .tpl-acc-head {
  border-bottom: 1px solid var(--border);
  box-shadow: inset 3px 0 0 var(--accent);
}
.tpl-acc-n {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; flex: 0 0 auto;
  background: var(--accent-dim); color: var(--accent);
  border-radius: 5px; font-size: 11px; font-weight: 700;
}
.tpl-acc-chev { margin-left: auto; display: flex; color: var(--text-muted); }
.tpl-acc-chev svg { display: block; transition: transform .15s; }
.tpl-acc-chev.up svg { transform: rotate(180deg); }
.tpl-acc-body[hidden] { display: none; }
.tpl-acc-body { padding: 14px; }

.tpl-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.tpl-hint { font-weight: 400; color: var(--text-muted); text-transform: none; letter-spacing: 0; }
.tpl-val { color: var(--accent); font-weight: 700; }
.tpl-color-row { display: flex; align-items: center; gap: 8px; }
.tpl-color {
  width: 40px; height: 38px; flex: 0 0 auto; padding: 3px; cursor: pointer;
  background: var(--bg);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
}
/* Trim the UA chrome so the control reads as a swatch rather than a button. */
.tpl-color::-webkit-color-swatch-wrapper { padding: 0; }
.tpl-color::-webkit-color-swatch { border: 0; border-radius: 4px; }
.tpl-hex {
  min-width: 0; flex: 1 1 auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px; text-transform: uppercase;
}
/* Typed but not yet a valid colour. The picker keeps the last good value, so
   this marks the text without changing what will be saved. */
.tpl-hex.bad { border-color: rgba(239,68,68,.6); color: #fca5a5; }
.tpl-check label { font-size: 12px; display: flex; align-items: center; gap: 8px; cursor: pointer; }
.tpl-delete { color: #f87171; border-color: rgba(248,113,113,.3); }
.tpl-delete:hover { color: #fca5a5; border-color: rgba(248,113,113,.6); }

/* ── Preview ───────────────────────────────────────────────── */
/* 1/4 of 1080x1920 — the same proportions the ASS is written for, so margins
   and font sizes in the preview map directly onto the export. */
.tpl-preview-wrap {
  /* Offset by the header, not from the viewport top. The header is sticky and
     62px tall, so a bare `top: 24px` parked the preview underneath it and cut
     off its own heading once the page scrolled. */
  position: sticky; top: calc(var(--header-h) + 24px);
  padding: 16px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius);
}
.tpl-preview-label {
  font-size: 10px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--text-muted); margin-bottom: 12px;
}
.tpl-preview {
  /* 1/3 of 1080x1920. Tied to SCALE in templates.js — change one and the other
     must follow, or the preview stops matching the export. */
  position: relative; width: 360px; height: 640px; margin: 0 auto; overflow: hidden;
  border-radius: 10px; border: 1px solid var(--border);
  /* A real frame rather than a flat dark panel: outline and shadow only earn
     their settings over actual picture detail, and judging them against a plain
     background is misleading. The gradient stays underneath in case the image
     fails to load. */
  background:
    url('/img/preview-bg.jpg') center / cover no-repeat,
    linear-gradient(160deg, #2a2f45 0%, #171a28 55%, #10121c 100%);
}
.tpl-preview-caption { position: absolute; text-align: center; word-break: break-word; }

/* ── Info rail ─────────────────────────────────────────────── */
.tpl-info { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.tpl-info-card {
  padding: 16px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius);
}
.tpl-info-title { font-size: 13px; font-weight: 700; margin-bottom: 4px; }
.tpl-info-note {
  font-size: 11.5px; line-height: 1.5; color: var(--text-muted);
  margin-bottom: 12px;
}
.tpl-info-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 7px 0;
  font-size: 12px;
  border-top: 1px solid var(--border);
}
.tpl-info-row:first-of-type { border-top: 0; }
.tpl-info-row > span:first-child { color: var(--text-muted); flex: 0 0 auto; }
.tpl-info-row > span:last-child {
  color: var(--text); text-align: right; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tpl-info-dim { color: var(--text-dim); }
.tpl-info-swatch { display: inline-flex; align-items: center; gap: 7px; font-size: 11.5px; }
.tpl-info-swatch i {
  width: 14px; height: 14px; border-radius: 4px;
  border: 1px solid var(--border2); flex: 0 0 auto;
}

/* Below this the three columns cannot all be read. The info rail goes under the
   preview rather than being dropped: it is the only place the saved values are
   visible. */
@media (max-width: 1280px) {
  .tpl-edit { grid-template-columns: minmax(0, 360px) minmax(0, 1fr); }
  .tpl-info { grid-column: 1 / -1; flex-direction: row; flex-wrap: wrap; }
  .tpl-info-card { flex: 1 1 260px; }
}
@media (max-width: 860px) {
  .tpl-edit { grid-template-columns: 1fr; }
  .tpl-preview-wrap { position: static; }
}

.tpl-preview-note { font-size: 10px; color: var(--text-muted); margin-top: 8px; text-align: center; }

@media (max-width: 820px) {
  .tpl-edit { grid-template-columns: 1fr; }
  .tpl-preview-wrap { position: static; justify-self: center; }
}





/* ── Rubric ratings on a highlight ─────────────────────────── */
/* Bands as dots rather than a number: the model judges categories reliably and
   invents precision when asked for a figure, so the display should not imply
   more than three levels. */
.rating-row {
  grid-area: rating;
  display: flex; flex-wrap: wrap; gap: 10px;
  margin-top: 4px; font-size: 10px; color: var(--text-muted);
}
.rating-dim { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
.dots { display: inline-flex; gap: 2px; }
.dots i {
  width: 5px; height: 5px; border-radius: 50%;
  background: var(--border2); display: inline-block;
}
.dots i.on { background: var(--accent); }

.rating-note {
  grid-area: note;
  margin-top: 3px; font-size: 10px; font-style: italic;
  color: var(--text-muted); opacity: .85;
  overflow: hidden; text-overflow: ellipsis; display: -webkit-box;
  -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}


/* ── Jobs pager ────────────────────────────────────────────── */
/* Count on the left, pages centred, rows-per-page on the right. Three slots
   rather than a centred row, so the count does not shift as the page number
   changes width. */
.jobs-pager {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 14px;
  margin: 24px 0 8px;
}
/* Declared so the author `display:grid` above does not beat [hidden]. */
.jobs-pager[hidden] { display: none; }

.pager-info { font-size: 12px; color: var(--text-muted); }
.pager-pages { display: flex; align-items: center; gap: 6px; justify-self: center; }
.pager-rows {
  display: flex; align-items: center; gap: 8px; justify-self: end;
  font-size: 12px; color: var(--text-muted);
}
.pager-rows select {
  padding: 5px 8px;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: 6px;
  font-family: inherit; font-size: 12px; cursor: pointer;
}

.pager-btn {
  min-width: 30px; height: 30px; padding: 0 9px;
  display: inline-flex; align-items: center; justify-content: center;
  font-family: inherit; font-size: 12px; font-weight: 600;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 6px; color: var(--text-muted); cursor: pointer;
  transition: color .13s, border-color .13s, background .13s;
}
.pager-btn:hover:not(:disabled):not(.active) { color: var(--text); border-color: var(--border2); }
.pager-btn.active {
  background: var(--accent); border-color: var(--accent); color: #fff; cursor: default;
}
.pager-btn:disabled { opacity: .35; cursor: not-allowed; }
/* A gap in the page run, e.g. 1 2 3 … 9. Not a button. */
.pager-gap { color: var(--text-dim); padding: 0 2px; font-size: 12px; }


/* ── Trim step + boundary words ────────────────────────────── */
.step-row { display: flex; align-items: center; gap: 8px; }
.step-label {
  font-size: 10px; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: var(--text-muted);
}
.step-select {
  font-size: 11px; font-weight: 600; padding: 4px 8px;
  background: var(--surface2); border: 1px solid var(--border);
  border-radius: 5px; color: var(--text); cursor: pointer;
}
.step-select:hover { border-color: var(--accent); }

/* Reading the boundary is exact where listening is approximate. */
.detail-edge { margin-top: 10px; font-size: 11px; line-height: 1.5; color: var(--text-muted); }
.detail-edge[hidden] { display: none; }
.detail-edge .cut  { opacity: .35; text-decoration: line-through; }
.detail-edge .keep { color: var(--text); font-weight: 600; }
.detail-edge .lbl  {
  display: inline-block; min-width: 34px; font-weight: 700; font-size: 10px;
  text-transform: uppercase; letter-spacing: .05em; color: var(--text-muted);
}


/* ── Template logo ─────────────────────────────────────────── */
.tpl-logo-row { display: flex; gap: 8px; align-items: center; }
.tpl-logo-btn, .tpl-logo-remove { font-size: 11px; padding: 6px 12px; }
.tpl-logo-remove { color: #f87171; border-color: rgba(248,113,113,.3); }

/* Positioned by fractions of the frame, so the arrangement here is exactly
   what ffmpeg overlays at 1080x1920. */
.tpl-logo {
  position: absolute;
  cursor: move;
  outline: 1px dashed rgba(255,255,255,.5);
  outline-offset: 2px;
  user-select: none;
}
.tpl-logo[hidden] { display: none; }
.tpl-logo img { display: block; width: 100%; height: auto; pointer-events: none; }

.tpl-logo-handle {
  position: absolute; right: -5px; bottom: -5px;
  width: 11px; height: 11px; border-radius: 2px;
  background: var(--accent); border: 1px solid #fff;
  cursor: nwse-resize;
}

.tpl-logo-name {
  color: var(--text); font-weight: 600;
  display: inline-block; max-width: 240px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  vertical-align: bottom;
}

/* Checkerboard behind the thumbnail so a transparent PNG reads as transparent
   rather than as a white block. */
.tpl-logo-thumb {
  width: 40px; height: 40px; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--border); border-radius: 5px; padding: 3px;
  background-color: #1b2333;
  background-image:
    linear-gradient(45deg, rgba(255,255,255,.06) 25%, transparent 25%),
    linear-gradient(-45deg, rgba(255,255,255,.06) 25%, transparent 25%),
    linear-gradient(45deg, transparent 75%, rgba(255,255,255,.06) 75%),
    linear-gradient(-45deg, transparent 75%, rgba(255,255,255,.06) 75%);
  background-size: 8px 8px;
  background-position: 0 0, 0 4px, 4px -4px, -4px 0;
}
.tpl-logo-thumb[hidden] { display: none; }
.tpl-logo-thumb img { max-width: 100%; max-height: 100%; object-fit: contain; }


/* ── Reframe editor ────────────────────────────────────────── */
/* Full bleed, and sized to the viewport: the picture and the timeline are used
   together — you scrub, watch the crop, drag a divider — so needing to scroll
   between them makes the editor unusable. The view owns the screen height and
   the stage takes whatever the fixed-height rows leave. */
.reframe-view {
  max-width: none; margin: 0;
  padding: 12px 32px 16px;
  height: calc(100vh - var(--header-h));
  display: flex; flex-direction: column;
  overflow: hidden;
}
.reframe-view.wide { max-width: none; }
.reframe-head {
  flex: 0 0 auto;
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  margin-bottom: 12px;
}
/* "Reframe › H2 of clip #4" — the action at full weight, what it acts on
   smaller beside it, so the heading reads as a place rather than a sentence. */
.reframe-head h2 {
  display: flex; align-items: center; gap: 8px;
  font-size: 18px; font-weight: 700; letter-spacing: -.01em;
}
.rf-title-sep { display: flex; color: var(--text-dim); }
.rf-title-sub { font-size: 14px; font-weight: 600; color: var(--text-muted); }

/* Chevrons sit on the text baseline in the nav buttons. */
.rf-chev { display: inline-block; vertical-align: -2px; }
.reframe-head .btn-primary { width: auto; padding: 8px 20px; font-size: 12px; }

.rf-empty {
  padding: 48px 20px; text-align: center; color: var(--text-muted); font-size: 13px;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
}
.rf-hint { font-size: 12px; max-width: 440px; line-height: 1.6; }
.rf-compute { margin-top: 14px; width: auto; padding: 9px 22px; font-size: 12px; }

/* Stage and timeline on the left, the selected shot's controls on the right. */
/* The link in the chain that was missing.
   .rf-layout is not a child of .reframe-view — it is rendered INTO #rf-body,
   which had no rules at all. So the view's definite height stopped there:
   #rf-body sized itself to its content, the grid row below it had nothing to
   resolve against, and every `height: 100%` and `max-height: 100%` further down
   silently did nothing. The picture grew to its natural size and pushed the
   timeline off the screen. */
.reframe-body {
  flex: 1; min-height: 0;
  display: flex; flex-direction: column;
}

.rf-layout {
  flex: 1; min-height: 0;
  display: grid;
  /* The rail carries the shot's whole state now, so it earns the width — and a
     narrower stage is not a worse one: the crop box is drawn from
     stage.clientWidth, so it stays correct at any size. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 380px);
  /* One row, exactly the grid's own height.
     Two things here are load-bearing. `minmax(0, 1fr)` rather than the default
     `auto`: an auto row is sized by its content, so .rf-main's `height: 100%`
     had nothing to resolve against and the picture grew without limit. And
     `align-items: stretch` rather than `start`: with `start` the items do not
     fill the row at all, which had the same effect. Between them the stage was
     never height-constrained, and the timeline sat below the fold. */
  grid-template-rows: minmax(0, 1fr);
  align-items: stretch;
  gap: 20px;
}
/* Stage grows, everything else keeps its natural height. `min-height: 0` is
   load-bearing on both: a grid/flex child defaults to min-height:auto and
   refuses to shrink below its content, which would push the timeline off the
   bottom of the screen instead of shrinking the picture. */
.rf-main {
  min-width: 0; min-height: 0;
  height: 100%;
  display: flex; flex-direction: column;
  /* A backstop: if anything in here ever fails to shrink again, it gets clipped
     rather than pushing the timeline out of the viewport. */
  overflow: hidden;
}
/* Every region sits in its own bordered panel, so the editor reads as a set of
   surfaces rather than controls floating on the page background. */
.rf-panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.rf-stage-fit {
  flex: 1; min-height: 0;
  display: flex; align-items: center; justify-content: center;
  padding: 12px;
}
.rf-tl-panel {
  flex: 0 0 auto;
  margin-top: 12px;
  padding: 10px 12px 12px;
}

/* Sits on the picture, top-left. `z-index: 5` clears the dimming masks, which
   are z-index 0/auto and would otherwise wash it out on a narrow crop. */
.rf-stage-badge {
  position: absolute; top: 10px; left: 10px; z-index: 5;
  display: inline-flex; align-items: center;
  padding: 5px 11px;
  font-size: 11px; font-weight: 600; line-height: 1;
  color: #fff;
  background: rgba(8,12,20,.72);
  border: 1px solid rgba(255,255,255,.14);
  border-radius: 999px;
  backdrop-filter: blur(6px);
  pointer-events: none;
}

/* Sizing the picture, which took three goes to get right. The rules:
     - stage.clientWidth MUST equal the rendered video width, because that is
       what drawBox() converts crop pixels against;
     - the stage must be able to give up HEIGHT, or it pushes the timeline off
       the bottom of the screen.

   `width: 100%` + `aspect-ratio` + `max-height: 100%` satisfies both. The width
   is definite, so the height derives from the ratio; when that height exceeds
   max-height the browser clamps it AND re-derives the width from the ratio, so
   the box shrinks on both axes together and never letterboxes.

   What did not work, for the next person: `height: 100%` with an aspect-ratio
   fixes the height, so max-width can only distort the box, never shorten it.
   And `max-height: 100%` on the VIDEO does nothing at all when the stage's own
   height is auto — a percentage max-height against an auto-height parent is
   ignored, so the video was never capped. */
.rf-stage {
  position: relative;
  width: 100%; max-width: 100%; max-height: 100%;
  border-radius: 8px; overflow: hidden; background: #000; line-height: 0;
}
.rf-stage video { display: block; width: 100%; height: 100%; object-fit: cover; }

/* Dim what the crop excludes, so the framing reads at a glance. Heavy enough
   that the kept region is obviously the subject — at .62 the two halves still
   competed for attention. */
.rf-mask { position: absolute; top: 0; bottom: 0; background: rgba(0,0,0,.78); pointer-events: none; }
.rf-mask-l { left: 0; }
.rf-mask-r { right: 0; left: auto; width: auto; }
.rf-box {
  position: absolute; top: 0; bottom: 0; z-index: 3; cursor: ew-resize;
  box-shadow: inset 0 0 0 2px var(--accent);
}
/* Thirds guides. Faint on purpose — they are for judging where the subject
   sits, not for looking at, and anything stronger competes with the picture.
   Drawn as gradients on a pseudo-element so they cost no extra nodes and
   cannot intercept the drag. */
.rf-box::before {
  content: '';
  position: absolute; inset: 0;
  pointer-events: none;
  background:
    linear-gradient(to right,
      transparent calc(33.333% - 0.5px), rgba(255,255,255,.22) calc(33.333% - 0.5px),
      rgba(255,255,255,.22) calc(33.333% + 0.5px), transparent calc(33.333% + 0.5px)),
    linear-gradient(to right,
      transparent calc(66.666% - 0.5px), rgba(255,255,255,.22) calc(66.666% - 0.5px),
      rgba(255,255,255,.22) calc(66.666% + 0.5px), transparent calc(66.666% + 0.5px)),
    linear-gradient(to bottom,
      transparent calc(33.333% - 0.5px), rgba(255,255,255,.22) calc(33.333% - 0.5px),
      rgba(255,255,255,.22) calc(33.333% + 0.5px), transparent calc(33.333% + 0.5px)),
    linear-gradient(to bottom,
      transparent calc(66.666% - 0.5px), rgba(255,255,255,.22) calc(66.666% - 0.5px),
      rgba(255,255,255,.22) calc(66.666% + 0.5px), transparent calc(66.666% + 0.5px));
}
.rf-grip {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%);
  width: 34px; height: 34px; border-radius: 50%;
  background: rgba(0,0,0,.45); border: 1px solid rgba(255,255,255,.5);
}

/* The rail scrolls on its own, so the stage never leaves the screen while the
   controls are being worked through. */
.rf-rail {
  display: flex; flex-direction: column; gap: 12px;
  min-width: 0; min-height: 0;
  height: 100%;
  overflow-y: auto;
  padding-right: 2px;
}
.rf-card {
  padding: 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
/* Nothing in the rail may shrink: it scrolls, and a squashed card is worse
   than a scrollbar. */
.rf-card { flex: 0 0 auto; }
.rf-card-title {
  font-size: 10px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
  color: var(--text-muted); margin-bottom: 10px;
}

/* Label/value rows for facts about the current shot. */
.rf-meta {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin-top: 10px; padding-top: 10px;
  border-top: 1px solid var(--border);
  font-size: 12px; color: var(--text-muted);
}
.rf-meta .rf-time {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px; color: var(--text); font-weight: 600;
  /* Fixed width so the row does not shift as the frame number gains a digit —
     the step buttons sit either side of it and would move under the cursor. */
  min-width: 76px; text-align: center;
  font-variant-numeric: tabular-nums;
}

.rf-step { display: inline-flex; align-items: center; gap: 10px; }
.rf-stepbtn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; padding: 0;
  background: var(--surface2); border: 1px solid var(--border);
  border-radius: 5px; color: var(--text-muted); cursor: pointer;
  font-family: inherit;
}
.rf-stepbtn:hover { color: var(--text); border-color: var(--border2); }
/* Disabled as a whole — the first shot has nothing to transition from. Kept in
   place rather than removed, or everything below it shifts between clicks. */
.rf-card.rf-disabled { opacity: .5; }

/* Three labelled columns. The label sits above its number so each is read as a
   pair, and the numbers are tabular so they do not jitter as the playhead moves
   between shots of different lengths. */
.rf-figures {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px;
  margin-bottom: 12px;
}
.rf-figures > div { min-width: 0; }
.rf-figures span {
  display: block;
  font-size: 10px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--text-muted); margin-bottom: 3px;
}
.rf-figures b {
  display: block;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px; font-weight: 700; color: var(--text);
  font-variant-numeric: tabular-nums;
}
.rf-reason { font-size: 11px; color: var(--text-muted); line-height: 1.5; }
/* Side by side in a split row, so they centre rather than reading as a list. */
.rf-split-row .rf-mode { text-align: center; }
.rf-mode {
  font-size: 12px; padding: 8px 10px; border-radius: 6px; cursor: pointer; text-align: left;
  background: var(--surface2); border: 1px solid var(--border); color: var(--text-muted);
}
/* Filled, not just outlined. A border tint was too quiet to answer "which one
   is on" at a glance, and the defaults are selected without the user having
   pressed anything — so the state has to be legible or it looks like nothing
   is chosen. */
/* Selected reads as a primary button — solid accent, white text — not as a
   tinted version of the unselected one. A wash was ambiguous at a glance,
   especially since the defaults are selected without anyone pressing them. */
.rf-mode.on,
.rf-navbtn.on {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
  font-weight: 600;
}
/* No border or fill: it undoes a choice rather than being one, and a third
   bordered control read as a peer of the two mode buttons above it. */
.rf-reset {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  width: 100%; margin-top: 10px; padding: 4px 0;
  background: none; border: 0;
  font-family: inherit; font-size: 12px; font-weight: 600;
  color: #fbbf24; cursor: pointer;
}
.rf-reset:hover:not(:disabled) { filter: brightness(1.15); }
.rf-reset:disabled { opacity: .35; cursor: not-allowed; }
.rf-reset svg { display: block; flex: 0 0 auto; }
/* Prev and Next sized to their own labels, with the count reading as a sentence
   between them. Stretching them to fill the card made two ordinary controls
   look like the card's main event. */
.rf-nav {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin-top: 12px;
}
.rf-nav .rf-navbtn { width: auto; flex: 0 0 auto; }
.rf-count {
  flex: 1 1 auto; text-align: center;
  font-size: 12px; color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.rf-navbtn {
  display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  font-family: inherit;
  font-size: 11px; padding: 7px 10px; border-radius: 5px; cursor: pointer;
  background: var(--surface2); border: 1px solid var(--border); color: var(--text-muted);
}
.rf-navbtn:hover:not(:disabled):not(.on) { color: var(--text); border-color: var(--border2); }
.rf-navbtn:disabled { opacity: .4; cursor: not-allowed; }

/* The primary action in this panel — everything else is a setting, and the
   surface-coloured button read as one of them. */
/* Full width at the foot of the rail. `flex-shrink: 0` so a scrolling rail
   cannot squash it, and it is the one control that should never be hard to
   hit. */
.rf-play {
  flex: 0 0 auto;
  width: 100%; padding: 11px 12px;
  font-family: inherit; font-size: 13px; font-weight: 600;
  border-radius: var(--radius-sm); cursor: pointer;
  background: var(--accent); border: 1px solid var(--accent); color: #fff;
}
.rf-play:hover { filter: brightness(1.08); }

/* Shots as proportional blocks, so the shape of the clip is visible and any
   shot is reachable. Zoomed by widening the track inside a scrolling wrapper —
   at 1x a one-second shot is a few pixels and cannot be worked with. */
/* Separate boxed controls rather than a joined strip, matching the transport
   button next to them so the row reads as one set of controls. */
.rf-zoom { display: flex; align-items: center; gap: 4px; }
.rf-zoombtn {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 26px; height: 24px; padding: 0 7px;
  font-size: 12px; line-height: 1; cursor: pointer;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 5px; color: var(--text-muted);
  font-family: inherit;
}
.rf-zoombtn:hover { color: var(--text); border-color: var(--border2); }
.rf-fit { font-size: 11px; font-weight: 600; }
.rf-zoomlabel {
  font-size: 11px; color: var(--text); font-weight: 600;
  min-width: 32px; text-align: center;
}
.rf-tl-hint { font-size: 11px; color: var(--text-muted); }

.rf-tl-wrap {
  flex: 0 0 auto;
  overflow-x: auto; overflow-y: hidden;
  border-radius: 6px;
  /* Reserve the scrollbar's height whether or not it is showing. Without this
     the whole editor shifted by a scrollbar's width the moment you zoomed in,
     and the bottom of the blocks was clipped. */
  scrollbar-gutter: stable;
  padding-bottom: 2px;
}
.rf-tl-inner { position: relative; width: 100%; }

.rf-ruler { position: relative; height: 18px; }
.rf-tick { position: absolute; top: 0; transform: translateX(-50%); }
.rf-tick i { display: block; width: 1px; height: 5px; margin: 0 auto; background: var(--border2); }
.rf-tick b {
  display: block; font-size: 9px; font-weight: 500; color: var(--text-muted);
  white-space: nowrap; margin-top: 1px;
}

.rf-timeline {
  position: relative; display: flex; gap: 0;
  height: 40px; border-radius: 6px; overflow: hidden;
  cursor: pointer; user-select: none;
}
/* pointer-events off so the container handles the drag as one surface — the
   blocks are a visual layer, not individual targets. */
.rf-tl-shot {
  background: var(--surface2); border-top: 3px solid transparent;
  min-width: 2px; pointer-events: none;
}
.rf-tl-shot.wide { background: #1d2a3d; }
.rf-tl-shot.on { border-top-color: var(--accent); background: #24304a; }
.rf-tl-shot.edited { border-top-color: #fbbf24; }
/* A boundary the user made, rather than one the cut detector found. */
.rf-tl-shot.split { box-shadow: inset 2px 0 0 #fbbf24; }
/* Dividers have to read as edges, since they can be dragged. */
.rf-tl-shot + .rf-tl-shot { border-left: 1px solid rgba(255,255,255,.22); }
/* Sits in the inner track, spanning the blocks but not inside them. */
/* Playhead: an accent line with a pin head, so the exact position is readable
   at a glance and there is something big enough to aim at when scrubbing. No
   time bubble — that number lives in the rail, and a label riding the line sat
   over the ruler ticks and moved while it was being read. */
.rf-tl-head {
  position: absolute; top: 18px; bottom: 0; width: 2px;
  background: var(--accent); pointer-events: none; z-index: 4;
}
/* The head. A rounded square whose bottom corner is squared off, rotated 45°,
   gives the tapered pin shape in one element — the alternative was a rect plus
   a border triangle, which never quite meet at the seam. */
.rf-tl-head::before {
  content: '';
  position: absolute;
  left: 50%; top: -10px;
  width: 10px; height: 10px;
  transform: translateX(-50%) rotate(45deg);
  background: var(--accent);
  border-radius: 3px 3px 3px 1px;
}
/* Sits over the rotated square so the flat top reads as a solid head rather
   than a diamond. */
.rf-tl-head::after {
  content: '';
  position: absolute;
  left: 50%; top: -13px;
  width: 11px; height: 9px;
  transform: translateX(-50%);
  background: var(--accent);
  border-radius: 3px;
}

.reframe-head-actions { display: flex; align-items: center; gap: 10px; }
.rf-cancel {
  font-size: 12px; font-weight: 600; padding: 8px 14px; border-radius: 6px; cursor: pointer;
  background: transparent; border: 1px solid var(--border); color: var(--text-muted);
  font-family: inherit;
}
.rf-cancel:hover { color: var(--text); border-color: var(--border2); }

.rf-undo {
  font-size: 12px; font-weight: 600; padding: 8px 14px; border-radius: 6px; cursor: pointer;
  background: var(--surface2); border: 1px solid var(--border); color: var(--text-muted);
}
.rf-undo:hover:not(:disabled) { color: var(--text); border-color: var(--accent); }
.rf-undo:disabled { opacity: .35; cursor: not-allowed; }


/* ── Clip action row ───────────────────────────────────────── */
/* One row. Space is made by shortening the buttons rather than by wrapping —
   the export controls read as a group and splitting them across lines makes the
   clip's state harder to take in at a glance. */
.sidebar-clip-actions {
  display: flex; align-items: center; gap: 8px;
  margin: 6px 0 0 32px;
}
.sidebar-clip-actions .sidebar-clip-export { flex: 1 1 auto; min-width: 0; }

/* Square, so the arrow reads as a button rather than a truncated label. */
.clip-export-dl.icon-only {
  padding: 0; width: 30px; min-width: 30px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 14px;
}

/* The three buttons in a topic's action row share one shape. They sit side by
   side, so any difference in padding or font size shows as a misalignment even
   when each is internally centred. */
.btn-reframe {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 11px; font-weight: 600; padding: 5px 9px;
  background: var(--surface2); border: 1px solid var(--border);
  border-radius: 5px; color: var(--text-muted); cursor: pointer;
  flex: 0 0 auto; white-space: nowrap;
}
.btn-reframe:hover { color: var(--text); border-color: var(--accent); }

/* Never wrap: a two-line button in this row makes the clip's state harder to
   read, and the row has to hold Reframe as well now. */
.clip-queue-btn { white-space: nowrap; }
.clip-queue-btn svg { flex: 0 0 auto; }

/* Icon-only, but still centred — an unaligned glyph in a 26px box is just as
   visible as one beside a label. */
.btn-reframe-sm {
  display: inline-flex; align-items: center; justify-content: center;
}

/* Icon-only in a highlight row, which is much tighter than a clip's. */
/* The action cell holds the reframe button and the export controls, so it
   becomes a flex row rather than a single slot. */
.highlight-item .btn-reframe-sm {
  padding: 5px 7px;
  flex: 0 0 auto;
}
.highlight-item-actions {
  grid-area: action; justify-self: end; align-self: center;
  display: inline-flex; align-items: center; gap: 4px;
}


/* ── Analyze form ──────────────────────────────────────────── */
.analyze-view { max-width: none; margin: 0; padding: 16px 32px 56px; }


.analyze-head { margin-bottom: 20px; max-width: 640px; }

/* Form and help rail. The rail is not decoration — it carries the recent list,
   which is the fastest way back to something already analyzed. */
.analyze-grid {
  display: grid;
  /* The form is short rows — a URL, a title, a select — so it does not need the
     width, and the rail carries the recent list, which does. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 420px);
  gap: 20px;
  align-items: start;
}
.analyze-main { display: flex; flex-direction: column; gap: 16px; min-width: 0; }

/* The hint under a field. Scoped to this view on purpose — .tpl-hint is shared
   with the template editor, where it sits inside a <label> beside the label
   text and a top margin would break that line. */
.analyze-card .tpl-hint { display: block; margin-top: 8px; font-size: 12px; }

.analyze-card {
  padding: 20px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.analyze-step {
  display: flex; align-items: center; gap: 9px;
  margin-bottom: 16px;
  font-size: 13px; font-weight: 600;
}
.analyze-step-n {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px;
  background: var(--accent-dim); color: var(--accent);
  border-radius: 5px; font-size: 11px; font-weight: 700;
}

/* A stated limit, not a control. */
.analyze-note {
  display: flex; align-items: flex-start; gap: 9px;
  padding: 11px 13px;
  font-size: 12px; line-height: 1.5; color: var(--text-muted);
  background: var(--bg);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.analyze-note strong { color: var(--text); }
.analyze-note svg { flex: 0 0 auto; margin-top: 1px; color: var(--text-muted); }

.analyze-actions { display: flex; justify-content: flex-end; gap: 10px; }
.analyze-actions .btn-ghost { width: auto; padding: 10px 18px; font-size: 12px; }
.analyze-actions .btn-accent {
  width: auto; padding: 10px 20px; font-size: 13px;
  display: inline-flex; align-items: center; gap: 7px;
}

/* ── Help rail ─────────────────────────────────────────────── */
.analyze-rail { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
.analyze-rail-card {
  padding: 18px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.analyze-rail-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.analyze-rail-title { font-size: 13px; font-weight: 700; }
.analyze-rail-head .analyze-rail-title { margin-bottom: 0; }
.analyze-rail-card > .analyze-rail-title { margin-bottom: 14px; }
.analyze-rail-link {
  background: none; border: 0; padding: 0;
  color: var(--accent); font-family: inherit; font-size: 12px; cursor: pointer;
}
.analyze-rail-link:hover { text-decoration: underline; }

.analyze-steps { list-style: none; display: flex; flex-direction: column; gap: 18px; }

/* A line joins the three markers, so the steps read as one sequence rather than
   three unrelated notes. Drawn on the <li> rather than between them: the run has
   to start at the marker's centre and reach the next marker's centre, which
   means it must span the gap — a border on the list item cannot. */
.analyze-steps li { position: relative; display: flex; gap: 11px; }
.analyze-steps li::before {
  content: '';
  position: absolute;
  left: 10px;                 /* centre of the 21px marker */
  top: 21px;                  /* immediately below it */
  bottom: -18px;              /* down through the gap to the next marker */
  width: 2px;
  background: linear-gradient(var(--accent), rgba(59,130,246,.25));
  border-radius: 1px;
}
/* Nothing follows the last step, so it has no line. */
.analyze-steps li:last-child::before { display: none; }

.analyze-steps-n {
  position: relative;         /* sits over the line rather than under it */
  display: flex; align-items: center; justify-content: center;
  width: 21px; height: 21px; flex: 0 0 auto;
  background: var(--accent); color: #fff;
  border-radius: 50%; font-size: 11px; font-weight: 700;
}
.analyze-steps strong { display: block; font-size: 12.5px; font-weight: 600; color: var(--text); }
.analyze-steps span { display: block; font-size: 12px; line-height: 1.5; color: var(--text-muted); margin-top: 3px; }

.analyze-recent {
  display: flex; align-items: center; gap: 12px;
  width: 100%; padding: 12px 0;
  background: none; border: 0; border-top: 1px solid var(--border);
  font-family: inherit; text-align: left; cursor: pointer;
}
.analyze-recent:first-of-type { border-top: 0; }
.analyze-recent:hover:not(:disabled) .analyze-recent-name { color: var(--accent); }
/* A job still analyzing has no editor to open yet. */
.analyze-recent:disabled { cursor: default; }
/* 16:9 at a size where the frame is actually readable — at 62px wide it was
   a coloured smudge, which is worse than no thumbnail for telling two shows
   apart. */
.analyze-recent-thumb {
  position: relative; flex: 0 0 auto;
  width: 120px; aspect-ratio: 16 / 9;
  border-radius: 6px; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg); color: var(--text-dim);
}
.analyze-recent-thumb img { width: 100%; height: 100%; object-fit: cover; }
.analyze-recent-thumb i {
  position: absolute; right: 4px; bottom: 4px;
  padding: 1px 5px; border-radius: 3px;
  background: rgba(0,0,0,.75); color: #fff;
  font-size: 10px; font-style: normal; font-weight: 600;
}
.analyze-recent-body { min-width: 0; flex: 1; }
.analyze-recent-name {
  display: block; font-size: 12.5px; font-weight: 600; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.analyze-recent-when { display: block; font-size: 11px; color: var(--text-muted); margin-top: 6px; }
.analyze-recent-state {
  display: inline-block; margin-top: 10px;
  padding: 1px 7px; border-radius: 4px;
  font-size: 10px; font-weight: 600;
}
.analyze-recent-state.done       { background: rgba(34,197,94,.14);  color: #4ade80; }
.analyze-recent-state.processing { background: rgba(59,130,246,.14); color: #93c5fd; }
.analyze-recent-state.error      { background: rgba(239,68,68,.14);  color: #fca5a5; }
.analyze-recent-empty { font-size: 12px; color: var(--text-muted); padding: 4px 0; }

@media (max-width: 1080px) {
  .analyze-grid { grid-template-columns: 1fr; }
}


/* ── Reframe transport ─────────────────────────────────────── */
.rf-transport {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 0 0 8px;
}
.rf-transport-sp { flex: 1 1 auto; }


/* Button rows inside the rail. These were never styled — the buttons simply
   flowed inline and wrapped wherever they landed. Equal columns so the choices
   read as a set, and so the control under the cursor does not move when a
   label changes width. */
.rf-split-row { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 6px; }
.rf-split-row + .rf-sub { margin-top: 10px; }
.rf-sub {
  display: block; margin-bottom: 6px;
  font-size: 11px; color: var(--text-muted);
}
/* Scoped to split rows. As a blanket `.rf-card .rf-navbtn` rule this stretched
   Prev and Next across the card too, which is what made that card look wrong. */
.rf-split-row .rf-navbtn { width: 100%; }
.rf-card .rf-reset { width: 100%; margin-top: 14px; }
.rf-nav { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.rf-nav .rf-navbtn { width: auto; }
#rf-pan-row { margin-top: 12px; }
#rf-pan-row[hidden] { display: none; }
.rf-card[hidden] { display: none; }

/* Below this the two regions cannot both be read side by side. The page goes
   back to scrolling rather than squeezing the picture into nothing. */
@media (max-width: 1100px) {
  .reframe-view { height: auto; overflow: visible; }
  .rf-layout { grid-template-columns: 1fr; }
  .rf-main { height: auto; }
  .rf-stage-fit { min-height: 320px; }
  .rf-rail { height: auto; overflow-y: visible; }
}


/* ── Exports (per video) ───────────────────────────────────── */
.exports-view { max-width: none; margin: 0; padding: 16px 32px 56px; }

/* The source video, so an export can be traced back to the show it came from. */
.exp-source {
  display: flex; align-items: center; gap: 20px;
  padding: 18px;
  margin-bottom: 20px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.exp-source-thumb {
  position: relative; flex: 0 0 auto;
  width: 440px; aspect-ratio: 16 / 9;
  border-radius: 10px; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg); color: var(--text-dim);
}
.exp-source-thumb img { width: 100%; height: 100%; object-fit: cover; }
.exp-source-thumb i {
  position: absolute; right: 6px; bottom: 6px;
  padding: 2px 6px; border-radius: 4px;
  background: rgba(0,0,0,.75); color: #fff;
  font-size: 11px; font-style: normal; font-weight: 600;
}
.exp-source-body { flex: 1; min-width: 0; }
.exp-source-title {
  font-size: 19px; font-weight: 700; letter-spacing: -.01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.exp-source-meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: 16px;
  font-size: 12.5px; color: var(--text-muted); margin-top: 16px;
}
.exp-source-meta span { display: inline-flex; align-items: center; gap: 6px; }
.exp-source-meta svg { opacity: .7; }
.exp-source-counts {
  display: flex; flex-wrap: wrap; align-items: center; gap: 16px;
  margin-top: 16px;
  font-size: 12.5px; color: var(--text-muted);
}
.exp-source-counts span { display: inline-flex; align-items: center; gap: 5px; }
.exp-source-counts svg { opacity: .7; }

/* Grid and rail. The rail is sticky so the totals stay in view while a long
   grid scrolls past them. */
.exp-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 320px);
  gap: 20px;
  align-items: start;
}
.exp-main { min-width: 0; }
.exp-rail {
  display: flex; flex-direction: column; gap: 14px;
  min-width: 0;
  /* Same as the template preview: clear the sticky header rather than sticking
     to the viewport, or the top of the first card ends up behind it. */
  position: sticky; top: calc(var(--header-h) + 16px);
}
.exp-card-panel {
  padding: 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.exp-panel-title { font-size: 13px; font-weight: 700; margin-bottom: 10px; }
.exp-panel-note { font-size: 12px; color: var(--text-muted); }
.exp-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding: 7px 0;
  font-size: 12px;
  border-top: 1px solid var(--border);
}
.exp-row:first-of-type { border-top: 0; }
.exp-row > span:first-child { color: var(--text-muted); flex: 0 0 auto; }
.exp-row > span:last-child {
  color: var(--text); text-align: right; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.exp-row-strong { font-weight: 600; }

/* Ring, with the total in the middle. Two segments — there is no third kind of
   export, so a third slice would always read zero. */
.exp-donut { position: relative; display: flex; justify-content: center; padding: 6px 0 14px; }
.exp-donut svg { display: block; }
.exp-donut-mid {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  padding-bottom: 8px;
  pointer-events: none;
}
.exp-donut-mid b { font-size: 20px; font-weight: 700; line-height: 1; }
.exp-donut-mid span { font-size: 10px; color: var(--text-muted); margin-top: 3px; }

.exp-legend { display: flex; flex-direction: column; gap: 8px; }
.exp-legend > div {
  display: flex; align-items: center; gap: 8px;
  font-size: 12px; color: var(--text-muted);
}
.exp-legend i { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; }
.exp-legend b { margin-left: auto; color: var(--text); font-weight: 600; }

.exp-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 16px; }
.exp-empty {
  grid-column: 1 / -1;
  padding: 48px 24px; text-align: center;
  font-size: 13px; color: var(--text-muted);
  background: var(--surface); border: 1px solid var(--border); border-radius: 16px;
}

.exp-card {
  display: flex; flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  transition: border-color .13s, transform .13s;
}
.exp-card:hover { border-color: var(--border2); transform: translateY(-2px); }

/* Taller than wide, because an export IS taller than wide. At 16:9 a 9:16
   poster was cropped to a horizontal strip through the middle — the one part of
   a vertical frame that says least about it. 4:5 rather than a true 9:16 so the
   cards stay a sensible height in a grid; the crop that remains is off the top
   and bottom, where captions and headroom sit. */
.exp-card-thumb {
  position: relative; width: 100%; aspect-ratio: 9 / 16;
  background: var(--bg); overflow: hidden;
}
.exp-card-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.exp-card-thumb i {
  position: absolute; right: 8px; bottom: 8px;
  padding: 2px 7px; border-radius: 4px;
  background: rgba(0,0,0,.75); color: #fff;
  font-size: 11px; font-style: normal; font-weight: 600;
}
/* Highlights and clips are different things — a highlight is what gets posted,
   a clip is the whole segment — so the card says which it is. */
.exp-card-src {
  position: absolute; top: 8px; left: 8px;
  padding: 3px 8px; border-radius: 5px;
  font-size: 10.5px; font-weight: 600;
  background: rgba(0,0,0,.6); backdrop-filter: blur(4px);
}
.exp-card-src.highlight { color: #93c5fd; }
.exp-card-src.clip      { color: #d8b4fe; }

.exp-card-body { padding: 13px; display: flex; flex-direction: column; flex: 1; }
.exp-card-title {
  font-size: 13px; font-weight: 600; line-height: 1.4;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.exp-card-meta { font-size: 11px; color: var(--text-muted); margin-top: 5px; }

/* The whole reason the exported span is recorded: a file that no longer matches
   the current trim is the one that gets posted by mistake. */
.exp-card-stale {
  display: flex; align-items: center; gap: 5px;
  margin-top: 8px; padding: 5px 8px;
  font-size: 11px; color: #fbbf24;
  background: rgba(251,191,36,.1);
  border: 1px solid rgba(251,191,36,.25);
  border-radius: 5px;
}

.exp-card-actions { display: flex; gap: 8px; margin-top: auto; padding-top: 12px; }
.exp-card-dl, .exp-card-open {
  flex: 1;
  display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  padding: 7px 10px;
  font-family: inherit; font-size: 11.5px; font-weight: 600;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border2);
  background: rgba(255,255,255,.05);
  color: var(--text); text-decoration: none; cursor: pointer;
}
.exp-card-dl { color: var(--accent); border-color: rgba(59,130,246,.35); }
.exp-card-dl:hover, .exp-card-open:hover { background: rgba(255,255,255,.1); }
.exp-card-gone { flex: 1; font-size: 11.5px; color: var(--text-dim); align-self: center; }

@media (max-width: 1100px) {
  .exp-layout { grid-template-columns: 1fr; }
  .exp-rail { position: static; flex-direction: row; flex-wrap: wrap; }
  .exp-card-panel { flex: 1 1 260px; }
}
@media (max-width: 720px) {
  .exp-source { flex-direction: column; align-items: flex-start; }
  .exp-source-thumb { width: 100%; }
}


/* ── Editor: highlights column ─────────────────────────────── */
.hl-empty { text-align: center; padding: 26px 12px; }
.hl-empty-icon { color: var(--text-dim); margin-bottom: 10px; }
.hl-empty-icon svg { width: 34px; height: 34px; }
.hl-empty-title { font-size: 13px; font-weight: 600; }
.hl-empty-sub {
  font-size: 12px; line-height: 1.5; color: var(--text-muted); margin-top: 6px;
}

.hl-settings {
  margin-top: 14px; padding: 13px;
  background: var(--bg);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.hl-settings-title {
  font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-muted); margin-bottom: 9px;
}
.hl-durations { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 6px; }
.hl-dur {
  padding: 7px 0;
  background: var(--surface2); color: var(--text-muted);
  border: 1px solid var(--border); border-radius: 5px;
  font-family: inherit; font-size: 12px; cursor: pointer;
}
.hl-dur:hover:not(.on) { color: var(--text); border-color: var(--border2); }
/* Same selected treatment as the reframe controls: a solid fill, not a tint. */
.hl-dur.on { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; }
.hl-hint { font-size: 11px; line-height: 1.5; color: var(--text-muted); margin-top: 9px; }
.hl-run {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  width: 100%; margin-top: 11px; padding: 9px;
  background: var(--accent); color: #fff;
  border: 1px solid var(--accent); border-radius: var(--radius-sm);
  font-family: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer;
}
.hl-run:hover:not(:disabled) { filter: brightness(1.1); }
.hl-run:disabled { opacity: .6; cursor: not-allowed; }

/* ── Editor: timeline ──────────────────────────────────────── */
.tl-head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 8px;
}
.tl-title { font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--text-muted); }
.tl-zoom { display: flex; align-items: center; gap: 4px; }
.tl-zoombtn {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 26px; height: 24px; padding: 0 7px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 5px;
  color: var(--text-muted); font-family: inherit; font-size: 12px; cursor: pointer;
}
.tl-zoombtn:hover { color: var(--text); border-color: var(--border2); }
.tl-fit { font-size: 11px; font-weight: 600; }
.tl-zoomlabel { font-size: 11px; font-weight: 600; color: var(--text); min-width: 30px; text-align: center; }

/* Horizontal scroll is how zoom works: the inner element grows past 100% and
   this clips it. `scrollbar-gutter` reserves the bar's height so the lanes do
   not shift the moment you zoom in. */
.tl-scroll { overflow-x: auto; overflow-y: hidden; scrollbar-gutter: stable; }
.tl-inner { position: relative; width: 100%; }

.tl-ruler { position: relative; height: 16px; }
.tl-tick {
  position: absolute; top: 0;
  font-size: 10px; color: var(--text-dim);
  transform: translateX(-50%); white-space: nowrap;
}
/* Pinned at the edges so neither hangs outside the track. */
.tl-tick.first { transform: none; }
.tl-tick.last  { transform: translateX(-100%); }

.tl-lane-label {
  margin: 10px 0 5px;
  font-size: 10px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--text-muted);
}
.tl-lane-label span { font-weight: 400; letter-spacing: 0; text-transform: none; }

/* Every highlight in the show. Drawn full width rather than inside the topic
   block, where a 30-second highlight in a 40-minute show was a couple of pixels
   sitting on its own parent. */
.tl-highlights {
  position: relative; height: 26px;
  background: var(--bg); border-radius: 5px;
}
/* Dimmed by default, full strength inside the selected topic. The lane shows
   the whole show, so it needs to say which part is being worked on without
   hiding the rest. */
.tl-hl {
  position: absolute; top: 3px; bottom: 3px;
  min-width: 3px;
  border-radius: 3px; opacity: .35; cursor: pointer;
  display: flex; align-items: center; justify-content: center; overflow: hidden;
  transition: opacity .12s;
}
.tl-hl.in-topic { opacity: .85; }
.tl-hl:hover { opacity: 1; }
.tl-hl.active { opacity: 1; box-shadow: 0 0 0 2px var(--text); }
.tl-hl span { font-size: 9px; font-weight: 700; color: #fff; white-space: nowrap; }

/* Spans both lanes and the ruler, so the position reads against everything. */
.editor-timeline .timeline-playhead { top: 16px; bottom: 0; }

/* Volume */
.ctrl-volume {
  width: 74px; height: 4px; cursor: pointer;
  accent-color: var(--accent);
}

.btn-analyze-topic {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 5px 9px;
  background: var(--surface2); color: var(--text-muted);
  border: 1px solid var(--border); border-radius: 5px;
  font-family: inherit; font-size: 11px; cursor: pointer;
}
.btn-analyze-topic:hover { color: var(--text); border-color: var(--accent); }

/* Colour bar tying a topic card to its block on the timeline. */
.sidebar-clip { border-left: 3px solid var(--topic-color, transparent); }

/* Icon plus label on one line. In a 290px column the label wrapped under the
   icon and the button grew to two lines. */
.hl-run, .btn-analyze-topic { white-space: nowrap; }
.hl-run svg, .btn-analyze-topic svg { flex: 0 0 auto; }


/* ── Editor: timeline block labels ─────────────────────────── */
.timeline-marker {
  display: flex; flex-direction: column; justify-content: center;
  padding: 0 10px; overflow: hidden;
}
/* Clipped, not truncated in JS: how much fits depends on the zoom, and the
   block re-renders on zoom anyway. The title attribute carries the full name
   for blocks too narrow to read. */
/* No text-shadow: it was compensating for a 60% opacity fill. Against the solid
   colour the text is legible on its own, and a shadow on 11px type just makes
   it look smudged. */
.tl-block-name {
  font-size: 12px; font-weight: 600; color: #fff;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  letter-spacing: -.01em;
}
.tl-block-time {
  font-size: 11px; color: rgba(255,255,255,.75);
  white-space: nowrap; overflow: hidden;
  margin-top: 3px;
}

/* ── Social accounts ───────────────────────────────────────── */
.social-view { max-width: none; margin: 0; padding: 16px 32px 56px; }

/* The OAuth round trip leaves the app entirely, so the outcome is reported
   here on the way back rather than in a toast that never survived the jump. */
.social-note {
  padding: 11px 14px; margin-bottom: 16px;
  font-size: 12.5px; color: var(--text);
  background: var(--accent-dim);
  border: 1px solid rgba(59,130,246,.3); border-radius: var(--radius-sm);
}

.social-platform {
  padding: 18px;
  margin-bottom: 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
/* Platforms that exist but cannot be offered yet. Dimmed rather than hidden, so
   the roadmap is legible without pretending they are one click away. */
.social-platform.muted { opacity: .6; }
.social-platform-head {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
}
.social-platform-name { font-size: 15px; font-weight: 700; }
.social-platform-sub { font-size: 12.5px; color: var(--text-muted); margin-top: 4px; }
.social-unconfigured { font-size: 12px; color: var(--text-dim); }

.social-list { margin-top: 16px; }
.social-account {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 0;
  border-top: 1px solid var(--border);
}
.social-avatar {
  display: flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; flex: 0 0 auto;
  border-radius: 50%; overflow: hidden;
  background: var(--bg); color: var(--text-dim);
}
.social-avatar img { width: 100%; height: 100%; object-fit: cover; }
.social-account-body { flex: 1; min-width: 0; }
.social-account-name {
  display: block; font-size: 13.5px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.social-account-meta { display: block; font-size: 11.5px; color: var(--text-muted); margin-top: 3px; }
.social-disconnect {
  flex: 0 0 auto; padding: 7px 13px;
  background: transparent; color: var(--text-muted);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  font-family: inherit; font-size: 12px; cursor: pointer;
}
.social-disconnect:hover { color: #fca5a5; border-color: rgba(248,113,113,.5); }
.social-empty { font-size: 12.5px; color: var(--text-muted); padding: 14px 0 2px; }

/* ── Publishing ────────────────────────────────────────────── */
.pub-modal { width: min(460px, 92vw); }
.pub-modal textarea.form-input { resize: vertical; font-family: inherit; }

/* State of the latest attempt, above the card's actions. The collection keeps
   every attempt; a card has room for one. */
.exp-card-pub {
  display: flex; align-items: center; gap: 6px;
  margin-top: 8px; padding: 5px 8px;
  font-size: 11px; border-radius: 5px;
  color: var(--text-muted);
  background: rgba(255,255,255,.04);
  border: 1px solid var(--border);
  text-decoration: none;
}
.exp-card-pub.ok  { color: #4ade80; background: rgba(34,197,94,.1);  border-color: rgba(34,197,94,.25); }
.exp-card-pub.bad { color: #fca5a5; background: rgba(239,68,68,.1);  border-color: rgba(239,68,68,.25); }
.exp-card-pub svg { flex: 0 0 auto; }

.exp-card-pubbtn {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; padding: 7px 0;
  background: rgba(255,255,255,.05); color: var(--accent);
  border: 1px solid rgba(59,130,246,.35); border-radius: var(--radius-sm);
  cursor: pointer;
}
.exp-card-pubbtn:hover { background: rgba(59,130,246,.18); }

/* ── Searchable channel picker ─────────────────────────────── */
/* A filtered list rather than a <select>: a broadcaster runs a channel per
   station, so this is twenty-odd entries and a native dropdown makes finding
   one of them a scroll. */
.combo { position: relative; }
.combo-list {
  position: absolute; left: 0; right: 0; top: calc(100% + 4px);
  z-index: 30;
  max-height: 220px; overflow-y: auto;
  background: var(--surface2);
  border: 1px solid var(--border2);
  border-radius: var(--radius-sm);
  box-shadow: 0 12px 32px rgba(0,0,0,.5);
}
.combo-list[hidden] { display: none; }
.combo-item {
  display: flex; align-items: center; gap: 8px;
  width: 100%; padding: 9px 12px;
  background: none; border: 0;
  color: var(--text); font-family: inherit; font-size: 13px;
  text-align: left; cursor: pointer;
}
.combo-item:hover { background: var(--accent-dim); }
.combo-warn {
  margin-left: auto; flex: 0 0 auto;
  font-size: 10px; color: #fbbf24;
}
.combo-empty { padding: 12px; font-size: 12px; color: var(--text-muted); }

/* A hint that has become a warning. */
.tpl-hint.bad { color: #fca5a5; }
.exp-card-pubbtn:disabled { opacity: .4; cursor: not-allowed; }

/* ── Publishing calendar ───────────────────────────────────── */
.cal-view { max-width: none; margin: 0; padding: 16px 32px 56px; }

.cal-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin-bottom: 16px; }
.cal-stat {
  display: flex; flex-direction: column; gap: 3px;
  padding: 14px 16px; text-align: left;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); font-family: inherit;
}
.cal-stat b { font-size: 22px; font-weight: 700; color: var(--text); }
.cal-stat span { font-size: 12px; color: var(--text-muted); }
/* Failures are the one thing here that needs action, so that card is a control
   rather than a number. */
button.cal-stat { cursor: pointer; }
button.cal-stat:disabled { cursor: default; }
.cal-stat.bad { border-color: rgba(239,68,68,.4); }
.cal-stat.bad b { color: #fca5a5; }

.toolbar-nav, .toolbar-today {
  padding: 8px 12px;
  background: var(--bg); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  font-family: inherit; font-size: 13px; cursor: pointer;
}
.toolbar-nav:hover, .toolbar-today:hover { border-color: var(--border2); }
.cal-period { font-size: 14px; font-weight: 600; min-width: 170px; text-align: center; }

.cal-layout {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 330px);
  gap: 20px; align-items: start;
}
.cal-main { min-width: 0; }

/* `minmax(0, 1fr)`, not `1fr`. A bare 1fr is minmax(AUTO, 1fr), and an auto
   minimum lets the widest chip title push its column wider than the rest —
   which is why two days ended up three times the width of the others. */
.cal-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 1px;
  background: var(--border); border: 1px solid var(--border); border-radius: var(--radius);
  overflow: hidden; }
.cal-dow {
  padding: 9px 10px; background: var(--surface);
  font-size: 11px; font-weight: 600; color: var(--text-muted); text-transform: uppercase;
  letter-spacing: .04em;
}
.cal-cell {
  min-width: 0;
  min-height: 108px; padding: 8px;
  background: var(--bg); cursor: pointer;
  display: flex; flex-direction: column; gap: 4px;
}
/* A week shows seven cells, so each can afford to be much taller. */
.cal-grid.week .cal-cell { min-height: 320px; }
.cal-cell:hover { background: var(--surface); }
/* Days from the neighbouring month, kept so the grid has no ragged rows. */
.cal-cell.outside { opacity: .4; }
.cal-cell.today .cal-date { background: var(--accent); color: #fff; }
.cal-cell.selected { outline: 2px solid var(--accent); outline-offset: -2px; }
.cal-date {
  align-self: flex-start;
  min-width: 22px; height: 22px; padding: 0 6px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 11px; font-size: 12px; font-weight: 600; color: var(--text-muted);
}

.cal-chip {
  /* Also min-width: 0 — a flex item will not shrink below its content either,
     so without this the chip keeps the column wide even once the grid is fixed. */
  min-width: 0; max-width: 100%;
  display: flex; align-items: center; gap: 6px;
  padding: 4px 6px; border-radius: 4px;
  background: var(--surface); border: 1px solid var(--border);
  font-size: 11px; overflow: hidden;
}
.cal-chip-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cal-chip-time { flex: 0 0 auto; color: var(--text-muted); font-size: 10px; }
.cal-dot { width: 6px; height: 6px; border-radius: 50%; flex: 0 0 auto; background: var(--text-dim); }
.cal-dot.scheduled { background: var(--accent); }
.cal-dot.published { background: #4ade80; }
.cal-dot.failed    { background: #f87171; }
.cal-dot.uploading { background: #fbbf24; }
.cal-more { font-size: 10px; color: var(--text-muted); padding-left: 2px; }

.cal-agenda-day {
  display: grid; grid-template-columns: 120px 1fr; gap: 14px;
  padding: 12px 0; border-top: 1px solid var(--border);
}
.cal-agenda-day:first-child { border-top: 0; }
.cal-agenda-date { font-size: 12px; font-weight: 600; color: var(--text-muted); }
.cal-agenda-day .cal-chip { margin-bottom: 5px; }

.cal-rail {
  position: sticky; top: calc(var(--header-h) + 16px);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 16px;
}
.cal-rail-head { margin-bottom: 12px; }
.cal-rail-date { font-size: 14px; font-weight: 700; }
.cal-rail-sub { font-size: 12px; color: var(--text-muted); margin-top: 3px; }
.cal-item { padding: 12px 0; border-top: 1px solid var(--border); }
.cal-item-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.cal-item-title {
  font-size: 13px; font-weight: 600; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cal-badge {
  flex: 0 0 auto; padding: 2px 7px; border-radius: 4px;
  font-size: 10px; font-weight: 600; text-transform: capitalize;
  background: rgba(255,255,255,.06); color: var(--text-muted);
}
.cal-badge.scheduled { background: var(--accent-dim); color: #93c5fd; }
.cal-badge.published { background: rgba(34,197,94,.14); color: #4ade80; }
.cal-badge.failed    { background: rgba(239,68,68,.14); color: #fca5a5; }
.cal-item-meta { font-size: 11.5px; color: var(--text-muted); margin-top: 4px; }
.cal-item-error {
  margin-top: 6px; padding: 6px 8px;
  font-size: 11px; color: #fca5a5;
  background: rgba(239,68,68,.1); border: 1px solid rgba(239,68,68,.25); border-radius: 5px;
}
.cal-item-actions { display: flex; gap: 8px; margin-top: 9px; }
.cal-item-btn {
  padding: 6px 11px; font-size: 11.5px; font-weight: 600;
  background: rgba(255,255,255,.05); color: var(--text);
  border: 1px solid var(--border2); border-radius: 5px;
  font-family: inherit; cursor: pointer; text-decoration: none;
}
.cal-item-btn:hover { background: rgba(255,255,255,.1); }
.cal-item-btn.danger { color: #fca5a5; border-color: rgba(248,113,113,.35); }
.cal-empty { padding: 32px 12px; text-align: center; font-size: 12.5px; color: var(--text-muted); }

@media (max-width: 1180px) {
  .cal-layout { grid-template-columns: 1fr; }
  .cal-rail { position: static; }
  .cal-stats { grid-template-columns: repeat(2, 1fr); }
}

/* Publish now / Schedule. */
.pub-when { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 6px; }
.pub-when-btn {
  padding: 8px 0;
  background: var(--surface2); color: var(--text-muted);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  font-family: inherit; font-size: 12.5px; cursor: pointer;
}
.pub-when-btn:hover:not(.on) { color: var(--text); border-color: var(--border2); }
/* Same selected treatment as everywhere else: a solid fill, not a tint. */
.pub-when-btn.on { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; }
#pub-schedule-row { margin-top: 9px; }
#pub-schedule-row[hidden] { display: none; }

/* The one native control left: the time field inside the custom picker. Dates
   are handled by our own grid, but a time input is a spinner rather than a
   popover and themes acceptably.
   `color-scheme: dark` is what tells the browser to render the picker
   indicator, the dropdown panel and the spinners for a dark background —
   without it the calendar glyph is near-black on near-black and the panel opens
   white. The filter is a fallback for engines that ignore color-scheme. */
.form-input[type="datetime-local"],
.form-input[type="date"],
.form-input[type="time"] {
  color-scheme: dark;
}
.form-input[type="datetime-local"]::-webkit-calendar-picker-indicator,
.form-input[type="date"]::-webkit-calendar-picker-indicator,
.form-input[type="time"]::-webkit-calendar-picker-indicator {
  filter: invert(1) brightness(1.6);
  opacity: .75;
  cursor: pointer;
}
.form-input[type="datetime-local"]::-webkit-calendar-picker-indicator:hover {
  opacity: 1;
}

/* Hints inside a modal sat flush against the field above them. The analyze view
   spaces its own; this covers every field in every modal rather than adding the
   same rule a third time. */
.modal .tpl-hint { display: block; margin-top: 8px; }


/* ── Date and time picker ──────────────────────────────────── */
/* Custom because the native datetime-local renders its own glyph and popover,
   neither of which can be styled — themed for this palette the field looked
   right and the panel that opened from it did not. */
.dtp { position: relative; }
.dtp-field {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  width: 100%; text-align: left; cursor: pointer;
}
.dtp-icon { display: flex; color: var(--text-muted); flex: 0 0 auto; }

.dtp-pop {
  position: absolute; left: 0; right: 0; top: calc(100% + 6px); z-index: 40;
  padding: 12px;
  background: var(--surface2);
  border: 1px solid var(--border2); border-radius: var(--radius);
  box-shadow: 0 16px 40px rgba(0,0,0,.55);
}
.dtp-pop[hidden] { display: none; }
.dtp-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.dtp-title { font-size: 13px; font-weight: 600; }
.dtp-nav {
  width: 26px; height: 26px;
  background: var(--bg); color: var(--text-muted);
  border: 1px solid var(--border); border-radius: 5px;
  font-family: inherit; cursor: pointer;
}
.dtp-nav:hover { color: var(--text); border-color: var(--border2); }

.dtp-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.dtp-dow {
  display: flex; align-items: center; justify-content: center;
  height: 22px; font-size: 10px; font-weight: 600; color: var(--text-dim);
}
.dtp-day {
  display: flex; align-items: center; justify-content: center;
  height: 30px; border-radius: 6px;
  background: transparent; border: 0;
  font-family: inherit; font-size: 12px; color: var(--text); cursor: pointer;
}
.dtp-day:hover:not(:disabled) { background: var(--accent-dim); }
.dtp-day.out { color: var(--text-dim); }
/* A scheduled publish must be in the future, so past days are not offered at
   all rather than accepted and then rejected on submit. */
.dtp-day:disabled { opacity: .25; cursor: not-allowed; }
.dtp-day.on { background: var(--accent); color: #fff; font-weight: 600; }

.dtp-time {
  display: flex; align-items: center; gap: 10px;
  margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border);
}
.dtp-time label { font-size: 12px; color: var(--text-muted); flex: 0 0 auto; }
.dtp-time .form-input { padding: 7px 10px; }

/* "Suggest description and tags" — a button, not an auto-fill.
   Sits between the title and the fields it fills, so it needs to read as an
   aside rather than as the modal's main action. */
.pub-ai {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  width: 100%; margin: -4px 0 16px; padding: 8px;
  background: var(--accent-dim); color: var(--accent);
  border: 1px solid rgba(59,130,246,.35); border-radius: var(--radius-sm);
  font-family: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer;
}
.pub-ai:hover:not(:disabled) { background: rgba(59,130,246,.22); }
.pub-ai:disabled { opacity: .6; cursor: not-allowed; }
.pub-ai svg { flex: 0 0 auto; }

/* A locked field must LOOK locked, or a form that ignores typing reads as
   broken rather than busy. */
.form-input:disabled,
textarea.form-input:disabled {
  opacity: .55;
  cursor: not-allowed;
}
