/* Storyboard UI — director's-sheet stylesheet
 *
 * Originated 2026-04-28 from specs/storyboard-ui/design-tokens.md (visual round
 * iterations 1–6). Locked aesthetic: D7–D13. Single stylesheet per the design
 * doc until the 1500-line threshold.
 *
 * Mobile-first per CLAUDE.md: defaults target desktop swimlane layout (matches
 * the design-tokens.md recipe shape), with the `@media (max-width: 47.999rem)`
 * block at the bottom flipping to vertical stacking on small viewports.
 */

/* ===========================================================================
 * 2.1 — Tokens
 * Per design-tokens.md "Tokens".
 * =========================================================================*/

:root {
  /* Paper / ink palette — strict monochrome (D7) — DARK MODE
   * Warm dark = inverted-cream director's-sheet. Paper is now deep
   * brown-black; ink is warm off-white. Same monochrome discipline,
   * same grid texture, same shadows — just inverted around the
   * paper/ink axis. */
  --paper:        #16110c;   /* warm near-black — body background */
  --paper-tinted: #100c08;   /* even deeper — image wells, sunken surfaces */
  --paper-deep:   #2a2218;   /* slightly lifted — perforation dots, accents */
  --ink:          #f0e8d0;   /* warm off-white — primary text, borders */
  --ink-mid:      #b8ad8e;   /* secondary text, faint labels */
  --ink-light:    #9a8e72;   /* faded annotations, dashed affordances, watermark numerals */

  /* RGB triples — match the hex tokens so rgba(...) composition
   * auto-inverts between light and dark themes. Use these inside
   * any rgba() that previously hardcoded paper-flavored or
   * ink-flavored colors. */
  --paper-rgb: 22, 17, 12;
  --ink-rgb:   240, 232, 208;

  /* Functional aliases — derived from the rgb triples so they
   * auto-invert. Semantics:
   *   --rule / --rule-strong: subtle hairlines drawn ON paper-bg
   *     surfaces (canvas grid, dashed separators). Implemented as
   *     ink-alpha so they read as ink marks.
   *   --shadow: polaroid / panel drop shadow on the canvas. Deep
   *     near-black in dark mode for proper card lift. */
  --rule:         rgba(var(--ink-rgb), 0.14);
  --rule-strong:  rgba(var(--ink-rgb), 0.26);
  --shadow:       rgba(0, 0, 0, 0.55);

  /* Card lift — inner edge highlight inside polaroid box-shadows.
   * Light mode used near-white (paper highlight on cream). Dark mode
   * uses a subtle warm rim of ink so the polaroid reads as "lit
   * from above" against the dark canvas. */
  --lift-highlight: rgba(var(--ink-rgb), 0.06);

  /* v2 polish — promoted from hardcoded literals so the dark theme
   * can tune them in one place.
   *   --scene-bg: slightly lifted paper for the polaroid card body
   *               (was #fbf6e3 in light mode, a touch brighter than --paper)
   *   --hover-bg: focus/hover surface tint for text inputs and menus
   *               (was #fffaea in light mode)
   *   --ink-hover: hover state for filled ink buttons (btn-generate)
   *                (was #2f231a in light mode) */
  --scene-bg:    #1d1610;
  --hover-bg:    #2a2218;
  --ink-hover:   #fffae0;

  /* Semantic chip colors (project / scene / frame scope) — brightened
   * for dark-mode legibility. Light mode used near-black-tinted hues
   * on cream; dark mode uses near-white-tinted hues on near-black. */
  --chip-project: #7fb7d4;   /* cool blue */
  --chip-scene:   #9bd09b;   /* sage green */
  --chip-frame:   #e2a070;   /* warm amber */

  /* Discard warm — brightened for dark-bg emphasis */
  --discard-warm:       #e0644f;
  --discard-warm-hover: #c95040;

  /* Type stack (D8) */
  --font-display: 'Fraunces', Georgia, serif;
  --font-body:    'Newsreader', Georgia, serif;
  --font-mono:    'JetBrains Mono', ui-monospace, monospace;

  /* v2: scene-aspect drives `.scene-image` aspect-ratio across the v1 + v2
   * surfaces. Default 16:9; the storyboard-view container overrides via
   * inline `style="--scene-aspect: <a> / <b>"` from the project's
   * :aspect-ratio field. */
  --scene-aspect: 16 / 9;
}

/* ===========================================================================
 * 2.2 — Body + canvas grid
 * Per design-tokens.md "The canvas".
 * =========================================================================*/

html, body {
  margin: 0;
  background-color: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-feature-settings: 'onum', 'kern';
}

body {
  /* Dot grid at intersection centers — quieter than ruled lines on
   * dark paper. One soft 1.5px dot per 32×32 tile reads as paper
   * reference marks rather than graph paper. */
  background-image:
    radial-gradient(circle, var(--rule) 1px, transparent 1.5px);
  background-size: 32px 32px;
  background-attachment: fixed;
  min-height: 100vh;
}

/* ===========================================================================
 * 2.3 — Type ramp helpers
 * Per design-tokens.md "Helper utility classes".
 * =========================================================================*/

.t-display {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 500;
  font-variation-settings: 'opsz' 96, 'SOFT' 100, 'WONK' 1;
  letter-spacing: -0.01em;
  line-height: 1.05;
}

.t-mono {
  font-family: var(--font-mono);
  font-feature-settings: 'onum' 0;
}

.t-mono-label {
  font-family: var(--font-mono);
  font-size: 0.65rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  font-weight: 500;
  color: var(--ink-mid);
}

/* Back-to-index affordance — small mono-cap link above the masthead
   title, ink-mid → ink on hover. Visually unobtrusive but reachable
   without typing the URL. */
.story-header-back {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-mid);
  text-decoration: none;
  margin-bottom: 0.6rem;
  transition: color 120ms ease;
}
.story-header-back:hover { color: var(--ink); }

/* ===========================================================================
 * 2.4 — Masthead
 * Per design-tokens.md "Masthead".
 * =========================================================================*/

.masthead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: 3rem 3rem 2rem;
}

.masthead h1 {
  margin: 0;
  font-size: 1.7rem;
}

.masthead .meta,
.story-header-meta {
  color: var(--ink-mid);
  font-size: 0.78rem;
}

.story-header-meta {
  display: flex;
  gap: 1.25rem;
  align-items: baseline;
}

/* ===========================================================================
 * 2.5 — Polaroid scene-card
 * Per design-tokens.md "Polaroid scene-card".
 * =========================================================================*/

.scene {
  /* v2 solidity fix (2026-05-07 prototype): card body sits a touch
   * lifted from the canvas (--scene-bg slightly distinct from --paper)
   * + inner highlight + paper grain so cards read as solid objects
   * lifted off the canvas, not transparent overlays. Applies to v1
   * polaroids too — the "transparent cards" issue Brian flagged in
   * the prototype review. */
  background: var(--scene-bg);
  border: 1px solid var(--ink);
  padding: 12px 12px 16px;
  width: 224px;
  flex: 0 0 224px;
  box-shadow:
    4px 5px 0 var(--shadow),
    inset 0 0 0 1px var(--lift-highlight);
  position: relative;
  transition: transform 180ms ease, box-shadow 180ms ease, opacity 200ms ease;
}

.scene.is-tilt-l { transform: rotate(-1deg); }
.scene.is-tilt-r { transform: rotate( 0.8deg); }
.scene.is-tilt-c { transform: rotate(-0.3deg); }

.scene:hover {
  transform: rotate(0deg) translate(-1px, -2px);
  box-shadow:
    6px 7px 0 rgba(0, 0, 0, 0.65),
    inset 0 0 0 1px var(--lift-highlight);
}

/* Faint paper grain — adds tactile texture so the card doesn't read as a flat
 * sticker against the canvas grid. Pseudo z-stacked behind content via :z-1 */
.scene::after {
  content: '';
  position: absolute;
  inset: 0;
  background-image:
    radial-gradient(circle at 25% 30%, rgba(var(--ink-rgb), 0.05) 0 1px, transparent 2px),
    radial-gradient(circle at 70% 80%, rgba(var(--ink-rgb), 0.05) 0 1px, transparent 2px);
  background-size: 7px 7px, 11px 11px;
  pointer-events: none;
  z-index: 0;
}
.scene > * { position: relative; z-index: 1; }

/* Active-scene fade — sibling cards dim/scale when one scene is focused. The
 * `is-faded` class is wired by the storyboard-view through $activeScene. */
.scene.is-faded { opacity: 0.55; transform: scale(0.97); }
.scene.is-faded:hover { opacity: 0.85; transform: scale(0.97) translate(-1px, -2px); }

/* Filmstrip-perforation across the top edge */
.scene::before {
  content: '';
  position: absolute;
  top: -1px;
  left: 12px;
  right: 12px;
  height: 8px;
  background-image: radial-gradient(circle at 4px 4px, var(--paper-deep) 1.5px, transparent 1.6px);
  background-size: 12px 8px;
  background-repeat: repeat-x;
}

/* Image well */
.scene-image {
  /* v2: aspect-ratio comes from --scene-aspect (default 16/9, project-driven).
   * v1 cards inherited a square (1/1); the v2 prototype standardized on
   * project-aspect. Existing v1 storyboards set the inline CSS variable on
   * the `storyboard-view` root which children read here. */
  aspect-ratio: var(--scene-aspect, 1);
  background: var(--paper-tinted);
  border: 1px solid var(--ink);
  margin-top: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  overflow: hidden;
}

.scene-image svg.doodle {
  width: 60%;
  height: 60%;
}

.scene-image svg path {
  fill: none;
  stroke: var(--ink);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.scene-image svg .filled {
  fill: var(--ink);
  stroke: none;
}

/* Title / description / meta */
.scene-title {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 500;
  font-variation-settings: 'opsz' 72, 'SOFT' 100, 'WONK' 1;
  font-size: 1.18rem;
  line-height: 1.1;
  margin: 14px 0 4px;
}

.scene-desc {
  font-size: 0.86rem;
  color: var(--ink-mid);
  margin: 0 0 12px;
  line-height: 1.4;
}

.scene-meta {
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-top: 1px dashed var(--rule-strong);
  padding-top: 8px;
}

.scene-runtime {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  cursor: pointer;
  transition: color 120ms ease;
}
.scene-runtime:hover { color: var(--ink); }

/* Status stamp (`:draft` / `:final`) — director's-stamp aesthetic */
.status {
  font-family: var(--font-mono);
  font-size: 0.6rem;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  padding: 3px 7px 2px;
  border: 1px solid var(--ink);
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease, transform 120ms ease;
}

.status.is-draft {
  background: transparent;
  color: var(--ink);
}
.status.is-draft:hover {
  background: var(--ink);
  color: var(--paper);
  transform: translateY(-1px);
}

.status.is-final {
  background: var(--ink);
  color: var(--paper);
}
.status.is-final:hover {
  background: transparent;
  color: var(--ink);
  transform: translateY(-1px);
}

/* ===========================================================================
 * 2.6 — Sticky paper-spine + horizontal-scroll act-lane
 * Per design-tokens.md "Sticky paper-spine + horizontal-scroll act lane".
 * Keystone: spine uses the SAME fixed-attachment grid as body so it
 * disappears into the canvas while staying opaque enough for cards to
 * slide cleanly under it.
 * =========================================================================*/

.act-lane {
  display: flex;
  align-items: stretch;
  overflow-x: auto;
  overflow-y: visible;
  scrollbar-width: none;
  -ms-overflow-style: none;
  margin: 0 0 5rem;
  position: relative;
}

.act-lane::-webkit-scrollbar { display: none; }

.act-spine {
  position: sticky;
  left: 0;
  flex: 0 0 192px;
  background-color: var(--paper);
  /* Dot grid — matches the body canvas pattern so the sticky spine
   * blends into the canvas instead of reading as a separate column. */
  background-image:
    radial-gradient(circle, var(--rule) 1px, transparent 1.5px);
  background-size: 32px 32px;
  background-attachment: fixed;
  padding: 0.25rem 1rem 0 1.5rem;
  z-index: 2;
}

/* Soft right-edge fade — softens the boundary where cards slide under */
.act-spine::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  right: -16px;
  width: 16px;
  background: linear-gradient(to right, var(--paper) 0%, transparent 100%);
  pointer-events: none;
}

/* Hover-only Remove trigger — mirrors .project-delete-trigger. Sits in the
   top-right of the spine, hidden by default, fades in on spine hover.
   Hidden while .is-editing so it doesn't compete with the in-edit Save/
   Cancel/Remove action row. */
.act-remove-trigger {
  position: absolute;
  top: 0.6rem;
  right: 1rem;
  z-index: 3;
  margin: 0;
  padding: 0;
  background: transparent;
  border: 0;
  font-family: var(--font-mono);
  font-size: 0.55rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-light);
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: opacity 160ms ease, color 120ms ease;
}

.act-spine:hover .act-remove-trigger { opacity: 1; }
.act-remove-trigger:hover { color: var(--ink); }
.act-spine.is-editing .act-remove-trigger { display: none; }

/* Scene-card hover-remove trigger — same discoverability pattern as the
 * act-spine variant. Top-right corner of the polaroid; hidden until
 * hover; hidden entirely when the card is in edit mode so it doesn't
 * compete with the in-edit Save/Cancel/Remove action bar. */
