/* =========================================================================
   DC-COMPONENTS.CSS — the canonical .ig-* component layer
   =========================================================================
   Single source of truth for the intake-workflow component system, extracted
   from the approved design: dc-intake-flow-4.html + IMPORT_GROUPS.md, then
   reviewed in the component gallery (www/dc/dc-components.html).

   Load AFTER colors_and_type.css (tokens) and dc-theme.css (shell + .dc-pill /
   .dc-chip base). Pages compose these components — do NOT restyle them per page.

   Behaviours (accordion toggle + drag-resize gutter) live in dc-components.js.
   ========================================================================= */

/* Dark-mode tokens are identical to — and now sourced from — colors_and_type.css
   (:root[data-bs-theme="dark"]). No token override needed here; component-specific
   dark rules (e.g. .ig-tab.is-active) live inline below. */

/* ── Strip bar + strip button (canonical action-bar system) ── */
/* Encoding feedback round (2026-07-29, item 3 — "a generate preview button
   at top right renders icon+label with no space between them"): every
   .ig-sb instance in this gallery reference already carries its own gap
   (was 5px); bumped to 6px here so ONE rule is the single source of truth
   for icon-label spacing across every icon+label button variant this app
   uses (.enc-card-cta/.enc-prevrow__actions buttons already settled on
   6px, second pass) — the operator's live-app screenshot showed this
   exact toolbar button (Generate previews / Run queue / Package all,
   etc.) with no visible gap, which this reference cannot reproduce
   (verified live in Chrome — the icon and label render with clear
   separation here); reinforcing the value at the component level, rather
   than trusting per-page inline overrides, is the most durable fix
   available without editing pages/encoding.py directly. App-wide, not
   Encoding-scoped — .ig-sb is shared chrome, and 5px→6px is visually
   negligible on every other page that uses it. */
.ig-sb { height: 100%; border: none; border-right: 1px solid var(--dc-line);
  border-radius: 0; background: transparent; padding: 0 12px;
  font-size: 12px; font-weight: 600; color: var(--dc-ink);
  display: inline-flex; align-items: center; gap: 6px;
  cursor: pointer; white-space: nowrap; transition: background 120ms ease; }
.ig-sb:hover { background: var(--dc-panel); }
.ig-sb:disabled { opacity: 0.35; cursor: not-allowed; }
.ig-sb--active { background: var(--dc-primary-soft); color: var(--dc-primary); }
.ig-bar { flex: none; display: flex; align-items: stretch;
  height: var(--dc-strip-h); background: var(--dc-panel-2);
  border-bottom: 1px solid var(--dc-line);
  /* Inset controls need room to breathe on BOTH edges (operator 2026-08-10:
     "button which are left aligned to the edge of a panel need margin to the
     left so they dont sit flush"). Tabs and the section label are the two
     things that SHOULD reach the edge, so they cancel it themselves below. */
  gap: 6px; padding: 0 8px; }
.ig-bar__right { margin-left: auto; display: flex; align-items: center; gap: 6px; }


/* ═════════════════════════════════════════════════════════════════════════
   TOP-STRIP CONTROLS — two registers (2026-08-10)
   ═════════════════════════════════════════════════════════════════════════
   Operator: "I asked for full height buttons, they don't really work, it
   gets too cluttered fast" + "tabs should be tabs, buttons not full height
   with rounded corners same as drop-downs ands search".

   The problem was that tabs, buttons and dropdowns were ALL full height,
   square and hairline-separated, so a strip rendered as one undifferentiated
   run of tiles — nothing said that "Preview & approve" (which view am I in)
   and "Run queue" (do a thing) are different KINDS of control. Scheduling's
   calendar toolbar was the extreme: seven identical segments.

   So the strip now carries two registers:
     - STRUCTURE — `.ig-tab` only. Full height, square, flush, welded to the
       panel it switches, active one taking the content's own background plus
       a 2px underline. Tabs are the sole thing that moves you elsewhere.
     - ACTION — everything you operate: `.ig-sb`, `.ig-sb-select`, `.ig-seg`,
       the rail search. Inset 24px inside the 34px band, bordered, sharing
       ONE radius (--dc-radius-lg, 10px, operator-picked from a 3px/5px/10px/
       pill strip 2026-08-10 — it also matches `.ig-tab-count`, the one
       already-round thing in the strip).

   SCOPED ON PURPOSE. These rules apply only inside `.ig-bar`/
   `.ig-canvas-head`. `.ig-sb` is shared chrome that ALSO serves in-card
   action strips (`.enc-card-cta`, `.enc-prevrow__actions`, `.enc-head-strip`
   — all full-height by design, see their own blocks below), and those must
   not become floating chips. Restyling the bare `.ig-sb` selector would have
   silently rewritten every card head in Encoding.
   ═════════════════════════════════════════════════════════════════════════ */

/* `.ig-rail__label`/`.ig-rail__filters` are strips too — any control in a
   rail band belongs in the ACTION register just as much as one in `.ig-bar`
   (2026-08-10, when Intake's New Group moved up into its section header). */
/* 2026-09-03: `.vnd-subrow`/`.fst-subrow` joined the scoping list — the
   drawer sub-entity control row (Vendors Media status filter) carries the
   same canonical select as the page bars (operator: "the dropdown needs
   to be aligned with the dropdowns on curation"). */
.ig-bar .ig-sb, .ig-canvas-head .ig-sb, .ig-rail__label .ig-sb, .ig-rail__filters .ig-sb,
.ig-bar .ig-sb-select, .ig-canvas-head .ig-sb-select, .ig-rail__label .ig-sb-select, .ig-rail__filters .ig-sb-select,
.vnd-subrow .ig-sb-select, .fst-subrow .ig-sb-select,
.ig-bar .ig-sb-date, .ig-canvas-head .ig-sb-date {
  height: 24px; align-self: center; flex: none;
  box-sizing: border-box;
  padding: 0 10px;
  border: 1px solid var(--dc-line);
  /* 999px, not the 10px picked on 2026-08-10 (operator 2026-08-26: "the
     generate preview should be a pill"). It keeps its BORDER — it is a thing
     you do, not a thing you are looking at, and a borderless soft fill would
     make it read as a status chip beside the tabs. Only the corner changed. */
  border-radius: var(--dc-radius-pill);
  background: var(--dc-panel);
  color: var(--dc-ink);
  transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
}
.ig-bar .ig-sb:hover, .ig-canvas-head .ig-sb:hover, .ig-rail__label .ig-sb:hover,
.ig-bar .ig-sb-select:hover, .ig-canvas-head .ig-sb-select:hover,
.vnd-subrow .ig-sb-select:hover, .fst-subrow .ig-sb-select:hover,
.ig-bar .ig-sb-date:hover, .ig-canvas-head .ig-sb-date:hover {
  background: var(--dc-panel); border-color: var(--dc-line-2);
}
/* Primary emphasis. `.is-active` is an accepted alias of `--active`:
   `components/dc_calendar.py` shipped `class="ig-sb sc-cal-viewbtn is-active"`
   while the only styled modifier was the BEM `--active`, so the calendar's
   current view button had NO visual state at all — verified live 2026-08-10,
   all three view buttons computed byte-identical. Supporting both spellings
   closes that class of bug rather than just this one instance. */
.ig-bar .ig-sb--active, .ig-canvas-head .ig-sb--active, .ig-rail__label .ig-sb--active,
.ig-bar .ig-sb.is-active, .ig-canvas-head .ig-sb.is-active {
  background: var(--dc-primary-soft);
  border-color: var(--dc-primary-line);
  color: var(--dc-primary);
}
/* Native select arrows can't be positioned, so strip selects draw their own
   chevron — same technique `.sc-cal-toolbar`/`#vnd_scope` used before this,
   now consolidated here so every strip select is identical by default. */
/* STRIP SIZE (2026-09-14). The rule above is the shared inset-pill shape
   at the 24px SECONDARY-band size (rail filters, drawer sub-rows). Inside a
   primary strip — `.ig-bar`, `.ig-canvas-head`, `.ig-rail__label` — the same
   controls are `--dc-strip-ctl-h` (30px) so they sit in the 48px band the
   way a 22px control sat in the 34px one. Selector list mirrors the shape
   rule's primary-strip members exactly; add a strip there, add it here. */
.ig-bar .ig-sb, .ig-canvas-head .ig-sb, .ig-rail__label .ig-sb,
.ig-bar .ig-sb-select, .ig-canvas-head .ig-sb-select, .ig-rail__label .ig-sb-select,
.ig-bar .ig-sb-date, .ig-canvas-head .ig-sb-date,
.ig-bar > .ig-seg, .ig-canvas-head > .ig-seg,
.ig-bar > .ig-tab, .ig-canvas-head > .ig-tab,
.ig-canvas-head > .ig-rail__search-box, .ig-bar > .ig-rail__search-box {
  height: var(--dc-strip-ctl-h);
}
.ig-bar > .ig-seg > button, .ig-canvas-head > .ig-seg > button { padding: 0 13px; font-size: var(--dc-fs-sm); }
.ig-bar > .ig-tab, .ig-canvas-head > .ig-tab { font-size: var(--dc-fs-sm); padding: 3px 12px; }
.ig-bar .ig-sb-select, .ig-canvas-head .ig-sb-select {
  appearance: none; -webkit-appearance: none;
  padding-right: 24px; font-weight: 600; cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='oklch(0.56 0.012 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' d='m3 6 5 5 5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 8px center;
  background-size: 9px 9px;
}
[data-bs-theme="dark"] .ig-bar .ig-sb-select,
[data-bs-theme="dark"] .ig-canvas-head .ig-sb-select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='oklch(0.72 0.012 260)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' d='m3 6 5 5 5-5'/%3E%3C/svg%3E");
}
/* :hover above uses the `background` LONGHAND precisely so it can't wipe
   background-image the way a shorthand would (the bug setup-page.css's own
   comment documents for #vnd_scope). */

/* Both edge-cancelling rules RETIRED for tabs on 2026-08-26. They existed
   because tabs were flush full-height segments that had to reach the panel
   edge and abut each other; a pill does neither. It sits inset like every
   other control, and the container's own gap separates it. The label keeps
   its cancel — that IS still flush structure. */
.ig-bar > .ig-canvas-head__label:first-child,
.ig-canvas-head > .ig-canvas-head__label:first-child { margin-left: -8px; }
/* 4px between tabs, matching `.dd-pills__set`; the container's 6px is for
   controls, which want more air than pills do. */
.ig-bar > .ig-tab + .ig-tab,
.ig-canvas-head > .ig-tab + .ig-tab { margin-left: -2px; }

/* ── Date picker (.ig-sb-date) ────────────────────────────────────────────
   A native <input type="date"> renders its value in the BROWSER's font, not
   the app's — operator 2026-08-10: "schedule date picker label is nt inline
   with dc_components design layout (wrong typo and size weight)". Restate the
   strip-control type so it matches every other control in the band. ── */
.ig-bar .ig-sb-date, .ig-canvas-head .ig-sb-date {
  font-family: var(--dc-font-sans);
  font-size: 12px;
  font-weight: var(--dc-fw-semi);
  color: var(--dc-ink);
  cursor: pointer;
}
.ig-bar .ig-sb-date::-webkit-calendar-picker-indicator,
.ig-canvas-head .ig-sb-date::-webkit-calendar-picker-indicator {
  cursor: pointer; opacity: 0.55; padding: 0; margin-left: 4px;
}
.ig-bar .ig-sb-date::-webkit-calendar-picker-indicator:hover,
.ig-canvas-head .ig-sb-date::-webkit-calendar-picker-indicator:hover { opacity: 1; }
/* Chrome renders date sub-fields with their own padding/colour. */
.ig-bar .ig-sb-date::-webkit-datetime-edit,
.ig-canvas-head .ig-sb-date::-webkit-datetime-edit { padding: 0; color: var(--dc-ink); }

/* ── Segmented control (.ig-seg) ──────────────────────────────────────────
   A mutually-exclusive group in the ACTION register, for a switcher that
   re-renders the SAME data at a different grain — the calendar's
   Week/Month/Year. Deliberately not `.ig-tab`: tabs say "you are somewhere
   else", and Week/Month/Year does not take you anywhere, it changes the zoom.
   Rendering it as tabs would promise a navigation that never happens.
   Inner edges are hairlines, outer corners take the shared radius. ── */
.ig-seg {
  display: inline-flex; align-items: stretch; align-self: center; flex: none;
  height: 24px; box-sizing: border-box;
  border: 1px solid var(--dc-line);
  border-radius: var(--dc-radius-lg);
  background: var(--dc-panel);
  overflow: hidden;
}
.ig-seg > button {
  border: none; background: transparent;
  padding: 0 11px;
  /* Strip label register — see .ig-canvas-head__label. Was 12px/600. */
  font-size: var(--dc-fs-xs); font-weight: var(--dc-fw-normal);
  color: var(--dc-muted);
  display: inline-flex; align-items: center; gap: 5px;
  cursor: pointer; white-space: nowrap;
  transition: background 120ms ease, color 120ms ease;
}
.ig-seg > button + button { border-left: 1px solid var(--dc-line); }
.ig-seg > button:hover { background: var(--dc-panel-2); color: var(--dc-ink); }
.ig-seg > button.is-active {
  background: var(--dc-primary-soft); color: var(--dc-primary);
}
[data-bs-theme="dark"] .ig-seg > button.is-active {
  color: color-mix(in oklch, var(--dc-primary) 35%, white);
}

/* ── Canvas header bar + tabs ── */
.ig-canvas-head { flex: none; height: var(--dc-strip-h); display: flex; align-items: stretch;
  background: var(--dc-panel-2); border-bottom: 1px solid var(--dc-line);
  gap: 6px; padding: 0 8px; }
/* THE STRIP LABEL REGISTER (2026-08-31, operator: "we're still all over the
   place with typography ... the dropdown weights has the correct type and
   color. please use this for all labels in the top strip").

   ONE type for every label in a strip: 11px, NORMAL weight, `--dc-muted`,
   sentence case. Taken from `.cur-wtoggle`, which the operator picked as the
   reference. Strips previously mixed three registers — this label at
   11px/700/uppercase/faint, `.ig-seg` buttons at 12px/600/muted, and the
   toggle at 11px/400/muted — which is what read as "all over the place".

   This DOES override rule 6's uppercase micro-label for strips specifically.
   `.dc-label` keeps that treatment where it still belongs: a section eyebrow
   inside a panel, which is a heading. A strip is a row of controls, and a
   700-weight uppercase word sitting beside a 400-weight button was the
   loudest thing in the band without being the most important. */
.ig-canvas-head__label { display: flex; align-items: center; padding: 0 14px;
  /* 12px since 2026-09-14 (was 11px): the strip grew to 48px and its labels
     took one type step with it, Curation first, then app-wide. */
  font-size: var(--dc-fs-sm); font-weight: var(--dc-fw-normal);
  letter-spacing: normal; text-transform: none;
  color: var(--dc-muted); border-right: 1px solid var(--dc-line);
  white-space: nowrap; flex: none; }
/* Plain flex space (2026-08-10). It used to carry the canvas background and
   a left hairline so the area right of the tabs read as "the panel"; with
   inset controls floating in the band that seam just fenced off a chip. */
.ig-canvas-head__fill { flex: 1 1 auto; }

/* Right-aligned mono count for a strip — "12 films · 8.4 h". Its own class
   because every workspace page wants one and they were being hand-styled.
   MONO and `--dc-faint` on purpose, and the one thing in a strip that does not
   take the label register above: it is machine data, not a label (rule 4), and
   it is the quietest thing in the band because it is a readout, not a control. */
.ig-canvas-head__meta {
  font-family: var(--dc-font-mono); font-size: var(--dc-fs-sm);
  color: var(--dc-faint); white-space: nowrap;
}

/* VERTICAL CENTRING (2026-08-31). `.ig-canvas-head`/`.ig-bar` are
   `align-items: stretch` so full-height `.ig-tab`s work, and that is correct.
   The consequence is that every OTHER control in a strip must opt into
   centring or it hangs off the top edge. `.ig-sb`/`.ig-sb-select`/`.ig-sb-date`
   already do (see the inset-pill rule above) and `.ig-canvas-head__label` sets
   its own `align-items`, which is why this never bit until a search box went
   into a pane head. Listed explicitly rather than a blanket `> *` so an
   arbitrary render.ui wrapper keeps stretching. */
.ig-canvas-head > .ig-rail__search-box, .ig-bar > .ig-rail__search-box,
.ig-canvas-head > .ig-seg,             .ig-bar > .ig-seg,
.ig-canvas-head > .ig-canvas-head__meta, .ig-bar > .ig-canvas-head__meta {
  align-self: center;
}
/* TABS — the structure register. Full height, square, flush to the left
   edge (hence the container's asymmetric padding), closed on the right by
   their own hairline. The active tab takes the CONTENT's background so it
   reads as continuous with the panel below, plus a 2px underline — with the
   fill no longer tinted, background alone was too weak a signal. */
