/* Prism cockpit styles — hand-authored from design_handoff_prism_design_system/README.md's
   token table (no shippable CSS exists in that handoff, see plan §5.1). Component classes
   translate the cockpit prototype's visual mechanisms (dashed dropzone, pill buttons/badges,
   colored-left-border cards) into real CSS, not copied inline-style markup. */

:root {
  color-scheme: light;
  font-family: "Sora", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;

  /* Core */
  --slate-900: #1d2b36;
  --primary-slate: #3e5c76;
  --ink: #22303b;

  /* Accents (accent-only — never large surfaces) */
  --violet: #8e7cc3;
  --violet-text: #5f4e8c;
  --violet-light: #c3b6e0;
  --teal: #5fa8ae;
  --teal-deep: #35707a;

  /* Neutrals */
  --slate-bg: #edf1f3;
  --white: #ffffff;
  --card-tint: #f6f8fa;
  --border: #e2e7ea;
  --body-text: #57646d;
  --muted: #78868f;
  --disabled: #c9d1d6;

  /* Functional */
  --error: #c33f5e;
  --error-dark: #7c2b41;
  --error-bg: #f6e4e9;
  --error-border: #ecc9d2;
  --warning: #c17a2e;
  --warning-dark: #7a5220;
  --warning-bg: #f6ebda;
  --warning-border: #ebd4b0;
  --success: #3c8f5c;
  --success-dark: #235939;
  --success-bg: #deeee2;
  --success-border: #c4dfc9;
  --info: #35707a;
  --info-dark: #1f4247;
  --info-bg: #deebec;
  --info-border: #c3dbdd;

  /* Radii */
  --radius-sm: 12px;
  --radius-md: 16px;
  --radius-lg: 18px;
  --radius-xl: 24px;
  --radius-pill: 999px;
}

* {
  box-sizing: border-box;
}

/* Standard clip-rect screen-reader-only pattern — keeps an element in the DOM
   (queryable, still state-driven) but visually inert. Used on the pipeline
   step badges (Story 18): the raw state string stays the source of truth for
   tests/assistive tech, the user-visible copy comes from a sibling
   .step-status element instead. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Without this, `element.hidden = true` silently does nothing on any element
   whose class also sets `display` (badges, the completion banner, the
   progress-bar wrap, etc.) — author CSS beats the browser's default
   `[hidden] { display: none }` UA rule at equal specificity, so a class like
   `.badge { display: inline-flex }` wins and the element stays visible
   regardless of the hidden attribute. Real bug, found via a live screenshot —
   invisible to DOM-property assertions in jsdom-based unit tests, since the
   `hidden` attribute/property itself was always toggling correctly; only the
   CSS cascade result was wrong. */
[hidden] {
  display: none !important;
}

body {
  margin: 0;
  padding: 0;
  background: var(--slate-bg);
  color: var(--ink);
}

h1,
h2,
h3 {
  font-family: "Sora", sans-serif;
  font-weight: 800;
  letter-spacing: -0.03em;
  color: var(--ink);
  margin: 0 0 0.5rem;
}

h1 {
  font-size: clamp(28px, 4vw, 40px);
}

h2 {
  font-size: clamp(19px, 2.6vw, 22px);
}

p {
  margin: 0 0 0.5rem;
  color: var(--body-text);
  line-height: 1.6;
}

a {
  color: var(--primary-slate);
}

.subtitle {
  color: var(--body-text);
  margin-bottom: 1.5rem;
}

/* --- Top nav ------------------------------------------------------------ */

.topnav {
  display: flex;
  align-items: center;
  gap: 2rem;
  padding: 1rem 2rem;
  background: var(--white);
  border-bottom: 1px solid var(--border);
}

.brand {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.1rem;
  text-decoration: none;
  color: var(--ink);
}

.brand-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.brand-mark {
  color: var(--primary-slate);
}

/* Real semver, allocated deliberately at release (not auto-generated per
   commit/build) — see frontend/src/lib/version.ts, the one place this
   number lives; every page's own bootstrap script injects it here rather
   than each of the three nav-carrying .html files hardcoding a copy that
   could silently drift out of sync with the other two. */
.brand-version {
  font-family: "JetBrains Mono", monospace;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.03em;
  color: var(--muted);
}

.brand-word {
  font-family: "Sora", sans-serif;
  font-weight: 800;
  font-size: 1.1rem;
}

.nav-links {
  display: flex;
  gap: 1.5rem;
}

.nav-link {
  font-family: "JetBrains Mono", monospace;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--muted);
  padding: 0.35rem 0;
  border-bottom: 2px solid transparent;
}

.nav-link.is-active,
.nav-link:hover {
  color: var(--violet-text);
  border-bottom-color: var(--teal);
}

/* --- Page layout ---------------------------------------------------------*/

/* Story 10 refinement: fluid, higher-ceiling width — desktop-first (this
   project's established posture, docs/PRD.md's Story 5b out-of-scope note),
   so this is about using more of a wide viewport rather than chasing mobile
   polish. The original min(1400px, 95vw) left a large dead margin on
   wide/ultra-wide monitors (Director-reported at 2560px). min(2400px, 97vw)
   keeps a sane reading/working width at typical desktop sizes (1440-1920px
   the ceiling never engages, it's pure 97vw there) while still capping
   growth on ultra-wide screens instead of stretching edge-to-edge. */