.scene-remove-trigger {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  z-index: 3;
  margin: 0;
  padding: 0;
  background: transparent;
  border: 0;
  font-family: var(--font-mono);
  font-size: 0.55rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-light);
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: opacity 160ms ease, color 120ms ease;
}
.scene:hover .scene-remove-trigger { opacity: 1; }
.scene-remove-trigger:hover { color: var(--ink); }
.scene.is-editing .scene-remove-trigger { display: none; }

.act-strip {
  display: flex;
  gap: 1.75rem;
  padding: 0.5rem 6rem 0.5rem 1rem;
  flex: 0 0 auto;
}

/* ===========================================================================
 * 2.7 — Act-spine content
 * Per design-tokens.md "Act spine content".
 * =========================================================================*/

.act-numeral {
  font-family: var(--font-display);
  font-style: italic;
  font-variation-settings: 'opsz' 144, 'SOFT' 100, 'WONK' 1;
  font-size: 3.5rem;
  line-height: 0.9;
  color: var(--ink-light);
  margin-bottom: 0.25rem;
}

.act-title {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 500;
  font-variation-settings: 'opsz' 72, 'SOFT' 100, 'WONK' 1;
  font-size: 1.5rem;
  line-height: 1.05;
  margin: 0 0 0.5rem;
}

.act-synopsis {
  font-size: 0.92rem;
  color: var(--ink-mid);
  line-height: 1.45;
  margin: 0 0 1.25rem;
  max-width: 10rem;
}

.act-stats {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-mid);
  display: flex;
  gap: 1.25rem;
}

/* ===========================================================================
 * 2.8 — Multi-act divider
 * Per design-tokens.md "Multi-act divider".
 * =========================================================================*/

.act-lane + .act-lane::before {
  content: '';
  position: absolute;
  top: -2.5rem;
  left: 1.5rem;
  right: 6rem;
  height: 1px;
  background: var(--rule-strong);
  pointer-events: none;
}

/* ===========================================================================
 * 2.9 — Right-edge canvas bleed
 * Per design-tokens.md "Right-edge canvas bleed".
 * =========================================================================*/

.canvas {
  position: relative;
  padding-bottom: 4rem;
}

.canvas::after {
  content: '';
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: 96px;
  background: linear-gradient(to right, transparent 0%, var(--paper) 80%);
  pointer-events: none;
  z-index: 5;
}

/* ===========================================================================
 * 2.10 — Inline-edit state (D11)
 * Per design-tokens.md "Inline-edit state".
 * =========================================================================*/

.scene.is-editing {
  transform: rotate(0deg);
  outline: 1px solid var(--ink);
  outline-offset: 3px;
}

.scene.is-editing:hover {
  transform: rotate(0deg) translate(-1px, -2px);
}

.scene-title-input {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 500;
  font-variation-settings: 'opsz' 72, 'SOFT' 100, 'WONK' 1;
  font-size: 1.18rem;
  line-height: 1.1;
  color: var(--ink);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--ink-mid);
  outline: none;
  width: 100%;
  padding: 0 0 2px 0;
  margin: 14px 0 4px;
  caret-color: var(--ink);
}

.scene-title-input::placeholder {
  color: var(--ink-light);
  font-style: italic;
}

.scene-title-input:focus {
  border-bottom-color: var(--ink);
}

/* ---------------------------------------------------------------------------
 * Phase 4 — interactive scene-card display/input toggle
 *
 * The `scene-card` defelem renders BOTH the read-only `.scene-title` /
 * `.scene-desc` markup AND the editing surfaces (`.scene-title-input`,
 * `.scene-desc-input`, `.scene-actions`, `.scene-doodle-toolbar`). CSS toggles
 * which surface is visible based on the `.is-editing` / `.is-doodling` state
 * classes (driven by `$editingScene` / `$doodlingScene` page-level signals).
 * --------------------------------------------------------------------------- */

/* Default state: editing surfaces hidden. */
.scene-title-input,
.scene-desc-input,
.scene-actions,
.scene-doodle-toolbar {
  display: none;
}

.scene.is-editing .scene-title { display: none; }
.scene.is-editing .scene-title-input { display: block; }
.scene.is-editing .scene-desc { display: none; }
.scene.is-editing .scene-desc-input {
  display: block;
  font-family: var(--font-body);
  font-size: 0.86rem;
  color: var(--ink-mid);
  background: transparent;
  border: 1px solid var(--rule-strong);
  outline: none;
  width: 100%;
  box-sizing: border-box;
  resize: vertical;
  padding: 6px 8px;
  margin: 0 0 12px;
  line-height: 1.4;
}
.scene.is-editing .scene-actions {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  border-top: 1px dashed var(--rule-strong);
  padding-top: 8px;
  margin-top: 8px;
}

.scene-action {
  font-family: var(--font-mono);
  font-size: 0.55rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  background: var(--paper);
  border: 1px solid var(--ink);
  padding: 2px 7px 1px;
  cursor: pointer;
  color: var(--ink);
  transition: background 100ms, color 100ms;
}
.scene-action:hover { background: var(--ink); color: var(--paper); }
.scene-action.primary { background: var(--ink); color: var(--paper); }
.scene-action.primary:hover { background: var(--paper); color: var(--ink); }

/* Doodle-active toolbar (the sibling overlay rendered inside scene-card). */
.scene.is-doodling .scene-doodle-toolbar { display: flex; }

/* ===========================================================================
 * 2.11 — Doodle-active state (D12)
 * Per design-tokens.md "Doodle-active state".
 * =========================================================================*/

.scene.is-doodling {
  transform: rotate(0deg);
}

.scene.is-doodling .scene-image {
  cursor: crosshair;
  background: var(--paper);
  background-image:
    linear-gradient(to right,  var(--rule) 1px, transparent 1px),
    linear-gradient(to bottom, var(--rule) 1px, transparent 1px);
  background-size: 16px 16px;
}

.scene.is-doodling .scene-image svg path {
  stroke-width: 2.5;
}

/* Pencil affordance — always-visible trigger that flips $doodlingScene.
   Sits in the upper-right corner of the scene image; hover lifts it
   slightly. Hidden while doodling so it doesn't compete with the
   active toolbar. */
.scene-doodle-trigger {
  position: absolute;
  top: 6px;
  right: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  background: var(--paper);
  border: 1px solid var(--ink);
  color: var(--ink);
  cursor: pointer;
  opacity: 0.85;
  transition: opacity 120ms ease, transform 120ms ease, background 120ms ease;
  z-index: 2;
}
.scene-doodle-trigger:hover {
  opacity: 1;
  transform: translateY(-1px);
  background: var(--ink);
  color: var(--paper);
}
.scene.is-doodling .scene-doodle-trigger { display: none; }

/* Stored + active doodle surfaces share viewBox + sizing so coords
   translate 1:1 between draw and persisted display. Both stretch
   over the entire `.scene-image`. The `svg.` qualifier matches the
   specificity of the predefined-doodle rule (`.scene-image svg.doodle
   { width: 60% }`) so it doesn't lose that battle. */
.scene-image svg.scene-doodle-stored,
.scene-image svg.scene-doodle-surface {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* Override the `.doodle-canvas` standalone-affordance cap of 320px
     (line ~1620). Inside the scene-image well we want the canvas to
     fill the full width of the new 540px card, not stay capped to
     the original narrow polaroid. */
  max-width: none;
}
.scene-doodle-stored path,
.scene-doodle-surface path {
  fill: none;
  stroke: var(--ink);
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Drawing surface invisible / non-interactive by default; becomes
   the active drawing canvas when `.scene` carries `.is-doodling`. */
.scene-doodle-surface {
  display: none;
  pointer-events: none;
  touch-action: none;
}
.scene.is-doodling .scene-doodle-surface {
  display: block;
  pointer-events: auto;
}

/* Hide the persisted doodle (and its `<span>` wrapper from
   `doodle-svg`) while doodling so the active surface owns the area.
   The persisted display reappears when `.is-doodling` clears. */
.scene.is-doodling .scene-doodle-stored { display: none; }
.scene.is-doodling .scene-image > span:has(.scene-doodle-stored) { display: none; }
/* Same for the AI image — when the user clicks ✎ on an :ai-generated
   scene the doodle canvas takes over. The image reappears on Cancel
   (state flips back when `$doodlingScene` clears); on Done, the
   save-doodle effect replaces `:image` with the new `:doodle` shape
   so this rule no longer matches anyway (no `.scene-ai-image` to
   hide). */
.scene.is-doodling .scene-ai-image { display: none; }
/* v2: hide the frame-panes while doodling so the "awaiting first frame"
   placeholder (and any other frame content) doesn't bleed through the
   doodle canvas. The doodle surface positions itself absolute over
   .scene-image; the .scene-image graph-paper background (set on
   .scene.is-doodling .scene-image) becomes the clean drawing surface. */
.scene.is-doodling .frame-pane { display: none; }

.doodle-toolbar {
  position: absolute;
  top: 6px;
  left: 6px;
  right: 6px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-family: var(--font-mono);
  font-size: 0.55rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink);
  pointer-events: none;
  z-index: 2;
}

.doodle-toolbar .group {
  display: flex;
  gap: 4px;
  pointer-events: auto;
}

.doodle-toolbar button {
  background: var(--paper);
  border: 1px solid var(--ink);
  padding: 1px 6px 0;
  font: inherit;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink);
  cursor: pointer;
  transition: background 100ms, color 100ms;
}

.doodle-toolbar button:hover {
  background: var(--ink);
  color: var(--paper);
}

.doodle-toolbar button.primary {
  background: var(--ink);
  color: var(--paper);
}

.doodle-toolbar button.primary:hover {
  background: var(--paper);
  color: var(--ink);
}

/* ===========================================================================
 * 2.12 — Add-scene affordance
 * Per design-tokens.md "Add-scene affordance".
 * =========================================================================*/

.scene-add {
  flex: 0 0 224px;
  width: 224px;
  align-self: stretch;
  background: transparent;
  border: 1px dashed var(--ink-light);
  color: var(--ink-mid);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 0.65rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  cursor: pointer;
  transition: border-color 120ms, color 120ms;
}

.scene-add:hover {
  border-color: var(--ink);
  color: var(--ink);
}

.scene-add .plus {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.5rem;
  margin-right: 0.5rem;
  font-variation-settings: 'opsz' 96, 'SOFT' 100, 'WONK' 1;
}

/* ===========================================================================
 * 2.13 — Project index page
 * Per design-tokens.md "Project index page".
 * NOTE: design-tokens.md respecifies `.canvas` padding for the index page
 * (`padding: 0 3rem 4rem`). We keep the storyboard-view `.canvas` rule
 * defined in 2.9 (with `padding-bottom: 4rem`) and place the index-page
 * override here scoped via `.canvas:has(.project-grid)` so the two pages
 * share the class without one stomping on the other.
 * =========================================================================*/

.canvas:has(.project-grid) {
  padding: 0 3rem 4rem;
}

.project-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 2.5rem 2rem;
  align-items: start;
  padding: 0.5rem 0;
}

.project {
  background: var(--paper);
  border: 1px solid var(--ink);
  padding: 14px 14px 18px;
  box-shadow: 5px 6px 0 var(--shadow);
  position: relative;
  transition: transform 200ms ease, box-shadow 200ms ease;
  cursor: pointer;
  display: block;
  text-decoration: none;
  color: inherit;
}

.project.is-tilt-l { transform: rotate(-1.5deg); }
.project.is-tilt-r { transform: rotate( 1.2deg); }
.project.is-tilt-c { transform: rotate(-0.4deg); }

.project:hover {
  transform: rotate(0deg) translate(-2px, -3px);
  box-shadow: 7px 8px 0 var(--shadow);
}

/* Inner clickable region wrapping the display content (cover + title +
   synopsis + stats). The article root deliberately doesn't carry the
   open-handler so the edit-mode action buttons (Save / Cancel) and form
   controls don't bubble click events into navigation. */
.project-card-link {
  display: block;
  cursor: pointer;
  color: inherit;
  text-decoration: none;
}

.project::before {
  content: '';
  position: absolute;
  top: -1px;
  left: 14px;
  right: 14px;
  height: 8px;
  background-image: radial-gradient(circle at 4px 4px, var(--paper-deep) 1.5px, transparent 1.6px);
  background-size: 12px 8px;
  background-repeat: repeat-x;
}

.project-cover {
  aspect-ratio: 4 / 3;
  background: var(--paper-tinted);
  border: 1px solid var(--ink);
  margin-top: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  overflow: hidden;
}

/* Fill the cover well with the doodle — `render-doodle-paths` uses
   preserveAspectRatio="none" so the 1:1 viewBox stretches into the
   4:3 cover frame edge-to-edge. The wrapping <span> from doodle-svg
   needs explicit sizing too or it shrinks to its inline content. */
.project-cover > span,
.project-cover svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* Slate marker — director's clapperboard label, bottom-left of cover */
.project-slate {
  position: absolute;
  bottom: 6px;
  left: 6px;
  font-family: var(--font-mono);
  font-size: 0.55rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink);
  background: var(--paper);
  padding: 1px 6px;
  border: 1px solid var(--ink);
}

.project-title {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 500;
  font-variation-settings: 'opsz' 96, 'SOFT' 100, 'WONK' 1;
  font-size: 1.35rem;
  line-height: 1.1;
  margin: 16px 0 6px;
}