/* ── Tabs, as the drawer's pills (operator 2026-08-26) ────────────────────
   Verdict on the previous treatment: "the main panels tabs look reaaaaly
   ugly". Blown up it was five signals for one bit of state — a full-bleed
   `inset 0 2px 0` accent that read as a stray rule across the whole strip,
   hairline dividers that made a run of tiles, a divider after the LAST tab
   separating nothing, and grey-inactive-on-white-active, which is button
   language: unselected tabs read as disabled.

   This is `.dc-pill`, the component the film drawer has used for its
   section tabs since 2026-08-25 and which the operator has been approving
   all week — soft fill, NO border, 999px. One tab language app-wide instead
   of two. Markup is untouched: still `.ig-tab` + `.ig-tab-count`.

   NOTE for the next reader: this is NOT Curation's `.cur-seg` (bordered box
   with dividers between buttons). That control was shown as a candidate and
   rejected on sight — "they look like 90s photoshop".

   This DOES revisit the 2026-08-10 register split, which made tabs
   "full height, square, flush" precisely so they could never be mistaken
   for controls. Decided knowingly: the drawer proved a pill strip still
   reads as "where am I", and consistency won. */
.ig-tab { height: 24px; align-self: center; flex: none;
  border: 0; border-radius: var(--dc-radius-pill);
  background: color-mix(in oklch, var(--dc-ink) 8%, transparent);
  color: var(--dc-muted);
  font-size: 11px; font-weight: var(--dc-fw-bold);
  padding: 3px 10px; box-sizing: border-box;
  display: inline-flex; align-items: center; gap: 5px;
  cursor: pointer; white-space: nowrap; position: relative;
  font-family: var(--dc-font-sans);
  transition: background var(--dc-dur-fast), color var(--dc-dur-fast); }
.ig-tab:hover { background: color-mix(in oklch, var(--dc-ink) 13%, transparent); color: var(--dc-ink); }
.ig-tab.is-active { background: var(--dc-primary-soft);
  color: color-mix(in oklch, var(--dc-primary) 72%, black);
  box-shadow: none; }
[data-bs-theme="dark"] .ig-tab.is-active { color: color-mix(in oklch, var(--dc-primary) 30%, white); }
/* The count rides inside the pill as the drawer's `.n` does — mono, dimmed,
   no chip of its own. A pill inside a pill was the old strip's own clutter. */
.ig-tab-count { font-family: var(--dc-font-mono); font-size: 10px; font-weight: 600;
  padding: 0; border-radius: 0; background: none; color: inherit;
  opacity: .72; line-height: 1.4; margin-left: 2px; }
.ig-tab.is-active .ig-tab-count { background: none; color: inherit; opacity: .72; }

/* ── Rail group row (2× topbar height). Operator 2026-07-21 set 3×=102px
   for a THREE-line row, because 2.5× broke the 34px grid rhythm against the
   sidebar bands. The middle line came off on 2026-09-16 (Avails is the only
   page using this row), so two lines now sit on 2×=68px — still a whole
   multiple of the topbar, still on the same grid. ── */
.ig-grouprow { display: flex; align-items: center; gap: 6px; padding: 0 12px;
  height: calc(2 * var(--dc-topbar-h)); box-sizing: border-box; cursor: pointer;
  position: relative; transition: background var(--dc-dur-fast); border-bottom: 1px solid var(--dc-line); }
.ig-grouprow:hover { background: color-mix(in oklch, var(--dc-ink) 4%, var(--dc-panel)); }
.ig-grouprow.is-selected { background: var(--dc-primary-soft); }
.ig-grouprow.is-selected::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0;
  width: var(--dc-edge-width, 4px); background: var(--dc-primary); border-radius: 0 3px 3px 0; }