.page {
  max-width: min(2400px, 97vw);
  margin: 0 auto;
  padding: 2rem clamp(1rem, 4vw, 3rem);
}

.eyebrow {
  font-family: "JetBrains Mono", monospace;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--violet-text);
}

/* --- Cards ---------------------------------------------------------------*/

.card {
  display: block;
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 1.5rem;
  margin-bottom: 1.5rem;
  text-decoration: none;
  color: inherit;
}

.section-body > *:last-child {
  margin-bottom: 0;
}

/* --- Buttons ---------------------------------------------------------------*/

.btn {
  font-family: "Sora", sans-serif;
  font-weight: 600;
  font-size: 14px;
  border-radius: var(--radius-pill);
  padding: 0.6rem 1.4rem;
  border: 1px solid transparent;
  cursor: pointer;
  display: inline-block;
  text-decoration: none;
  text-align: center;
}

.btn-primary {
  background: var(--primary-slate);
  color: var(--white);
}

.btn-primary:disabled {
  background: var(--disabled);
  cursor: not-allowed;
}

.btn-outline {
  background: transparent;
  border-color: var(--primary-slate);
  color: var(--primary-slate);
}

.btn-outline:disabled {
  border-color: var(--disabled);
  color: var(--disabled);
  cursor: not-allowed;
}

.btn-sm {
  padding: 0.35rem 0.9rem;
  font-size: 12px;
}

/* Story 20 follow-up: "Delete" on a pushed draft — an outline button in the
 * existing error/danger palette, distinct from the plain .btn-outline used
 * for every non-destructive action. */
.btn-danger {
  border-color: var(--error-border);
  color: var(--error-dark);
}

.btn-danger:disabled {
  border-color: var(--disabled);
  color: var(--disabled);
  cursor: not-allowed;
}

.draft-delete-control {
  margin-top: 0.75rem;
}

/* --- Dropzone --------------------------------------------------------------*/

.dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  border: 2px dashed var(--border);
  border-radius: var(--radius-lg);
  padding: 2.5rem 1.5rem;
  cursor: pointer;
  background: var(--card-tint);
  text-align: center;
}

.dropzone:hover,
.dropzone.is-dragover {
  border-color: var(--teal);
  background: var(--info-bg);
}

.dropzone-icon {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--white);
  border: 1px solid var(--border);
  color: var(--primary-slate);
}

.dropzone-title {
  font-weight: 700;
  color: var(--ink);
  margin: 0;
}

.dropzone-hint {
  font-family: "JetBrains Mono", monospace;
  font-size: 12px;
  color: var(--muted);
  margin: 0;
}

.dropzone-actions {
  margin-top: 1rem;
  text-align: center;
}

/* --- Status / progress -----------------------------------------------------*/

.status-text {
  font-size: 14px;
}

.status-text.muted {
  color: var(--muted);
}

.status-text.error,
.error {
  color: var(--error-dark);
  font-weight: 600;
}

/* Render-in-progress banner (Director request, 15 Aug 2026): shown above a
   still-playable prior reel while a new render is genuinely queued/running —
   plain inline status-text would be too easy to miss sitting right above an
   already-familiar video player, so this gets a light background pill (the
   same --info tone this app already uses for other "here's the current
   status" surfaces) instead of just color-only text. */
.status-text.render-in-progress {
  display: inline-block;
  padding: 0.4rem 0.75rem;
  border-radius: var(--radius-md);
  background: var(--info-bg);
  color: var(--info-dark);
  font-weight: 600;
  margin-bottom: 0.75rem;
}

.piece-id {
  font-family: "JetBrains Mono", monospace;
  font-size: 12px;
  color: var(--muted);
}

.progress {
  width: 100%;
  margin-top: 0.75rem;
  accent-color: var(--teal);
}

/* --- Badges ------------------------------------------------------------- */

.badge {
  display: inline-flex;
  align-items: center;
  font-family: "JetBrains Mono", monospace;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 0.25rem 0.65rem;
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
}

.badge-not-started {
  background: var(--card-tint);
  color: var(--muted);
  border-color: var(--border);
}

.badge-running {
  background: var(--info-bg);
  color: var(--info-dark);
  border-color: var(--info-border);
}

.badge-done,
.badge-approved,
.badge-exported {
  background: var(--success-bg);
  color: var(--success-dark);
  border-color: var(--success-border);
}

.badge-failed {
  background: var(--error-bg);
  color: var(--error-dark);
  border-color: var(--error-border);
}

/* Operator-stopped, not an error — same muted tone as badge-not-started
   (neutral "inert" reading) rather than the alarm-red of badge-failed. */
.badge-cancelled {
  background: var(--card-tint);
  color: var(--muted);
  border-color: var(--border);
}

.badge-draft {
  background: var(--card-tint);
  color: var(--muted);
  border-color: var(--border);
}

.badge-held {
  background: var(--warning-bg);
  color: var(--warning-dark);
  border-color: var(--warning-border);
}

/* Distinct filled/solid treatment vs. badge-exported's tinted-outline style
   — "posted" is the operator's own record-keeping claim (Surface.postedAt),
   a step past "exported" but still never a live-publish signal, so it reads
   as more resolved (solid fill) rather than a wholly different status. */
.badge-posted {
  background: var(--success-dark);
  color: var(--white);
  border-color: var(--success-dark);
}

.badge-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 0.5rem;
}

/* --- Step tracker ----------------------------------------------------------*/

.step-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  counter-reset: step-counter;
}