.project-synopsis {
  font-size: 0.9rem;
  color: var(--ink-mid);
  margin: 0 0 14px;
  line-height: 1.45;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.project-stats {
  display: flex;
  gap: 1rem;
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-mid);
  border-top: 1px dashed var(--rule-strong);
  padding-top: 10px;
}

.project-stats .sep {
  color: var(--ink-light);
}

.project-new {
  align-self: stretch;
  background: transparent;
  border: 1px dashed var(--ink-light);
  color: var(--ink-mid);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  cursor: pointer;
  transition: border-color 120ms, color 120ms;
  min-height: 320px;
  padding: 2rem;
}

.project-new:hover {
  border-color: var(--ink);
  color: var(--ink);
}

.project-new .plus {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 3rem;
  font-variation-settings: 'opsz' 144, 'SOFT' 100, 'WONK' 1;
  line-height: 1;
}

/* ===========================================================================
 * Storyboard view layout & OUTLINE sidebar
 *
 * The storyboard-view defelem composes [masthead, outline, canvas] as flat
 * children of `.story-app.is-storyboard`. Without explicit layout the three
 * stacked vertically with default browser flow — the OUTLINE label flushed
 * against the page's left edge and the canvas's act-spine appeared *below*
 * the outline contents. This grid arranges them: masthead spans full width
 * on top; outline occupies a fixed-width left column (sticky-top so it
 * follows scroll); canvas takes the remaining space and houses the
 * sticky-spine act-lanes from section 2.6.
 *
 * design-tokens.md left this surface explicitly unspecified ("Outline
 * sidebar... discover in sandbox during Phase 4. Read-only nav, small
 * surface area. Reuse mono caps + ink-mid + dashed dividers."). Recipe:
 * mono-cap heading; italic-Fraunces act titles tied to mono-cap roman
 * numerals; ink-light scene titles indented under each act behind a
 * dashed left rule.
 * =========================================================================*/

.story-app.is-storyboard {
  display: grid;
  grid-template-columns: 240px minmax(0, 1fr);
  grid-template-rows: auto auto 1fr;
  grid-template-areas:
    "masthead  masthead"
    "art-strip art-strip"
    "outline   canvas";
  min-height: 100vh;
  align-items: start;
  transition: grid-template-columns 0.2s ease;
}

.story-app.is-storyboard > .masthead  { grid-area: masthead; }
.story-app.is-storyboard > .art-strip { grid-area: art-strip; }
.story-app.is-storyboard > .outline   { grid-area: outline; }
.story-app.is-storyboard > .canvas    { grid-area: canvas; min-width: 0; }

/* Collapsed state — outline column shrinks to a thin rail, contents hide,
   only the toggle remains as the affordance to re-expand. Canvas reflows
   into the reclaimed width via the grid-template-columns change. */
.story-app.is-storyboard.is-outline-collapsed {
  grid-template-columns: 36px minmax(0, 1fr);
}
.story-app.is-storyboard.is-outline-collapsed > .outline {
  padding: 1.25rem 0 4rem;
  overflow: hidden;
}
.is-outline-collapsed > .outline > .outline-heading,
.is-outline-collapsed > .outline > .outline-acts {
  display: none;
}

.outline {
  position: sticky;
  top: 0;
  align-self: start;
  max-height: 100vh;
  overflow-y: auto;
  padding: 1.25rem 1.5rem 4rem 3rem;
  border-right: 1px dashed var(--rule-strong);
  font-family: var(--font-body);
  color: var(--ink-mid);
  scrollbar-width: thin;
}

.outline-heading {
  margin: 0 0 1.25rem;
  /* .t-mono-label provides typography; no override needed */
}

.outline-acts {
  list-style: none;
  margin: 0;
  padding: 0;
}

.outline-act + .outline-act {
  margin-top: 1.25rem;
  padding-top: 1.25rem;
  border-top: 1px dashed var(--rule);
}

.outline-act-numeral {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 0.65rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-light);
}

.outline-act-title {
  margin: 0.2rem 0 0.6rem;
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 500;
  font-variation-settings: 'opsz' 24, 'SOFT' 100, 'WONK' 1;
  font-size: 0.98rem;
  line-height: 1.2;
  color: var(--ink);
}

.outline-scenes {
  list-style: none;
  margin: 0;
  padding: 0 0 0 0.75rem;
  border-left: 1px dashed var(--rule);
}

.outline-scene {
  padding: 0.2rem 0 0.2rem 0.75rem;
  font-size: 0.82rem;
  line-height: 1.35;
  color: var(--ink-mid);
}

/* Collapse toggle — sits in the upper-right of the outline rail near the
   dashed boundary that separates outline from canvas. Glyph flips based on
   the global $outlineCollapsed signal (is-outline-collapsed on the root).
   Desktop-only; mobile hides the toggle (see mobile breakpoint). */
.outline-toggle {
  position: absolute;
  top: 1.1rem;
  right: 0.5rem;
  background: transparent;
  border: 0;
  padding: 2px 6px;
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 1.1rem;
  line-height: 1;
  color: var(--ink-light);
  transition: color 0.15s ease;
}
.outline-toggle:hover { color: var(--ink); }
.outline-toggle::before { content: '‹'; }
.is-outline-collapsed .outline-toggle::before { content: '›'; }

/* ===========================================================================
 * 2.14 — Mobile breakpoint (≤ 47.999rem / < 768px)
 * Per design-tokens.md "Mobile breakpoint".
 * Swimlane → vertical stacking; spine → top-of-act header; scenes stack
 * centered max-width 320px; no rotation; lighter shadow.
 * =========================================================================*/

@media (max-width: 47.999rem) {
  .masthead {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.5rem;
    padding: 2rem 1.5rem 1.5rem;
  }
  .masthead h1 { font-size: 1.5rem; }

  /* Mobile art-strip padding override lives at the END of the file
   * (after the base `.art-strip` rule) because both rules have the
   * same specificity — later source wins. */

  .canvas { padding-bottom: 3rem; }
  .canvas::after { display: none; }

  .act-lane {
    display: block;
    overflow-x: visible;
    margin: 0 0 3.5rem;
  }
  .act-lane + .act-lane::before { display: none; }
  .act-lane + .act-lane {
    border-top: 1px solid var(--rule-strong);
    padding-top: 2.5rem;
    margin-top: 2.5rem;
  }

  .act-spine {
    position: static;
    flex: initial;
    background-attachment: initial;
    padding: 0.5rem 1.5rem 1.5rem;
    width: auto;
  }
  .act-spine::after { display: none; }
  .act-numeral { font-size: 2.5rem; }
  .act-title   { font-size: 1.35rem; }
  .act-synopsis { max-width: 100%; }

  .act-strip {
    flex-direction: column;
    gap: 1.5rem;
    padding: 0 1.5rem;
    align-items: center;
  }

  .scene {
    width: 100%;
    max-width: 320px;
    flex: 0 0 auto;
    box-shadow: 3px 4px 0 var(--shadow);
  }
  .scene.is-tilt-l,
  .scene.is-tilt-r,
  .scene.is-tilt-c { transform: rotate(0deg); }
  .scene:hover { transform: translate(-1px, -2px); }

  .scene-add {
    width: 100%;
    max-width: 320px;
    flex: 0 0 auto;
    min-height: 64px;
  }

  /* Project index */
  .canvas:has(.project-grid) { padding: 0 1.5rem 3rem; }
  .project-grid { gap: 2rem; }
  .project.is-tilt-l,
  .project.is-tilt-r,
  .project.is-tilt-c { transform: rotate(0deg); }

  /* Storyboard view layout — collapse the sidebar grid into a column.
     Outline becomes a top strip with a dashed bottom divider; canvas
     flows below at full width. */
  .story-app.is-storyboard {
    display: block;
  }
  .outline {
    position: static;
    max-height: none;
    overflow-y: visible;
    padding: 1rem 1.5rem;
    border-right: none;
    border-bottom: 1px dashed var(--rule-strong);
  }
  /* Mobile uses a top strip — the desktop collapse toggle is meaningless
     here; force-show outline contents in case the signal is true. */
  .outline-toggle { display: none; }
  .is-outline-collapsed > .outline > .outline-heading { display: block; }
  .is-outline-collapsed > .outline > .outline-acts { display: block; }
}

/* ===========================================================================
 * Phase 4 — interactive act-spine display/input toggle
 * =========================================================================*/

.act-title-input,
.act-synopsis-input,
.act-actions {
  display: none;
}

.act-spine.is-editing .act-title { display: none; }
.act-spine.is-editing .act-title-input {
  display: block;
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 500;
  font-variation-settings: 'opsz' 72, 'SOFT' 100, 'WONK' 1;
  font-size: 1.5rem;
  line-height: 1.05;
  color: var(--ink);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--ink-mid);
  outline: none;
  width: 100%;
  padding: 0 0 2px 0;
  margin: 0 0 0.5rem;
  caret-color: var(--ink);
}
.act-spine.is-editing .act-synopsis { display: none; }
.act-spine.is-editing .act-synopsis-input {
  display: block;
  font-family: var(--font-body);
  font-size: 0.92rem;
  color: var(--ink-mid);
  line-height: 1.45;
  background: transparent;
  border: 1px solid var(--rule-strong);
  outline: none;
  width: 100%;
  max-width: 10rem;
  resize: vertical;
  padding: 6px 8px;
  margin: 0 0 1.25rem;
}
.act-spine.is-editing .act-actions {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 0.5rem;
}

.act-action {
  font-family: var(--font-mono);
  font-size: 0.55rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  background: var(--paper);
  border: 1px solid var(--ink);
  padding: 2px 7px 1px;
  cursor: pointer;
  color: var(--ink);
  transition: background 100ms, color 100ms;
}
.act-action:hover { background: var(--ink); color: var(--paper); }
.act-action.primary { background: var(--ink); color: var(--paper); }
.act-action.primary:hover { background: var(--paper); color: var(--ink); }

/* ===========================================================================
 * Phase 4 — interactive project-card display/input toggle
 * =========================================================================*/

.project-title-input,
.project-synopsis-input,
.project-actions {
  display: none;
}

.project.is-editing { transform: rotate(0deg); outline: 1px solid var(--ink); outline-offset: 3px; }
.project.is-editing:hover { transform: rotate(0deg) translate(-2px, -3px); }

.project.is-editing .project-title { display: none; }
.project.is-editing .project-title-input {
  display: block;
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 500;
  font-variation-settings: 'opsz' 96, 'SOFT' 100, 'WONK' 1;
  font-size: 1.35rem;
  line-height: 1.1;
  color: var(--ink);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--ink-mid);
  outline: none;
  width: 100%;
  padding: 0 0 2px 0;
  margin: 16px 0 6px;
  caret-color: var(--ink);
}
.project.is-editing .project-synopsis { display: none; }
.project.is-editing .project-synopsis-input {
  display: block;
  font-family: var(--font-body);
  font-size: 0.9rem;
  color: var(--ink-mid);
  line-height: 1.45;
  background: transparent;
  border: 1px solid var(--rule-strong);
  outline: none;
  width: 100%;
  resize: vertical;
  padding: 6px 8px;
  margin: 0 0 14px;
}
.project.is-editing .project-actions {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  border-top: 1px dashed var(--rule-strong);
  padding-top: 10px;
  margin-top: 4px;
}

.project-action {
  font-family: var(--font-mono);
  font-size: 0.55rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  background: var(--paper);
  border: 1px solid var(--ink);
  padding: 2px 7px 1px;
  cursor: pointer;
  color: var(--ink);
  transition: background 100ms, color 100ms;
}
.project-action:hover { background: var(--ink); color: var(--paper); }
.project-action.primary { background: var(--ink); color: var(--paper); }
.project-action.primary:hover { background: var(--paper); color: var(--ink); }

/* ===========================================================================
 * Polish A — project-grid create-flow
 *
 * Click `+ New project` flips `$creatingProject = true`; the plus-tile
 * hides via `.project-new.is-creating { display: none }` and the in-place
 * italic-input editor (`.project-create-form.is-creating`) reveals in its
 * footprint. The form inherits the polaroid card dimensions so the swap
 * is visually in-place. Uses the same dashed-rule + paper background as
 * the plus-tile to read as the same affordance flipped open.
 * =========================================================================*/

.project-new.is-creating { display: none; }

.project-create-form {
  display: none;
  align-self: stretch;
  background: var(--paper);
  border: 1px solid var(--ink);
  flex-direction: column;
  justify-content: space-between;
  gap: 1rem;
  min-height: 320px;
  padding: 2rem;
  box-shadow: 4px 5px 0 var(--shadow);
}

.project-create-form.is-creating { display: flex; }

.project-create-input {
  display: block;
  width: 100%;
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 500;
  font-variation-settings: 'opsz' 96, 'SOFT' 100, 'WONK' 1;
  font-size: 1.5rem;
  line-height: 1.1;
  color: var(--ink);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--ink-mid);
  outline: none;
  padding: 0 0 4px 0;
  caret-color: var(--ink);
}

.project-create-input::placeholder {
  color: var(--ink-light);
  font-style: italic;
}

.project-create-actions {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  border-top: 1px dashed var(--rule-strong);
  padding-top: 10px;
}

