/* ═══════════════════════════════════════════════════════════════
   Composer v2 — unified LumiYing-style generation composer
   slice-dashboard-composer-redesign (Phase 2A: shell + toolbar)

   Frontend reskin only. The legacy #panel-image / #panel-video stay
   in the DOM as the engine (DOM-ID contract); this surface is the new
   face. Everything binds to theme.css tokens so dark + light both work.
   Accent is --accent (= --violet, CG purple). NEVER --cyan.
   ═══════════════════════════════════════════════════════════════ */

/* ── Mode swap ───────────────────────────────────────────────────
   composer.js adds body.composer-v2 once it has initialised. Until
   then (and if the script ever throws) the legacy workbench renders,
   so this is a graceful fallback, not a hard cutover. */
#composer-v2 { display: none; }
body.composer-v2 #composer-v2 { display: block; }
body.composer-v2 #panel-image,
body.composer-v2 #panel-video { display: none !important; }

/* ── Hide legacy control surfaces redundant with the composer ─────
   ALL scoped to body.composer-v2 and display:none only — nothing
   leaves the DOM. Toggle the class off (devtools) and the full legacy
   UI returns intact, preserving the graceful fallback + DOM-ID
   contract (costFor / generate* / ref systems still read these). */

/* Legacy model rail (the two #sidebar model-list panels). The
   composer toolbar's model dropdown is now the sole model picker.
   Profile link + Brand Kit + Credits panels in the sidebar stay. */
body.composer-v2 #sidebar section.panel:has(.model-list) { display: none; }

/* Brand Kit + Credits sidebar panels — redundant in the composer
   (Credits stays reachable via the topbar credits pill). CSS-only,
   DOM intact; toggle composer-v2 off and both return. */
body.composer-v2 #brandKitPanel,
body.composer-v2 #sidebarCreditsPanel { display: none !important; }
/* With both its only children (Brand Kit + Credits) hidden and the
   Director reparented into .sidebar-scroll (not here), .sidebar-pinned is
   fully empty under v2 — hide the whole container so its border-top
   (var(--border2), theme-aware) doesn't draw an orphaned divider line. */
body.composer-v2 #sidebar .sidebar-pinned { display: none !important; }

/* Legacy Image / Video tab buttons — the composer's media switch owns
   that choice. Storyboard / Shot List share this .tabs bar and must
   stay reachable, so we hide ONLY the image/video buttons. */
body.composer-v2 .tabs .tab[data-tab="image"],
body.composer-v2 .tabs .tab[data-tab="video"] { display: none; }

/* When an admin opens Storyboard / Shot List the composer hides
   (body gets .cmp-aux from composer.js); re-expose the Image/Video
   tabs there so they have a way back to the composer. */
body.composer-v2.cmp-aux .tabs .tab[data-tab="image"],
body.composer-v2.cmp-aux .tabs .tab[data-tab="video"] { display: inline-flex; }