.step-row {
  counter-increment: step-counter;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem 1rem;
  border-radius: var(--radius-sm);
  background: var(--card-tint);
  border-left: 3px solid var(--border);
}

.step-row[data-state="not_started"],
.step-row[data-state="queued"],
.step-row[data-state="cancelled"] {
  background: var(--card-tint);
}

.step-row[data-state="running"] {
  background: var(--card-tint);
}

.step-row[data-state="done"] {
  background: var(--success-bg);
}

.step-row[data-state="failed"] {
  background: var(--error-bg);
}

.step-icon {
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: "JetBrains Mono", monospace;
  font-size: 12px;
  font-weight: 500;
}

.step-icon::before {
  content: counter(step-counter);
}

.step-row[data-state="not_started"] .step-icon,
.step-row[data-state="queued"] .step-icon,
.step-row[data-state="cancelled"] .step-icon {
  background: var(--card-tint);
  color: var(--disabled);
  border: 1px solid var(--border);
}

.step-row[data-state="running"] .step-icon {
  background: var(--primary-slate);
  color: var(--white);
  border: 1px solid var(--primary-slate);
}

.step-row[data-state="done"] .step-icon {
  background: var(--success);
  color: var(--white);
}

.step-row[data-state="done"] .step-icon::before {
  content: "✓";
}

.step-row[data-state="failed"] .step-icon {
  background: var(--error);
  color: var(--white);
}

.step-row[data-state="failed"] .step-icon::before {
  content: "!";
}

.step-label {
  flex: 1;
  font-weight: 600;
  font-size: 14px;
}

.step-status {
  font-family: "JetBrains Mono", monospace;
  font-size: 12px;
  font-weight: 500;
}

.step-row[data-state="not_started"] .step-status,
.step-row[data-state="queued"] .step-status,
.step-row[data-state="cancelled"] .step-status {
  color: var(--muted);
}

.step-row[data-state="running"] .step-status {
  color: var(--ink);
}

.step-row[data-state="done"] .step-status {
  color: var(--success-dark);
}

.step-row[data-state="failed"] .step-status {
  color: var(--error-dark);
}

.step-error {
  flex-basis: 100%;
  font-size: 13px;
  color: var(--error-dark);
  margin: 0.35rem 0 0;
}

.step-progress-wrap {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.step-progress {
  width: 100px;
  height: 8px;
  accent-color: var(--primary-slate);
}

.step-progress::-webkit-progress-bar {
  background: var(--card-tint);
  border-radius: var(--radius-pill);
}

.step-progress::-webkit-progress-value {
  background: var(--primary-slate);
  border-radius: var(--radius-pill);
}

.step-progress-label {
  font-family: "JetBrains Mono", monospace;
  font-size: 11px;
  color: var(--muted);
}

/* Story 10(C): horizontal stepper on a wide viewport — same markup/data as
   the vertical stacked list (ingest.ts's renderPipeline is untouched), just
   laid out as roughly-equal-width columns side by side instead of stacked
   rows (3 as of the "Rendering reel" card's removal — see the note below the
   step-list markup in index.html: render isn't auto-chained during ingest,
   it only starts from an explicit user action in Staging). Falls back to
   the original vertical .step-list/.step-row (flex column) below the
   breakpoint. */
@media (min-width: 900px) {
  .step-list {
    flex-direction: row;
    align-items: stretch;
  }

  .step-row {
    flex: 1 1 0;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.5rem;
  }

  .step-progress-wrap {
    width: 100%;
  }
}

/* --- Completion banner -------------------------------------------------- */

.banner {
  background: var(--success-bg);
  border: 1px solid var(--success-border);
  border-radius: var(--radius-sm);
  padding: 1.125rem 1.25rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}

#completion-text {
  color: var(--success-dark);
  font-weight: 700;
  margin: 0;
}

/* --- Media ----------------------------------------------------------------*/

video {
  width: 100%;
  border-radius: var(--radius-md);
  background: #000;
  margin-top: 0.5rem;
}

/* --- Staging: piece list -----------------------------------------------*/

/* Table view (Story 19, replacing the earlier one-badge-column-per-
   pipeline-step shape with Source | Ingested | Duration | Drafts | Status |
   Open, per docs/technical-plan-cockpit-table-view-migration.md §2) — same
   overflow-x/min-width/sticky-header precedent as .candidate-table above.
   Wrapped in .table-scroll (matching .candidate-groups's own overflow-x:
   auto role) rather than reusing .candidate-groups itself, since that class
   also carries candidate-table-specific spacing this page doesn't need. */
.table-scroll {
  overflow-x: auto;
}

.piece-table,
.drafts-table {
  width: 100%;
  min-width: 44rem;
  table-layout: fixed;
  border-collapse: separate;
  border-spacing: 0;
  margin-top: 0.75rem;
}

.piece-table th,
.piece-table td,
.drafts-table th,
.drafts-table td {
  text-align: left;
  vertical-align: top;
  padding: 0.6rem 0.6rem;
  border-bottom: 1px solid var(--disabled);
}

.piece-table th,
.drafts-table th {
  font-family: "JetBrains Mono", monospace;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--muted);
  font-weight: 600;
  background: var(--card-tint);
  border-bottom: 2px solid var(--muted);
  /* Sticky within .table-scroll's own scroll box — this table can get 10+
     rows from a folder-drop batch, so keeping the header visible while
     scrolling matters here the same way it does for .candidate-table. */
  position: sticky;
  top: 0;
  z-index: 1;
}