/* ===========================================================================
 * Polish B — story-header edit-mode (project title + synopsis on /projects/:id)
 *
 * Click the title/synopsis to set `$editingProject = '<id>'`; CSS flips
 * `.masthead.is-editing` and swaps the display elements for italic-input
 * + Newsreader textarea siblings. Stats row stays visible. Save commits
 * via the chained save-project effect (rename + set-synopsis); cancel
 * restores the read-only display by resetting the signal.
 * =========================================================================*/

.masthead .story-header-title-input,
.masthead .story-header-synopsis-input,
.masthead .story-header-actions {
  display: none;
}

.masthead .story-header-title { cursor: text; }
.masthead .story-header-synopsis { cursor: text; }

.masthead.is-editing .story-header-title { display: none; }
.masthead.is-editing .story-header-synopsis { display: none; }

.masthead.is-editing .story-header-title-input {
  display: block;
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 500;
  font-variation-settings: 'opsz' 96, 'SOFT' 100, 'WONK' 1;
  font-size: 1.7rem;
  line-height: 1.05;
  letter-spacing: -0.01em;
  color: var(--ink);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--ink-mid);
  outline: none;
  width: 100%;
  padding: 0 0 4px 0;
  margin: 0 0 0.5rem;
  caret-color: var(--ink);
}
.masthead.is-editing .story-header-title-input::placeholder {
  color: var(--ink-light);
  font-style: italic;
}
.masthead.is-editing .story-header-title-input:focus {
  border-bottom-color: var(--ink);
}

.masthead.is-editing .story-header-synopsis-input {
  display: block;
  font-family: var(--font-body);
  font-size: 0.95rem;
  color: var(--ink-mid);
  line-height: 1.45;
  background: transparent;
  border: 1px solid var(--rule-strong);
  outline: none;
  width: 100%;
  resize: vertical;
  padding: 6px 8px;
  margin: 0 0 0.5rem;
}

.masthead.is-editing .story-header-actions {
  display: flex;
  gap: 12px;
  align-items: baseline;
  margin-top: 0.5rem;
}

.story-header-action {
  margin: 0;
  padding: 0;
  background: transparent;
  border: 0;
  cursor: pointer;
  transition: color 120ms ease, background 120ms ease;
}

/* cancel — italic Fraunces underline (mirrors the discard "keep" idiom). */
.story-header-action-cancel {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 400;
  font-variation-settings: 'opsz' 24;
  font-size: 0.95rem;
  line-height: 1;
  color: var(--ink-mid);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}
.story-header-action-cancel:hover { color: var(--ink); }

/* Save — mono-cap, ink-fill (mirrors the discard "Discard" idiom and other
   primary action buttons across the visual language). */
.story-header-action-save {
  padding: 5px 11px 4px;
  background: var(--ink);
  border: 1px solid var(--ink);
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--paper);
}
.story-header-action-save:hover {
  background: var(--paper);
  color: var(--ink);
}

/* ===========================================================================
 * Phase 4 — project-card discard affordance
 *
 * Director's-sheet language: type-only trigger, rubber-stamp confirm. No
 * icon-button — the affordance is a mono-cap `DISCARD` label that sits
 * quietly in the upper-right corner of each polaroid until the card is
 * hovered. Click sets `$deletingProject = <id>` (the data-class binding
 * flips `.project.is-deleting`); the confirm panel then slides up from
 * the bottom of the card, dimming the content and presenting an italic
 * prompt + two side-by-side actions. No modal, no scrim, no global
 * overlay — the question lives on the card itself.
 * =========================================================================*/

.project-delete-trigger,
.project-edit-trigger {
  position: absolute;
  top: 14px;
  z-index: 2;
  margin: 0;
  padding: 0;
  background: transparent;
  border: 0;
  font-family: var(--font-mono);
  font-size: 0.55rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-light);
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: opacity 160ms ease, color 120ms ease;
}

.project-delete-trigger { right: 16px; }
.project-edit-trigger   { left: 16px; }

.project:hover .project-delete-trigger,
.project:hover .project-edit-trigger { opacity: 1; }
.project-delete-trigger:hover,
.project-edit-trigger:hover { color: var(--ink); }

/* Hide both triggers while editing — buttons should not compete with
   the active edit-mode controls. The edit trigger is also hidden while
   deleting so the confirm panel takes priority unambiguously. */
.project.is-editing .project-delete-trigger,
.project.is-editing .project-edit-trigger,
.project.is-deleting .project-edit-trigger { display: none; }

/* Confirm panel — hidden by default, revealed when `.is-deleting`. */
.project-delete-confirm {
  display: none;
}

/* `.is-deleting` straightens the card from its tilt and frames it in a
   thick ink rule — the polaroid is "selected for removal." Content layer
   dims so the prompt has visual primacy without removing the card from
   the layout (it stays in place; it just transforms). */
.project.is-deleting {
  transform: rotate(0deg) translate(-2px, -3px);
  outline: 2px solid var(--ink);
  outline-offset: 2px;
  box-shadow: 7px 8px 0 var(--shadow);
}
.project.is-deleting:hover {
  transform: rotate(0deg) translate(-2px, -3px); /* lock — no extra hover bump */
}

/* Dim the regular content so the prompt reads as the active surface,
   without removing it from flow (preserves the polaroid silhouette). */
.project.is-deleting .project-card-link {
  opacity: 0.18;
  pointer-events: none;
  transition: opacity 160ms ease;
}
.project.is-deleting .project-delete-trigger,
.project.is-deleting .project-title-input,
.project.is-deleting .project-synopsis-input,
.project.is-deleting .project-actions { display: none; }

.project.is-deleting .project-delete-confirm {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 0;
  background: linear-gradient(
    to bottom,
    transparent 0%,
    var(--paper) 35%,
    var(--paper) 100%
  );
}

/* Top rule — thick ink — gives the panel the "stamped band" feel without
   covering the polaroid silhouette underneath. */
.project-delete-confirm::before {
  content: '';
  position: absolute;
  left: 14px;
  right: 14px;
  bottom: 96px;
  height: 1px;
  background: var(--ink);
}

.project-delete-prompt {
  margin: 0 0 4px;
  padding: 0 16px;
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 500;
  font-variation-settings: 'opsz' 96, 'SOFT' 100, 'WONK' 1;
  font-size: 1.25rem;
  line-height: 1.2;
  color: var(--ink);
}

.project-delete-meta {
  margin: 0 0 14px;
  padding: 0 16px;
  font-family: var(--font-mono);
  font-size: 0.55rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-mid);
}

.project-delete-actions {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 0 16px 16px;
}

/* Keep — italic Fraunces, underlined like a hyperlink. The "low-commitment"
   side of the binary: not a button, just a way out. */
.project-delete-keep {
  margin: 0;
  padding: 0;
  background: transparent;
  border: 0;
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 400;
  font-variation-settings: 'opsz' 24;
  font-size: 0.95rem;
  line-height: 1;
  color: var(--ink-mid);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  cursor: pointer;
  transition: color 120ms ease;
}
.project-delete-keep:hover { color: var(--ink); }

/* Discard — mono-cap, ink-fill, the heavy commitment. Visually identical
   weight to the existing primary `.project-action.primary` so the
   destructive action reads as "the firm choice" without introducing a
   red accent (would break monochrome). Letter-spaced like a rubber stamp. */
.project-delete-confirm-action {
  margin: 0;
  padding: 5px 11px 4px;
  background: var(--ink);
  border: 1px solid var(--ink);
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--paper);
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease;
}
.project-delete-confirm-action:hover {
  background: var(--paper);
  color: var(--ink);
}

/* ===========================================================================
 * Phase 4 — canvas-paper add-act affordance
 * =========================================================================*/

.act-add {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  margin: 0 3rem 4rem;
  padding: 1.5rem 2rem;
  background: transparent;
  border: 1px dashed var(--ink-light);
  color: var(--ink-mid);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  cursor: pointer;
  transition: border-color 120ms, color 120ms;
}
.act-add:hover { border-color: var(--ink); color: var(--ink); }
.act-add .plus {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.5rem;
  font-variation-settings: 'opsz' 96, 'SOFT' 100, 'WONK' 1;
}

/* ===========================================================================
 * Phase 4 — doodle-canvas (interactive standalone overlay)
 * =========================================================================*/

.doodle-canvas {
  position: relative;
  width: 100%;
  max-width: 320px;
}

.doodle-canvas-surface {
  position: relative;
  cursor: crosshair;
  background: var(--paper);
  background-image:
    linear-gradient(to right,  var(--rule) 1px, transparent 1px),
    linear-gradient(to bottom, var(--rule) 1px, transparent 1px);
  background-size: 16px 16px;
  border: 1px solid var(--ink);
}

.doodle-canvas-surface svg.doodle {
  width: 100%;
  height: 100%;
  display: block;
  touch-action: none;
}

.doodle-canvas-surface svg path {
  fill: none;
  stroke: var(--ink);
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* ===========================================================================
 * 5 — AI scene-image generation
 *
 * The storyboard-view defelem renders `style="--scene-aspect: <a> / <b>"` on
 * its root wrapper based on the project's `:aspect-ratio`. CSS reads it via
 * `var(--scene-aspect)` so the same scene-image well, doodle canvas, and AI
 * `<img>` all stay in lockstep with the project's chosen ratio.
 *
 * `.is-generating` lives on two scopes:
 *  - `.story-app.is-generating` — root-level treatment that softly disables
 *    the whole view while a generate run is in flight (driven by the
 *    `$generatingScene` non-empty signal). Pointer-events stay on for the
 *    affordances inside the active card so the loader can clear naturally.
 *  - `.scene.is-generating` — the per-card overlay + spinner, with
 *    `pointer-events: none` to disable clicks on the active card so the
 *    user can't fire a second generate while the first runs.
 * =========================================================================*/

.story-app {
  /* Default to 16:9 if the storyboard-view defelem ever forgets to set
     this on the root style. Children read `var(--scene-aspect)` so they
     never have to know what ratio is in play. */
  --scene-aspect: 16 / 9;
}

.scene-image {
  /* Override the original `aspect-ratio: 1` (set above for the polaroid
     image well) with the project's chosen ratio. */
  aspect-ratio: var(--scene-aspect);
}

/* AI image fills the well with the same aspect-ratio. The `<img>` is the
   only direct child of `.scene-image` for AI-generated scenes; it absorbs
   the well's sizing entirely. */
.scene-image .scene-ai-image {
  aspect-ratio: var(--scene-aspect);
  display: block;
  width: 100%;
  height: 100%;
}

/* Doodle canvas (active drawing surface) honors the project ratio. The
   stored doodle uses `inset:0`/100% sizing so it inherits whatever
   .scene-image's aspect-ratio resolves to, no extra rule needed. */
.doodle-canvas-surface {
  aspect-ratio: var(--scene-aspect);
}

/* ---------------------------------------------------------------------------
 * 5.1 — ✱ generate-image trigger
 * --------------------------------------------------------------------------- */

.scene-generate-trigger {
  position: absolute;
  top: 6px;
  right: 38px; /* sits left of the .scene-doodle-trigger (right: 6px) */
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  background: var(--paper);
  border: 1px solid var(--ink);
  color: var(--ink);
  cursor: pointer;
  opacity: 0.85;
  transition: opacity 120ms ease, transform 120ms ease, background 120ms ease;
  z-index: 2;
}

.scene-generate-trigger:hover:not([disabled]) {
  opacity: 1;
  transform: translateY(-1px);
  background: var(--ink);
  color: var(--paper);
}

/* Disabled state — when the scene has no description, generation would
   silently fail server-side (Q21). Render the button visibly inert so
   the user sees the affordance but can't fire it; the title attribute
   carries "Add a scene description first" via the disabled-cursor. */
.scene-generate-trigger[disabled] {
  cursor: not-allowed;
  opacity: 0.35;
}

.scene-generate-glyph {
  font-family: var(--font-display);
  font-size: 14px;
  line-height: 1;
  font-weight: 500;
}

/* Hide both action triggers (✎ doodle + ✱ generate) while doodling or
   generating so they don't compete with the active overlay. */
.scene.is-doodling   .scene-generate-trigger,
.scene.is-generating .scene-generate-trigger,
.scene.is-generating .scene-doodle-trigger {
  display: none;
}

/* ---------------------------------------------------------------------------
 * 5.2 — Generation loader overlay
 *
 * A translucent veil over the .scene-image area with a centered spinner.
 * Visible only when `.scene.is-generating`. Disables clicks on the active
 * card so a second click can't trigger a duplicate run.
 * --------------------------------------------------------------------------- */

.scene-generate-loader {
  position: absolute;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(var(--paper-rgb), 0.75); /* paper @ 75% */
  pointer-events: none;
  z-index: 3;
}

.scene.is-generating .scene-generate-loader {
  display: flex;
}

.scene.is-generating {
  pointer-events: none;
}

/* Re-enable pointer-events on the loader's container (the .scene-image)
   isn't strictly necessary because nothing inside it is interactive while
   generating, but keep clicks from accidentally bubbling outward. */

.scene-generate-spinner {
  width: 28px;
  height: 28px;
  border: 2px solid var(--ink);
  border-right-color: transparent;
  border-radius: 50%;
  animation: scene-generate-spin 0.9s linear infinite;
}

@keyframes scene-generate-spin {
  to { transform: rotate(360deg); }
}

/* ---------------------------------------------------------------------------
 * 5.3 — View-level soft-disable while any scene is generating
 *
 * Dims the canvas slightly so the user sees something is in flight. The
 * loader card itself paints fully opaque on top because `.scene-generate-loader`
 * is positioned `inset: 0` and the dim is just opacity on `.canvas` siblings.
 * --------------------------------------------------------------------------- */

.story-app.is-generating .canvas {
  opacity: 0.92;
  transition: opacity 200ms ease;
}

/* ===========================================================================
 * 6 — Wide scene-card with stacked layout
 *
 * Cards are wider than the original 224px polaroid so the AI image and
 * description both have room. Stack image-on-top + body-below: gives the
 * doodle / view full card width, and lets the description wrap at a
 * comfortable line length. Storyboard-view sets `--scene-aspect` on its
 * root; the image well uses it (section 5) so the image area is always at
 * the project's chosen ratio while the card itself grows naturally.
 * =========================================================================*/

.scene {
  /* Override the original 224px portrait card with a wider stacked one.
     Card height grows to fit image-then-body; no outer aspect-ratio so
     long descriptions don't overflow a locked outline. */
  width: 540px;
  flex: 0 0 540px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.scene .scene-image {
  /* Take the full card width; height is computed from
     `aspect-ratio: var(--scene-aspect)` set in section 5. */
  width: 100%;
  margin-top: 0;
  flex: 0 0 auto;
}

/* Body wrapper introduced in scene-card so title / description / meta /
   actions stack as a column under the image. `min-width: 0` lets long
   single-word titles wrap instead of forcing the card wider. */
.scene .scene-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* The action bar pins to the bottom of the body when in edit mode so
   Save / Cancel / Remove always sit at the foot of the body. */
.scene .scene-body .scene-actions {
  margin-top: auto;
}

/* ===========================================================================
 * 7 — Lightbox modal for AI scene images
 *
 * Click on `.scene-ai-image` flips the page-level `$lightboxImage` signal
 * to the image's src. The modal element below renders the same image at
 * full size centered on a dimmed backdrop. Click the backdrop or press
 * Escape to clear the signal — Datastar's `data-show` then hides the
 * modal via `display: none`.
 * =========================================================================*/

.scene-lightbox {
  position: fixed;
  inset: 0;
  /* Default display is flex; Datastar's data-show toggles `display: none`
     when `$lightboxImage` is empty. */
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(var(--paper-rgb), 0.92);
  padding: 24px;
  z-index: 1000;
  cursor: zoom-out;
  /* Smooth fade-in via opacity transition; the show/hide is binary but
     this softens the appearance. */
  animation: scene-lightbox-in 140ms ease-out;
}

@keyframes scene-lightbox-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.scene-lightbox-image {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  background: var(--paper);
  border: 2px solid rgba(var(--ink-rgb), 0.92);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
  /* Reset zoom-out cursor — clicking the image itself shouldn't close
     the modal; only clicking the backdrop should. The defelem stops
     propagation on the image click. */
  cursor: default;
}

/* ---------------------------------------------------------------------------
 * 5.4 — Generation-error toast (Q21)
 *
 * Surfaced when `$generationError` is non-empty. Click anywhere on the
 * toast to dismiss. Without this surface, server-side bails (no
 * description, openai failure, etc.) silently set the signal and the
 * user sees no feedback for a click they just made.
 * --------------------------------------------------------------------------- */
.scene-generate-error {
  position: fixed;
  top: 1.25rem;
  right: 1.25rem;
  z-index: 900;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  max-width: 24rem;
  padding: 0.75rem 1rem;
  background: var(--paper);
  border: 1px solid var(--ink);
  box-shadow: 4px 5px 0 var(--shadow);
  font-family: var(--font-body);
  font-size: 0.92rem;
  line-height: 1.35;
  color: var(--ink);
  cursor: pointer;
  animation: scene-generate-error-in 180ms ease-out;
}

@keyframes scene-generate-error-in {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: translateY(0); }
}

.scene-generate-error-icon {
  flex: 0 0 auto;
  width: 1.5rem;
  height: 1.5rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--ink);
  background: var(--paper-tinted);
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: 0.85rem;
}