/* ── Card shell ──────────────────────────────────────────────── */
#composer-v2 {
  --cmp-radius: 18px;
  --cmp-pill-radius: 10px;
}
.cmp-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 18px;
  background: var(--surface, #0f1128);
  border: 1px solid var(--line-strong, rgba(255,255,255,0.14));
  border-radius: var(--cmp-radius);
  box-shadow: var(--shadow, 0 4px 20px rgba(0,0,0,0.5));
}
.cmp-card:focus-within {
  border-color: var(--accent, #7c6dfa);
  box-shadow: var(--shadow-violet, 0 8px 32px rgba(124,109,250,0.3));
}

/* ── Cascading reference slots ───────────────────────────────── */
.cmp-refs {
  display: flex;
  align-items: flex-start;
  gap: 10px;
}

/* ── High-count reference grid (slice s25-refgrid) ─────────────
   .cmp-refs is a NON-WRAPPING flex row of fixed 72px tiles — right for 3-8
   slots, broken at 30: thirty tiles need 2450px, the container is 790px at
   a 1440px viewport, overflow-x is visible and no ancestor scrolls. Roughly
   two thirds of the slots were rendered but invisible and unclickable at
   EVERY width — present in the DOM, absent from the page.

   Applied as a MODIFIER (composer.js adds it only when shown > 8) rather
   than by changing .cmp-refs, so the 8-slot seedance family and the 3-slot
   kling3 cascade keep their existing flex row byte-for-byte at every width
   — including the pre-existing <=1280px 8-slot overflow, deliberately left
   alone here rather than silently reshaped.

   TEN COLUMNS AT EVERY WIDTH, deliberately. The first attempt stepped down
   to five columns below 1360px to keep tiles large; measurement killed it.
   Five columns means six rows, the tracks stretch to 118px, and the block
   becomes 758px tall — taller than the room the bottom-anchored composer
   has. Five tiles then sat below the viewport with documentElement.scrollHeight
   === clientHeight, i.e. the page could not scroll to them. That trades a
   horizontal unreachability bug for a vertical one, which is worse: a wide
   row can be scrolled sideways, a too-tall composer cannot be scrolled at
   all. Height is the scarce axis here, so the row count is pinned at 3.

   minmax(48px, 96px) does the rest: tracks shrink toward the 48px floor as
   the container narrows (71px @1440, 56px @1280) and stop growing at 96px so
   the grid never balloons on a wide screen. Below ~1150px ten 48px tiles no
   longer fit, and overflow-x:auto gives a real scrollbar — reachable, unlike
   the silent clipping this replaces. */
body.composer-v2 .cmp-refs--grid {
  display: grid;
  grid-template-columns: repeat(10, minmax(48px, 96px));
  justify-content: start;
  gap: 8px;
  width: 100%;
  overflow-x: auto;
  /* Tiles are square; without this the row can collapse mid-animation. */
  grid-auto-rows: min-content;
}
body.composer-v2 .cmp-refs--grid .cmp-ref-slot {
  /* Hand sizing to the grid track; the fixed 72px width is precisely what
     made the row overflow instead of reflowing. */
  width: auto;
  height: auto;
  aspect-ratio: 1 / 1;
}

.cmp-ref-slot {
  position: relative;
  width: 72px;
  height: 72px;
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  background: var(--field-bg, rgba(8,10,28,0.85));
  border: 1px dashed var(--line-strong, rgba(255,255,255,0.18));
  border-radius: 12px;
  color: var(--mute, rgba(240,242,255,0.62));
  cursor: pointer;
  font-family: inherit;
  transition: border-color .16s ease, color .16s ease, transform .16s ease;
}
.cmp-ref-slot:hover {
  border-color: var(--accent, #7c6dfa);
  color: var(--text, #f0f2ff);
  transform: translateY(-1px);
}
.cmp-ref-slot.is-primary { border-style: solid; }
/* Drag-and-drop affordance — highlight the tile a file is hovering over.
   Scoped to body.composer-v2 so the reskin stays isolated (no global leak). */
body.composer-v2 .cmp-ref-slot.is-drop-hover {
  border-style: solid;
  border-color: var(--accent, #7c6dfa);
  color: var(--text, #f0f2ff);
  box-shadow: 0 0 0 2px var(--accent, #7c6dfa) inset;
  transform: translateY(-1px);
}
/* Phase 2: drop affordance on the prompt textarea + the choice popover. */
body.composer-v2 .cmp-prompt.is-drop-hover,
body.composer-v2 .cmp-prompt-wrap:has(.cmp-prompt.is-drop-hover) {
  box-shadow: 0 0 0 2px var(--accent, #7c6dfa) inset;
  border-radius: 12px;
}
body.composer-v2 .cmp-prompt-drop-popover {
  display: flex;
  flex-direction: column;
  min-width: 180px;
  padding: 6px;
  gap: 4px;
  background: var(--panel, #14162e);
  border: 1px solid var(--line-strong, rgba(255,255,255,0.18));
  border-radius: 10px;
  box-shadow: 0 12px 32px rgba(0,0,0,0.5);
}
body.composer-v2 .cmp-prompt-drop-popover button {
  display: block;
  width: 100%;
  text-align: left;
  padding: 8px 10px;
  background: transparent;
  border: 0;
  border-radius: 7px;
  color: var(--text, #f0f2ff);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}
body.composer-v2 .cmp-prompt-drop-popover button:hover {
  background: var(--accent, #7c6dfa);
  color: #fff;
}
.cmp-ref-plus { font-size: 22px; line-height: 1; font-weight: 300; }
.cmp-ref-hint { font-size: 10px; letter-spacing: .02em; }
.cmp-ref-slot[hidden] { display: none; }
/* a filled slot shows its thumb; wired in 2C */
.cmp-ref-slot .cmp-ref-thumb {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; border-radius: 11px;
  display: none;
}
.cmp-ref-slot.is-filled .cmp-ref-thumb { display: block; }
.cmp-ref-slot.is-filled .cmp-ref-plus,
.cmp-ref-slot.is-filled .cmp-ref-hint { display: none; }

/* Remove (×) affordance on a FILLED tile (slice-composer-polish UC1).
   A <span> inside the slot <button> — composer.js intercepts clicks on it
   before the open-picker path. Hidden until hover (always visible on
   touch / keyboard focus-within so it stays reachable without hover). */
.cmp-ref-x {
  position: absolute;
  top: 3px;
  right: 3px;
  z-index: 2;
  width: 18px;
  height: 18px;
  display: none;
  align-items: center;
  justify-content: center;
  line-height: 1;
  font-size: 13px;
  border-radius: 50%;
  background: rgba(8, 10, 28, 0.82);
  color: #fff;
  border: 1px solid rgba(255, 255, 255, 0.22);
  cursor: pointer;
}
.cmp-ref-slot.is-filled .cmp-ref-x { display: flex; }
.cmp-ref-x:hover { background: var(--red, #ef4444); border-color: var(--red, #ef4444); }
/* Only reveal on hover/focus on hover-capable pointers; coarse pointers
   (touch) keep it always-on (set above via is-filled). */
@media (hover: hover) {
  .cmp-ref-slot.is-filled .cmp-ref-x { display: none; }
  .cmp-ref-slot.is-filled:hover .cmp-ref-x,
  .cmp-ref-slot.is-filled:focus-within .cmp-ref-x { display: flex; }
}

/* ── Prompt ──────────────────────────────────────────────────────
   UC3 (slice-composer-polish): roomier default that fills the freed
   space + JS auto-grow (composer.js) up to a cap, so it's usable
   without a manual drag. The max-height cap keeps the toolbar +
   Generate row on-screen at 800/900px viewport heights. resize:vertical
   is retained as a manual override.
   UC2: the textarea is wrapped (.cmp-prompt-wrap) so the @image token
   chip mirror can sit behind it — the wrap owns the field background +
   border, and the textarea goes transparent so the chips show through. */
.cmp-prompt-wrap {
  position: relative;
  width: 100%;
  background: var(--field-bg, rgba(8,10,28,0.85));
  border: 1px solid var(--line, rgba(255,255,255,0.07));
  border-radius: 12px;
}
.cmp-prompt-wrap:focus-within { border-color: var(--accent, #7c6dfa); }
.cmp-prompt {
  display: block;
  width: 100%;
  min-height: 140px;
  max-height: min(46vh, 420px);
  resize: vertical;
  padding: 12px 14px;
  background: var(--field-bg, rgba(8,10,28,0.85));
  border: 1px solid var(--line, rgba(255,255,255,0.07));
  border-radius: 12px;
  color: var(--text, #f0f2ff);
  font-family: inherit;
  font-size: 14px;
  line-height: 1.5;
  overflow-y: auto;
}
/* When wrapped, the wrap draws the field chrome; the textarea is a
   transparent layer on top of the chip mirror. */
.cmp-prompt-wrap > .cmp-prompt {
  position: relative;
  z-index: 1;
  background: transparent;
  border-color: transparent;
}
.cmp-prompt::placeholder { color: var(--faint, rgba(240,242,255,0.42)); }
.cmp-prompt:focus { outline: none; border-color: var(--accent, #7c6dfa); }
.cmp-prompt-wrap > .cmp-prompt:focus { border-color: transparent; }
/* The token chip mirror over #cmpPrompt reuses the multichar chip
   classes (multichar-refs.css). Pin it under the textarea inside the wrap. */
.cmp-prompt-wrap .multichar-token-mirror { z-index: 0; }

/* ── Bottom toolbar ──────────────────────────────────────────── */
.cmp-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}
.cmp-tools-l,
.cmp-tools-r {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
/* Slice composer-sound-toggle: cost + Generate get their own full-width row
   below the pill toolbar, flush RIGHT (LumiYing bottom-right submit). The
   toolbar's justify-content:space-between would otherwise pin this single
   wrapped item to the LEFT of its line. flex-basis:100% forces the dedicated
   row (no cramming onto the 6-pill row); flex-end right-aligns within it.
   Order stays [cost] [Generate]. The ≤680px column layout overrides below. */
.cmp-tools-r {
  flex-basis: 100%;
  justify-content: flex-end;
}

/* Pills — base control used by every dropdown trigger + actions */
.cmp-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 36px;
  padding: 0 12px;
  background: var(--glass, rgba(255,255,255,0.04));
  border: 1px solid var(--line-strong, rgba(255,255,255,0.14));
  border-radius: var(--cmp-pill-radius);
  color: var(--text, #f0f2ff);
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  transition: border-color .16s ease, background .16s ease, color .16s ease;
}
.cmp-pill:hover {
  border-color: var(--accent, #7c6dfa);
  background: var(--glass-strong, rgba(255,255,255,0.07));
}
.cmp-pill svg { width: 15px; height: 15px; flex: 0 0 auto; }
.cmp-pill .cmp-caret { width: 12px; height: 12px; opacity: .6; }
.cmp-pill[hidden] { display: none; }
.cmp-pill-label { font-weight: 700; }

/* Dropdown container + menu */
.cmp-dd { position: relative; }
.cmp-dd.is-open > .cmp-pill {
  border-color: var(--accent, #7c6dfa);
  background: var(--glass-strong, rgba(255,255,255,0.07));
}
.cmp-menu {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 0;
  z-index: 60;
  min-width: 220px;
  max-height: 360px;
  overflow-y: auto;
  padding: 6px;
  background: var(--bg-elevated, #0a0c20);
  border: 1px solid var(--line-strong, rgba(255,255,255,0.14));
  border-radius: 14px;
  box-shadow: var(--shadow-lg, 0 8px 40px rgba(0,0,0,0.6));
  display: none;
}
.cmp-dd.is-open > .cmp-menu { display: block; }
.cmp-menu-models { min-width: 280px; }
.cmp-menu-settings { min-width: 300px; }

/* Menu options (model list + media switch) with checkmark */
.cmp-opt {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 10px;
  background: transparent;
  border: 0;
  border-radius: 9px;
  color: var(--text, #f0f2ff);
  font-family: inherit;
  font-size: 13px;
  text-align: left;
  cursor: pointer;
}
.cmp-opt:hover { background: var(--glass-strong, rgba(255,255,255,0.07)); }
.cmp-opt .cmp-check {
  width: 14px;
  flex: 0 0 auto;
  color: var(--accent, #7c6dfa);
  opacity: 0;
  font-size: 13px;
}
.cmp-opt.is-active .cmp-check { opacity: 1; }
.cmp-opt.is-active { color: var(--accent, #7c6dfa); }
/* Per-project disabled model (cosmetic; the server gate in jobs.js is the wall) */
.cmp-opt.is-disabled { opacity: .42; cursor: not-allowed; }
.cmp-opt.is-disabled:hover { background: transparent; }
/* "Working in: … · limited" picker hint */
.cmp-proj-limited { font-size: 11px; color: var(--text3, #8a8a99); margin-left: 2px; opacity: .85; }
.cmp-opt-main { display: flex; flex-direction: column; gap: 1px; flex: 1; min-width: 0; }
.cmp-opt-name { font-weight: 600; }
.cmp-opt-meta { font-size: 11px; color: var(--mute, rgba(240,242,255,0.62)); }
.cmp-opt-credit {
  margin-left: auto;
  font-size: 11px;
  font-weight: 700;
  color: var(--mute, rgba(240,242,255,0.62));
  white-space: nowrap;
}
.cmp-badge {
  font-size: 9px;
  font-weight: 800;
  letter-spacing: .04em;
  padding: 1px 5px;
  border-radius: 5px;
  text-transform: uppercase;
}
.cmp-badge.is-new  { background: rgba(6,247,161,0.16);  color: var(--green, #06f7a1); }
.cmp-badge.is-beta { background: rgba(124,109,250,0.18); color: var(--accent, #7c6dfa); }

/* Combined settings panel (Ratio + Resolution + Duration) */
.cmp-set-group { padding: 6px 4px; }
.cmp-set-group + .cmp-set-group {
  border-top: 1px solid var(--line, rgba(255,255,255,0.07));
  margin-top: 4px;
}
.cmp-set-group[hidden] { display: none; }
.cmp-set-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--faint, rgba(240,242,255,0.42));
  margin: 2px 6px 8px;
}
.cmp-seg {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 0 4px;
}
.cmp-seg-opt {
  padding: 6px 12px;
  background: var(--glass, rgba(255,255,255,0.04));
  border: 1px solid var(--line-strong, rgba(255,255,255,0.14));
  border-radius: 8px;
  color: var(--text2, rgba(240,242,255,0.7));
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  transition: border-color .14s ease, color .14s ease, background .14s ease;
}
.cmp-seg-opt:hover { border-color: var(--accent, #7c6dfa); }
.cmp-seg-opt.is-active {
  background: var(--accent, #7c6dfa);
  border-color: var(--accent, #7c6dfa);
  color: var(--accent-text, #fff);
}

/* Cost readout + Generate */
.cmp-cost {
  font-size: 13px;
  font-weight: 700;
  color: var(--text2, rgba(240,242,255,0.7));
  white-space: nowrap;
}
.cmp-cost strong { color: var(--text, #f0f2ff); }
.cmp-generate {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 40px;
  padding: 0 20px;
  background: var(--accent, #7c6dfa);
  border: 1px solid var(--accent, #7c6dfa);
  border-radius: 12px;
  color: var(--accent-text, #fff);
  font-family: inherit;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  transition: background .16s ease, transform .12s ease, box-shadow .16s ease;
}
.cmp-generate:hover {
  background: var(--accent-hover, #9b8fff);
  box-shadow: var(--shadow-violet, 0 8px 32px rgba(124,109,250,0.3));
}
.cmp-generate:active { transform: translateY(1px); }
.cmp-generate svg { width: 16px; height: 16px; }
.cmp-generate.is-busy { opacity: .7; pointer-events: none; }

/* ── Presets panel — anchored to the #cmpPresets button like the model /
   settings dropdowns (absolute within a positioned wrapper, opening
   upward), NOT pinned to the viewport. composer.js moves it into
   .cmp-presets-dd; togglePresets toggles its display. Right-aligned to
   the button (presets sits on the right of the toolbar) so it extends
   left and stays on-screen on narrow windows. ─────────────────────── */
.cmp-presets-dd { position: relative; display: inline-flex; }
body.composer-v2 #presets-panel {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 0;            /* extend rightward from the button, like the model/settings menus */
  right: auto;
  z-index: 90;
  width: min(420px, calc(100vw - 48px));
  /* The legacy rule sets max-width: calc(100% - 48px); now the panel's
     containing block is the small .cmp-presets-dd anchor, so that would
     clamp it to ~button width. Override it. */
  max-width: none;
  max-height: 70vh;
  overflow-y: auto;
}

/* ── Topaz upscale — collapsed section below the composer ──────── */
.cmp-upscale {
  margin-top: 14px;
  background: var(--surface, #0f1128);
  border: 1px solid var(--line-strong, rgba(255,255,255,0.14));
  border-radius: var(--cmp-radius, 18px);
  padding: 4px 18px;
}
.cmp-upscale-summary {
  cursor: pointer;
  padding: 12px 0;
  font-size: 14px;
  font-weight: 700;
  color: var(--text, #f0f2ff);
  list-style: none;
  user-select: none;
}
.cmp-upscale-summary::-webkit-details-marker { display: none; }
.cmp-upscale-summary::after { content: '▾'; float: right; opacity: .6; transition: transform .2s; }
.cmp-upscale[open] .cmp-upscale-summary::after { transform: rotate(180deg); }
.cmp-upscale .upscale-section { margin-top: 0 !important; border-top: 0 !important; padding-top: 8px !important; padding-bottom: 16px; }

/* ── Responsive ──────────────────────────────────────────────── */
@media (max-width: 680px) {
  .cmp-toolbar { flex-direction: column; align-items: stretch; }
  .cmp-tools-r { justify-content: space-between; }
  .cmp-menu { min-width: min(280px, calc(100vw - 48px)); }
}

/* ── Duration slider (slice s25-duration-slider) ───────────────
   Only rendered when a model exposes too many duration values for segmented
   buttons (Seedance 2.5: every second 4-30 = 27 chips, five rows in a ~280px
   panel shared with three other controls).

   The price readout sits directly beside the value and updates on every step,
   so the cost of each second is MORE visible than with buttons, not less —
   that was the objection this design had to answer. The number comes from
   window.costFor, the same function the Generate button reads, so it cannot
   drift from the charge. step="1" keeps the control on whole seconds: there
   is no landing between integers. */
body.composer-v2 .cmp-dur {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 2px 4px 0;
}
body.composer-v2 .cmp-dur-range {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 4px;
  border-radius: 999px;
  background: var(--line-strong, rgba(255,255,255,0.18));
  outline: none;
  cursor: pointer;
}
body.composer-v2 .cmp-dur-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--accent, #7c6dfa);
  border: 2px solid var(--panel, #14162e);
  box-shadow: 0 1px 4px rgba(0,0,0,0.45);
  cursor: pointer;
}
body.composer-v2 .cmp-dur-range::-moz-range-thumb {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--accent, #7c6dfa);
  border: 2px solid var(--panel, #14162e);
  box-shadow: 0 1px 4px rgba(0,0,0,0.45);
  cursor: pointer;
}
body.composer-v2 .cmp-dur-range:focus-visible {
  box-shadow: 0 0 0 2px var(--accent, #7c6dfa);
}
/* Tick marks — the increments should be felt, not just implied. Rendered as a
   repeating gradient rather than native datalist ticks, which Safari and
   Firefox style inconsistently. One notch every 2s. */
body.composer-v2 .cmp-dur-range {
  background-image: repeating-linear-gradient(
    to right,
    var(--line-strong, rgba(255,255,255,0.28)) 0 1px,
    transparent 1px calc(100% / 13)
  );
  background-repeat: repeat-x;
  background-size: 100% 100%;
}
body.composer-v2 .cmp-dur-readout {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  font-size: 12px;
  line-height: 1;
}
body.composer-v2 .cmp-dur-val {
  font-weight: 600;
  color: var(--text, #f0f2ff);
  font-variant-numeric: tabular-nums;   /* no width jitter while dragging */
}
body.composer-v2 .cmp-dur-cost {
  color: var(--accent, #7c6dfa);
  font-variant-numeric: tabular-nums;
}