.piece-table th:nth-child(1),
.piece-table td.piece-cell-title {
  width: 28%;
}

.piece-table td.piece-cell-title {
  font-family: "JetBrains Mono", monospace;
  font-size: 13px;
  color: var(--ink);
  /* Long filenames wrap rather than truncate — this table exists
     specifically for at-a-glance batch scanning, so the full name should
     stay readable rather than being cut off with an ellipsis. */
  overflow-wrap: break-word;
}

.piece-table th:nth-child(2),
.piece-table td.piece-cell-created {
  width: 11rem;
  white-space: nowrap;
}

.piece-table th:nth-child(3),
.piece-table td.piece-cell-duration {
  width: 6rem;
  white-space: nowrap;
}

.piece-table th:nth-child(4),
.piece-table td.piece-cell-drafts {
  width: 6.5rem;
  white-space: nowrap;
}

.piece-table td.piece-cell-drafts {
  font-weight: 600;
}

.piece-table th:nth-child(5),
.piece-table td.piece-cell-status {
  width: 9rem;
}

.piece-table th:nth-child(6),
.piece-table td.piece-cell-open {
  width: 6.5rem;
  text-align: right;
}

.piece-row:hover {
  background: var(--card-tint);
}

.empty-state {
  text-align: center;
  padding: 3rem 1.5rem;
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}

/* --- Schedule: selectable table + persistent detail pane (Story 21) -------*/
/* Replaces the earlier read-only weekly-card layout (.schedule-weeks/
   .schedule-week/.schedule-entry*, now removed) with a single table —
   same overflow-x/sticky-header/table-layout conventions as .piece-table
   above, just with its own column widths. */

.schedule-table-wrap {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

.schedule-table {
  width: 100%;
  min-width: 40rem;
  table-layout: fixed;
  border-collapse: separate;
  border-spacing: 0;
  margin-top: 0.75rem;
}

.schedule-table th,
.schedule-table td {
  text-align: left;
  vertical-align: top;
  padding: 0.6rem 0.6rem;
  border-bottom: 1px solid var(--disabled);
}

.schedule-table th {
  font-family: "JetBrains Mono", monospace;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--muted);
  font-weight: 600;
  background: var(--card-tint);
  border-bottom: 2px solid var(--muted);
  position: sticky;
  top: 0;
  z-index: 1;
}

.schedule-table th:first-child,
.schedule-table td.schedule-cell-date {
  width: 12rem;
  white-space: nowrap;
}

.schedule-table th:nth-child(2),
.schedule-table td.schedule-cell-piece {
  width: auto;
  overflow-wrap: break-word;
}

.schedule-table th:nth-child(3),
.schedule-table td.schedule-cell-kind {
  width: 8.5rem;
}

.schedule-table th:nth-child(4),
.schedule-table td.schedule-cell-status {
  width: 8.5rem;
}

/* Row selection — same left-accent-border + tint pattern as
   .surface-list-item.is-selected, adapted to a <tr> via a border on its
   first cell (a border on the <tr> itself doesn't render reliably across
   browsers with border-collapse: separate). */
.schedule-row {
  cursor: pointer;
}

.schedule-row:hover {
  background: var(--card-tint);
}

.schedule-row td:first-child {
  border-left: 3px solid transparent;
}

.schedule-row.is-selected {
  background: var(--card-tint);
}

.schedule-row.is-selected td:first-child {
  border-left-color: var(--primary-slate);
}

.schedule-row:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: -2px;
}

/* Story 21 live-review rewrite: aligned to the prototype's editorial-card
   shape (docs/Prism Cockpit - Table View - Standalone.html) — an eyebrow
   header row, a big title, a status-pill subtitle, then a two-column grid
   (video-stage-with-overlay + caption on the left, one unified gate box on
   the right), rather than the earlier stacked single-column layout. */

.schedule-detail-pane {
  padding: 2rem;
}

.schedule-detail-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.schedule-detail-date {
  font-family: "JetBrains Mono", monospace;
  font-size: 11px;
  color: var(--muted);
}

.schedule-detail-title {
  margin: 0.5rem 0 0.1rem;
}

.schedule-detail-subtitle {
  margin-bottom: 1.25rem;
}

.schedule-detail-grid {
  display: grid;
  grid-template-columns: 1.2fr 1fr;
  gap: 1.5rem;
  align-items: start;
}

@media (max-width: 900px) {
  .schedule-detail-grid {
    grid-template-columns: 1fr;
  }
}

.schedule-detail-left {
  min-width: 0;
}

.schedule-detail-text {
  white-space: pre-wrap;
}

.schedule-detail-frames {
  padding-left: 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.schedule-text-stage {
  background: var(--card-tint);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 1rem;
}

/* Video stage: position:relative so the overlay bar can sit absolutely at
   its bottom edge, same "info burned onto the preview" treatment as the
   prototype's reel card. Overrides the global `video { width:100%;
   margin-top:0.5rem }` rule for this context only — here the video needs to
   fill the stage exactly (object-fit: cover), not sit as an inline block
   with its own top margin. */
.schedule-video-stage {
  position: relative;
  aspect-ratio: 16 / 9;
  background: #000;
  border-radius: var(--radius-md);
  overflow: hidden;
}

.schedule-video-stage video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  border-radius: 0;
  /* Real bug, live-caught: object-fit: cover crops to fill its box — fine
     in the small preview (this stage's own aspect happens to match the
     video's), but wrong the moment the video goes fullscreen: the browser's
     fullscreen viewport is a completely different (usually landscape)
     aspect ratio, and "cover" zoomed/cropped the top and bottom off this
     vertical reel — including the brand badge and burned-in caption bands
     — instead of showing the real, complete frame. contain never crops;
     it letterboxes instead, so what's reviewed is honestly what will
     actually be exported/posted, fullscreen or not. */
  object-fit: contain;
}