.scene-generate-error-msg {
  flex: 1 1 auto;
  font-style: italic;
}

.scene-generate-error-dismiss {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 0.55rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-light);
}

.scene-generate-error:hover .scene-generate-error-dismiss { color: var(--ink); }

/* ===========================================================================
 * v2 — schema-v2 surfaces (frames, references, camera, transitions, art-strip)
 *
 * Origin: bases/storyboard/specs/v2-prototype.css (locked 2026-05-07).
 * Threshold note: this block pushes the file ~600 lines past the html-sandbox
 * 1500-line split threshold. Split into v2-frames / v2-references / etc.
 * deferred — keeping additive on this single file matches Phase 4 Key Decision
 * "Additive on storyboard-ui.css preserves v1 styles".
 *
 * Section index:
 *   v2.1  Frame strip + frame pane + frame pager
 *   v2.2  Filmstrip thumbs
 *   v2.3  Editable text fields (title-edit, prompt-area, synopsis-edit, style-anchor-edit)
 *   v2.4  Field labels + scope chips + helpers
 *   v2.5  Composed-prompt block (PROMPT/IMAGE_LIST/SIZE table)
 *   v2.6  Reference chips (with thumbnail previews)
 *   v2.7  Cinematography meta-box + control selectors
 *   v2.8  Anchor-positioned tooltip (cine-tip)
 *   v2.9  Frame action bar (generate / reroll / inspect)
 *   v2.10 Outputs row (footer per scene — was "WILL GENERATE")
 *   v2.11 Transition pill (paired-frame continuity preview)
 *   v2.12 Project art-direction strip (style plates, cast grid, character cards, pipeline panel)
 *   v2.13 Inspect side-panel
 *   v2.14 Lucide icon sizing
 * =========================================================================*/

/* v2.1 — Frame pane + pager ------------------------------------------------ */

.scene-image .frame-pane {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--paper-tinted);
}
.scene-image .frame-pane img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.scene-image .frame-pane.is-pending,
.scene-image.is-empty {
  background:
    repeating-linear-gradient(135deg, transparent 0 12px, var(--rule) 12px 13px),
    var(--paper-tinted);
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-light);
  display: flex;
  align-items: center;
  justify-content: center;
}

.frame-pager {
  position: absolute;
  bottom: 6px;
  right: 6px;
  display: flex;
  align-items: center;
  gap: 2px;
  background: var(--paper);
  color: var(--ink);
  border: 1px solid var(--ink);
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  padding: 1px 4px;
  z-index: 2;
}
.frame-pager button {
  background: transparent;
  border: 0;
  color: inherit;
  cursor: pointer;
  padding: 1px 3px;
  font: inherit;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.frame-pager button:hover { background: var(--ink); color: var(--paper); }
.frame-pager .count { padding: 0 4px; }

/* v2.2 — Filmstrip thumbs -------------------------------------------------- */

.filmstrip {
  display: flex;
  gap: 4px;
  padding: 8px 0 0;
  overflow-x: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.filmstrip::-webkit-scrollbar { display: none; }
.filmstrip-thumb {
  flex: 0 0 64px;
  position: relative;
  aspect-ratio: var(--scene-aspect);
  background: var(--paper-tinted);
  border: 1px solid var(--ink);
  cursor: pointer;
  padding: 0;
  transition: transform 120ms;
  overflow: hidden;
}
.filmstrip-thumb:hover { transform: translate(-1px, -1px); box-shadow: 2px 2px 0 var(--shadow); }
.filmstrip-thumb.is-active { outline: 2px solid var(--ink); outline-offset: 2px; }

/* Wrapper around thumb-button + hover-✕ remove. Sized to the thumb's
 * own flex basis so the layout doesn't shift when wrapped thumbs sit
 * beside un-wrapped ones (the read-only filmstrip case). When wrapped,
 * the inner thumb is no longer a direct flex child of `.filmstrip`, so
 * its own `flex: 0 0 64px` would be ignored — explicitly fill the wrap. */
.filmstrip-thumb-wrap {
  position: relative;
  flex: 0 0 64px;
}
.filmstrip-thumb-wrap > .filmstrip-thumb {
  width: 100%;
}
.filmstrip-thumb-remove {
  position: absolute;
  top: -6px;
  right: -6px;
  z-index: 4;
  width: 18px;
  height: 18px;
  padding: 0;
  border: 1px solid var(--ink);
  border-radius: 50%;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: opacity 160ms ease;
}
.filmstrip-thumb-wrap:hover .filmstrip-thumb-remove { opacity: 1; }
.filmstrip-thumb-remove:hover { background: var(--discard-warm); color: var(--paper); }
.filmstrip-thumb img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.filmstrip-thumb svg.doodle-mini {
  width: 70%;
  height: 70%;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
.filmstrip-thumb svg.doodle-mini path,
.filmstrip-thumb svg.doodle-mini circle {
  fill: none;
  stroke: var(--ink);
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.filmstrip-thumb .at-chip {
  position: absolute;
  top: 2px;
  left: 2px;
  font-family: var(--font-mono);
  font-feature-settings: 'onum' 0;
  font-size: 0.55rem;
  letter-spacing: 0.04em;
  background: var(--paper);
  color: var(--ink);
  padding: 0 3px;
  border: 1px solid var(--ink);
}
.filmstrip-thumb.is-pending {
  background:
    repeating-linear-gradient(135deg, transparent 0 6px, var(--rule) 6px 7px),
    var(--paper-tinted);
}
.filmstrip-add {
  flex: 0 0 64px;
  aspect-ratio: var(--scene-aspect);
  background: transparent;
  border: 1px dashed var(--ink-light);
  color: var(--ink-light);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.4rem;
  font-variation-settings: 'opsz' 96, 'SOFT' 100, 'WONK' 1;
  cursor: pointer;
  line-height: 1;
  padding: 0;
}
.filmstrip-add:hover { border-color: var(--ink); color: var(--ink); }
.filmstrip-add .lucide { width: 1.1rem; height: 1.1rem; }

/* v2.2.1 — Per-frame description (Q24) ------------------------------------
   One <p.frame-desc> per frame, swapped client-side via data-show on
   $current_<sid>. Sits between the filmstrip and the cinematography
   panel. The mono-caps "FRAME N · " prefix sets it apart from the
   scene-level description in .scene-body below. */
.frame-desc-stack {
  margin: 0.5rem 0 0.4rem;
}
.frame-desc {
  margin: 0;
  font-size: 0.84rem;
  line-height: 1.4;
  color: var(--ink);
}
.frame-desc-prefix {
  font-family: var(--font-mono);
  font-size: 0.58rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-light);
  margin-right: 0.15rem;
}
.frame-desc-empty {
  color: var(--ink-light);
  font-style: italic;
}

/* Q28: subtle metadata tag indicating frame[0] is showing the scene's
   description as a fallback. Mono-caps so it reads as label, not prose. */
.frame-desc-inherited-hint {
  font-family: var(--font-mono);
  font-size: 0.55rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-light);
  margin-left: 0.25rem;
}

/* Click-to-edit affordance on the read-only line — subtle hover so the
   line reads as interactive without competing with the title-click /
   pencil triggers nearby. */
.frame-desc[data-on\:click]:hover {
  cursor: text;
  background: var(--paper-tinted);
  outline: 1px dashed var(--ink-light);
  outline-offset: 2px;
}

/* Per-frame edit textarea (Q25). Sized to match the read-only line so
   the swap doesn't shift surrounding layout. */
.frame-desc-input {
  display: block;
  width: 100%;
  box-sizing: border-box;
  margin: 0;
  padding: 0.35rem 0.5rem;
  font-family: var(--font-body);
  font-size: 0.84rem;
  line-height: 1.4;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--ink-mid);
  border-radius: 0;
  outline: none;
  resize: vertical;
  min-height: 2.4rem;
}
.frame-desc-input:focus { border-color: var(--ink); }

/* v2.3 — Editable text fields --------------------------------------------- */

.title-edit {
  display: block;
  width: 100%;
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 500;
  font-variation-settings: 'opsz' 72, 'SOFT' 100, 'WONK' 1;
  font-size: 1.18rem;
  line-height: 1.1;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--ink-mid);
  outline: none;
  padding: 0 0 2px;
  margin: 14px 0 6px;
  color: var(--ink);
  caret-color: var(--ink);
}
.title-edit:focus {
  border-bottom-color: var(--ink);
  border-bottom-width: 2px;
  padding-bottom: 1px;
}

.prompt-area {
  display: block;
  width: 100%;
  font-family: var(--font-body);
  font-style: italic;
  font-size: 0.92rem;
  line-height: 1.45;
  color: var(--ink);
  caret-color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--rule-strong);
  border-left: 3px solid var(--ink);
  outline: none;
  padding: 8px 10px;
  resize: vertical;
  min-height: 4.4rem;
  margin: 0 0 6px;
}
.prompt-area:focus,
.prompt-area.is-active {
  border-color: var(--ink);
  border-left-width: 3px;
  background: var(--hover-bg);
}
.prompt-area::placeholder { color: var(--ink-light); font-style: italic; }

.style-anchor-edit {
  display: block;
  width: 100%;
  box-sizing: border-box;
  font-family: var(--font-body);
  font-style: italic;
  font-size: 0.95rem;
  line-height: 1.45;
  color: var(--ink-mid);
  caret-color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--rule-strong);
  border-left: 3px solid var(--ink);
  outline: none;
  padding: 8px 10px;
  resize: vertical;
  min-height: 3.4rem;
}
.style-anchor-edit:focus { border-color: var(--ink); background: var(--hover-bg); }