.ig-grouprow__body { flex: 1 1 auto; min-width: 0; }
.ig-grouprow__top { display: flex; align-items: center; gap: 4px; margin-bottom: 2px; min-width: 0; }
.ig-grouprow__code { font-family: var(--dc-font-mono); font-size: 12px; font-weight: 600; color: var(--dc-ink); white-space: nowrap; flex-shrink: 0; }
.ig-grouprow__name { font-size: 13px; color: var(--dc-ink); font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ig-grouprow__meta { font-size: 11px; color: var(--dc-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ── Films table (dense rows, 4px state edge) ── */
.ig-films-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 13px; }
.ig-films-table thead th { text-align: left; font-size: 10.5px; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--dc-faint); font-weight: 700;
  height: var(--dc-topbar-h); padding: 0 10px; border-bottom: 1px solid var(--dc-line);
  white-space: nowrap; background: var(--dc-panel-2); }
.ig-films-table tbody td { padding: 0 10px; height: var(--dc-topbar-h); max-height: var(--dc-topbar-h);
  border-bottom: 1px solid var(--dc-line); vertical-align: middle; white-space: nowrap; overflow: hidden; box-sizing: border-box; }
.ig-films-table tbody tr { height: var(--dc-topbar-h); max-height: var(--dc-topbar-h); background: var(--dc-panel); }
.ig-films-table tbody td:nth-child(2) { border-left: 4px solid transparent; padding-left: 8px; }
.ig-films-table tbody tr.row-locked td:nth-child(2)   { border-left-color: var(--dc-edge-locked); }
.ig-films-table tbody tr.row-decision td:nth-child(2) { border-left-color: var(--dc-edge-decision); }
.ig-films-table tbody tr.row-running td:nth-child(2)  { border-left-color: var(--dc-edge-running); }
.ig-films-table tbody tr.row-danger td:nth-child(2)   { border-left-color: var(--dc-danger); }
.ig-films-table tbody tr.row-pending td:nth-child(2)  { border-left-color: var(--dc-edge-pending); }
.ig-films-table tbody tr.is-selected td { background: var(--dc-primary-soft) !important; }
.ig-films-table tbody tr.is-selected td:nth-child(2) { border-left-color: var(--dc-primary) !important; }
.ig-film-orig-col { font-family: var(--dc-font-mono); font-size: 12px; color: var(--dc-muted); font-style: italic; }

/* ── Spinner + approve cell ── */
.ig-spinner { display: inline-block; width: 10px; height: 10px;
  border: 1.5px solid currentColor; border-right-color: transparent;
  border-radius: 50%; animation: ig-spin 0.7s linear infinite; vertical-align: middle; }
@keyframes ig-spin { to { transform: rotate(360deg); } }
.ig-approve-cell { display: flex; align-items: center; gap: 4px; }

/* ── Chip state variants (base .dc-chip lives in dc-theme.css) ── */
.ig-chip-ok   { border-color: color-mix(in oklch, var(--dc-ok)     40%, var(--dc-line)); color: color-mix(in oklch, var(--dc-ok) 60%, var(--dc-ink)); background: var(--dc-ok-soft); }
.ig-chip-warn { border-color: color-mix(in oklch, var(--dc-warn)   35%, var(--dc-line)); color: color-mix(in oklch, var(--dc-warn) 55%, var(--dc-ink)); background: var(--dc-warn-soft); }
.ig-chip-danger { border-color: color-mix(in oklch, var(--dc-danger) 35%, var(--dc-line)); color: color-mix(in oklch, var(--dc-danger) 55%, var(--dc-ink)); background: var(--dc-danger-soft); }

/* ── Canonical accordion drawer (8 sections) ── */
.ig-accordion { border: 1px solid var(--dc-line); border-radius: 0; margin-bottom: -1px; }
.ig-accordion:first-child { border-top: none; }
.ig-bar + .ig-accordion { border-top: none; }  /* no double border under the action bar */
.ig-accordion-head { display: flex; align-items: center; justify-content: space-between;
  height: var(--dc-topbar-h); padding: 0 12px; background: var(--dc-panel-2); border-bottom: none;
  cursor: pointer; font-size: 12px; font-weight: 400; color: var(--dc-ink);  /* operator 2026-07-21: 600 too heavy */
  user-select: none; transition: background 120ms ease; border-left: 3px solid transparent; }
.ig-accordion-head:hover { background: var(--dc-panel); }
.ig-accordion.is-open > .ig-accordion-head { border-left-color: var(--dc-primary); border-bottom: 1px solid var(--dc-line); }
.ig-accordion-body { display: none; padding: 12px; background: var(--dc-panel); border-top: none; font-size: 12.5px; }
.ig-accordion.is-open > .ig-accordion-body { display: block; }
.ig-accordion-head__chevron { color: var(--dc-faint); font-size: 11px; transition: transform 120ms ease; }
.ig-accordion.is-open > .ig-accordion-head .ig-accordion-head__chevron { transform: rotate(90deg); }
.ig-accordion-edit { display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; margin-right: 4px; border: none; border-radius: var(--dc-radius); background: transparent;
  color: var(--dc-faint); font-size: 11px; cursor: pointer; transition: background 120ms ease, color 120ms ease; flex: none; }
.ig-accordion-edit:hover { background: var(--dc-line); color: var(--dc-ink); }

/* ── Key-value grid (accordion bodies, detail panels) ── */
.ig-kv { display: grid; grid-template-columns: 90px 1fr; gap: 4px 10px; }
.ig-kv-label { color: var(--dc-faint); font-weight: 600; font-size: 12px; }
.ig-kv-value { color: var(--dc-ink); font-size: 12.5px; }

/* ── Square flush input (.ig-input) — THE form control for edit states
   and add-rows. Promoted 2026-07-21 from the gallery's page-local
   .gx-input (dc-components.html edit-state + add-row frames) so apps and
   gallery share one definition. ── */
/* `.dc-modal .form-control` joins these selector lists rather than
   re-declaring them (2026-08-10). Shiny's `ui.input_text`/`input_text_area`
   render a Bootstrap `.form-control` and give the caller no hook to swap
   the class, so inside a `.dc-modal` that rendered control IS the
   `.ig-input` — one definition, two selectors. Copying the properties into
   a modal-local block instead would be exactly the lookalike shim this
   system rejects: two definitions to keep in sync, drifting on the first
   token change. */
/* EDITING REGISTER IS ROUNDED (2026-08-31). `dc-theme.css` §447 already gives
   every `.form-control`/`.form-select` `var(--dc-radius)`; the square corners
   here were LOCAL OVERRIDES fighting that default, left over from before the
   flush/square rule was scoped to the shell. Rule 11 makes the shell, the
   table and popovers flush — it never covered the things you type into.
   Removing the override, not adding a radius. */
.ig-input,
.dc-modal .form-control { width: 100%; box-sizing: border-box; border: 1px solid var(--dc-line); border-radius: var(--dc-radius);
  background: var(--dc-panel); color: var(--dc-ink); font-size: 12.5px; padding: 4px 8px; height: 28px;
  font-family: var(--dc-font-sans); }
.ig-input:focus,
.dc-modal .form-control:focus { outline: none; border-color: var(--dc-primary-line); box-shadow: 0 0 0 2px var(--dc-primary-soft); }
textarea.ig-input,
.dc-modal textarea.form-control { height: auto; min-height: 28px; }

/* Add-row grid (gallery § Add-row state — festivals & platforms) */
.ig-add-row { display: grid; gap: 6px; align-items: center; margin-top: 8px; }

/* ── Dense in-section table (accordion bodies: Files / Festivals /
   Platform tables in the canonical drawer). Encodes the table treatment
   the gallery frames carry as inline styles — one class, used by app
   drawers and (eventually) the gallery markup itself. ── */
.ig-dtable { width: 100%; border-collapse: collapse; font-size: 12px; }
.ig-dtable th, .ig-dtable td { white-space: nowrap; }  /* dense rows never wrap; .cell-trunc adds ellipsis */
.ig-dtable thead tr { border-bottom: 1px solid var(--dc-line); }
.ig-dtable th { text-align: left; padding: 4px 8px; color: var(--dc-faint); font-weight: 600; }
.ig-dtable td { padding: 5px 8px; color: var(--dc-ink); }
.ig-dtable th:first-child, .ig-dtable td:first-child { padding-left: 0; }
.ig-dtable th:last-child,  .ig-dtable td:last-child  { padding-right: 0; }
.ig-dtable tbody tr { border-bottom: 1px solid var(--dc-line); }
.ig-dtable tbody tr:last-child { border-bottom: none; }
.ig-dtable--fixed { table-layout: fixed; }
.ig-dtable .cell-trunc { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ig-dtable .cell-mono { font-family: var(--dc-font-mono); }
.ig-dtable .cell-muted { color: var(--dc-muted); }
.ig-dtable .cell-ok { color: var(--dc-ok); font-weight: 600; }
.ig-dtable .cell-warn { color: var(--dc-warn); font-weight: 600; }
.ig-dtable .cell-danger { color: var(--dc-danger); font-weight: 600; }
.ig-dtable .cell-r { text-align: right; }

/* ── Intake workspace layout (IMPORT_GROUPS.md §2) — three-column
   fullscreen operator workspace: rail | gutter | canvas | gutter | drawer ── */
.ig-workspace {
  flex: 1 1 auto; min-height: 0;
  display: flex; overflow: hidden;
}
.ig-rail {
  /* 268px → 340px (operator 2026-08-03): the rail was "not operational
     on a 14 inch screen" at 268 — dense rail tables need the room. */
  width: 340px; flex: 0 0 340px;
  display: flex; flex-direction: column;
  background: var(--dc-panel);
  /* No border-right: `.ig-rail-gutter` owns both seam borders (see its own
     rule). Both drawing one produced a 2px seam — operator 2026-08-10,
     "2nd panel left boarder should only be 1px as well". */
  overflow: hidden;
}
.ig-rail__label {
  height: var(--dc-strip-h); flex: none;
  display: flex; align-items: center; padding: 0 12px;
  font-size: 11px; font-weight: 700; letter-spacing: 0.07em;
  text-transform: uppercase; color: var(--dc-faint);
  border-bottom: 1px solid var(--dc-line); background: var(--dc-panel-2);
}
/* Filter band — sits BELOW the search, holding the rail's dropdowns
   (operator 2026-08-10: "move filter out of the header ... dropdowns below
   search"). Same 34px band and bottom rule as its siblings; the selects
   inside are already inset 24px controls via `.sc-rail-head`. */
.ig-rail__filters {
  height: var(--dc-topbar-h); flex: none; box-sizing: border-box;
  display: flex; align-items: center; gap: 6px; padding: 0 8px;
  border-bottom: 1px solid var(--dc-line);
  background: var(--dc-panel);
}
/* Search and filters read as one block, so no rule between them (operator
   2026-08-10: "no divider between search bar and dropdowns please"). The
   band below still closes against the table header. */
.ig-rail__search:has(+ .ig-rail__filters) { border-bottom: none; }
/* Left-aligned at their natural width, not stretched across the band. */
.ig-rail__filters > * { flex: 0 0 auto; min-width: 0; }

/* Search joins the ACTION register (2026-08-10). It was a borderless input
   filling its own 34px band, which is what made it the odd one out — every
   other control gained a shell, and search would have been the last flush,
   unbounded thing in the strip system. The band keeps its height and bottom
   rule; the control inside it is now a bordered 24px box on the shared
   radius, focus-lit as a whole. Markup gains one wrapper:
     .ig-rail__search > .ig-rail__search-box > (i + input)          */
.ig-rail__search {
  height: var(--dc-topbar-h); flex: none; box-sizing: border-box;
  display: flex; align-items: center; padding: 0 8px;
  border-bottom: 1px solid var(--dc-line);
}
.ig-rail__search-box {
  display: flex; align-items: center; gap: 7px;
  flex: 1 1 auto; min-width: 0;
  height: 24px; box-sizing: border-box;
  padding: 0 9px;
  border: 1px solid var(--dc-line);
  border-radius: var(--dc-radius-lg);
  background: var(--dc-panel);
  transition: border-color 120ms ease, box-shadow 120ms ease;
}
.ig-rail__search-box:focus-within {
  border-color: var(--dc-primary-line);
  box-shadow: 0 0 0 2px var(--dc-primary-soft);
}
/* Hung off the BOX, not the band (2026-08-31). `.ig-rail__search-box` is now
   used outside a rail — Curation puts one in an `.ig-canvas-head` — and the
   icon and input styling has to travel with the control, not with the strip
   it happened to be born in. No-op for every existing call site: they are all
   `.ig-rail__search > .ig-rail__search-box > (i + input)`. */
.ig-rail__search-box i { font-size: 12px; color: var(--dc-faint); flex: none; }
.ig-rail__search-box input {
  flex: 1 1 auto; min-width: 0; border: none; background: none; outline: none;
  font-size: 12.5px; color: var(--dc-ink); padding: 0;
}
.ig-rail__search-box input::placeholder { color: var(--dc-faint); }
.ig-rail__list { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
.ig-canvas {
  flex: 1 1 auto; min-width: 0;
  display: flex; flex-direction: column;
  overflow: hidden; background: var(--dc-panel);
  /* No border-left — the gutter owns this seam too (see .ig-rail above). */
}
.ig-canvas__body {
  flex: 1 1 auto; min-height: 0; overflow-y: auto; background: var(--dc-panel);
}
.ig-drawer-pane {
  width: 520px; flex: 0 0 520px;
  display: flex; flex-direction: column;
  border-left: 1px solid var(--dc-line);
  overflow: hidden; background: var(--dc-panel);
}
/* Dropzone hero (dc-intake-flow-1.html — Awaiting Form canvas) */
.ig-dropzone-hero {
  border: 2px dashed var(--dc-primary-line);
  border-radius: var(--dc-radius-lg);
  background: color-mix(in oklch, var(--dc-primary) 4%, var(--dc-panel));
  padding: 40px 32px;
  text-align: center;
  cursor: pointer;
  transition: border-color var(--dc-dur-fast), background var(--dc-dur-fast);
}
.ig-dropzone-hero:hover,
.ig-dropzone-hero.is-dragover {
  border-color: var(--dc-primary);
  background: color-mix(in oklch, var(--dc-primary) 8%, var(--dc-panel));
}

/* Canvas empty state (no group selected / group has no films yet) */
.ig-canvas__empty {
  flex: 1 1 auto; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 8px;
  color: var(--dc-muted); font-size: 12.5px; padding: 40px 20px;
}
.ig-canvas__empty i { font-size: 26px; color: var(--dc-faint); }

/* ── COLLAPSIBLE PANES (2026-08-31) ──────────────────────────────────────
   Any pane in an `.ig-workspace` can be folded away, and the neighbours take
   the width back because they are already `flex: 1 1 auto`. `display: none`
   rather than `width: 0`: a zero-width pane leaves its borders and its gutter
   behind as a 8px scar. The pane's gutter carries the same class so the two
   disappear together.

   A collapsed pane is ALWAYS replaced by an `.ig-pane-edge`, never by
   nothing — a pane with no visible way back is a pane the operator has lost.
   Markup:
     <div class="ig-pane-edge is-on">          <- click target, whole strip
       <i class="bi bi-chevron-double-right"></i>
       <span class="ig-pane-edge__label">All films</span>
       <span class="ig-pane-edge__n">34</span>
     </div>
   Persist the state per pane in localStorage ('dc.pane.<id>'), the way
   dc_table already persists a column layout. ──────────────────────────── */
.ig-canvas.is-collapsed,
.ig-rail.is-collapsed,
.ig-rail-gutter.is-collapsed { display: none; }

.ig-pane-edge {
  display: none;   /* `.is-on` shows it — it exists only while folded */
  flex: none; width: 24px; box-sizing: border-box;
  flex-direction: column; align-items: center;
  gap: var(--dc-space-4); padding: var(--dc-space-3) 0;
  background: var(--dc-panel-2);
  border-right: 1px solid var(--dc-line);
  color: var(--dc-faint); cursor: pointer;
  transition: background var(--dc-dur-fast), color var(--dc-dur-fast);
}
.ig-pane-edge.is-on { display: flex; }
.ig-pane-edge:hover { background: var(--dc-panel); color: var(--dc-ink); }
.ig-pane-edge i { font-size: 12px; }
/* The pane keeps its name while folded, set vertically. A 24px strip with
   only a chevron on it does not say what it would reopen. */
.ig-pane-edge__label {
  writing-mode: vertical-rl; white-space: nowrap;
  font-size: var(--dc-fs-xs); font-weight: var(--dc-fw-bold);
  letter-spacing: var(--dc-track-label); text-transform: uppercase;
}
.ig-pane-edge__n {
  writing-mode: vertical-rl;
  font-family: var(--dc-font-mono); font-size: 10px; opacity: 0.8;
}

/* ── Drag-resize gutter (panel↔panel and drawer; owns both edge borders) ── */
.ig-rail-gutter { width: 6px; flex: none; cursor: col-resize; background: var(--dc-panel-2);
  border-left: 1px solid var(--dc-line); border-right: 1px solid var(--dc-line); display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 3px; user-select: none; }

/* =========================================================================
   SCHEDULING COMPONENTS (2026-07-22) — Schedule drawer section, FullCalendar
   v6 canvas, Select-films rail. Design-approval gate: authored by
   claude-design-implementer for the operator's live review BEFORE the
   Scheduling page build. Reuses .ig-accordion/.ig-kv/.ig-bar/.ig-sb/
   .ig-input/.ig-films-table verbatim; the classes below are the genuinely
   NEW, additive, scoped pieces those recipes don't already cover.
   ========================================================================= */

/* ── Generic banner (.ig-banner) — retires the legacy .ddb2-warn banner for
   any NEW surface (Schedule section here; ddb2-warn itself is untouched —
   Export Groups' frozen drawer still uses it). Warn variant shown; the
   shape supports --ok/--danger siblings later without new markup. ── */
.ig-banner { display: flex; align-items: flex-start; gap: var(--dc-space-3);
  padding: var(--dc-space-3) var(--dc-space-4); border-radius: var(--dc-radius);
  font-size: 12px; line-height: 1.45; margin-top: var(--dc-space-4); }
.ig-banner i { flex-shrink: 0; margin-top: 1px; font-size: 13px; }
.ig-banner--warn { background: var(--dc-warn-soft);
  border: 1px solid color-mix(in oklch, var(--dc-warn) 35%, var(--dc-line));
  color: color-mix(in oklch, var(--dc-warn) 55%, black); }
[data-bs-theme="dark"] .ig-banner--warn { color: color-mix(in oklch, var(--dc-warn) 35%, white); }

/* ── Strip-bar select (.ig-sb-select) — a <select> in the .ig-bar visual
   language (flush, hairline divider, hover reveal) for calendar-toolbar-style
   dropdowns. Native arrow (no data-uri chevron — keeps every color a token). ── */
.ig-sb-select { height: 100%; border: none; border-right: 1px solid var(--dc-line); border-radius: 0;
  background: var(--dc-panel-2); padding: 0 8px; font-size: 12px; font-weight: 600; color: var(--dc-ink);
  cursor: pointer; }
.ig-sb-select:hover { background: var(--dc-panel); }
.ig-sb-select:focus { outline: none; }

/* ── Schedule drawer section — platform value, datetime row (Component 1) ── */
.sc-platform-value { display: inline-flex; align-items: center; gap: 5px; font-weight: 600; }
.sc-platform-value--yt { color: var(--dc-platform-yt); }
.sc-platform-value--pt { color: color-mix(in oklch, var(--dc-platform-pt) 70%, var(--dc-ink)); }
.sc-datetime-row { display: flex; gap: 6px; }
.sc-datetime-row input[type="date"] { flex: 1 1 auto; min-width: 0; }
.sc-datetime-row input[type="time"] { flex: 0 0 84px; font-family: var(--dc-font-mono); }
.sc-clear-row { margin-top: var(--dc-space-4); }

/* ── Calendar canvas (Component 2) — FullCalendar v6 tokenized to the
   system via its --fc-* custom properties + scoped overrides. headerToolbar
   is disabled in JS; the toolbar above the mount is composed from
   .ig-bar + .ig-seg (Week/Month/Year) + .ig-sb-date (the date picker). ── */
.sc-cal-toolbar { flex: none; }
/* The whole block of `.sc-cal-toolbar .ig-sb` / `.ig-sb-select` overrides
   that used to live here — wider padding, a divider before the Year select,
   and a bespoke SVG chevron — is GONE as of 2026-08-10. Every control it
   targeted was removed when the toolbar dropped from seven controls to two
   (segmented view switcher + one date picker), and the chevron technique it
   pioneered is now the default for every strip select in dc-components.css
   §TOP-STRIP CONTROLS, so nothing here needs to restate it. */
/* No border-top: `.sc-cal-toolbar` is an `.ig-bar` and already draws the
   1px bottom rule on this seam. Both drawing one made the divider under the
   calendar toolbar 2px — operator 2026-08-10, "only 1px lines please". */
.sc-cal-wrap { background: var(--dc-panel); }
.sc-cal {
  --fc-border-color: var(--dc-line);
  --fc-page-bg-color: var(--dc-panel);
  --fc-neutral-bg-color: var(--dc-panel-2);
  --fc-list-event-hover-bg-color: var(--dc-panel-2);
  --fc-now-indicator-color: var(--dc-primary);
  --fc-today-bg-color: color-mix(in oklch, var(--dc-primary) 7%, var(--dc-panel));
  --fc-small-font-size: 11px;
  --fc-highlight-color: var(--dc-primary-soft);
  font-family: var(--dc-font-sans);
}
.sc-cal .fc-header-toolbar { display: none; }  /* own toolbar sits above the mount */
.sc-cal .fc-scrollgrid, .sc-cal .fc-scrollgrid table,
.sc-cal .fc-multimonth { border-color: var(--dc-line); }
.sc-cal .fc-col-header-cell { background: var(--dc-panel-2); }
.sc-cal .fc-col-header-cell-cushion { color: var(--dc-faint); font-size: 10.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.06em; padding: 8px 4px; text-decoration: none; }
.sc-cal .fc-daygrid-day-number { color: var(--dc-muted); font-size: 11px; font-family: var(--dc-font-mono);
  padding: 4px 6px; text-decoration: none; }
.sc-cal .fc-day-today .fc-daygrid-day-number { color: var(--dc-primary); font-weight: 700; }
.sc-cal .fc-daygrid-day-frame { min-height: 64px; }
.sc-cal .fc-daygrid-day.fc-day-other .fc-daygrid-day-number { color: var(--dc-faint); opacity: .5; }
.sc-cal .fc-multimonth-title { color: var(--dc-ink); font-size: 12px; font-weight: 700; padding: 8px; }
.sc-cal .fc-daygrid-event-harness { margin-top: 2px; }
.sc-cal .fc-more-link { color: var(--dc-primary); font-size: 10.5px; font-weight: 600; }
.sc-cal a { text-decoration: none; }

/* Event chip (eventContent-rendered) — platform-colored, warning + released
   states. Draggable by default; .sc-chip--released turns drag off (JS sets
   editable:false per-event too — the CSS cursor is the visual confirmation). */
/* ROOM TO READ THE TITLE (operator 2026-08-31). The chip was locked to an
   18px single line with `white-space: nowrap`, so every title longer than the
   day column ellipsed after two or three words — and a calendar you cannot
   read the films off is a calendar you have to click through.

   Height is now content-driven with an 18px floor, and the title wraps to at
   most TWO lines before ellipsing. Two, not unlimited: a day cell holds
   `dayMaxEvents` chips before it collapses to "+N more", and one four-line
   chip would push the others out of a day that had room for them. */
.sc-chip { display: flex; align-items: flex-start; gap: 5px;
  min-height: 18px; padding: 3px 6px;
  border-radius: var(--dc-radius-sm); border: 1px solid transparent;
  font-size: var(--dc-fs-xs); font-weight: 600; line-height: 1.3;
  overflow: hidden; cursor: grab; }
/* Chips stack directly against each other in a day cell otherwise. */
.sc-chip + .sc-chip, .fc-daygrid-event-harness + .fc-daygrid-event-harness {
  margin-top: 2px; }
/* Nudged to sit on the FIRST line of a wrapped title rather than centring
   itself against the whole block. */
.sc-chip__icon { font-size: 9px; flex-shrink: 0; margin-top: 2px; }
.sc-chip__title {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; overflow-wrap: anywhere; }
.sc-chip--yt { background: var(--dc-platform-yt-soft); border-color: var(--dc-platform-yt-line);
  color: color-mix(in oklch, var(--dc-platform-yt) 60%, var(--dc-ink)); }
.sc-chip--pt { background: var(--dc-platform-pt-soft); border-color: var(--dc-platform-pt-line);
  color: color-mix(in oklch, var(--dc-platform-pt) 45%, var(--dc-ink)); }
.sc-chip--warn { border-style: dashed; border-color: var(--dc-warn);
  background: var(--dc-warn-soft); color: color-mix(in oklch, var(--dc-warn) 55%, var(--dc-ink)); }
.sc-chip--released { cursor: default; opacity: .55; filter: grayscale(0.4); }

/* ── Select-films rail (Component 3) ── */
.sc-rail-head { justify-content: space-between; }
/* …except in the filter band, which has no label to push against, so
   space-between just flung the two dropdowns to opposite ends (operator
   2026-08-10: "dropdowns left alignes"). Two classes so it beats the
   single-class rule above regardless of source order. */
.ig-rail__filters.sc-rail-head { justify-content: flex-start; }
.sc-rail-head .ig-input { width: 122px; height: 24px; }
.ig-rail__label.sc-rail-head .ig-input { height: var(--dc-strip-ctl-h); }
.sc-date-cell--warn { color: var(--dc-warn); font-weight: 600; }
.sc-date-cell--unset { color: var(--dc-faint); font-style: italic; }

/* "Schedule selected" bulk panel — two stacked .ig-bar strips (bulk
   scheduling is the one explicit exception to "actions execute directly,
   no staged batch buttons" — operator-requested). Two rows so the panel
   fits a narrow (268–320px) rail without overflow: row 1 = count +
   platform; row 2 = date + time + Apply.
   Operator feedback round (2026-07-22): the date/Apply row read cramped —
   .ig-bar's base fixed height (--dc-topbar-h, 34px) left almost no margin
   around the 24px inputs. Each row now sizes to its own content (height:
   auto + real vertical padding) instead of the fixed strip-bar height, and
   inputs are back at .ig-input's default 28px (was shrunk to 24px to force
   the fit — no longer needed now the row itself has room). */
.sc-bulk-panel { flex: none; border-top: 1px solid var(--dc-line); }
.sc-bulk-bar { height: auto; align-items: center; gap: 10px; padding: 9px 14px; }
.sc-bulk-bar .ig-input { width: auto; flex: 1 1 auto; min-width: 0; }
.sc-bulk-bar input[type="time"].ig-input { flex: 0 0 82px; }
.sc-bulk-count { font-size: 12px; font-weight: 700; color: var(--dc-ink); white-space: nowrap; }
.sc-bulk-platform { display: flex; align-items: center; gap: 5px; font-size: 12px;
  color: var(--dc-muted); white-space: nowrap; }

/* =========================================================================
   DRAWER HEAD — poster + metadata (2026-07-22, poster-round follow-up)
   Operator picked "C · tight reclaim" from the 3 poster variants as THE
   drawer-head treatment (promoted into the canonical drawer). The head
   metadata set was reconciled to exactly: title · original title (only
   when it differs) · year · runtime · on-platform indicators. The old
   traffic-light dot + internal-id/status line are DROPPED from the head
   (no 1:1 home in the new set — see SCREEN_RECIPES.md's Library entry for
   where, if anywhere, they should resurface). Poster sizing/padding here
   ARE variant C's values (60×84, 6px vertical padding) — page-agnostic,
   not Scheduling-specific, despite living in this file alongside it.
   ========================================================================= */
.dd-head { display: flex; align-items: center; justify-content: space-between; gap: 6px;
  min-height: calc(2 * var(--dc-topbar-h)); height: auto; box-sizing: border-box;
  padding: 6px 12px; background: var(--dc-panel-2); border-bottom: 1px solid var(--dc-line); }
.dd-head__left { display: flex; align-items: center; gap: 8px; min-width: 0; }
/* Poster drop zone (2026-09-08): the slot below, wrapped. Tools appear on
   hover; a dashed primary outline says "drop here" while a file is over it. */
.dd-poster { position: relative; flex-shrink: 0; width: 60px; height: 84px; }
.dd-poster__tools { position: absolute; left: 0; right: 0; bottom: 0;
  display: flex; justify-content: center; gap: 2px; padding: 2px;
  background: color-mix(in oklch, var(--dc-panel) 82%, transparent);
  opacity: 0; transition: opacity var(--dc-dur-fast); }
.dd-poster:hover .dd-poster__tools, .dd-poster.is-over .dd-poster__tools { opacity: 1; }
.dd-poster__tools .dc-iconbtn { width: 20px; height: 20px; }
.dd-poster__tools .dc-iconbtn i { font-size: 11px; }
.dd-poster.is-over { outline: 2px dashed var(--dc-primary); outline-offset: 2px;
  border-radius: 4px; }
.dd-head__poster { width: 60px; height: 84px; flex-shrink: 0; object-fit: cover;
  border-radius: var(--dc-radius-sm); background: var(--dc-panel); }
.dd-head__text { min-width: 0; }
.dd-head__title { font-size: 13.5px; font-weight: 400; color: var(--dc-ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dd-head__orig { font-size: 11.5px; font-style: italic; color: var(--dc-muted); margin-top: 2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dd-head__meta { font-size: 11px; font-family: var(--dc-font-mono); color: var(--dc-muted);
  margin-top: 2px; }
/* Platform indicator row — small pill per platform, live vs not-live. Same
   token composition as .sc-chip--yt/--pt (soft fill + line border + tinted
   icon) so a "live" pill and a calendar chip read as the same visual
   language, just resized for the tight head instead of a calendar cell. */
.dd-head-plat { display: flex; gap: 5px; margin-top: 4px; }
.dd-head-plat__item { display: inline-flex; align-items: center; gap: 4px;
  font-size: 10px; font-weight: 600; line-height: 1; white-space: nowrap;
  padding: 3px 6px; border-radius: var(--dc-radius-pill);
  border: 1px solid var(--dc-line); background: transparent; color: var(--dc-faint); }
.dd-head-plat__item i { font-size: 10.5px; }
.dd-head-plat__item.is-live--yt { background: var(--dc-platform-yt-soft);
  border-color: var(--dc-platform-yt-line);
  color: color-mix(in oklch, var(--dc-platform-yt) 60%, var(--dc-ink)); }
.dd-head-plat__item.is-live--pt { background: var(--dc-platform-pt-soft);
  border-color: var(--dc-platform-pt-line);
  color: color-mix(in oklch, var(--dc-platform-pt) 45%, var(--dc-ink)); }

/* ── STRIP VARIANT (2026-09-08) — what the film drawer actually wears now.
   The 2 × topbar poster head above is retired as the FILM drawer's head:
   the poster, original title, "year · runtime" line and platform pill moved
   into the Core Metadata identity block (`.dd-id--poster`), because the head
   and that block were the same basic metadata rendered twice, once above the
   pills and once below. What is left is a strip — mono internal id · title,
   close X — pinned to `--dc-topbar-h` so it aligns across the screen with
   `.lib-topbar` / `.ig-canvas-head` in the pane to its left, which is the
   same reason the old head was pinned to 2 × that value.

   The poster rules above STAY as they are: `.dd-head__poster` and
   `.dd-head-plat` are still rendered, just one level down in the panel.

   The id/sep/title register below is deliberately the same one `.dd-chrome__id`
   / `__sep` / `__title` (drawer.css) give the vendor and festival record
   drawers — mono semibold ink id, faint separator, 13px muted title. Restated
   here rather than shared because the two strips are assembled by different
   modules (film_detail.py vs record_drawer.py) and the film drawer's classes
   should all be findable in one place; if a third strip ever appears, merge
   the three selectors into one rule instead of adding a fourth copy. */
.dd-head--strip { height: var(--dc-strip-h); min-height: 0; padding: 0 12px; }
.dd-head--strip .dd-head__left { gap: 6px; }
.dd-head__id { font-family: var(--dc-font-mono); font-size: 13px;
  font-weight: var(--dc-fw-semi); color: var(--dc-ink); flex-shrink: 0; }
.dd-head__sep { color: var(--dc-faint); font-size: 13px; flex-shrink: 0; }
.dd-head--strip .dd-head__title { font-size: 13px; color: var(--dc-muted); }

/* =========================================================================
   DELIVERY (2026-07-22) — the fulfilment-only page (formerly Export Groups):
   film-by-film file curation → package → handoff → log upload → live.
   Reuses the same .ig-workspace three-column shell as Import Groups/
   Scheduling (rail | canvas | drawer-pane) — no new shell classes. The
   canonical drawer (.ig-accordion, 9 sections) is UNCHANGED by this page —
   Delivery adds nothing to the drawer, only new canvas/rail pieces below.
   Namespace: .dlv-*. Full recipe: DELIVERY.md in this skill dir.
   ========================================================================= */

/* ── Rail — "due" urgency signal on the select-films table's date column.
   Modeled directly on .sc-date-cell--warn/--unset (Scheduling) but a
   distinct 3-state semantic: due TODAY (amber, act now) vs. due SOON
   (plain/muted, no action needed yet) vs. OVERDUE (red, past due). ── */
.dlv-due { display: inline-flex; align-items: center; gap: 4px; font-family: var(--dc-font-mono); font-size: 11.5px; font-weight: 600; white-space: nowrap; }
.dlv-due i { font-size: 11px; }
.dlv-due--today { color: var(--dc-warn); }
.dlv-due--soon  { color: var(--dc-muted); font-weight: 400; }
.dlv-due--over  { color: var(--dc-danger); }

/* ── Rail — "Move to delivery" bulk bar. Same shape/precedent as
   Scheduling's .sc-bulk-bar (the one operator-approved exception to
   "actions execute directly, no staged batch buttons" — selecting films
   to bring into the delivery workspace is a batching step, not a
   per-film action). Single row: count + spacer + primary CTA. ── */
.dlv-bulk-panel { flex: none; border-top: 1px solid var(--dc-primary-line); }
.dlv-bulk-bar { height: auto; padding: 9px 14px; }
.dlv-bulk-count { font-size: 12px; font-weight: 700; color: var(--dc-ink); display: inline-flex; align-items: center; gap: 6px; }
.dlv-bulk-count i { color: var(--dc-primary); }

/* ── Canvas — per-film delivery card. Reuses .ig-accordion verbatim for the
   collapsible shell (chevron toggle already works via dc-components.js
   event delegation — no new JS). Only the HEAD's inner content is new:
   film identity + a single .dc-pill status + a remove button, all on one
   line, mirroring the "one pill per row" rule from the old Export Groups
   rebuild rail. Status maps onto the EXISTING 4-state pill vocabulary —
   no new pill classes: Ready to package = --decision (blue, needs action),
   Packaging = --running (amber, worker busy), Packaged/ready-to-upload =
   --locked (green, settled), Missing requirement = --danger (red). ── */
.dlv-card-head { flex: 1 1 auto; display: flex; align-items: center; gap: 8px; min-width: 0; }
.dlv-card-title { font-size: 13px; font-weight: 600; color: var(--dc-ink); white-space: nowrap; }
.dlv-card-title:hover { color: var(--dc-primary); cursor: pointer; text-decoration: underline; }
.dlv-card-sub { font-size: 11.5px; color: var(--dc-muted); white-space: nowrap; }
.dlv-card-spacer { flex: 1 1 auto; }
.dlv-card-remove { flex: none; }

/* ── Expanded-card body indent (operator live-review, 2026-08-03: "main
   panel film items: use the same indent as on encoding on expanding film
   items") — matches .enc-card-body VERBATIM (same padding-left/::before
   indent-guide numbers below "ENCODING — new components"), under its own
   .dlv-* name since this class stacks onto Delivery's own .ig-accordion-
   body per-page, same "additional class, not a wrapper div" approach.
   Reverses the 2026-07-29 Round 2 note that this divergence was
   intentional — the operator has now asked for the two pages to read
   identically here. ── */
.dlv-card-body { position: relative; padding-left: 24px; }
.dlv-card-body::before { content: ""; position: absolute; left: 12px; top: 0; bottom: 0; width: 2px; background: var(--dc-line); }

/* ── Canvas — card-list wrapper.
   `.ig-accordion:first-child { border-top: none }` drops the top border
   globally, for accordions sitting under a bar/section head. In 2026-07-27
   the Delivery canvas had NO head above its first card, so this restored it
   ("first film item needs a top border"). The canvas HEAD (tabs) arrived
   afterwards and brought its own `border-bottom`, so the restore started
   stacking against it — a 2px grey divider under the tabs on both Delivery
   and Encoding, measured 1px+1px at the same y. Operator 2026-08-10: "it was
   the grey divider below that is still fat ... make the grey divider 1px."
   The head owns this seam; the first card must not add to it. ── */
.dlv-cards .ig-accordion:first-child { border-top: none; }

/* ── Canvas — folder/scan info line at the top of the "Ready to package"
   body: mono library path + scan age, actions right-aligned. The bottom
   padding keeps the action buttons off .dlv-reqs's border-top (operator
   2026-07-27). ── */
.dlv-scan-row { display: flex; align-items: center; gap: 8px; font-size: 11px; color: var(--dc-muted); padding-bottom: 9px; }
.dlv-scan-row__actions { margin-left: auto; display: flex; gap: 6px; }

/* ── Canvas — platform-requirements chip row (inside "Ready to package"
   body). Thin label + the EXISTING .ig-chip-ok/.ig-chip-danger variants —
   no new chip colors, just a row wrapper + uppercase micro-label. ── */
.dlv-reqs { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; padding: 10px 0; border-top: 1px solid var(--dc-line); }
.dlv-reqs__label { font-size: 10px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--dc-faint); margin-right: 2px; }

/* ── Canvas — package file-curation row. Filename/meta/type reuse
   .ig-dtable's cell-* vocabulary (see the Files section of the canonical
   drawer); the per-row include/exclude control reuses .ig-accordion-edit
   verbatim (same 22×22 transparent icon button) — swap the icon between
   bi-x-lg (included, click to exclude) and bi-plus-lg (excluded, click to
   re-include). Excluded rows just dim; nothing is deleted. ── */
.dlv-pfile-row.is-excluded { opacity: 0.45; }
.dlv-pfile-row.is-excluded .cell-trunc { text-decoration: line-through; text-decoration-color: var(--dc-faint); }

/* ── Canvas — file-relabel EDIT MODE (operator 2026-07-27: "we need to be
   able to relabel files — what is a master, trailer, subtitle and what
   language — this is the most important part"). Pencil in .dlv-scan-row
   flips the file table's Type column to native selects (+ a Language
   select on subtitle rows), Save/Cancel in the footer. Same fields and
   vocabulary as the drawer's Files-section editor (film_detail.py
   _file_edit_row: Video master · Subtitle · Artwork · Trailer · Document;
   language = ISO + name) — this edits the SAME film_assets record, just
   without leaving the card. Selects are the native .ig-input compacted to
   the 30px dense-row height. ── */
.dlv-pfile-row select.ig-input { height: 24px; font-size: 11.5px; padding: 1px 6px; }

/* ── Canvas — packaging-in-progress body (reuses .dc-progress/.dc-pill--
   running verbatim from dc-theme.css — no new classes needed here). ── */

/* ── Canvas — packaged/ready-to-upload folder path chip. Plain .dc-chip
   with the mono font already built in — no new class. ── */

/* =========================================================================
   ENCODING — new components (2026-07-22). Ports the locked dc-encoding.html
   (2026-07-08) picker→preview→queue workspace onto dc-components, per
   feature_platform_readiness.md. Reuses the rail/canvas/accordion-as-card/
   tabs/pill/chip/progress/step-strip system almost entirely — see
   ENCODING.md §0 for the full reuse inventory. Everything below is
   genuinely new, additive, scoped .enc-*. ── */

/* ── Canvas — card-list wrapper. Encoding feedback round (2026-07-29,
   item 1 — "flush cards, like Delivery"). Mirrors .dlv-cards exactly:
   .ig-accordion's own base `margin-bottom: -1px` already collapses each
   card's bottom border onto the next card's top border (zero gap between
   ANY two adjacent cards — that part is shared chrome and was never
   actually broken here). The one real, provable gap versus Delivery's own
   list: `.ig-accordion:first-child { border-top: none; }` (designed for
   accordions sitting directly under a bar/section head, e.g. the drawer)
   left Encoding's very first card with NO top border at all, unlike
   Delivery's (which restores it via `.dlv-cards .ig-accordion:first-child`).
   `.enc-cards` is that same restore, scoped identically. If the live app
   still shows a visible gap between cards after this, the far more likely
   cause is Shiny wrapping each `_processing_card()` output in its own
   per-film container (e.g. one `ui.output_ui()`/`@render.ui` call per
   row) — CSS margin-collapse only works between TRUE ADJACENT SIBLINGS,
   not across an intervening wrapper div, however invisible that wrapper
   looks. See ENCODING.md §2's flush-cards note for the exact build
   instruction this implies. ── */
.dlv-cards .ig-accordion:first-child { border-top: none; }  /* see above */



/* ═════════════════════════════════════════════════════════════════════════
   CARD STACK AS A TABLE (operator 2026-08-26)
   ═════════════════════════════════════════════════════════════════════════
   "is there a way to make it more like a table with accordions?"

   The diagnosis that produced this: the card heads were free-flowing flex
   rows, so every value started wherever the title before it happened to end.
   Measured across three rows, the state pill sat at x=332, x=386 and x=244.
   Nothing was a column, so the eye had to re-find every value on every line
   — and THAT is what read as rough, not the typography.

   One `grid-template-columns`, declared here and inherited by the header row
   and every card head, so a column is a column. Expanding is unchanged: the
   body still drops in place, full width, under its own row.

   This supersedes the floating-card round of the same day, which changed the
   container and left the contents misaligned ("this is not working was a bad
   idea but well made"). The container is back to flush and full width, per
   2026-08-03; only the alignment changed.

   THE COST, accepted knowingly: a fixed column truncates a long title with an
   ellipsis instead of pushing its neighbours sideways. The rail beside it has
   always done the same. */
.dlv-cards {
  /* edge gutter is the card's own 4px border-left, so it is NOT a track.
     EVERY track except the first is a FIXED width, and that is load-bearing:
     the header row and each card head are SEPARATE grid containers, so an
     `auto` track resolves against its own container's content. The action
     column sized to the word "ACTION" in the header and to a 247px button
     cluster in the rows — 187px apart, measured. Only fixed tracks agree
     across containers; `1fr` then resolves to the same remainder in each. */
  --enc-cols: 1fr 88px 152px 250px 34px;
}

.enc-thead {
  display: grid;
  grid-template-columns: var(--enc-cols);
  align-items: center;
  column-gap: var(--dc-space-5);
  height: 28px;
  /* The header must reproduce a CARD'S BOX MODEL exactly, or `1fr` resolves
     against a different remainder and every column drifts. A card is
     `border-left: 4px` (the state edge) + `border-right: 1px` + the head's
     own `padding: 0 12px`. Transparent borders here reserve the same space
     without drawing anything. */
  border-left: var(--dc-edge-width, 4px) solid transparent;
  border-right: 1px solid transparent;
  padding: 0 12px;
  background: var(--dc-panel-2);
  border-bottom: 1px solid var(--dc-line);
  font-size: var(--dc-fs-xs);
  font-weight: var(--dc-fw-bold);
  letter-spacing: var(--dc-track-label);
  text-transform: uppercase;
  color: var(--dc-faint);
}

/* The head becomes a row of that same grid. */
.dlv-cards > .ig-accordion > .ig-accordion-head {
  display: grid;
  grid-template-columns: var(--enc-cols);
  align-items: center;
  column-gap: var(--dc-space-5);
  justify-content: initial;   /* was space-between, meaningless in a grid */
  /* The head's OWN 3px transparent border goes. It was the flush-stack's
     `is-open` accent, and with the card already drawing a 4px state edge it
     did two harmful things: drew a second bar just inside the first, and
     inset the head's grid by 3px so its tracks no longer matched the
     header's. Measured: FILM at 425 against the header's 422, while YEAR and
     STATE agreed — the tell that the box models differed, not the tracks. */
  border-left: 0;
}
/* ...so the accent lives on the CARD for both pages. Encoding already put it
   there per state (`.enc-proc-card.row-*`); Delivery relied on the head's
   `is-open` border, which no longer exists, so it moves here too. */
.dlv-cards > .ig-accordion.is-open {
  border-left: var(--dc-edge-width, 4px) solid var(--dc-primary);
}
/* Film cell — title then platform, ellipsised rather than allowed to shove
   the columns after it. `min-width: 0` because a grid item will not shrink
   below its content without it. */
.dlv-cards .dlv-card-head {
  min-width: 0; display: flex; align-items: baseline; gap: var(--dc-space-3);
  flex: initial;
}
.dlv-cards .dlv-card-title {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dlv-cards .dlv-card-sub { white-space: nowrap; }
/* The spacer is what pushed the actions right in the flex head. A grid places
   them, so it would just eat a column. */
.dlv-cards .dlv-card-spacer { display: none; }
.enc-col-year { font-family: var(--dc-font-mono); font-size: 12px; color: var(--dc-muted); }
.enc-col-state { display: flex; align-items: center; min-width: 0; }
.dlv-cards .ig-accordion-head__chevron { justify-self: center; }
/* The action cluster starts at the LEFT of its track, so it begins on the
   same x as the header's "Action" label. Right-aligning it was also
   consistent row-to-row (a grid track is one width for every row) but it put
   the label above the trailing × instead of above the button it names.

   `margin-left: 0` cancels the 8px the flush head gave it to clear the title.
   A grid track already provides that separation, and the leftover margin put
   the cluster 8px past its own column. */
/* Specificity deliberately raised to (0,4,0): the pill round's
   `.ig-accordion-head .enc-card-cta { margin-left: 8px }` is (0,2,0) and sits
   LATER in this file, so an equal-weight selector here loses to it. */
.dlv-cards > .ig-accordion > .ig-accordion-head > .enc-card-cta {
  justify-self: start; margin-left: 0; }

/* ── Queue tab — flush job rows (operator 2026-08-03: "make the items
   full width, remove margins"). Same border-collapse trick as the
   .ig-accordion card stack: side borders + radius dropped, each row's
   bottom border overlaps the next row's top border (margin-bottom:-1px
   also reaches across the #encQueueList wrapper boundary, where the
   `.dc-row + .dc-row` sibling rule never could). Scoped to the Encoding
   queue wrapper only — .dc-row keeps its floating-pill look everywhere
   else in the app. ── */
.enc-jobs .dc-row { border-radius: 0; border-left: none; border-right: none; margin-bottom: -1px; }
.enc-jobs .dc-row, .enc-jobs .dc-row + .dc-row { margin-top: 0; }

/* ── Readiness badge (dot + word) — Ready / Remux / Encode. The one new
   piece of vocabulary this feature introduces; reused wherever a per-
   (film × platform) readiness verdict needs a compact inline indicator —
   the rail table AND the drawer Platform panel below both use it. ── */
.enc-rl { display: inline-flex; align-items: center; gap: 5px; font-size: 11.5px; font-weight: 600; white-space: nowrap; flex: none; }
.enc-rl__dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.enc-rl--ready  { color: var(--dc-ok); }     .enc-rl--ready  .enc-rl__dot { background: var(--dc-ok); }
.enc-rl--remux  { color: var(--dc-warn); }   .enc-rl--remux  .enc-rl__dot { background: var(--dc-warn); }
.enc-rl--encode { color: var(--dc-danger); } .enc-rl--encode .enc-rl__dot { background: var(--dc-danger); }

/* ── Rail — "Move to processing" bulk bar. THIRD occurrence of the same
   shape as .sc-bulk-bar / .dlv-bulk-bar (count + spacer + primary CTA, the
   operator-approved exception to "actions execute directly, no staged
   batch buttons"). Worth generalizing to one shared class if a 4th page
   ever needs it — see ENCODING.md §0. ── */
.enc-bulk-panel { flex: none; border-top: 1px solid var(--dc-primary-line); }
.enc-bulk-bar { height: auto; padding: 9px 14px; }
.enc-bulk-count { font-size: 12px; font-weight: 700; color: var(--dc-ink); display: inline-flex; align-items: center; gap: 6px; }
.enc-bulk-count i { color: var(--dc-primary); }

/* ── Canvas — preview card body. Shell is .ig-accordion (verbatim, same
   chevron-toggle JS as every other accordion); head content reuses
   .dlv-card-head/-title/-sub/-spacer/-remove VERBATIM (same "title · sub ·
   spacer · one pill · remove" shape as Delivery's card — zero new head
   CSS). Only the body pieces below are new. ── */

/* ── Round 2 (2026-07-29) visual-hierarchy pass. Operator: "we already
   build a good set of components we just need to create visual hierarchy
   ... the no margin items in the main panel work better than the ones on
   the encoding page" (Delivery's flush, dense card body vs. Encoding's
   looser one) + "once a panel is expanded the content should be slightly
   indented to underline grouping" + "we should have a one click create
   recommended without expanding the panel." Two structural additions:

   1. .enc-card-cta — a NEW slot in the SAME flex row .dlv-card-head/
      .dlv-card-spacer/remove/chevron already share (.ig-accordion-head,
      fixed --dc-topbar-h/34px) — one more child, sitting between the
      spacer and the remove button. Carries a verdict-appropriate primary
      action so the common path never requires opening the card at all —
      see ENCODING.md §2's per-state head-CTA table. Every button here
      dispatches the SAME server call the expanded body's own equivalent
      control already used pre-round-2 — this only relocates the choice
      onto the collapsed head, it invents no new backend semantics. Cards
      also now default COLLAPSED (pages/encoding.py::_processing_card()
      previously forced is-open unconditionally) — expanding via the
      chevron is the EXCEPTION path, for inspecting specs or picking a
      non-recommended profile.

   2. .enc-card-body — an additional class stacked onto .ig-accordion-body
      itself (not a wrapper div), so every existing bleed-to-edge trick
      (.enc-prevrow's margin:0 -12px, etc.) keeps working unmodified —
      only the LEFT padding changes (12px -> 24px, both on the 4px scale)
      plus a hairline left rule, so expanded content reads as "hanging"
      off the collapsed head instead of sharing its exact left edge.
      Originally scoped to Encoding's cards only — Delivery/Import Groups'
      own .ig-accordion-body was left UNCHANGED (their design was
      separately approved; this round only reopened Encoding for review,
      per the operator's brief). REVERSED 2026-08-03 (operator live-review:
      "main panel film items: use the same indent as on encoding on
      expanding film items") — see .dlv-card-body below, which gives
      Delivery's own expanded card body the identical treatment. ── */
/* Round 2, FIFTH pass (2026-07-29, operator amendment #5 — "the card
   heads are too busy visually... switch head actions to the top-strip
   button language"). This supersedes `.enc-card-cta`'s original flex-row-
   of-pill-buttons shape entirely (first pass through second-pass amendment
   #2's gap rule, both retired below) — .enc-card-cta is now a STRIP
   segment group, not a button row: full-height flush segments, each
   separated by its own border-left, composed directly from `.ig-sb` (the
   SAME class `.ig-canvas-head`'s own toolbar button already is — not a
   new component, a direct reuse of existing strip-button geometry).

   Why this works geometrically: `.ig-accordion-head` is already the exact
   same fixed height as `.ig-canvas-head` (`--dc-topbar-h`), so `.ig-sb`'s
   own `height:100%` + border-left/hover/disabled rules apply completely
   unmodified once .enc-card-cta itself is stretched to fill that height
   (`.ig-accordion-head` uses `align-items:center`, so `.enc-card-cta`
   needs its own `align-self:stretch` to escape that and actually reach
   full height; its children then inherit flex's default `align-items:
   stretch` for free). `margin-right:-12px` bleeds the group past the
   head's own 12px right padding so the last segment's edge is flush with
   the card's own outer border — exactly how `.ig-canvas-head__fill`-
   adjacent toolbar buttons already reach the canvas's true right edge.
   `margin-left:8px` gives the group breathing room from whatever sits to
   its left (the chevron — see below).

   The remove (✕) button JOINS this strip as its own segment (previously
   a separate `.ig-accordion-edit.dlv-card-remove` sibling AFTER
   .enc-card-cta) — it's now the LAST child inside .enc-card-cta on every
   state, plain `.ig-sb` (neutral hover, not the accent below), so a state
   with nothing else to decide (approved) still gets a full-height,
   flush, single-segment strip rather than the small round icon-button
   Delivery/Import Groups still use for their own (unchanged) remove.

   The chevron MOVES to sit BEFORE .enc-card-cta (immediately after
   .dlv-card-spacer) instead of after it — .ig-accordion-head__chevron's
   own CSS is a descendant selector (`.ig-accordion.is-open >
   .ig-accordion-head .ig-accordion-head__chevron`), position-in-DOM
   agnostic, so relocating it in markup doesn't touch its rotate-on-open
   behavior at all. This is the one deliberate exception to "everything
   becomes a strip segment" — the chevron isn't a button (the whole head
   is already the click target), so it stays the same small, unbordered,
   `--dc-faint` icon it always was; it just changes neighbours.

   Scoping note: every strip-specific rule below is qualified with
   `.ig-accordion-head` on purpose — the frozen "Approve button weight"
   comparison strip further down (kept as a historical decision record,
   not rebuilt in this pass) still uses bare `.enc-card-cta` OUTSIDE any
   `.ig-accordion-head` context, with its own inline `margin-bottom:14px`
   stacking. Qualifying every strip rule keeps that frozen demo rendering
   exactly as it did before this pass — only the 8 real card states (all
   genuinely inside `.ig-accordion-head`) pick up the new geometry. The
   base (unqualified) rule below is the fallback both contexts share. */
.enc-card-cta { display: flex; align-items: center; gap: 8px; flex: none; }
/* 2026-08-26 — the card head joins the ACTION register.
   The 2026-08-10 split (STRUCTURE = square flush tabs, ACTION = inset
   bordered controls on one radius) was scoped to `.ig-bar`/`.ig-canvas-head`
   and deliberately left these in-card strips full-height and square. The
   operator's verdict on seeing them next to Curation: "we still have the
   full height boxy buttons we switched to a friendlier pill form a while
   ago." So the exclusion goes — a control you OPERATE looks the same
   wherever it sits, and a card head is not a tab strip.

   This supersedes the 2026-07-29 "flush strip segments, not bordered pill
   buttons" decision for the card head. That call was made against a row of
   gapped `.btn`s inside a 34px head and judged "too busy visually"; the
   thing replacing it now is not that row — it is the same 24px inset
   geometry, one radius, that every other strip in the app already uses.
   `.enc-head-strip`'s blue-accented-never-filled hover carries over
   untouched, so the "which one is the important action" signal survives. */
.ig-accordion-head .enc-card-cta { align-self: center; height: auto; gap: var(--dc-space-3); margin-left: var(--dc-space-4); margin-right: 0; }
.ig-accordion-head .enc-card-cta > * {
  height: 24px; box-sizing: border-box;
  border: 1px solid var(--dc-line);
  border-radius: var(--dc-radius-lg);
  background: var(--dc-panel);
}
.ig-accordion-head .enc-card-cta .ig-sb { border-right: 1px solid var(--dc-line); }
/* The progress readout is a READOUT, not a control — it keeps no border and
   no box, so the register stays honest about what is operable. */
.ig-accordion-head .enc-card-cta .enc-card-cta__progress {
  border: none; background: none; height: auto;
}
.enc-card-cta__progress { display: flex; align-items: center; gap: 7px; flex: none; }
.enc-card-cta__progress .dc-progress { width: 80px; }
.ig-accordion-head .enc-card-cta__progress { padding: 0 12px; }

/* The one genuinely new piece: a hover-color modifier, NOT a new button
   shape. Plain `.ig-sb:hover` reads as neutral chrome (grey reveal, "this
   is navigation/utility") — right for the ▶ preview trigger and the
   remove segment (both stay plain `.ig-sb`, no modifier), wrong for the
   verdict-driven CONFIRM action ("Preview recommended" / "Queue remux" /
   "Approve" — whichever the card_state calls for), which still needs to
   read as the accented, forward action even without a filled background.
   `.enc-head-strip` pairs with `.ig-sb` (`class="ig-sb enc-head-strip"`)
   and swaps ONLY the hover fill to the primary-soft/primary pairing
   `.ig-sb--active` already establishes elsewhere as "the accented state"
   (CHEATSHEET §5) — composed from an existing token pairing, not a new
   color. This is also where the Round 2 fourth-pass "Approve weight —
   DECIDED: D, ghost" outcome now lives for the HEAD's own Approve/Queue-
   remux/Preview-recommended actions: same spirit (blue-accented, not
   filled), new geometry (strip segment, not a bordered pill button) — the
   compare-ROW's own Approve button (`.enc-prevrow__actions`, unaffected
   by this pass) still renders the literal `btn-outline-primary` the
   fourth pass decided on, since that button was never part of any head. */
.enc-card-cta .ig-sb.enc-head-strip:hover { background: var(--dc-primary-soft); color: var(--dc-primary); }

/* Row-level icon+label gap (compare rows / generate row) — UNCHANGED by
   the fifth pass; these buttons are still plain Bootstrap buttons, not
   strip segments, so they still need their own gap rule (head-CTA
   buttons no longer do — .ig-sb supplies its own gap natively, see the
   item-3 fix above). Icon-only buttons in this same container (none
   currently, kept in case one is added later) would be unaffected —
   gap does nothing with a single child. */
.enc-prevrow__actions button { display: inline-flex; align-items: center; gap: 6px; }
/* Same pill geometry as the select beside them (2026-08-26). These were the
   last square controls in the card.
   BOTH selectors are needed: the compare rows use real `.btn`s, while the
   generate row's own trigger is `class="ig-sb enc-head-strip"` — and a bare
   `.ig-sb` outside `.ig-bar`/`.ig-canvas-head` still inherits the flush
   full-height default (height:100%, radius 0, hairline right border). That
   default is correct for tab-strip chrome and wrong inside a card. */
.enc-prevrow__actions .btn,
.enc-prevrow--gen .btn,
.enc-prevrow__actions .ig-sb,
.enc-prevrow--gen .ig-sb {
  border: 1px solid var(--dc-line);
  border-radius: var(--dc-radius-pill);
  height: 24px; box-sizing: border-box;
  padding: 0 12px; font-size: 12px;
  background: var(--dc-panel);
  display: inline-flex; align-items: center; align-self: center; flex: none; }
.enc-prevrow__actions .ig-sb:hover,
.enc-prevrow--gen .ig-sb:hover { background: var(--dc-panel-2); }

/* Round 2, fourth pass (2026-07-29) — Approve-button-weight comparison
   strip, DECIDED. **Partially superseded by the fifth pass above**: the
   head CTA's Approve/Queue-remux/Preview-recommended actions no longer
   render as `btn-outline-primary` pill buttons at all (they're `.ig-sb
   enc-head-strip` segments now) — but the DECISION itself (blue-accented,
   never filled) is exactly what `.enc-head-strip`'s hover carries forward
   into the new geometry, and the compare-ROW's own Approve button (a
   different, still-a-regular-button context) is untouched and still
   literally `btn-outline-primary`. Kept below verbatim as the historical
   record of HOW that color decision was made — only the head's own
   container shape changed, not the reasoning. The operator's third-pass
   verdict was "perfect" except the blue .btn-primary Approve buttons on
   these cards read as too dominant; they asked for alternatives to
   compare (see the "Encoding · Approve button weight" gallery section —
   now marked DECIDED, kept as the record — + ENCODING.md §2).

   Four treatments were compared, three pure reuse — no new CSS: plain
   .btn-primary (the rejected original), the existing dashed-border
   .dc-btn-line (already used on this same card's secondary actions), and
   Bootstrap's own .btn-outline-primary (already the app's documented
   "secondary, paired with a primary" role — CHEATSHEET §3D's "Compare"
   example).

   .dc-btn-neutral was the fourth, genuinely NEW option — a solid/filled
   grey counterpart, since no filled-neutral button existed anywhere yet
   (only the outline-only .btn-outline-secondary, dc-theme.css, used for
   "More filters/View/Columns" utility buttons). Composed entirely from
   tokens already in use elsewhere for muted UI (`--dc-line-2` is the same
   token .dc-btn-line's own border and .btn-outline-secondary's border/
   hover-bg already draw from; `--dc-ink` is the standard body-text color)
   — no new color was minted. **NOT the chosen option** — kept defined
   here only because the comparison strip still renders it for the
   decision record (option C); do not reach for it as "the" neutral button
   without checking whether a future round revisits this.

   ★ DECIDED: option D, .btn-outline-primary, is now THE Approve/confirm
   treatment for this card — the head CTA's Approve (previews_ready
   verdict) and every compare-row's own Approve action all use plain
   `btn btn-sm btn-outline-primary`, verbatim, no new class. It was already
   a proper, reusable, globally-available component (CHEATSHEET §3D) before
   this decision — nothing needed "promoting," this comment IS that
   promotion: **`.btn-outline-primary` is the canonical low-weight
   approve/confirm action app-wide**, not an Encoding-specific choice, so
   reach for it the next time any page needs a de-emphasized confirm
   button next to other actions. Icon+label gap comes for free from the
   `.enc-card-cta button`/`.enc-prevrow__actions button` rule above (that
   selector matches any button in those containers, regardless of which
   button class is applied) — no per-variant gap rule needed. */
.dc-btn-neutral { background: var(--dc-line-2); color: var(--dc-ink); border: 1px solid var(--dc-line-2); }
.dc-btn-neutral:hover { background: color-mix(in oklch, var(--dc-ink) 10%, var(--dc-line-2)); border-color: color-mix(in oklch, var(--dc-ink) 10%, var(--dc-line-2)); }

/* Round 2, second pass (2026-07-29, operator amendment #3) — "the left
   rule should move inward to meet where the horizontal dividers start."
   The rule can no longer be a real `border-left` on .enc-card-body itself
   (that draws at the box's own edge, x=0) — a bled .enc-prevrow's
   `margin:0 -12px` inside this now-24px-padded body lands its own
   border-top at x=12px (24 padding - 12 bleed), so the rule has to sit at
   that same x=12px to visually meet it. Done with a ::before positioned
   from the padding edge (which equals the border edge here, since
   .enc-card-body carries no border of its own) instead of a border. */
.enc-card-body { position: relative; padding-left: 24px; }
.enc-card-body::before { content: ""; position: absolute; left: 12px; top: 0; bottom: 0; width: 2px; background: var(--dc-line); }

/* Round 2, third pass (2026-07-29, operator amendment #1) — "the expanded
   card shows TWO left verticals: the thin outline plus the thicker
   indented rule. Keep only the thicker one." The thin one is
   .ig-accordion's own base `border: 1px solid var(--dc-line)` (all four
   sides) — when a card is OPEN, that 1px border-left now runs the card's
   full (taller) height right alongside the new x=12px indent rule above,
   reading as a redundant second line. `.enc-proc-card` is a marker class
   on the Preview & approve card's OUTER `.ig-accordion` div (added in the
   gallery/page markup, Encoding cards only — Delivery/Import Groups'
   accordions don't carry it, so their own borders are untouched). Only
   `border-left` is zeroed, and only while open — top/right/bottom stay so
   the card still reads as a card; collapsed cards are unaffected (nothing
   to compare against yet, no complaint there). The head's own separate
   `border-left` accent (the existing blue "is-open" motif, `.ig-accordion.
   is-open > .ig-accordion-head`) is untouched — that's the standard
   active-state edge every accordion uses, not the redundant line the
   operator flagged. */
.enc-proc-card.is-open { border-left: none; }

/* State strip (2026-08-10). Same 4px left edge and the same --dc-edge-*
   colours the rail rows use (dc-table.css `.tabulator-row.row-*`), so a
   film reads the same colour in the rail and on its card. Overrides the
   is-open rule above deliberately: the strip is state, not chrome, and must
   survive expanding the card. */
.enc-proc-card.row-locked,   .enc-proc-card.row-locked.is-open   { border-left: 4px solid var(--dc-edge-locked); }
.enc-proc-card.row-running,  .enc-proc-card.row-running.is-open  { border-left: 4px solid var(--dc-edge-running); }
.enc-proc-card.row-decision, .enc-proc-card.row-decision.is-open { border-left: 4px solid var(--dc-edge-decision); }
.enc-proc-card.row-danger,   .enc-proc-card.row-danger.is-open   { border-left: 4px solid var(--dc-danger); }
.enc-proc-card.row-pending,  .enc-proc-card.row-pending.is-open  { border-left: 4px solid var(--dc-edge-pending); }

/* Small "Compare" eyebrow above the version-row list once there's more
   than one row to weigh — reuses .dc-label verbatim (CHEATSHEET rule 6),
   no new micro-label class. Omitted when a card has zero/one row (nothing
   to compare yet — see ENCODING.md §2). */
.enc-compare-label { margin: 2px 0 6px; }

/* Compact Remux/Encode segmented toggle — miniaturized version of the
   .ig-sb is-active visual language, for inline use inside a card-body row
   (remux-eligible films only). */
.enc-modeseg { display: inline-flex; border: 1px solid var(--dc-line-2); border-radius: 0; overflow: hidden; flex: none; }
.enc-modeseg button { border: none; background: var(--dc-panel); color: var(--dc-muted); font-size: 11px; font-weight: 600; padding: 4px 10px; cursor: pointer; }
.enc-modeseg button + button { border-left: 1px solid var(--dc-line-2); }
.enc-modeseg button.is-active { background: var(--dc-primary-soft); color: var(--dc-primary); }

/* Version/preview row — the flat compare list (locked design's own shape,
   not the older eg-pv-vrow pattern — see ENCODING.md §2 for why).
   2026-07-22 operator decision: NO inline video thumbnail — previews live
   only in the modal, opened on click. The row is a click target (margin
   bleeds -12px to match the accordion body's own 12px inset, so the hover/
   focus fill runs edge-to-edge); .enc-prevrow--gen (the trailing "generate
   a new profile" row) has nothing to preview yet, so it opts out of the
   pointer cursor and hover/focus fill via :not(). Hover reuses
   --dc-panel-2, the same token .dc-table row-hover uses. */
.enc-prevrow { display: flex; align-items: center; gap: 12px; margin: 0 -12px; padding: 9px 12px; border-top: 1px solid var(--dc-line); cursor: pointer; }
.enc-prevrow:first-child { border-top: none; }
.enc-prevrow:not(.enc-prevrow--gen):hover,
.enc-prevrow:not(.enc-prevrow--gen):focus-visible { background: var(--dc-panel-2); }
.enc-prevrow:not(.enc-prevrow--gen):focus-visible { outline: 2px solid var(--dc-primary); outline-offset: -2px; }
.enc-prevrow__body { flex: 1 1 auto; min-width: 0; }
.enc-prevrow__label { font-size: 12.5px; color: var(--dc-ink); display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.enc-prevrow__meta { font-size: 11px; color: var(--dc-muted); font-family: var(--dc-font-mono); margin-top: 2px; }
.enc-prevrow__preview { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; font-weight: 600; color: var(--dc-primary); flex: none; white-space: nowrap; }
.enc-prevrow__actions { display: flex; gap: 6px; flex: none; align-items: center; }
/* Round 2, second pass (2026-07-29, operator amendment #1) — this used to
   zero out bottom padding unconditionally, which was only ever safe back
   when a card had exactly one .enc-prevrow--gen row and it was always the
   list's last child. Round 2 can stack MULTIPLE --gen rows in one card
   (a generating row + a failed row + the trailing generate-a-different-
   profile row, see STATE 6) — with padding-bottom zeroed, a non-last --gen
   row's content sat flush against the next row's own border-top ("rows
   sit directly on the bottom divider"). Dropping the override restores the
   same 9px bottom padding every other row already has — general row
   padding consistency — including on a genuinely last row, where the
   extra 9px is harmless (nothing below it to touch; .ig-accordion-body's
   own 12px bottom padding still provides the card's outer margin). */
.enc-prevrow--gen { cursor: default; }
.enc-prevrow--gen .enc-prevrow__body { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.enc-selected-badge { display: inline-flex; align-items: center; gap: 5px; font-size: 11px; font-weight: 600; color: var(--dc-ok); }

/* Walkthrough feedback 2026-07-28 ("ui for film items in main panel not
   even close to the dc components") — side-by-side against the gallery
   found the generate row's profile <select> rendering at Bootstrap's
   default .form-select size (taller, larger font, rounded corners)
   instead of the gallery's plain flush .ig-input select — pages/
   encoding.py's _generate_row() uses a real Shiny ui.input_select(), which
   has no class_ param to attach .ig-input directly (see .dlv-pfile-row
   select.ig-input above for the same class of fix on Delivery's own
   per-card selects). Also zeroes the empty label/wrapper's default
   Bootstrap margin, same pattern .sc-rail-head's picker selects use. */
.enc-prevrow--gen .shiny-input-container { margin-bottom: 0 !important; width: auto !important; flex: 1 1 auto; min-width: 0; max-width: 360px; }
.enc-prevrow--gen label.control-label { display: none; }
/* Pill, 24px, 12px — the geometry every other control in the app settled on
   (2026-08-26). It was explicitly `border-radius: 0` at 28px/12.5px, which is
   what made it read as a stray Bootstrap widget dropped into the card. */
.enc-prevrow--gen .form-select {
  width: 100%; box-sizing: border-box; border: 1px solid var(--dc-line);
  border-radius: var(--dc-radius-pill);
  background: var(--dc-panel); color: var(--dc-ink); font-size: 12px;
  padding: 0 28px 0 12px;
  height: 24px; font-family: var(--dc-font-sans);
}
.enc-prevrow--gen .form-select:focus { outline: none; border-color: var(--dc-primary-line); box-shadow: 0 0 0 2px var(--dc-primary-soft); }

/* Walkthrough feedback 2026-07-28 item E ("select too wide... on a 27
   inch comical") — .ig-input/.form-select are both width:100% by design
   (fill whatever bounded container they're given — see the gallery's own
   plain <select class="ig-input">, which only ever looks right because
   its demo frame is ~1000px wide). The real app's canvas has no such
   bound on a wide monitor. Sized to fit the longest real label, not a
   percentage — never go back to `width:100%`/`flex:1` unbounded here.

   Round 2, third pass (2026-07-29, operator amendment #3) — 320px turned
   out too tight in the live browser ("the recommended profile's text
   collides with the dropdown arrow"): the longest label actually rendered
   here ("hd_digital — 1920×1080 · 18 Mbps (recommended)", "uhd_grain —
   3840×2160 · 85 Mbps (recommended)") measures ~285-292px of text at this
   row's 12.5px font-size, and the box also reserves 8px left padding +
   24px right padding for the arrow (see `.form-select` above) — a ~323px
   minimum before any margin for error, already over the old 320px cap.
   Widened to 360px: ~37px of headroom past that tightest real label,
   still a modest (+12.5%) bump over the old value, not the "comically
   wide" shape the 2026-07-28 feedback above ruled out. Targets BOTH the
   Shiny-rendered .form-select (above) and a plain gallery-style select.
   ig-input in this same slot, so the cap applies identically whichever
   markup renders here. */
.enc-prevrow--gen .enc-prevrow__body select.ig-input { max-width: 360px; }

/* Round 2, sixth pass (operator live-review, 2026-08-03: "film item
   generate preview line on expanded panel — please make this line also
   have full height buttons like the top strip, same height as top
   strip"). Reuses .ig-sb's flush/font/gap/hover-accent language
   (.enc-head-strip pairs the confirm-accent hover, same pairing the head
   CTA's own "Preview recommended" segment uses) but needs its own height
   + border rule: .ig-sb's base `height:100%` has no definite-height
   ancestor here (.enc-prevrow--gen's own row height is intrinsic/content-
   driven, unlike .ig-accordion-head's fixed --dc-topbar-h strip), so it's
   pinned to that SAME --dc-topbar-h value directly instead; and this
   button stands ALONE (not one segment among several in a shared strip),
   so it needs a full border box, not just .ig-sb's own border-right
   divider. Scoped to the "Generate preview" row's own button only — the
   sibling failed-row's "Try again" button (_failed_prevrow, same
   .enc-prevrow--gen class) keeps its existing .dc-btn-line treatment,
   unaffected since it never carries .ig-sb. */
.enc-prevrow--gen .enc-prevrow__actions .ig-sb {
  height: var(--dc-topbar-h);
  box-sizing: border-box;
  border: 1px solid var(--dc-line);
}
.enc-prevrow--gen .enc-prevrow__actions .ig-sb.enc-head-strip:hover { background: var(--dc-primary-soft); color: var(--dc-primary); }

/* ── Preview modal — sample-start scrubber. Genuinely new: no scrub-bar
   precedent exists anywhere else in dc-components; needs real drag JS
   (see ENCODING.md §3). ── */
.enc-scrub { margin-top: 16px; }
.enc-scrub__bar { position: relative; height: 8px; border-radius: 999px; background: var(--dc-line); cursor: pointer; margin: 8px 0 10px; }
.enc-scrub__fill { position: absolute; left: 0; top: 0; bottom: 0; background: color-mix(in oklch, var(--dc-primary) 45%, var(--dc-line)); border-radius: 999px; }
.enc-scrub__play { position: absolute; top: 50%; transform: translate(-50%, -50%); width: 15px; height: 15px; border-radius: 50%; background: var(--dc-primary); border: 2px solid var(--dc-panel); box-shadow: 0 1px 5px rgba(0,0,0,.35); cursor: grab; }
.enc-scrub__row { display: flex; align-items: center; gap: 12px; }
.enc-scrub__tc { font-family: var(--dc-font-mono); font-size: 12px; color: var(--dc-ink); }
/* ── Spec strip (redesigned 2026-08-26) ───────────────────────────────────
   Operator on the expanded card: "typo and spacing is all rough". It was —
   six type sizes in one card, of which the spec label's 9px sat BELOW the
   smallest size the system has (--dc-fs-xs, 11px), and the values ran as one
   wrapping sentence so a long Container shoved everything after it out of
   alignment.

   It is a table of measurements pretending to be a sentence. So: label over
   value in an auto-fitting grid, and the values set in mono like every other
   measurement in the app (the drawer's Files table, the rail's Release
   column). Mono also makes the columns line up for free.

   The 24px indent and its 2px guide above are UNTOUCHED — operator request
   2026-08-03, and Delivery was matched to them deliberately. */
.enc-specrow { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: var(--dc-space-5) var(--dc-space-6);
  margin-top: var(--dc-space-6); padding-top: var(--dc-space-5);
  border-top: 1px solid var(--dc-line); }
/* `min-width: 0` + `overflow-wrap` because a grid track does NOT shrink below
   its content by default, and Container arrives as one unbroken comma string
   (`mov,mp4,m4a,3gp,3g2,mj2` — FFprobe's raw brand list, no spaces to break
   on). Without both, that cell overflowed its track and printed straight
   through Bitrate's value. Caught on the first render of the grid. */
.enc-specrow > span { display: flex; flex-direction: column; align-items: flex-start;
  gap: 1px; min-width: 0; }
.enc-specrow .v { overflow-wrap: anywhere; }
.enc-specrow .k { color: var(--dc-faint); text-transform: uppercase;
  font-size: var(--dc-fs-xs); letter-spacing: var(--dc-track-label); font-weight: var(--dc-fw-bold); }
.enc-specrow .v { color: var(--dc-ink); font-size: 13px; font-family: var(--dc-font-mono);
  overflow-wrap: anywhere; }

/* ── Encoding queue — reuses .dc-row/--pending/--running/--danger,
   .dc-progress, .dc-pill, .dc-chip verbatim (dc-theme.css; the row SHAPE
   itself — grip, poster, identity, profile chip, est-size, position,
   action button — is already fully specified in EXPORT_GROUPS.md §4
   STEP 2's "Up next" section). The only new pieces are the drag-affordance
   styling below. ── */
.enc-qrow.is-dragging { opacity: .5; }
.enc-qrow__grip { cursor: grab; color: var(--dc-faint); font-size: 14px; flex: none; }
.enc-qrow__grip:active { cursor: grabbing; }
.enc-qrow__pos { font-family: var(--dc-font-mono); font-size: 12px; color: var(--dc-faint); width: 16px; text-align: center; flex: none; }

/* ═════════════════════════════════════════════════════════════════════════
   MODAL — form dialogs (.dc-modal)
   ═════════════════════════════════════════════════════════════════════════
   NEW 2026-08-10, from operator feedback on the Vendors New-vendor dialog:
   "modal for new vendor need a complete design overhaul, please add
   references to the dc_components."

   WHY THIS DIDN'T EXIST. Until now the system had no modal component at
   all. The gallery said so explicitly in the Delivery section: "Bootstrap's
   modal is already skinned by dc-theme.css's token→--bs-* map, so there is
   genuinely nothing new to design here." That holds for a modal that is one
   paragraph and two buttons (the confirm dialogs). It does NOT hold for a
   FORM. A form modal inherits Bootstrap's control language wholesale —
   rounded 6px inputs, 38px tall, 16px type, stacked bold labels — while
   every other editing surface in this app (drawer edit states, add-rows,
   the intake row editor) uses `.ig-input`: square, flush, 28px, 12.5px.
   The New-vendor dialog was eight of those Bootstrap controls in one
   undifferentiated column, and it read as a different product.

   WHAT THIS IS. Modal chrome + a form layer in the app's own vocabulary,
   composed from what already exists rather than restyled alongside it:
     - `.dc-modal__head` is the `.dd-chrome` shape (title left, close X
       right, 1px bottom rule) — the drawer's header, on a dialog.
     - `.dc-modal__group` reuses the `.ig-accordion-head` label treatment
       (uppercase, tracked, --dc-faint) to name a block of fields, so eight
       fields read as three groups instead of one list.
     - Fields are `.ig-input` — THE form control (see its own block above).
       This file adds no new control; `.dc-modal__field` is only label +
       control stacking and grid placement.
     - Footer buttons are the app's existing `.btn`/`dc-btn-line` /
       `btn-outline-primary` ghost language (2026-07-29 canonical
       low-weight confirm button). No new button variant.

   HOW IT MOUNTS ON SHINY. `ui.modal(title=None, footer=None,
   class_="dc-modal")` renders a bare `.modal > .modal-dialog >
   .modal-content > .modal-body`, and puts that class on the **`.modal-body`**
   (kwargs become attributes of the element `*args` are children of — not of
   the outer `.modal`). So `.dc-modal` IS the body element, and the
   head/body/foot regions below are its direct children. Its padding is
   zeroed here so those three regions own their own spacing. Passing
   `title=`/`footer=` instead would hand Bootstrap back the chrome, which is
   the thing being replaced.

   The gallery frames use this exact structure — `.modal-content >
   .modal-body.dc-modal` — so the reference markup and the rendered app
   markup are comparable line for line, which is the point of a reference
   frame. Do not "simplify" either side into a different nesting.
   ───────────────────────────────────────────────────────────────────── */

.modal-content:has(> .dc-modal) {
  background: var(--dc-panel);
  border: 1px solid var(--dc-line);
  border-radius: var(--dc-radius);
  box-shadow: var(--dc-shadow-lg);
}
/* `.dc-modal` is the .modal-body itself — the three regions own the
   spacing, so Bootstrap's own body padding goes. */
.dc-modal { padding: 0; }

/* ── Head — the `.dd-chrome` shape, on a dialog ── */
.dc-modal__head {
  display: flex; align-items: center; gap: 10px;
  padding: 0 var(--dc-space-4);
  height: var(--dc-strip-h);
  box-sizing: border-box;
  border-bottom: 1px solid var(--dc-line);
}
.dc-modal__title {
  font-size: var(--dc-fs-md);
  font-weight: var(--dc-fw-semi);
  color: var(--dc-ink);
  line-height: var(--dc-lh-tight);
}
/* Optional one-line "what this dialog is for", muted, after the title. */
.dc-modal__sub {
  font-size: var(--dc-fs-sm);
  color: var(--dc-muted);
  margin-left: 2px;
}
.dc-modal__close {
  margin-left: auto; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px;
  border: none; background: none; padding: 0;
  color: var(--dc-faint); font-size: 13px; cursor: pointer;
  border-radius: var(--dc-radius-sm);
  transition: background var(--dc-dur-fast) var(--dc-ease),
              color var(--dc-dur-fast) var(--dc-ease);
}
.dc-modal__close:hover { background: var(--dc-panel-2); color: var(--dc-ink); }

/* ── Body — scrolls on its own so head and footer stay put on a short
   viewport (a form modal is the one dialog shape that can outgrow the
   screen; the confirm dialogs never will). ── */
.dc-modal__body {
  padding: var(--dc-space-4);
  max-height: min(62vh, 620px);
  overflow-y: auto;
}

/* ── Field group — an ACCENT EDGE naming a block of related fields.
   ────────────────────────────────────────────────────────────────────────
   Settled 2026-08-10 across two review rounds. Worth reading before
   changing anything here, because the two rejected attempts are both
   things a reasonable person would try again.

   Attempt 1 gave the group label the SAME treatment as the field label —
   both 10.5px, --dc-fw-bold, tracked, uppercase, --dc-faint, split only by
   a hairline rule. Operator: "Grouping is not working you can't really se
   a visual hirachy." Structurally true: two levels rendered identically
   cannot form a hierarchy, and spacing cannot rescue that.

   Attempt 2 made the group a full-width band on --dc-panel-2 (borrowed
   from `.ig-accordion-head`). Operator: "not a fan ... the seperator,
   label and seperator itself feels blend und technical." Also fair — a
   hairline above AND below holding a small grey label is the visual
   grammar of a TABLE SECTION DIVIDER, so the form read as schema; and it
   spent three devices (rule + fill + rule) on the single job of saying
   "new group".

   What shipped instead: no rules anywhere. NN/g's proximity principle —
   a gap wide enough to read as separation needs no line — does the
   grouping, and the group NAME is marked by a 3px --dc-primary edge. That
   edge is the app's own state-edge motif (active nav, open accordion,
   `.dc-row__edge`), i.e. the one place DC already spends colour, so this
   composes rather than invents. Picked from a four-way strip (quiet /
   accent edge / icon / numbered).

   The edge hangs into the body's left padding so the label TEXT stays
   optically aligned with the fields below it: margin-left cancels the
   padding, then 3px border + 9px padding restores it. ── */
.dc-modal__body { padding: var(--dc-space-5) var(--dc-space-5) var(--dc-space-4); }
.dc-modal__group + .dc-modal__group { margin-top: var(--dc-space-6); }
.dc-modal__group-label {
  display: flex;
  align-items: center;
  gap: var(--dc-space-4);
  /* Flush to the modal's OUTER edge (operator, 2026-08-10: "the blue strips
     haev a 1px gap tot he left"). Cancelling the body padding alone lands
     the strip on `.modal-content`'s inner edge — with that element's own
     1px `--dc-line` border still showing to its left, which reads as a
     hairline gap. The extra -1px walks it onto that border so the blue
     replaces the grey for the height of the label. `padding-left` gains the
     same 1px back, so the label text stays optically aligned with the
     fields below it. */
  margin: 0 0 var(--dc-space-3) calc(-1 * var(--dc-space-5) - 1px);
  padding-left: 10px;
  border-left: 3px solid var(--dc-primary);
  font-size: 12.5px;
  font-weight: var(--dc-fw-semi);
  color: var(--dc-ink);
}

/* ── Field grid — 12 columns.
   Rows are deliberately NOT padded out to fill 12. Operator, 2026-08-10:
   "the inputs ave random width, city is full width for no reason at all" —
   correct, the old spans came from what balanced on the grid rather than
   from what goes in the box. Baymard's finding is that a mismatched width
   makes users doubt they read the label right ("perhaps they had to enter
   something else in the field?"), so a full-width City quietly asks for
   something longer than "München". Every span is now derived from expected
   content length, and the trailing space at the end of a short row is the
   visible proof that a width was chosen rather than stretched. See
   `pages/vendors.py::_new_vendor_modal_ui` for the per-field rationale. ── */
.dc-modal__grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--dc-space-3);
  align-items: start;
}
/* Every span 1–12 exists, because content-derived widths land on odd
   numbers (postal 3, region 5, email 7, street 9) — a modifier set of only
   4/6/8 was what pushed the first draft back onto tidy-looking halves. */
.dc-modal__field { grid-column: span var(--span, 12); min-width: 0; }
.dc-modal__field--1 { --span: 1; }   .dc-modal__field--2 { --span: 2; }
.dc-modal__field--3 { --span: 3; }   .dc-modal__field--4 { --span: 4; }
.dc-modal__field--5 { --span: 5; }   .dc-modal__field--6 { --span: 6; }
.dc-modal__field--7 { --span: 7; }   .dc-modal__field--8 { --span: 8; }
.dc-modal__field--9 { --span: 9; }   .dc-modal__field--10 { --span: 10; }
.dc-modal__field--11 { --span: 11; }

.dc-modal__label {
  display: block;
  font-size: 10.5px;
  font-weight: var(--dc-fw-bold);
  /* No --dc-track-label. Letter-spacing on a 10.5px uppercase string is
     most of what made the first draft read as a spec sheet; at this size
     it buys nothing but width (2026-08-10). */
  letter-spacing: 0;
  text-transform: uppercase;
  color: var(--dc-faint);
  margin-bottom: 5px;
  /* A content-fit field can be narrower than its own label — truncate
     rather than wrap and break the row's baseline. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Required marker. NOT a bare "*" appended to the label — an asterisk is a
   footnote glyph with no legend, and the original dialog used eight of them
   with nothing on screen explaining what they meant.
   Lowercase, deliberately: at first it was "· required" in the label's own
   tracked caps, which made every required field read as a longer label
   rather than as a label plus a note (2026-08-10). */
.dc-modal__req {
  margin-left: 6px;
  font-weight: var(--dc-fw-normal);
  letter-spacing: 0;
  text-transform: lowercase;
  color: var(--dc-muted);
}
/* Per-field helper line (format hints, "optional" qualifiers). */
.dc-modal__hint {
  margin-top: 4px;
  font-size: var(--dc-fs-xs);
  color: var(--dc-faint);
}

/* ── Error state — field-level and dialog-level. The current New-vendor
   dialog reports "Name, country and email are required" as one message
   above the buttons, so the operator has to map a sentence back onto eight
   inputs. The field ring says WHICH; the banner says WHAT. ── */
/* Covers BOTH the gallery frames' hand-written `.ig-input` and the Shiny
   `.form-control`/`.form-select`/selectize controls the app actually
   renders — the reference and the port must show the same ring, and an
   `.ig-input`-only selector silently skipped every real field. */
.dc-modal__field.is-invalid .ig-input,
.dc-modal__field.is-invalid .form-control,
.dc-modal__field.is-invalid .form-select,
.dc-modal__field.is-invalid .selectize-input {
  border-color: var(--dc-danger);
}
.dc-modal__field.is-invalid .ig-input:focus,
.dc-modal__field.is-invalid .form-control:focus,
.dc-modal__field.is-invalid .selectize-input.focus {
  border-color: var(--dc-danger);
  box-shadow: 0 0 0 2px color-mix(in oklch, var(--dc-danger) 22%, transparent);
}
.dc-modal__error {
  margin-top: 4px;
  font-size: var(--dc-fs-xs);
  color: var(--dc-danger);
}
/* Dialog-level status line, sits between body and footer. Reuses the
   `.ig-banner` idea but scoped to the modal's own spacing. */
.dc-modal__status {
  display: flex; align-items: flex-start; gap: 8px;
  padding: 9px var(--dc-space-4);
  font-size: var(--dc-fs-sm);
  border-top: 1px solid var(--dc-line);
  background: var(--dc-danger-soft);
  color: var(--dc-danger);
}
.dc-modal__status--ok { background: var(--dc-ok-soft); color: var(--dc-ok); }

/* Prose body — a confirm dialog's sentence. No grid, no labels; just
   readable text at the drawer's own body size. */
.dc-modal__prose {
  font-size: 12.5px;
  line-height: var(--dc-lh-base);
  color: var(--dc-ink);
}

/* ── Footer ── */
.dc-modal__foot {
  display: flex; align-items: center; gap: 8px;
  padding: 10px var(--dc-space-4);
  border-top: 1px solid var(--dc-line);
  background: var(--dc-panel-2);
  border-radius: 0 0 var(--dc-radius) var(--dc-radius);
}
/* Left slot for a secondary/destructive action; pushes the confirm pair
   right without a spacer div. */
.dc-modal__foot-fill { margin-left: auto; }

/* Shiny's ui.input_selectize/input_select render a .form-select or a
   selectize control inside the field; match `.ig-input`'s square, flush
   28px shape so a dropdown and a text box sit on the same line without
   a height jump. */
.dc-modal .form-select {
  height: 28px; padding: 4px 26px 4px 8px;
  font-size: 12.5px; border-radius: var(--dc-radius);
  border-color: var(--dc-line); background-color: var(--dc-panel);
}
/* Selectize renders its own padding/line-height and lands at 30.75px next
   to a 28px `.ig-input` — visibly off on a row where a dropdown and a text
   box sit side by side (Country + State/region). Pinned to 28px
   border-box so the row is flat. */
.dc-modal .selectize-input {
  box-sizing: border-box;
  min-height: 28px; height: 28px;
  padding: 3px 8px; line-height: 20px;
  font-size: 12.5px; border-radius: var(--dc-radius);
  border-color: var(--dc-line); background: var(--dc-panel);
  box-shadow: none;
}
.dc-modal .selectize-control.single .selectize-input::after { margin-top: -2px; }
.dc-modal .selectize-input.focus {
  border-color: var(--dc-primary-line);
  box-shadow: 0 0 0 2px var(--dc-primary-soft);
}
.dc-modal .selectize-dropdown { font-size: 12.5px; border-radius: 0; }
/* Shiny wraps every input in .form-group/.shiny-input-container with a
   bottom margin; the grid owns spacing here, so cancel it. */
.dc-modal .form-group,
.dc-modal .shiny-input-container { margin-bottom: 0; width: 100%; }
/* Every field passes label=None and `.dc-modal__label` supplies the label,
   so Shiny's own (empty) label element is hidden. A CHECKBOX is the one
   exception — there the label IS the control's content, not a duplicate. */
.dc-modal .shiny-input-container > label { display: none; }
.dc-modal .form-check label,
.dc-modal .checkbox label { display: inline-flex; align-items: center; gap: 7px;
  font-size: 12.5px; color: var(--dc-ink); cursor: pointer; }
.dc-modal .form-check, .dc-modal .checkbox { margin: 0; padding: 0; min-height: 0; }
.dc-modal .form-check input[type="checkbox"],
.dc-modal .checkbox input[type="checkbox"] { width: 14px; height: 14px; margin: 0; flex: none; }

/* ══════════════════════════════════════════════════════════════════════
   SYSTEM PAGE — tab-strip panes (feature_system_page.md, built 2026-08-11)
   Namespace: .sys-*. Page-local layout glue for pages/system.py, ported
   from claude-design/skills/dc-skill/dc-system-v2.html (operator-picked
   v2, "brilliant this is it"). Not a new component vocabulary — the page
   reuses .ig-canvas-head/.ig-tab/.ig-dtable/.ig-kv/.ig-accordion/.dc-pill/
   .dc-modal as-is; these classes are only the page's own pane-head/body
   spacing shell, same convention as the .dlv-* block above.
   ══════════════════════════════════════════════════════════════════════ */
/* No page-level max-width (operator 2026-08-11: "tabs panel is not full
   width" — .dc-card.sys-canvas fills .dc-main__scroll's full width, same
   as every other canvas page; the 960px cap this frame's static demo used
   under its own page strip is gone along with that page strip). */
.sys-canvas { overflow: hidden; }

/* Danger zone tab — pushed right of .ig-canvas-head__fill, red accent
   instead of the primary-blue every other tab uses. */
.sys-tab-danger.ig-tab { color: var(--dc-danger); }
.sys-tab-danger.ig-tab:hover { background: var(--dc-danger-soft); color: var(--dc-danger); }
.sys-tab-danger.ig-tab.is-active { color: var(--dc-danger); box-shadow: inset 0 2px 0 var(--dc-danger); }

/* ═════════════════════════════════════════════════════════════════════════
   SYSTEM — status band + section list (v3 round, variant E, ported 2026-08-18)
   ═════════════════════════════════════════════════════════════════════════
   Reference: claude-design/skills/dc-skill/dc-system-v3e.html, the variant
   the operator picked out of five ("I like the top health check but the rest
   is almost impossible to look at. so we need to simplify"). The page is a
   six-tile status band over ONE full-width stack of collapsed sections. The
   stack itself is `.ig-accordion` verbatim — same component as the film
   drawer, same delegated toggle in dc-components.js. Nothing below overrides
   a shipped spacing value; these only build the band's tile and turn the
   shipped 34px accordion head into a four-slot row.

   The tab strip above (`.sys-tab-danger`, and `.ig-tab` on this page) is
   retired by the same port and kept only for the archived v2 reference frame.
   ───────────────────────────────────────────────────────────────────────── */

/* ── Status band — one tile per health group. A tile is a VERDICT, not a
   report: label + pill + one mono line. Every per-check detail lives in the
   section that owns it, which is what removes v2's health-board/NAS-tab/
   FFmpeg-tab duplication. State rides the 3px left edge, the same motif
   `.ig-films-table` uses for row state. ── */
.sys-tiles { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 12px; }
@media (max-width: 1400px) { .sys-tiles { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 760px)  { .sys-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.sys-tile { min-width: 0; padding: 12px 14px; background: var(--dc-panel-2);
  border: 1px solid var(--dc-line); border-left: 3px solid var(--dc-ok);
  border-radius: var(--dc-radius); }
.sys-tile--warn { border-left-color: var(--dc-warn); }
.sys-tile--fail { border-left-color: var(--dc-danger); }
.sys-tile__top { display: flex; align-items: center; justify-content: space-between;
  gap: 8px; margin-bottom: 8px; }
.sys-tile__top .dc-label { margin: 0; }
.sys-tile__detail { font-family: var(--dc-font-mono); font-size: 11.5px;
  color: var(--dc-muted); line-height: 1.5; overflow-wrap: anywhere; }

/* ── Section list — `.ig-accordion` rows in a `.dc-card`. The row is
       [ name 200px ][ state 76px ][ summary ]……[ action ][ chevron ]
   Both leading slots are FIXED width on purpose: that is what puts every
   pill on one vertical line and every summary on the next, and that column
   of pills is how the list is scanned. A section with no health data still
   holds the state column, so nothing shifts left. ── */
.sys-sections { margin-top: 16px; overflow: hidden; }

/* `.ig-accordion-head` is already the right 34px row; it only has to stop
   distributing (its own `justify-content: space-between`) and start aligning. */
.sys-sec-head { justify-content: flex-start; gap: 12px; }
.sys-sec-name { width: 200px; flex: none; font-weight: 600; }
.sys-sec-state { width: 76px; flex: none; display: flex; align-items: center; }
.sys-sec-sum { flex: 1 1 auto; min-width: 0; color: var(--dc-muted);
  font-family: var(--dc-font-mono); font-size: 11.5px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* A section body is as wide as the page; a four-column table is not. Left to
   stretch, a zone name and its status pill end up ~400px apart and the row
   stops reading as one line. Tables and kv pairs get a measure. */
.sys-sec .ig-dtable, .sys-sec .ig-kv { max-width: 960px; }

/* Danger zone still has to read as itself once it is one line in a list. */
.sys-sec--danger > .ig-accordion-head { border-left-color: var(--dc-danger); }
.sys-sec--danger .sys-sec-name { color: var(--dc-danger); }
[data-bs-theme="dark"] .sys-tab-danger.ig-tab,
[data-bs-theme="dark"] .sys-tab-danger.ig-tab.is-active { color: color-mix(in oklch, var(--dc-danger) 35%, white); }

/* Per-pane header — real 16px/700 title + description, one full step up
   from .ig-canvas-head__label's 11px uppercase eyebrow (v1→v2 feedback:
   "panel headers felt too low / no presence"). */
.sys-pane-head { display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px; padding: 20px 20px 16px; border-bottom: 1px solid var(--dc-line); }
.sys-pane-head__title { font-size: 16px; font-weight: 700; color: var(--dc-ink); }
.sys-pane-head__sub { font-size: 12px; color: var(--dc-muted); margin-top: 4px; max-width: 560px; line-height: 1.5; }
.sys-pane-head .btn { flex: none; margin-top: 1px; }
.sys-pane--danger .sys-pane-head { border-bottom-color: color-mix(in oklch, var(--dc-danger) 30%, var(--dc-line)); }
.sys-pane--danger { background: var(--dc-danger-soft); }
.sys-pane--danger .sys-pane-head__title { color: var(--dc-danger); }

/* Body rhythm — 20px pane padding (v1's 14px read as squeezed against
   table/row edges). Danger zone gets the top step (24px). */
.sys-body { padding: 20px; }
.sys-pane--danger .sys-body { padding: 24px; }
.sys-note { font-size: 12px; color: var(--dc-muted); margin: 0 0 12px; line-height: 1.5; }
.sys-group-row td { padding-top: 14px; }
.sys-group-row:first-child td { padding-top: 4px; }
.sys-kv-row { display: grid; grid-template-columns: 90px 1fr; gap: 4px 10px; margin-bottom: 12px; }
.sys-kv-row:last-child { margin-bottom: 0; }
.sys-flex-row { display: flex; align-items: center; gap: 16px; padding: 14px 0; border-bottom: 1px solid var(--dc-line); }
.sys-flex-row:last-child { border-bottom: none; padding-bottom: 2px; }
.sys-flex-row:first-child { padding-top: 2px; }
.sys-flex-title { font-size: 13px; font-weight: 600; color: var(--dc-ink); }
.sys-flex-sub { font-size: 11.5px; color: var(--dc-muted); margin-top: 2px; }
.sys-logtail { font-family: var(--dc-font-mono); font-size: 11px; line-height: 1.6; color: var(--dc-muted);
  background: var(--dc-panel-2); border: 1px solid var(--dc-line); border-radius: var(--dc-radius-sm);
  padding: 8px 10px; max-height: 110px; overflow-y: auto; white-space: pre-wrap; margin-top: 12px; }
.sys-reveal-wrap { display: flex; align-items: center; gap: 2px; }
.sys-danger-block { border: 1px solid color-mix(in oklch, var(--dc-danger) 35%, var(--dc-line));
  border-radius: var(--dc-radius); background: var(--dc-panel); padding: 20px; }

/* ── Environment tab — plain (non-collapsible) kv sections, round 3
   (2026-08-11). Operator: "no point in closing them" — with no per-section
   edit yet (that's Session 2), there was nothing to actually collapse, so
   the .ig-accordion chevron/hover/click-to-toggle affordance was pure
   noise promising an interaction these rows don't have.
   NOT built by reusing .ig-accordion-head/-body directly: dc-components.js's
   accordion toggle is a single delegated `.ig-accordion-head` click
   listener with no page scoping ("click an .ig-accordion-head to open/close
   its section") — keeping that class here would make every section
   actually collapsible again on click, the opposite of what was asked. So
   this is its own small shell, same RESTING visual as .ig-accordion's
   is-open state (box + panel-2 head band + blue left edge — nothing
   redesigned) but with no JS hook, no cursor:pointer, no hover state, and
   no is-open class at all, because there is only ever one state now. */
.sys-kv-block { border: 1px solid var(--dc-line); border-radius: 0; margin-bottom: -1px; }
.sys-kv-block:first-child { border-top: none; }
.sys-kv-head { display: flex; align-items: center; justify-content: space-between;
  height: var(--dc-topbar-h); padding: 0 12px; background: var(--dc-panel-2);
  border-left: 3px solid var(--dc-primary); border-bottom: 1px solid var(--dc-line);
  font-size: 12px; font-weight: 400; color: var(--dc-ink); }
.sys-kv-body { padding: 12px; background: var(--dc-panel); font-size: 12.5px; }

/* ── System v4-B port (2026-09-03, dc-system-v4b.html): the page moved off
   the .sys-sections accordion list onto the locked drawer pill shell.
   Two rules, verbatim from the picked frame:
   1. The drawer shell assumes it owns a full-height column (.dd-drawer is
      height:100% and its panel scrolls). In a page card the card owns the
      height and the page owns the scroll, so the shell runs at natural
      height and the panel does not scroll on its own.
   The retired .sys-sections/.sys-sec* rules above stay for the gallery's
   archived variant-E reference frame only — no page builds on them.
   (The 860px content cap the port shipped with was removed 2026-09-03 —
   operator: tab content runs full width, like the band above it.) */
.sys-pillwrap { padding: 0; }
.sys-pillwrap .dd-drawer { height: auto; }
.sys-pillwrap .dd-panel { overflow-y: visible; padding: 10px 16px 18px; }

/* ===========================================================================
 DRAWER NAV — PILL TABS
 Promoted from the gallery 2026-08-25 after four review rounds. Replaces the
 `.ig-accordion` shell for the FILM drawer only; `.ig-accordion` itself stays
 exactly as it is, because Delivery's cards, the record drawer and the System
 page all reuse it and none of those became tabs.

 Composed from `.dc-pill` (dc-theme.css §410) rather than restyled: the
 active pill is `--decision`, the rest `--pending`. No new colour, no new
 shape, no new token.

 One section is visible at a time and switching is CLIENT-SIDE — every body
 is rendered and all but one hidden, which is exactly what the accordion
 already did with its closed sections, so this costs no extra render and
 needs no per-page Shiny wiring. See dc-components.js.
 =========================================================================== */
/* The bottom hairline is BACK (operator, later on 2026-09-08: "I kinda miss
   it") — it went with the strip head that morning on the reasoning that the
   active pill's fill separated the band on its own; in use, the section
   below reads as loose without the line. */
.dd-pills { display: flex; align-items: flex-start; gap: 4px;
  padding: 10px 12px 8px; background: var(--dc-panel);
  border-bottom: 1px solid var(--dc-line); }
.dd-pills__set { display: flex; flex-wrap: wrap; gap: 4px; flex: 1 1 auto; min-width: 0; }
.dd-pills .dc-pill { border: 0; cursor: pointer; font-family: var(--dc-font-sans);
  padding: 3px 10px; transition: background var(--dc-dur-fast), color var(--dc-dur-fast); }
.dd-pills .dc-pill--pending:hover { background: color-mix(in oklch, var(--dc-ink) 13%, transparent);
  color: var(--dc-ink); }
.dd-pills .dc-pill .n { font-family: var(--dc-font-mono); font-size: 10px; opacity: .72; margin-left: 2px; }

/* THE DRAWER IS ALWAYS FULL HEIGHT (operator 2026-08-25), whether or not
   the section fills it. A drawer that shrink-wraps its content changes
   height every time you click a pill, and the gutter you drag it by would
   move with it. Head and pills are fixed; the section area takes the rest
   and scrolls inside itself. */
.dd-drawer { display: flex; flex-direction: column; height: 100%; background: var(--dc-panel); }
.dd-drawer > .dd-head, .dd-drawer > .dd-pills { flex: none; }

/* `min-height: 0` is what actually lets the section area shrink and scroll:
   a flex child's default min-height is auto, so without this the panel
   refuses to go below its content and pushes the drawer taller instead of
   scrolling — the exact bug this rule exists to prevent. */
.dd-section { flex: 1 1 auto; min-height: 0; display: flex;
  flex-direction: column; }

/* The controls sit in a BAND above the panel, in normal flow (operator
   2026-09-02: "edit / add / delete need a bit more space reserved up top").
   They were absolutely pinned to the section's top-right with a 26px corner
   reservation in the panel — right for one pencil, wrong the moment a
   section entered edit mode and the controls became Cancel + Save text
   buttons overlaying the first input (Notes compose, Credits edit). The
   band still sits OUTSIDE the scroller, so controls stay put while the body
   scrolls — the property the pinning existed for — but now they own their
   height instead of borrowing the body's. A section with no controls
   renders no band and its panel gets the full height. */
.dd-section__edit-slot,
.dd-section > .dd-section__edit { display: flex; justify-content: flex-end;
  align-items: center; gap: 4px; padding: 6px 8px 4px; flex: none; }
.dd-section__edit-slot > .dd-section__edit { display: flex; gap: 4px; }
/* The slot holds every section's control group, hidden except the active
   one — when the ACTIVE section has no controls the slot is all-hidden and
   must not leave an empty band. */
.dd-section__edit-slot:not(:has(> :not([hidden]))) { display: none; }

.dd-panel { flex: 1 1 auto; min-height: 0; overflow-y: auto;
  padding: 6px 12px 14px;
  background: var(--dc-panel); font-size: 12.5px; scrollbar-gutter: stable; }

/* Label ABOVE its value, not beside it (operator 2026-08-25: the 90px label
   column "takes up too much horizontal space"). A MODIFIER on .ig-kv rather
   than a second key-value component, so it inherits every .ig-kv-label /
   .ig-kv-value rule the app already has and ports as three added lines.

   The label treatment is lifted from `label.gx-l`, the app's existing
   above-the-field label in every edit state, rather than invented — and the
   docs/Todo.md design-system note already wants a 10px micro-header as a
   real entry in the scale. Same device, now in read mode too. */
/* Every READ-MODE kv inside a drawer panel is stacked. Scoped here rather
   than by adding `ig-kv--stack` at twenty call sites in film_detail.py: the
   rule "this drawer stacks its fields" belongs to the drawer, and a call site
   that forgot the class would be a silently inconsistent section.

   `:not(.ig-kv--edit)` matters. The edit grid puts an .ig-input in the value
   column and sizes it against the 90px label column (drawer.css §368);
   stacking that would restyle every edit state in the app as a side effect of
   a read-mode decision. Edit mode keeps the two-column grid. */
.ig-kv--stack,
.dd-panel .ig-kv:not(.ig-kv--edit) { grid-template-columns: 1fr; gap: 0; }
.ig-kv--stack .ig-kv-label,
.dd-panel .ig-kv:not(.ig-kv--edit) .ig-kv-label {
  font-size: 10.5px; font-weight: var(--dc-fw-bold);
  letter-spacing: var(--dc-track-label); text-transform: uppercase;
  color: var(--dc-faint); margin-top: 6px; }
.ig-kv--stack .ig-kv-label:first-child,
.dd-panel .ig-kv:not(.ig-kv--edit) .ig-kv-label:first-child { margin-top: 0; }
.ig-kv--stack .ig-kv-value,
.dd-panel .ig-kv:not(.ig-kv--edit) .ig-kv-value { line-height: 1.45; }
/* `.dd-list-row` supersedes `.cur-pl-line` in curation-page.css — the fold
   the earlier note here asked for happened in the 2026-09-02 tab-pages lock:
   film_detail's Playlists body now renders these rows, with the divider the
   locked design gave every list idiom. (.dd-subhead and the .dd-entries
   label variant were gallery-only devices of the pre-lock stub bodies and
   are gone with them; Notes entries are .dd-note below.) */
.dd-list-row { display: flex; align-items: center; gap: 8px; font-size: 12.5px;
  padding: 6px 0; border-bottom: 1px solid var(--dc-line); }
.dd-list-row:last-of-type { border-bottom: 0; }
.dd-list-row__name { font-size: 13px; font-weight: var(--dc-fw-semi); color: var(--dc-ink); }
.dd-list-row__meta { color: var(--dc-faint); font-family: var(--dc-font-mono);
  font-size: 11px; margin-left: auto; }
/* Proposals (2026-09-08): a status pill after the name, and a small action
   group (accept / decline, or withdraw) where the single remove x used to
   sit. Same slot, one to three buttons. */
.dd-list-row__pill { margin-left: 6px; vertical-align: 1px; }
.dd-list-row__acts { display: inline-flex; gap: 2px; flex: none; }
.dd-tags { display: flex; flex-wrap: wrap; gap: 4px; padding-top: 2px; }

/* ===========================================================================
 DRAWER TAB PAGES — locked 2026-09-02 (Core Metadata variant C)
 The pages behind the drawer's pills, in Curation's language: a film identity
 block with real type hierarchy, baseline kv rows with a hairline divider
 under each, micro-headers to break a long section, entry rows for Festivals,
 two-line .dd-file rows for Files. Promoted from the gallery's design-pass
 section on operator approval; the superseded .dd-file/.dd-log/.dd-credit
 blocks left drawer.css the same day so each rule lives exactly once.
 =========================================================================== */

/* ── Identity block (Core Metadata, Vendor) — Curation's .dd-id ──────────── */
.dd-id { padding: 4px 0 8px; border-bottom: 1px solid var(--dc-line); }
.dd-id__t { font-size: 15px; font-weight: var(--dc-fw-semi); color: var(--dc-ink); line-height: 1.3; }
.dd-id__o { font-size: 12px; color: var(--dc-faint); font-style: italic; line-height: 1.35; }
.dd-id__meta { font-size: 11px; color: var(--dc-faint); font-family: var(--dc-font-mono);
  margin-top: 4px; display: flex; gap: 7px; align-items: center; flex-wrap: wrap; }
/* Poster variant (2026-09-08) — the film's Core Metadata block, which now
   leads with the 60×84 poster the drawer head used to carry. `.dd-id` itself
   is unchanged, so the Vendor block (no poster) keeps rendering exactly as
   before; `min-width: 0` on the text column is what lets a long title wrap
   inside the remaining width instead of pushing the poster off at the
   drawer's 320px minimum. */
/* No bottom hairline on this one (operator 2026-09-08). `.dd-id`'s divider
   exists to separate an identity block from the kv rows under it; with the
   poster in the block the poster's own edge already draws that line, and the
   two together read as a boxed-in header. The Vendor block keeps its hairline
   — it has no poster, so it still needs one. */
.dd-id--poster { display: flex; align-items: flex-start; gap: 10px;
  border-bottom: 0; }
.dd-id--poster > .dd-head__poster { margin-top: 1px; }
.dd-id__text { min-width: 0; flex: 1 1 auto; }

/* ── Divider kv row — Curation's .kv: baseline flex, 104px faint label,
   hairline under every row but the last. ─────────────────────────────────── */
.dd-kvr { display: flex; gap: 8px; font-size: 12.5px; padding: 6px 0;
  border-bottom: 1px solid var(--dc-line); align-items: baseline; }
.dd-kvr > b { flex: 0 0 104px; color: var(--dc-faint); font-weight: var(--dc-fw-medium);
  font-size: 12px; }
.dd-kvr > .ig-kv-value { flex: 1 1 0; min-width: 0; line-height: 1.45; }
.dd-kvr:last-child { border-bottom: 0; }

/* Micro section header (Synopsis, …) — Curation's .det-sech. */
.dd-sech { font-size: 10.5px; font-weight: var(--dc-fw-bold);
  letter-spacing: var(--dc-track-label); text-transform: uppercase;
  color: var(--dc-faint); margin: 14px 0 4px; }
.dd-prose { line-height: 1.55; color: var(--dc-ink); font-size: 12.5px; }

/* Schedule: the one value the tab exists for, at identity scale. */
.dd-sched-date { font-size: 14px; font-weight: var(--dc-fw-semi);
  font-family: var(--dc-font-mono); }

/* ── Festivals — entry rows, not a data table. Trophy + green for the award;
   the source demoted to faint mono on the right. ─────────────────────────── */
.dd-fest { display: flex; gap: 9px; padding: 8px 0; border-bottom: 1px solid var(--dc-line);
  align-items: baseline; }
.dd-fest:last-child { border-bottom: 0; }
.dd-fest__ic { flex: none; font-size: 13px; color: var(--dc-ok); align-self: flex-start;
  margin-top: 1px; width: 16px; text-align: center; }
.dd-fest__main { flex: 1 1 0; min-width: 0; }
.dd-fest__head { display: flex; gap: 7px; align-items: baseline; }
.dd-fest__name { font-size: 13px; font-weight: var(--dc-fw-semi); color: var(--dc-ink); }
.dd-fest__year { font-family: var(--dc-font-mono); font-size: 11px; color: var(--dc-faint); }
.dd-fest__sub { font-size: 12px; color: var(--dc-muted); margin-top: 1px; line-height: 1.4; }
.dd-fest__sub .aw { color: var(--dc-ok); font-weight: var(--dc-fw-semi); }
.dd-fest__src { flex: none; font-family: var(--dc-font-mono); font-size: 10px;
  letter-spacing: .04em; text-transform: uppercase; color: var(--dc-faint); }

/* ── Files — two-line rows: icon + filename + size, full spec string under.
   Carries every probe value at every drawer width, no truncation. Supersedes
   both the 7-column .ig-dtable Files body and drawer.css's old .dd-file. ── */
.dd-file { display: flex; gap: 9px; padding: 7px 0; border-bottom: 1px solid var(--dc-line);
  align-items: flex-start; }
.dd-file:last-child { border-bottom: 0; }
.dd-file__ic { font-size: 15px; color: var(--dc-faint); flex-shrink: 0; margin-top: 1px; }
.dd-file__ic--master { color: var(--dc-primary); }
.dd-file__main { flex: 1 1 0; min-width: 0; }
.dd-file__row { display: flex; gap: 8px; align-items: baseline; }
.dd-file__name { font-family: var(--dc-font-mono); font-size: 12px; color: var(--dc-ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1 1 0; min-width: 0; }
.dd-file__size { flex: none; font-family: var(--dc-font-mono); font-size: 11px;
  color: var(--dc-faint); }
.dd-file__spec { font-family: var(--dc-font-mono); font-size: 11px; color: var(--dc-muted);
  margin-top: 2px; line-height: 1.4; }

/* ── Notes — entries divided by hairlines; timestamp line carries author and
   the "edited" micro-pill. ───────────────────────────────────────────────── */
.dd-note { padding: 8px 0; border-bottom: 1px solid var(--dc-line); }
.dd-note:last-child { border-bottom: 0; }
.dd-note__tc { display: flex; align-items: center; gap: 6px;
  font-family: var(--dc-font-mono); font-size: 10.5px; color: var(--dc-faint); }
.dd-note__body { margin-top: 3px; font-size: 12.5px; color: var(--dc-ink);
  line-height: 1.5; white-space: pre-wrap; }
.dd-note__edited { font-size: 9.5px; font-weight: var(--dc-fw-bold); letter-spacing: .04em;
  text-transform: uppercase; color: var(--dc-faint); background: var(--dc-panel-2);
  border: 1px solid var(--dc-line); border-radius: var(--dc-radius-pill);
  padding: 0 5px; line-height: 14px; font-family: var(--dc-font-sans); }

/* ── Credits — role groups divided by hairlines, names at 13px, provenance
   right-aligned in faint mono (the brand chip stays an edit-mode device).
   Moved from drawer.css and restyled in the same lock. ──────────────────── */
.dd-credit-group { padding: 6px 0; border-bottom: 1px solid var(--dc-line); }
.dd-credit-group:last-child { border-bottom: 0; }
.dd-credit-row { display: flex; align-items: baseline; column-gap: 10px; padding: 1px 0; }
.dd-credit-role { flex: 0 0 90px; font-size: 10px; font-weight: var(--dc-fw-bold);
  letter-spacing: var(--dc-track-label); text-transform: uppercase; color: var(--dc-faint); }
.dd-credit-name { font-size: 13px; color: var(--dc-ink); flex: 1 1 0; min-width: 0; }
.dd-credit-src { flex: none; font-family: var(--dc-font-mono); font-size: 10px;
  letter-spacing: .04em; text-transform: uppercase; color: var(--dc-faint); }

/* ── Logs — fixed timestamp column so event text left-aligns, hairline
   between rows. Moved from drawer.css and restyled in the same lock. ─────── */
.dd-log { display: flex; flex-direction: column; }
.dd-log-row { display: flex; gap: 8px; align-items: baseline; padding: 5px 0;
  border-bottom: 1px solid var(--dc-line); }
.dd-log-row:last-child { border-bottom: 0; }
.dd-log-ts { font-family: var(--dc-font-mono); font-size: 11px; color: var(--dc-faint);
  flex: none; min-width: 78px; }
.dd-log-txt { font-size: 12px; color: var(--dc-ink); }
.dd-log-row--sub .dd-log-txt { padding-left: 14px; }
.dd-log-tag { flex: none; align-self: center; padding: 0 5px;
  font-family: var(--dc-font-mono); font-size: 9px; line-height: 14px;
  text-transform: uppercase; letter-spacing: .04em;
  color: color-mix(in oklch, var(--dc-primary) 60%, var(--dc-ink));
  background: var(--dc-primary-soft);
  border: 1px solid color-mix(in oklch, var(--dc-primary) 40%, var(--dc-line));
  border-radius: var(--dc-radius-pill); }
.dd-log-row--next .dd-log-txt { color: var(--dc-primary); font-weight: var(--dc-fw-semi); }
.dd-log-row--warn .dd-log-txt { color: color-mix(in oklch, var(--dc-warn) 60%, var(--dc-ink)); }
.dd-log-row--danger .dd-log-txt { color: var(--dc-danger); }

/* ── Drawer-wide empty state — the ONE empty idiom (locked 2026-09-02).
   Moved from library-page.css; every tab's empty body renders this. ──────── */
.det-empty { padding: 40px 12px; text-align: center;
  color: var(--dc-faint); font-size: 13px; }
.det-empty i.bi { font-size: 24px; color: var(--dc-line-2); display: block; }
.det-empty__msg { margin-top: 8px; color: var(--dc-muted); }