.schedule-video-stage .status-text {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  color: var(--muted);
}

.schedule-video-overlay {
  position: absolute;
  bottom: 0.6rem;
  left: 0.6rem;
  right: 0.6rem;
  background: rgba(23, 33, 41, 0.72);
  border-radius: var(--radius-sm);
  padding: 0.5rem 0.75rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-family: "JetBrains Mono", monospace;
  font-size: 11px;
  color: var(--white);
}

.schedule-video-overlay-framing {
  color: var(--teal);
}

.schedule-detail-caption-label {
  font-family: "JetBrains Mono", monospace;
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  margin-top: 0.75rem;
  margin-bottom: 0.35rem;
}

.schedule-detail-caption {
  background: var(--card-tint);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.75rem;
  min-height: 2.5rem;
}

.schedule-gate-box {
  background: var(--card-tint);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 1.25rem;
}

.schedule-gate-fields-holder {
  margin-top: 0.75rem;
  margin-bottom: 1rem;
}

.schedule-gate-fields {
  width: 100%;
  border-collapse: collapse;
  font-size: 13.5px;
}

.schedule-gate-field-key {
  padding: 0.4rem 0;
  color: var(--body-text);
  border-bottom: 1px solid var(--border);
  width: 40%;
}

.schedule-gate-field-value {
  padding: 0.4rem 0;
  font-weight: 600;
  text-align: right;
  border-bottom: 1px solid var(--border);
}

.schedule-detail-gate {
  margin-top: 0.75rem;
}

/* Director follow-up: the gate is a 3-way status selector (Draft / Approve
   / Hold), side by side, not a stacked action-button pair — the button
   matching the surface's current status is highlighted solid in its color;
   the other two stay as lighter outline buttons you can click to switch.
   Colors reuse this app's existing functional tokens rather than inventing
   new ones: Draft=warning(yellow), Approve=success(green), Hold=error(red). */
.schedule-gate-actions {
  display: flex;
  flex-direction: row;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.schedule-status-btn {
  flex: 1;
  min-width: 5rem;
  padding: 0.7rem 0.75rem;
  font-weight: 700;
  font-size: 13.5px;
  border-radius: var(--radius-pill);
  border: 1.5px solid transparent;
  cursor: pointer;
  background: transparent;
}

.schedule-status-btn:disabled {
  cursor: not-allowed;
}

.schedule-status-draft {
  border-color: var(--warning-border);
  color: var(--warning-dark);
  background: var(--warning-bg);
}

.schedule-status-draft.is-active {
  background: var(--warning);
  border-color: var(--warning);
  color: var(--white);
}

.schedule-status-approve {
  border-color: var(--success-border);
  color: var(--success-dark);
  background: var(--success-bg);
}

.schedule-status-approve.is-active {
  background: var(--success);
  border-color: var(--success);
  color: var(--white);
}

.schedule-status-hold {
  border-color: var(--error-border);
  color: var(--error-dark);
  background: var(--error-bg);
}

.schedule-status-hold.is-active {
  background: var(--error);
  border-color: var(--error);
  color: var(--white);
}

.badge-kind {
  color: var(--ink);
  background: var(--card-tint);
  border: 1px solid var(--border);
}

/* --- Staging: piece detail ----------------------------------------------*/

.piece-detail .card {
  border-left: 3px solid var(--teal);
}

.back-link {
  display: inline-block;
  margin-bottom: 1rem;
  font-family: "JetBrains Mono", monospace;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.segment-title {
  font-weight: 700;
  color: var(--ink);
}

.meta {
  font-family: "JetBrains Mono", monospace;
  font-size: 12px;
  color: var(--muted);
}

.surface-textarea {
  width: 100%;
  min-height: 120px;
  font-family: "Sora", sans-serif;
  font-size: 14px;
  color: var(--ink);
  background: var(--card-tint);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.75rem;
  resize: vertical;
}

.story-frames {
  list-style: decimal;
  padding-left: 1.25rem;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.story-frame {
  position: relative;
}

.cta-tag {
  display: inline-block;
  margin-left: 0.5rem;
  font-family: "JetBrains Mono", monospace;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--violet-text);
  background: var(--violet-light);
  border-radius: var(--radius-pill);
  padding: 0.1rem 0.5rem;
}

.approval-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

.surface-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  margin-top: 0.5rem;
}

.export-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

.edit-actions {
  display: flex;
  gap: 0.5rem;
  margin-top: 0.5rem;
}

.mark-posted-field {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 13px;
  color: var(--ink);
}

.schedule-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  margin-top: 0.5rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--border);
}

.schedule-label {
  font-family: "JetBrains Mono", monospace;
  font-size: 12px;
  color: var(--muted);
}

.schedule-controls .form-field {
  width: auto;
}

.form-field {
  font-family: "JetBrains Mono", monospace;
  font-size: 13px;
  color: var(--ink);
  background: var(--card-tint);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.4rem 0.6rem;
  width: 6.5rem;
}