.synopsis-edit {
  display: block;
  width: 100%;
  max-width: 60ch;
  font-family: var(--font-body);
  font-size: 0.95rem;
  line-height: 1.5;
  color: var(--ink);
  caret-color: var(--ink);
  background: transparent;
  border: 0;
  border-bottom: 1px dashed var(--rule-strong);
  outline: none;
  padding: 0 0 4px;
  resize: none;
}
.synopsis-edit:focus { border-bottom-color: var(--ink); border-bottom-style: solid; }

/* v2.4 — Field labels + scope chips + helpers ----------------------------- */

.field-label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: 0.58rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-light);
  margin: 10px 0 4px;
}
.field-label .scope-chip { vertical-align: baseline; }
.field-label .helper {
  margin-left: auto;
  text-transform: none;
  letter-spacing: 0.04em;
  font-size: 0.65rem;
  font-style: italic;
  color: var(--ink-light);
  font-family: var(--font-body);
}

/* Scope chips — letter chip with hue-tinted border (P/S/F) */
.scope-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  font-family: var(--font-mono);
  font-size: 0.55rem;
  font-weight: 600;
  border: 1px solid var(--ink);
  background: var(--paper);
  vertical-align: middle;
  letter-spacing: 0;
}
.scope-chip.is-project { color: var(--chip-project); border-color: var(--chip-project); background: rgba(127, 183, 212, 0.08); }
.scope-chip.is-scene   { color: var(--chip-scene);   border-color: var(--chip-scene);   background: rgba(155, 208, 155, 0.08); }
.scope-chip.is-frame   { color: var(--chip-frame);   border-color: var(--chip-frame);   background: rgba(226, 160, 112, 0.08); }

/* Editable static text — hover reveals an inline edit pencil. */
.editable {
  display: inline-flex;
  align-items: baseline;
  gap: 0.4rem;
  cursor: pointer;
  border-bottom: 1px dashed transparent;
  transition: border-color 120ms;
}
.editable:hover { border-bottom-color: var(--rule-strong); }
.editable .edit-pencil {
  opacity: 0;
  transition: opacity 120ms;
  color: var(--ink-light);
  margin-left: 0.2rem;
}
.editable:hover .edit-pencil { opacity: 1; }

/* v2.5 — Composed-prompt block (PROMPT / IMAGE_LIST / SIZE) --------------- */

.composed-block {
  background: var(--paper-tinted);
  border: 1px dashed var(--rule-strong);
  padding: 0;
  margin: 6px 0 0;
  font-family: var(--font-mono);
  font-size: 0.66rem;
  line-height: 1.5;
}
.composed-block .row {
  display: grid;
  grid-template-columns: 6.2rem 1fr;
  gap: 0;
  border-bottom: 1px solid var(--rule-strong);
}
.composed-block .row:last-child { border-bottom: 0; }
.composed-block .row > .key {
  background: var(--paper-deep);
  padding: 8px 8px 8px 10px;
  font-size: 0.55rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-mid);
  border-right: 1px solid var(--rule-strong);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.composed-block .row > .key .key-name { color: var(--ink); }
.composed-block .row > .key .key-type {
  font-style: italic;
  text-transform: none;
  letter-spacing: 0;
  color: var(--ink-light);
}
.composed-block .row > .val {
  padding: 8px 10px;
  color: var(--ink);
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
}
.composed-block .row > .val .seg-text {
  display: block;
  width: 100%;
  font-family: var(--font-body);
  font-style: italic;
  font-size: 0.84rem;
  color: var(--ink);
  line-height: 1.4;
}
.composed-block .row > .val .seg-faint { color: var(--ink-light); }
.composed-block .row > .val .img-tile {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 6px 2px 2px;
  background: var(--paper);
  border: 1px solid var(--rule-strong);
  font-size: 0.62rem;
}
.composed-block .row > .val .img-tile img {
  width: 18px;
  height: 18px;
  object-fit: cover;
}

/* v2.6 — Reference chips (with thumbnail previews) ------------------------ */

.refs-row {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  align-items: center;
  padding: 8px 0;
  border-top: 1px dashed var(--rule-strong);
  margin: 4px 0 0;
}
.refs-row .label {
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-light);
  margin-right: 4px;
}
.ref-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 7px 2px 2px;
  border: 1px solid var(--ink);
  background: var(--paper);
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.08em;
  color: var(--ink);
  cursor: pointer;
  transition: background 120ms, color 120ms;
}
.ref-chip .ref-thumb {
  width: 22px;
  height: 22px;
  border-right: 1px solid var(--ink);
  margin-right: 4px;
  background: var(--paper-tinted);
  overflow: hidden;
  flex: 0 0 22px;
}
.ref-chip .ref-thumb img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.ref-chip .ref-thumb.is-empty {
  background:
    repeating-linear-gradient(135deg, transparent 0 4px, var(--rule) 4px 5px),
    var(--paper-tinted);
}
.ref-chip:hover { background: var(--ink); color: var(--paper); }
.ref-chip:hover .ref-thumb { border-color: var(--paper); }
.ref-chip:hover .scope-chip { background: var(--paper); color: var(--ink); border-color: var(--paper); }
.ref-chip.is-pulsing {
  animation: pulse 1.4s ease-in-out infinite;
  outline: 2px solid var(--ink);
  outline-offset: 1px;
}
/* Pause the keyframe background-cycle while hovering a pulsing chip so
   the hover's dark bg + light text stay readable. The outline remains
   visible so the chip still reads as "selected". */
.ref-chip.is-pulsing:hover {
  animation: none;
  background: var(--ink);
}
@keyframes pulse {
  0%, 100% { background: var(--paper); }
  50%      { background: var(--paper-deep); }
}
.ref-chip.is-add {
  border: 1px dashed var(--ink-light);
  color: var(--ink-light);
}
.ref-chip.is-add:hover { border-color: var(--ink); color: var(--ink); background: var(--paper); }

/* v2.7 — Cinematography meta-box + control selectors --------------------- */

.cine-box {
  margin: 8px 0 0;
  padding: 8px 10px;
  background: var(--paper-tinted);
  border: 1px dashed var(--rule-strong);
  display: grid;
  /* 2 cols: label + control. (The original 3-col `max-content max-content 1fr`
   * was specced for `label + scope-chip + control`, but Phase 4 didn't render
   * the scope chip so cells flowed wrong — PACING ended up in col 3 of row 1.
   * Restore the scope chip later if the design wants it.) */
  grid-template-columns: max-content 1fr;
  gap: 4px 10px;
  align-items: baseline;
  /* Lift above .outputs-row AND .scene-cast-strip (both at z-1 or z-10) so
   * the anchor-positioned .cine-tip and the cine-menu dropdown paint cleanly
   * over later siblings. The `.scene > *` paper-grain rule pins every direct
   * child to z-index:1, so cine-tip's z-50 and cine-menu's z-30 are trapped
   * inside the cine-box context — cine-box's own z-index is what governs
   * peer ordering. Must be > .scene-cast-strip's z-10 since cine-box is
   * earlier in DOM order (cast-strip would otherwise paint over it). */
  position: relative;
  z-index: 20;
}
.cine-box .heading {
  grid-column: 1 / -1;
  font-family: var(--font-mono);
  font-size: 0.58rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-light);
  padding-bottom: 2px;
}
.cine-box .label {
  font-family: var(--font-mono);
  font-size: 0.65rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-mid);
}
.cine-box .value {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 0.88rem;
  color: var(--ink);
}
.cine-box .control {
  position: relative;
  /* `flex` (not `inline-flex`) + `width: 100%` so each control fills its
   * grid column. Without this, controls size to their content (e.g.,
   * "dolly-in" vs "slow") and visually mis-align row-to-row. */
  display: flex;
  width: 100%;
  /* Without box-sizing: border-box, width: 100% + padding (8 + 22) + border
   * (1 + 1) overflows the grid cell by ~32px and the chevron sits past the
   * card's right edge. */
  box-sizing: border-box;
  align-items: center;
  gap: 6px;
  background: var(--paper);
  border: 1px solid var(--ink);
  padding: 2px 22px 2px 8px;
  font-family: var(--font-display);
  font-style: italic;
  font-size: 0.9rem;
  color: var(--ink);
  cursor: pointer;
  min-width: 9rem;
}
.cine-box .control::after {
  content: '\25BE';
  position: absolute;
  right: 6px;
  top: 50%;
  transform: translateY(-50%);
  font-family: var(--font-mono);
  font-size: 0.6rem;
  color: var(--ink-light);
}
.cine-box .control:hover { background: var(--hover-bg); }
.cine-box .control:hover::after { color: var(--ink); }

/* Cinematography dropdown menu — opens below the control on click. The
 * .cine-menu sits inside the .control span (which is position: relative),
 * absolutely positioned below the control's bottom edge. Datastar gates
 * visibility via `$openCine === '<scene-id>:<which>'`. */
.cine-menu {
  position: absolute;
  top: calc(100% + 2px);
  left: -1px;
  right: -1px;
  z-index: 30;
  background: var(--paper);
  border: 1px solid var(--ink);
  box-shadow: 2px 2px 0 var(--shadow);
  max-height: 16rem;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  padding: 2px 0;
  text-align: left;
}
.cine-menu .cine-option {
  display: block;
  width: 100%;
  background: transparent;
  border: 0;
  padding: 4px 10px;
  font-family: var(--font-display);
  font-style: italic;
  font-size: 0.9rem;
  color: var(--ink);
  cursor: pointer;
  text-align: left;
}
.cine-menu .cine-option:hover { background: var(--hover-bg); }
.cine-menu .cine-option.is-current {
  background: var(--paper-tinted);
  font-weight: 600;
}

/* v2.8 — Anchor-positioned tooltip (cine-tip) ----------------------------- */

/* `.tip-anchor` carries a unique CSS `anchor-name` per consumer (derived from
 * scene-id + control-name). The fallback for browsers without anchor support
 * is `position: relative` on the anchor + manual `top/left` on the tip — the
 * tip simply lands at the default flow position. Chrome / Edge 125+ supports. */
.tip-anchor {
  position: relative;
}
.tip-anchor .tip-trigger {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  cursor: help;
}
.tip-anchor .tip-trigger .info-icon {
  color: var(--ink-light);
  transition: color 120ms;
}
.tip-anchor:hover .tip-trigger .info-icon,
.tip-anchor:focus-within .tip-trigger .info-icon { color: var(--ink); }

.cine-tip {
  position: absolute;
  top: calc(anchor(bottom) + 6px);
  left: anchor(left);
  width: 22rem;
  z-index: 50;

  background: var(--ink);
  color: var(--paper);
  border: 1px solid var(--ink);
  padding: 10px 12px;
  font-family: var(--font-body);
  font-size: 0.85rem;
  line-height: 1.45;
  box-shadow: 4px 5px 0 var(--shadow);

  opacity: 0;
  transform: translateY(-4px);
  transition: opacity 140ms ease, transform 140ms ease;
  pointer-events: none;
  position-try-fallbacks: flip-block, flip-inline;
}
.tip-anchor:hover > .cine-tip,
.tip-anchor:focus-within > .cine-tip {
  opacity: 1;
  transform: translateY(0);
  /* keep pointer-events: none even when shown — the tooltip is informational
   * (no clickable links inside) and its visual area would otherwise block
   * clicks on neighboring controls underneath. */
}
.cine-tip .tip-head {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1rem;
  color: var(--paper);
  margin: 0 0 4px;
}
.cine-tip .tip-body { margin: 0 0 6px; color: rgba(var(--paper-rgb), 0.82); }
.cine-tip .tip-meta {
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px dashed rgba(var(--paper-rgb), 0.25);
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.1em;
  color: var(--paper-deep);
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.cine-tip .tip-meta .key { color: var(--paper); }
.cine-tip::before {
  content: '';
  position: absolute;
  top: -5px;
  left: 14px;
  width: 8px;
  height: 8px;
  background: var(--ink);
  border-left: 1px solid var(--ink);
  border-top: 1px solid var(--ink);
  transform: rotate(45deg);
}

/* v2.9 — Frame action bar ------------------------------------------------- */

.frame-action-bar {
  display: flex;
  gap: 6px;
  align-items: center;
  margin: 8px 0 0;
  padding: 8px 0 0;
  border-top: 1px dashed var(--rule-strong);
}
.btn-generate {
  flex: 1;
  background: var(--ink);
  color: var(--paper);
  border: 1px solid var(--ink);
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  padding: 6px 10px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}
.btn-generate .star {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1rem;
  line-height: 1;
}
.btn-generate:hover { background: var(--ink-hover); }
.btn-generate.is-secondary {
  flex: 0 0 auto;
  background: transparent;
  color: var(--ink);
  font-size: 0.6rem;
}
.btn-generate.is-secondary:hover { background: var(--paper-deep); }

/* v2.10 — Outputs row ----------------------------------------------------- */

/* Re-frame from prototype's "WILL GENERATE" — this app produces data shapes,
 * external systems generate video. The footer describes outputs declaratively. */
.outputs-row {
  margin-top: 8px;
  padding: 8px 10px;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.16em;
  display: flex;
  align-items: center;
  gap: 8px;
  position: relative;
}
.outputs-row::before {
  content: 'OUTPUTS \2192';
  font-size: 0.55rem;
  letter-spacing: 0.22em;
  color: var(--paper-deep);
  margin-right: 0.5rem;
}
.outputs-row .clip-pair {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.outputs-row .clip-thumb {
  width: 28px;
  height: 16px;
  background: var(--paper-tinted);
  border: 1px solid var(--paper);
  overflow: hidden;
  flex: 0 0 auto;
}
.outputs-row .clip-thumb img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.outputs-row .clip-thumb.is-empty {
  background:
    repeating-linear-gradient(135deg, transparent 0 4px, rgba(var(--paper-rgb), 0.2) 4px 5px);
}
.outputs-row .clip-arrow { color: var(--paper-deep); font-size: 0.7rem; }
.outputs-row .clip-meta {
  margin-left: auto;
  font-size: 0.6rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--paper);
}
.outputs-row .clip-static {
  font-style: italic;
  color: var(--paper-deep);
  font-size: 0.65rem;
  letter-spacing: 0.1em;
  text-transform: none;
}
.outputs-row .empty-msg {
  font-style: italic;
  color: var(--paper-deep);
  font-size: 0.65rem;
  letter-spacing: 0.1em;
  text-transform: none;
}

/* v2.11 — Transition pill (paired-frame continuity preview) -------------- */

.transition-pill {
  flex: 0 0 auto;
  align-self: center;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 0.6rem 0.4rem;
  background: transparent;
  border: 0;
  cursor: pointer;
  font-family: var(--font-mono);
  color: var(--ink-mid);
  position: relative;
  transition: color 120ms;
}
.transition-pill:hover { color: var(--ink); }
.transition-pill .continuity-pair {
  display: flex;
  align-items: center;
  gap: 3px;
}
.transition-pill .continuity-thumb {
  width: 28px;
  height: 16px;
  background: var(--paper-tinted);
  border: 1px solid var(--ink);
  overflow: hidden;
}
.transition-pill .continuity-thumb img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.transition-pill .continuity-thumb.is-empty {
  background:
    repeating-linear-gradient(135deg, transparent 0 4px, var(--rule) 4px 5px),
    var(--paper-tinted);
}
.transition-pill .glyph {
  font-family: var(--font-display);
  font-style: italic;
  font-variation-settings: 'opsz' 96, 'SOFT' 100, 'WONK' 1;
  font-size: 1.4rem;
  line-height: 1;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--ink);
  width: 1.9rem;
  height: 1.9rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-shadow: 2px 2px 0 var(--shadow);
}
.transition-pill .name {
  font-size: 0.55rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-mid);
}