.segment-edit-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.75rem;
}

/* --- Story 20: Segment & crop panel (region 1) --------------------------- */

/* --- Settings: brand asset slot cards --------------------------------------
   settings.html's 3 slot cards (badge/logo/cta) reuse .card for the outer
   shell and .form-field/.btn/.status-text for inputs, buttons, and inline
   errors — this block adds only what's genuinely new: the preview box and
   the field layout around it. */

.slot-card .meta {
  margin-top: -0.5rem;
  margin-bottom: 1rem;
}

.slot-preview-wrap {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 8rem;
  background: var(--card-tint);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  margin-bottom: 1rem;
  overflow: hidden;
}

.slot-preview {
  max-width: 100%;
  max-height: 12rem;
  object-fit: contain;
}

.slot-preview-placeholder {
  margin: 0;
}

.slot-fields {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 1.5rem;
  margin-bottom: 1rem;
}

.slot-field {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  font-family: "JetBrains Mono", monospace;
  font-size: 12px;
  color: var(--muted);
}

.slot-field .slot-duration-input {
  width: 6.5rem;
}

.slot-actions {
  display: flex;
  gap: 0.5rem;
}

.slot-status {
  margin-top: 0.75rem;
  margin-bottom: 0;
}

/* Director follow-up, 20 Aug 2026: one row of buttons (Blurred background,
   9:16, 1:1, 4:5) replacing the old radio pair — picking a ratio directly is
   the entry point into speaker-crop mode now, not a separate step after a
   generic "Speaker crop" toggle. */
.segment-crop-framing-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  margin-top: 0.5rem;
}

/* Story 14 — LLM-assisted speaker-crop tile detection. The host tightly
   shrink-wraps the source <video> (inline-block, so its box exactly matches
   the video's own rendered size regardless of the surrounding section's
   padding); .tile-overlays fills that box exactly via inset:0, and is
   itself click-through (pointer-events: none) so it never blocks the
   video's own controls — only the individual boxes re-enable clicks. */
.tile-overlay-host {
  position: relative;
  display: inline-block;
  max-width: 100%;
}

.tile-overlays {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.tile-overlay-box {
  position: absolute;
  pointer-events: auto;
  border: 2px solid var(--teal, #5fa8ae);
  background: rgba(95, 168, 174, 0.18);
  color: var(--white, #fff);
  font-family: "JetBrains Mono", monospace;
  font-size: 11px;
  padding: 0.15rem 0.3rem;
  text-align: left;
  align-items: flex-start;
  cursor: pointer;
  border-radius: var(--radius-sm);
}

.tile-overlay-box:hover {
  background: rgba(95, 168, 174, 0.32);
}

/* Story 20 — percentage-space drag-to-crop canvas, replacing the old
   pixel-space .crop-adjust-box/.crop-handle* system outright (plan §3: two
   drag systems doing the same job is strictly worse than one). The canvas
   itself is transparent (the source <video> shows through, drawn behind it
   inside .tile-overlay-host); `touch-action: none` stops the browser's own
   touch-drag/scroll gestures from competing with pointer-drag on the
   box/handles. */
/* Director-reported bug, 20 Aug 2026: this used to be the actual click
   target for drawing a new box, covering the full video — including the
   native <video controls> bar underneath, making play/pause/scrubbing
   unreachable while crop mode was active. pointer-events: none here so
   clicks pass through to the video by default; .crop-draw-zone below (a
   child, sized to stop short of the control bar) is what actually re-enables
   pointer-events and catches draw-a-new-box clicks. cropCanvas itself stays
   full-size (inset: 0) because staging.ts's clientToPercent() reads its
   getBoundingClientRect() as the 0-100% reference frame — shrinking it
   directly would throw off every crop-percentage calculation. */
.crop-canvas {
  position: absolute;
  inset: 0;
  border-radius: var(--radius-md);
  pointer-events: none;
  touch-action: none;
}

.crop-draw-zone {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  bottom: 44px; /* reserves the native video control bar's real estate */
  cursor: crosshair;
  pointer-events: auto;
}

.crop-canvas-box {
  position: absolute;
  border: 2px solid var(--white);
  cursor: move;
  pointer-events: auto;
  touch-action: none;
}

/* Director request, 20 Aug 2026: dim everything outside the crop box, not
   just outline it. Replaces an earlier box-shadow-spread attempt (`0 0 0
   2000px rgba(...)` on .crop-canvas-box) that wasn't actually rendering
   live — four explicit rects instead, positioned by staging.ts's own
   renderCropCanvas() using the exact same percent math as the box itself,
   so there's no dependency on box-shadow/overflow-clipping behavior to get
   right. pointer-events: none so a click in a dimmed area still reaches
   .crop-canvas underneath to start drawing a new box — these are a pure
   visual layer, never a click target of their own. */
.crop-dim {
  position: absolute;
  background: rgba(34, 48, 59, 0.55); /* rgba of --ink (#22303b) */
  pointer-events: none;
}

.crop-dim-top {
  left: 0;
  top: 0;
  width: 100%;
}

.crop-dim-bottom {
  left: 0;
  width: 100%;
}

.crop-dim-left {
  left: 0;
}

/* Corner resize handles: the visible dot stays a small ~14px circle (via the
   `::before` pseudo below), but each handle element itself is a ≥44×44px
   invisible hit area (UI/UX Designer 1's mandate) centered on the same
   corner point, so the clickable target is far larger than what's drawn. */
.crop-canvas-handle {
  position: absolute;
  width: 44px;
  height: 44px;
  margin: -22px;
  touch-action: none;
  cursor: pointer;
  pointer-events: auto;
}

.crop-canvas-handle::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 14px;
  height: 14px;
  transform: translate(-50%, -50%);
  background: var(--white);
  border: 2px solid var(--violet);
  border-radius: 50%;
}

.crop-canvas-handle-nw {
  top: 0;
  left: 0;
  cursor: nwse-resize;
}

.crop-canvas-handle-ne {
  top: 0;
  right: 0;
  cursor: nesw-resize;
}

.crop-canvas-handle-sw {
  bottom: 0;
  left: 0;
  cursor: nesw-resize;
}

.crop-canvas-handle-se {
  bottom: 0;
  right: 0;
  cursor: nwse-resize;
}

.generate-drafts-wrap {
  margin-top: 0.75rem;
}

/* --- Story 20: piece-detail four-region layout ---------------------------*/
/* Replaces the old exclusive-selection two-pane surface list/detail shell
   (`.surface-pane-layout`/`.surface-list*`/`.surface-detail-pane`/
   `.surface-detail-grid`/`.reel-detail-layout`/`.reel-detail-grid`/
   `.reel-editing-column`/`.reel-preview-column`/`.reel-column-resizer*`, all
   removed) with four stacked, independently-owned regions.
   docs/technical-plan-story20-staging-detail-rewrite.md §4/§8. */

.staging-detail-header {
  margin-bottom: 1rem;
}

.staging-detail-header h1 {
  margin-top: 0.25rem;
}

.status-strip {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  padding: 0.5rem 0;
  background: var(--slate-bg);
}

.status-strip-cell {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

/* Real bug, live-caught: segment-crop-region and source-suggestions-region
   were rendering as two separate full-width stacked blocks — the prototype
   pairs them as one row (Segment & crop ~58% left, Source details + AI
   suggestions stacked ~42% right). .segment-crop-grid is the shared row;
   source-suggestions-region is now a plain vertical stack of its own two
   cards, not a second grid. */
.segment-crop-grid {
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  gap: 1.5rem;
  margin-bottom: 1.5rem;
}

@media (max-width: 860px) {
  .segment-crop-grid {
    grid-template-columns: 1fr;
  }
}

.segment-crop-region {
  position: relative;
}

.source-suggestions-region {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

.source-details-region,
.segment-suggestions-region {
  margin-bottom: 0;
}

/* Transcript view (Director request, 20 Aug 2026): capped to ~20 lines with
   its own scrollbar rather than growing the page indefinitely for a long
   transcript — line-height set explicitly on .transcript-text so the
   20-line math (max-height = 20 * line-height) is a real, predictable
   number, not dependent on inherited body-text line-height drifting later. */
.transcript-view {
  margin-top: 0.75rem;
  max-height: 30rem;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.75rem;
  background: var(--card-tint);
}

.transcript-text {
  margin: 0;
  white-space: pre-wrap;
  line-height: 1.5rem;
  font-size: 0.9rem;
}

.drafts-preview-region {
  display: grid;
  grid-template-columns: 1fr 220px;
  gap: 1.5rem;
}

@media (max-width: 860px) {
  .drafts-preview-region {
    grid-template-columns: 1fr;
  }
}

.drafts-preview-caption-col .eyebrow,
.drafts-preview-video-col .eyebrow {
  margin-bottom: 0.75rem;
}

/* Story 20 follow-up: the relocated schedule control and the "Push to
 * draft" row both sit BELOW the caption/video columns, spanning the full
 * card width — without this, grid auto-placement would tuck them into the
 * narrow 220px second column instead. */
.drafts-preview-region .schedule-controls,
.drafts-preview-push-row {
  grid-column: 1 / -1;
}

.drafts-preview-region .schedule-controls {
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--border);
}

.drafts-preview-push-row {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-top: 1rem;
}

.drafts-preview-push-row .push-success-text {
  color: var(--primary-slate);
  margin: 0;
}

/* Real bug, live-caught via screenshot: the rendered-reel video used to sit
   inside its own separate nested .card ("Rendered reel" as a second card
   inside this one) instead of sharing this card with the caption — see
   buildRenderSection's own doc comment in staging.ts. Dark 9:16 stage
   matching the prototype's rendered-reel preview box. */
.rendered-reel-stage {
  aspect-ratio: 9 / 16;
  background: #000;
  border-radius: var(--radius-md);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}

.rendered-reel-stage video {
  width: 100%;
  height: 100%;
  margin: 0;
  border-radius: 0;
  /* Director-reported bug, 20 Aug 2026: fullscreen showed only a
     zoomed/cropped middle slice of the reel, missing the brand badge and
     burned-in caption. Same root cause as .schedule-video-stage video's own
     fix — see its comment. */
  object-fit: contain;
}

.rendered-reel-stage .status-text {
  color: var(--muted);
  text-align: center;
  padding: 0.75rem;
}

.drafts-table-region {
  margin-bottom: 0;
}

/* --- Ingest: uploads list (ingest-batch-list-view story) -------------------
   Reuses .drafts-table/.table-scroll's own shape (th/td padding, sticky
   header) via the shared class on the <table> itself — only a couple of
   column-width/wrap tweaks are bespoke here, same precedent as
   .drafts-table-caption-cell above. */
.upload-list-title-cell {
  font-family: "JetBrains Mono", monospace;
  font-size: 13px;
  overflow-wrap: break-word;
}

.upload-list-title-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.upload-list-status-cell {
  width: 10rem;
  white-space: nowrap;
}

.upload-list-actions-cell {
  white-space: nowrap;
  text-align: right;
}

.upload-list-actions-cell .btn {
  margin-left: 0.4rem;
}

.upload-list-expanded-row .step-list {
  padding: 0.5rem 0;
}

.drafts-table-caption-cell {
  max-width: 22rem;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Director follow-up, 20 Aug 2026: same caption/video ratio as
 * .drafts-preview-region (region 3's own card) — a wide text column plus a
 * fixed ~220px video column, not the video stretched full-width above the
 * text. */
.drafts-table-expanded-inner {
  display: grid;
  grid-template-columns: 1fr 220px;
  gap: 1.5rem;
  padding: 0.75rem 0;
}

@media (max-width: 860px) {
  .drafts-table-expanded-inner {
    grid-template-columns: 1fr;
  }
}

.surface-kind-label {
  font-family: "JetBrains Mono", monospace;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
  white-space: nowrap;
}

/* --- Staging: segment candidate flat list (live-review follow-up, 20 Aug
   2026, superseding the earlier round-grouped/collapsible <table> — see
   buildCandidateRow's own doc comment in staging.ts for the full history.
   Matches the prototype's "time range + reason + Use button" row shape. */

.segment-candidate-list {
  display: flex;
  flex-direction: column;
}

.candidate-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.7rem 0;
  border-bottom: 1px solid var(--border);
}

.candidate-info {
  min-width: 0;
  flex: 1;
}

.candidate-range {
  display: block;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--ink);
}

.candidate-round-tag {
  margin-left: 0.5rem;
}

.candidate-thesis {
  margin: 0.15rem 0 0;
  color: var(--body-text);
}

.candidate-row-chosen {
  background: var(--info-bg);
  border-radius: var(--radius-sm);
  padding-left: 0.5rem;
  padding-right: 0.5rem;
}

/* Row-click-to-select. Only non-chosen rows get this treatment; the chosen
   row has nothing to do on click. */
.candidate-row-selectable {
  cursor: pointer;
}

.candidate-row-selectable:hover {
  background: var(--card-tint);
}

.candidate-row-selectable:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: -2px;
}

.candidate-row.is-selecting {
  opacity: 0.6;
  pointer-events: none;
}

.candidate-row-actions {
  display: flex;
  flex-wrap: wrap;
  flex: none;
  gap: 0.4rem;
  align-items: flex-start;
}

.segment-edit-wrap {
  margin-top: 0.5rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--border);
}

/* --- Staging: Story 12 segment round history + "Propose more" ----------- */

.propose-more-controls {
  margin-bottom: 0.75rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid var(--border);
}

.propose-more-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.5rem;
}