/* v2.12 — Project art-direction strip + character cards + pipeline panel - */

/* Post-shipping (foundational-references): restructure to two rows.
 * Row 1: image dominant on the left (3fr), narrow right rail (1fr) stacking
 * textarea + pipeline. Row 2: cast spans full width.
 * Mobile (< 60rem): everything stacks single-column. */

.art-strip {
  display: flex;
  flex-direction: column;
  gap: 2rem;
  padding: 0 3rem 2.25rem;
  margin: 0 0 3rem;
  border-bottom: 1px solid var(--rule-strong);
}

.art-strip-top {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}
@media (min-width: 60rem) {
  .art-strip-top {
    display: grid;
    grid-template-columns: 3fr 1fr;
    gap: 2rem;
    align-items: start;
  }
}

.art-strip-right-rail {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  min-width: 0;
}

/* The Anchor textarea grows vertically to fill the right rail when the
 * rail's natural height is less than the image's; CSS Grid won't stretch
 * children by default but flex-direction: column + flex-grow on the
 * textarea works because the textarea is itself a flex container parent. */
.art-block-anchor {
  flex: 1 1 auto;
  min-height: 0;
}
.art-block-anchor .style-anchor-edit {
  flex: 1 1 auto;
  min-height: 7rem;
}
.art-block {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
.art-block .art-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-light);
}
.art-block .art-head .scope-chip { vertical-align: baseline; }

.style-plate {
  position: relative;
  border: 1px solid var(--ink);
  background: var(--paper-tinted);
  aspect-ratio: 16 / 9;
  overflow: hidden;
  box-shadow: 4px 5px 0 var(--shadow);
}
.style-plate img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.style-plate.is-empty {
  background:
    repeating-linear-gradient(135deg, transparent 0 12px, var(--rule) 12px 13px),
    var(--paper-tinted);
}
.style-plate-caption {
  font-family: var(--font-body);
  font-style: italic;
  font-size: 0.92rem;
  color: var(--ink-mid);
  line-height: 1.4;
}
.style-gallery {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  align-items: stretch;
}
.style-plate-add {
  flex: 0 0 96px;
  aspect-ratio: 16 / 9;
  background: transparent;
  border: 1px dashed var(--ink-light);
  color: var(--ink-light);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.4rem;
  font-variation-settings: 'opsz' 96, 'SOFT' 100, 'WONK' 1;
  cursor: pointer;
  line-height: 1;
}
.style-plate-add:hover { border-color: var(--ink); color: var(--ink); }

/* Post-shipping (foundational-references): cast cards live below the
 * image+textarea+pipeline top row now, so they have room to breathe.
 *
 * The grid is kept as fallback for stories/sandbox that still render
 * standalone character-card defelems; the art-strip now uses a
 * master-detail pattern (thumb strip + large detail pane) instead. */
.cast-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 1.25rem;
}

/* ── Cast master-detail ─────────────────────────────────────────────
 * Horizontal thumb strip (always visible, bird's-eye view) + a single
 * large detail pane below for the selected character. Driven by the
 * `$activeCastMember` page-level signal. */

.cast-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 1.25rem;
}

.cast-thumb {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  width: 78px;
  padding: 6px 4px 4px;
  background: var(--paper);
  border: 1px solid var(--rule);
  cursor: pointer;
  transition: transform 120ms ease, border-color 120ms ease, box-shadow 120ms ease;
  font-family: var(--font-display);
  font-style: italic;
  color: var(--ink);
}
.cast-thumb:hover {
  transform: translate(-1px, -1px);
  border-color: var(--ink);
}
.cast-thumb.is-active {
  border-color: var(--ink);
  box-shadow: 0 0 0 2px var(--ink);
  background: var(--paper-tinted);
}
.cast-thumb-face {
  width: 64px;
  height: 64px;
  background: var(--paper-tinted);
  border: 1px solid var(--ink);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}
.cast-thumb-face img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.cast-thumb-empty {
  font-size: 1.4rem;
  color: var(--ink-light);
}
.cast-thumb-name {
  font-size: 0.78rem;
  line-height: 1.05;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cast-thumb-add {
  border-style: dashed;
  color: var(--ink-light);
  justify-content: center;
}
.cast-thumb-add .plus {
  font-size: 2.4rem;
  line-height: 0.9;
  padding: 14px 0;
}

.cast-detail {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.5rem;
  align-items: start;
}
@media (min-width: 60rem) {
  .cast-detail {
    grid-template-columns: minmax(0, 320px) minmax(0, 1fr);
  }
}

.cast-detail-image {
  position: relative;
  aspect-ratio: 1;
  border: 1px solid var(--ink);
  background: var(--paper-tinted);
  overflow: hidden;
  box-shadow: 4px 5px 0 var(--shadow);
}
.cast-detail-face,
.cast-detail-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.cast-detail-face.is-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-body);
  font-style: italic;
  color: var(--ink-light);
  padding: 1rem;
  text-align: center;
}
.cast-detail-generate {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 2;
  width: 32px;
  height: 32px;
  background: var(--paper);
  border: 1px solid var(--ink);
  color: var(--ink);
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.15rem;
  line-height: 1;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.cast-detail-generate[disabled] {
  opacity: 0.35;
  cursor: not-allowed;
}
.cast-detail-remove {
  position: absolute;
  top: 8px;
  left: 8px;
  z-index: 2;
  background: transparent;
  border: 0;
  font-family: var(--font-mono);
  font-size: 0.65rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-light);
  cursor: pointer;
  opacity: 0;
  transition: opacity 160ms ease, color 120ms ease;
}
.cast-detail-image:hover .cast-detail-remove { opacity: 1; }
.cast-detail-remove:hover { color: var(--ink); }

/* Phase 4.3g confirm overlay — covers the image well while the user
 * decides. Visibility gated by `data-show="$deletingCharacter === '<cid>'"`. */
.cast-detail-confirm {
  position: absolute;
  inset: 0;
  z-index: 4;
  background: rgba(var(--ink-rgb), 0.92);
  color: var(--paper);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  text-align: center;
  gap: 0.75rem;
}
.cast-detail-confirm-msg {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.15rem;
  margin: 0;
}
.cast-detail-confirm-sub {
  font-family: var(--font-body);
  font-size: 0.85rem;
  color: rgba(var(--paper-rgb), 0.75);
  margin: 0;
  line-height: 1.4;
}
.cast-detail-confirm-actions {
  display: flex;
  gap: 8px;
  margin-top: 0.4rem;
}
.cast-detail-confirm-cancel,
.cast-detail-confirm-yes {
  font-family: var(--font-mono);
  font-size: 0.65rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  border: 1px solid var(--paper);
  background: transparent;
  color: var(--paper);
  padding: 6px 14px;
  cursor: pointer;
}
.cast-detail-confirm-yes {
  background: var(--discard-warm);
  border-color: var(--discard-warm);
}
.cast-detail-confirm-yes:hover { background: var(--discard-warm-hover); }
.cast-detail-confirm-cancel:hover { background: rgba(var(--paper-rgb), 0.12); }
.cast-detail-loader {
  display: none;
  position: absolute;
  inset: 0;
  background: rgba(var(--paper-rgb), 0.7);
  align-items: center;
  justify-content: center;
  z-index: 3;
  pointer-events: none;
}
.cast-detail.is-generating .cast-detail-loader { display: flex; }
.cast-detail-spinner {
  width: 28px;
  height: 28px;
  border: 2px solid var(--ink);
  border-top-color: transparent;
  border-radius: 50%;
  animation: sp-spin 700ms linear infinite;
}
.cast-detail-image .history-pager,
.scene-image .frame-pane .history-pager {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
}

.cast-detail-form {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
  /* Pull the form up so the first label sits flush with the top of
   * the image well — the label's own margin-top adds breathing room. */
  margin-top: -1.5rem;
}
.cast-detail-label {
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-light);
  margin-top: 4px;
}
.cast-detail-name,
.cast-detail-voice {
  width: 100%;
  box-sizing: border-box;
  border: 1px solid var(--rule-strong);
  border-left: 3px solid var(--ink);
  background: var(--paper);
  padding: 8px 10px;
  outline: none;
}
.cast-detail-name {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.1rem;
}
.cast-detail-voice {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  letter-spacing: 0.06em;
}
.cast-detail-description {
  width: 100%;
  box-sizing: border-box;
  border: 1px solid var(--rule-strong);
  border-left: 3px solid var(--ink);
  background: var(--paper);
  padding: 10px 12px;
  outline: none;
  font-family: var(--font-body);
  font-size: 0.95rem;
  line-height: 1.5;
  resize: vertical;
  min-height: 11rem;
}
.cast-detail-name:focus,
.cast-detail-voice:focus,
.cast-detail-description:focus {
  border-color: var(--ink);
  background: var(--hover-bg);
}

.cast-detail-actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  margin-top: 8px;
}
.cast-detail-cancel,
.cast-detail-save {
  font-family: var(--font-mono);
  font-size: 0.65rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  border: 1px solid var(--ink-light);
  background: var(--paper);
  padding: 6px 12px;
  cursor: pointer;
  color: var(--ink);
}
.cast-detail-save {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
}
.character-card {
  border: 1px solid var(--ink);
  background: var(--paper);
  box-shadow: 3px 3px 0 var(--shadow);
  display: flex;
  flex-direction: column;
  cursor: pointer;
  transition: transform 120ms;
  min-width: 0;
}
.character-card:hover { transform: translate(-1px, -2px); }
.character-card .face {
  width: 100%;
  aspect-ratio: 1;
  border-bottom: 1px solid var(--ink);
  background: var(--paper-tinted);
  overflow: hidden;
}
.character-card .face img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.character-card .name {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1rem;
  padding: 8px 10px 4px;
  line-height: 1.15;
}
.character-card .voice {
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-light);
  padding: 4px 10px 8px;
}
.character-card.is-add {
  border: 1px dashed var(--ink-light);
  box-shadow: none;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Match the natural height of a populated card so the grid stays
   * visually aligned (face = aspect-ratio 1 + name + description +
   * voice padding ≈ width × 1.4). */
  aspect-ratio: 5 / 7;
  color: var(--ink-light);
}
.character-card.is-add .plus {
  font-family: var(--font-display);
  font-style: italic;
  font-variation-settings: 'opsz' 144, 'SOFT' 100, 'WONK' 1;
  font-size: 3rem;
  line-height: 1;
}
.character-card.is-add:hover { border-color: var(--ink); color: var(--ink); }

.proj-settings {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  font-size: 0.92rem;
}
.proj-settings .row {
  display: flex;
  align-items: baseline;
  border-bottom: 1px dashed var(--rule-strong);
  padding-bottom: 0.35rem;
  gap: 0.5rem;
}
.proj-settings .row:last-child { border-bottom: 0; }
.proj-settings .label {
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-light);
  flex: 0 0 5.5rem;
}
.proj-settings .value {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 0.92rem;
  color: var(--ink);
}