/* Bounded-scroll treatment for the round-groups list specifically — piling
   up "Propose more candidates" rounds must never grow the reel card's left
   column height without bound (the original bug report). The propose-more
   preset buttons and Cancel button sit in siblings above this element
   (proposeMoreHolder/cancelHolder in staging.ts's buildSegmentSection), so
   they stay fixed in place, not swallowed into the scroll area. The border +
   background-clipped radius gives it a visible boundary so it reads as
   "this scrolls" rather than "the page ends here". */
.candidate-groups {
  /* Director request, 15 Aug 2026: the box's height was a fixed 20rem with
     no way to see more rows without scrolling inside a cramped area —
     `resize: vertical` gives it a native browser drag handle (bottom-right
     corner) the operator can pull to see more at once, no custom JS divider
     needed (unlike the reel card's left/right column split, which genuinely
     needed one — a plain single-axis height resize is exactly what the
     browser's own `resize` property is for). `height` (not `max-height`) is
     the starting size so the drag has real room to grow from; `min-height`/
     `max-height` bound the drag range so it can't be shrunk to nothing or
     dragged absurdly tall. Vertical-only, not `resize: both` — the box's
     width is already driven by the reel card's own column layout. No
     overflow-x needed since the flat candidate list (20 Aug 2026, replacing
     the earlier round-grouped table) wraps text naturally instead of
     needing a fixed min-width to stay readable. */
  height: 20rem;
  min-height: 8rem;
  max-height: 50vh;
  resize: vertical;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  /* Director caught this live: padding directly on a `position: sticky`
     element's own scrolling ancestor is a known problematic browser
     combination — confirmed with a Playwright scroll simulation
     screenshotted mid-scroll, and confirmed the fix the same way (padding
     removed → the exact same test renders cleanly): the row that had just
     scrolled behind the sticky table header visibly bled through in the
     padding's own space above the header instead of being covered by it.
     Breathing room now comes entirely from each cell's own padding
     (0.6rem, set below) plus the table's margin-top, not from padding on
     this scrolling box itself. */
}

/* Phase 2 — Settings screen's Instagram credential form. Stacked
   label/input pairs (unlike .schedule-controls'/.segment-edit-form's inline
   layout) — a password-type token field reads better with room to breathe
   than squeezed into a 6.5rem inline .form-field. */
.settings-form {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.75rem;
  max-width: 28rem;
}

.settings-form label {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  width: 100%;
  font-family: "JetBrains Mono", monospace;
  font-size: 12px;
  color: var(--muted);
}

.settings-form .form-field {
  width: 100%;
}