/* v2.13 — Inspect side-panel --------------------------------------------- */

.inspect-panel {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(40rem, 92vw);
  background: var(--paper);
  border-left: 1px solid var(--ink);
  box-shadow: -8px 0 24px rgba(var(--ink-rgb), 0.18);
  z-index: 60;
  transform: translateX(100%);
  transition: transform 220ms ease;
  display: flex;
  flex-direction: column;
}
.inspect-panel.is-visible { transform: translateX(0); }
.inspect-panel .inspect-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: 1rem 1.25rem;
  border-bottom: 1px solid var(--rule-strong);
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-mid);
}
.inspect-panel .inspect-head .title {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.05rem;
  color: var(--ink);
  text-transform: none;
  letter-spacing: 0;
}
.inspect-panel .inspect-close {
  background: transparent;
  border: 0;
  color: var(--ink-mid);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  cursor: pointer;
}
.inspect-panel .inspect-close:hover { color: var(--ink); }
.inspect-panel .inspect-body {
  flex: 1;
  overflow: auto;
  padding: 1rem 1.25rem;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  line-height: 1.5;
  color: var(--ink);
  white-space: pre-wrap;
  word-break: break-word;
  background: var(--paper-tinted);
}

/* Q43 — visual preview block at the top of the inspect panel (doodle /
 * predefined / upload / ai-generated image). When the scene has no
 * image at scene-level OR frame[0], the wrapper stays in the DOM but
 * collapses via :empty so the panel layout stays consistent. */
.inspect-panel .inspect-preview {
  flex: 0 0 auto;
  padding: 0.75rem 1.25rem;
  border-bottom: 1px dashed var(--rule-strong);
  background: var(--paper);
  display: flex;
  align-items: center;
  justify-content: center;
  max-height: 240px;
  overflow: hidden;
}
.inspect-panel .inspect-preview:empty {
  display: none;
}
.inspect-panel .inspect-preview .inspect-preview-image {
  max-width: 100%;
  max-height: 220px;
  object-fit: contain;
  border: 1px solid var(--rule);
}
.inspect-panel .inspect-preview .inspect-preview-svg {
  display: block;
  width: 100%;
  max-height: 220px;
}
.inspect-panel .inspect-preview .inspect-preview-svg .doodle,
.inspect-panel .inspect-preview .inspect-preview-svg svg {
  width: 100%;
  height: auto;
  max-height: 220px;
  stroke: var(--ink);
  fill: none;
}

/* v2.14 — Lucide icon sizing --------------------------------------------- */

.lucide {
  width: 1em;
  height: 1em;
  flex: 0 0 auto;
  stroke: currentColor;
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  vertical-align: -0.15em;
}
.lucide.is-sm { width: 0.85em; height: 0.85em; }
.lucide.is-md { width: 1.15em; height: 1.15em; }

/* Editing-state spotlight for v2 polish — applied to .scene to mirror v1 */
.scene.is-editing {
  transform: rotate(0deg) translate(-1px, -3px);
  outline: 1px solid var(--ink);
  outline-offset: 4px;
  box-shadow:
    6px 8px 0 rgba(var(--ink-rgb), 0.3),
    inset 0 0 0 1px rgba(var(--paper-rgb), 0.7);
}
.scene.is-editing:hover { transform: rotate(0deg) translate(-1px, -3px); }

/* Mobile padding override for .art-strip lives at the end of the file so
 * it wins the source-order tiebreak against the base `.art-strip` rule
 * (~line 2969). Both rules have specificity 0,0,1,0; later source wins.
 * Brings the art-strip's horizontal padding in line with the masthead +
 * act-strip on mobile (1.5rem vs the desktop 3rem). */
@media (max-width: 47.999rem) {
  .art-strip { padding: 0 1.5rem 2.25rem; }
}


/* ─────────────────────────────────────────────────────────────────────
 * Foundational-references Phase 4 — style ✱, character cards (✱/edit/remove),
 * per-scene chip strip, history pager.
 * Mobile-first; desktop overrides at the `min-width: 48rem` breakpoint.
 * ───────────────────────────────────────────────────────────────────── */

/* Style plate — ✱ generate button + loader */
.style-plate-generate {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 24px;
  height: 24px;
  background: var(--paper);
  border: 1px solid var(--ink);
  color: var(--ink);
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  z-index: 2;
  box-shadow: 2px 2px 0 var(--shadow);
}
.style-plate-generate:hover { transform: translate(-1px, -1px); }
.style-plate-generate[disabled] {
  opacity: 0.4;
  cursor: not-allowed;
  pointer-events: none;
}
.style-plate-loader {
  display: none;
  position: absolute;
  inset: 0;
  background: rgba(var(--paper-rgb), 0.6);
  align-items: center;
  justify-content: center;
  pointer-events: none;
  z-index: 1;
}
.style-plate.is-generating .style-plate-loader { display: flex; }
.style-plate.is-generating .style-plate-generate { display: none; }
.style-plate-spinner {
  width: 18px;
  height: 18px;
  border: 2px solid var(--ink-light);
  border-top-color: var(--ink);
  border-radius: 50%;
  animation: sp-spin 0.8s linear infinite;
}
@keyframes sp-spin { to { transform: rotate(360deg); } }

/* Character card — ✱ generate, hover ✕ remove, inline editor, loader */
.character-card { position: relative; }
.character-card .description {
  font-family: var(--font-body);
  font-size: 0.8rem;
  color: var(--ink-mid);
  padding: 0 10px 6px;
  line-height: 1.4;
}
.character-card .character-name-input,
.character-card .character-description-input,
.character-card .character-voice-input {
  display: none;
  width: calc(100% - 20px);
  box-sizing: border-box;
  margin: 4px 10px;
  border: 1px solid var(--ink);
  background: var(--paper);
  font-family: var(--font-body);
  font-size: 0.85rem;
  padding: 6px 8px;
  line-height: 1.4;
}
.character-card .character-description-input {
  resize: vertical;
  min-height: 5rem;
}
.character-card .character-name-input {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1rem;
}
.character-card .character-voice-input {
  font-family: var(--font-mono);
  font-size: 0.7rem;
}
.character-card.is-editing .name,
.character-card.is-editing .description,
.character-card.is-editing .voice {
  display: none;
}
.character-card.is-editing .character-name-input,
.character-card.is-editing .character-description-input,
.character-card.is-editing .character-voice-input {
  display: block;
}
.character-generate {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 26px;
  height: 26px;
  background: var(--paper);
  border: 1px solid var(--ink);
  color: var(--ink);
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  z-index: 2;
}
.character-generate[disabled] {
  opacity: 0.35;
  cursor: not-allowed;
}
.character-remove {
  position: absolute;
  top: 6px;
  left: 8px;
  background: transparent;
  border: 0;
  font-family: var(--font-mono);
  font-size: 0.65rem;
  color: var(--ink-light);
  cursor: pointer;
  opacity: 0;
  transition: opacity 120ms;
  z-index: 2;
}
.character-card:hover .character-remove { opacity: 1; }
.character-loader {
  display: none;
  position: absolute;
  inset: 0;
  background: rgba(var(--paper-rgb), 0.6);
  align-items: center;
  justify-content: center;
  pointer-events: none;
  z-index: 1;
}
.character-card.is-generating .character-loader { display: flex; }
.character-spinner {
  width: 14px;
  height: 14px;
  border: 2px solid var(--ink-light);
  border-top-color: var(--ink);
  border-radius: 50%;
  animation: sp-spin 0.8s linear infinite;
}

/* "+ ADD CHARACTER" tile — flips to inline editor when .is-creating */
.character-card.is-add { position: relative; padding: 10px; }
.character-card.is-add .character-card-add-trigger {
  background: transparent;
  border: 0;
  color: var(--ink-light);
  width: 100%;
  height: 100%;
  cursor: pointer;
  font-family: var(--font-display);
  font-style: italic;
}
.character-card.is-add .character-card-add-editor {
  display: none;
  flex-direction: column;
  gap: 6px;
}
.character-card.is-add.is-creating .character-card-add-trigger { display: none; }
.character-card.is-add.is-creating .character-card-add-editor { display: flex; }
.character-card.is-add .character-name-input {
  display: block;
  width: 100%;
  box-sizing: border-box;
  margin: 0;
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1rem;
  border: 1px solid var(--ink);
  padding: 6px 8px;
}
.character-card.is-add .character-description-input {
  display: block;
  width: 100%;
  box-sizing: border-box;
  margin: 0;
  font-family: var(--font-body);
  font-size: 0.85rem;
  border: 1px solid var(--ink);
  padding: 6px 8px;
  resize: vertical;
  min-height: 5rem;
  line-height: 1.4;
}
.character-create-actions {
  display: flex;
  gap: 6px;
  justify-content: flex-end;
}
.character-create-cancel,
.character-create-save {
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  border: 1px solid var(--ink-light);
  background: var(--paper);
  padding: 4px 10px;
  cursor: pointer;
  color: var(--ink);
}
.character-create-save.primary {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
}

/* Per-scene cast chip strip */
.scene-cast-strip {
  display: flex;
  gap: 0.4rem;
  flex-wrap: wrap;
  align-items: center;
  padding: 0.4rem 0.6rem;
  border-top: 1px dashed var(--rule);
  border-bottom: 1px dashed var(--rule);
  font-family: var(--font-mono);
  font-size: 0.6rem;
  /* Override `.scene > * { z-index: 1 }` so the cast-add dropdown can
     escape and paint above later siblings (outputs-row, scene-body). */
  z-index: 10;
}
.scene-cast-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: var(--paper-tinted);
  border: 1px solid var(--ink-light);
  padding: 2px 6px 2px 2px;
  position: relative;
}
.scene-cast-chip .chip-face {
  display: inline-block;
  width: 18px;
  height: 18px;
  overflow: hidden;
  background: var(--paper);
  border: 1px solid var(--rule-strong);
}
.scene-cast-chip .chip-face.is-empty {
  background:
    repeating-linear-gradient(135deg, transparent 0 4px, var(--rule) 4px 5px),
    var(--paper);
}
.scene-cast-chip .chip-face img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.scene-cast-chip .chip-name {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 0.7rem;
  color: var(--ink);
}
.scene-cast-chip .chip-remove {
  background: transparent;
  border: 0;
  color: var(--ink-light);
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 0.55rem;
  padding: 0 2px;
}
.scene-cast-chip:hover .chip-remove { color: var(--ink); }
.scene-cast-add { position: relative; }
.scene-cast-add-trigger {
  background: transparent;
  border: 1px dashed var(--ink-light);
  color: var(--ink-light);
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  line-height: 1;
  padding: 2px 6px;
}
.scene-cast-add-trigger:hover { border-color: var(--ink); color: var(--ink); }
.scene-cast-menu {
  display: block;
  position: absolute;
  top: 100%;
  left: 0;
  min-width: 8rem;
  background: var(--paper);
  border: 1px solid var(--ink);
  box-shadow: 4px 4px 0 var(--shadow);
  z-index: 30;
  margin-top: 2px;
}
.scene-cast-option {
  display: block;
  width: 100%;
  background: transparent;
  border: 0;
  text-align: left;
  font-family: var(--font-display);
  font-style: italic;
  font-size: 0.85rem;
  padding: 4px 10px;
  cursor: pointer;
  color: var(--ink);
}
.scene-cast-option:hover { background: var(--paper-tinted); }

/* History pager */
.history-pager {
  display: flex;
  align-items: center;
  gap: 4px;
  font-family: var(--font-mono);
  font-size: 0.55rem;
  color: var(--ink-mid);
  padding: 2px 4px;
  background: rgba(var(--paper-rgb), 0.85);
  border-top: 1px solid var(--rule);
}
.history-pager-prev,
.history-pager-next {
  background: transparent;
  border: 0;
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  cursor: pointer;
  padding: 1px 4px;
  line-height: 1;
}
.history-pager-prev:disabled,
.history-pager-next:disabled,
.history-pager-pop:disabled {
  opacity: 0.3;
  cursor: not-allowed;
}
.history-pager-label {
  flex: 1;
  text-align: center;
}
.history-pager-version-count { letter-spacing: 0.1em; }
.history-pager-loading {
  font-style: italic;
  color: var(--ink-light);
  letter-spacing: 0;
  text-transform: lowercase;
}
.history-pager-pop {
  background: transparent;
  border: 0;
  color: var(--ink-mid);
  font-family: var(--font-mono);
  font-size: 0.65rem;
  cursor: pointer;
  padding: 1px 4px;
  line-height: 1;
  margin-left: 4px;
}
.history-pager-pop:not(:disabled):hover {
  color: var(--discard-warm);
}

/* style-plate + character-card history pagers sit at the bottom edge */
.style-plate .history-pager,
.character-card .history-pager {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 2;
}

/* Mobile breakpoint — tighten chip strip and grid spacing under 48rem */
@media (max-width: 47.999rem) {
  .scene-cast-strip { font-size: 0.55rem; padding: 0.3rem 0.5rem; gap: 0.3rem; }
  .scene-cast-chip { padding: 1px 4px 1px 1px; }
  .scene-cast-chip .chip-face { width: 14px; height: 14px; }
  .history-pager { font-size: 0.5rem; }
  .character-card .description { font-size: 0.75rem; }
}
