/* PictureMeClubbing Slideshow Maker — Batch 2 editor shell
   Mobile-first. No horizontal overflow. Large touch targets.

   Dark Editor Theme + Visual Polish batch: this file now ships a real
   Light/Dark theme system built on CSS custom properties ("design tokens"),
   selected by a single `data-theme="dark"|"light"` attribute on <html> (set
   by app/js/theme.js, plus an early inline <head> script in index.html/
   videos.html that applies the persisted/system preference before first
   paint — see those files' own comments for why that duplication exists).
   Every surface/text/border/status color below reads from the token block
   immediately following this comment; there is deliberately no second,
   scattered set of ".dark-mode-something" overrides anywhere else in this
   file — a component's rule references a token ONCE and looks correct in
   both themes automatically, because the token's own VALUE changes between
   the :root block (light, default) and the :root[data-theme="dark"] block
   right after it. Dark Mode is an EDITOR-CHROME-ONLY concept: nothing in
   this file touches canvas rendering (that lives in app/js/*.js, drawing to
   <canvas> with brand colors it reads from brand-state.js, completely
   independent of this stylesheet), and a small number of intentionally
   theme-INVARIANT tokens (--pmc-ink/--pmc-ink-soft, used only for
   canvas-adjacent placeholder chrome like the preview frame backdrop and
   the Adjust Grid Photos reposition frame, which were already dark in the
   original light-only design) are deliberately left constant across both
   themes rather than folded into the light/dark split. */

:root {
  /* ---- Brand accent — identical in both themes (existing PictureMeClubbing
     pink/magenta; the task for this batch explicitly asked to reuse the
     current brand color rather than invent a new one). ---- */
  --pmc-pink: #e91e8c;

  /* ---- Theme-invariant "ink" tones — used only for canvas-adjacent
     placeholder/backdrop chrome (preview frame background, Adjust Grid
     Photos reposition frame, video-card placeholder background, small
     chip badges drawn on top of photo thumbnails) that was already dark in
     the pre-Dark-Mode design and sits immediately next to real photos/video,
     not next to the rest of the editor UI — so it intentionally does not
     shift with the Light/Dark toggle. ---- */
  --pmc-ink: #14141b;
  --pmc-ink-soft: #1f1f29;

  --radius-md: 10px;
  --radius-lg: 16px;
  --touch-min: 44px;

  /* ---- Design tokens: LIGHT theme (default) ----
     Conceptual roles (kept close to this project's pre-existing --pmc-*
     names where one already existed, so the several hundred existing
     `var(--pmc-border)`-style call sites throughout this file automatically
     become theme-aware for free, with zero per-call-site edits — only
     genuinely NEW surface/status concepts this batch introduces get a new
     token name):
       --app-bg              page background
       --panel-bg            top-level card/section background
       --panel-elevated-bg   modal surfaces + the currently-OPEN accordion
                              card's own background (one step "up" from the
                              page, distinct from a collapsed sibling)
       --input-bg            text input / select / textarea surface
       --pmc-text            primary text
       --pmc-text-muted      secondary/muted text
       --pmc-border          "strong" border (card outlines, dropzone,
                              control outlines that need to stay legible)
       --border-subtle       "quiet" divider — replaces most of the
                              dashed-gray-line separators this batch's own
                              task explicitly asked to reduce
       --pmc-pink / hover    accent / accent hover-or-pressed variant
       --pmc-danger/-success/-warning   status text/icon colors
       --*-soft-bg            low-contrast tinted chip backgrounds paired
                              with the status colors above (preset cards,
                              inline warnings, guidance chips, active-filter
                              pills) — restrained accent use, not solid fills
       --shadow-color         subtle elevation shadow tint
       --scrollbar-thumb      custom scrollbar thumb color (where supported) */
  --app-bg: #f4f2f7;
  --panel-bg: #ffffff;
  --panel-elevated-bg: #fbfaff;
  --input-bg: #f6f4f9;
  --pmc-text: #1a1a22;
  --pmc-text-muted: #6b6b78;
  --pmc-border: #ddd7e0;
  --border-subtle: #ece8f0;
  --pmc-pink-dark: #b0136a; /* LIGHT theme: hover/pressed goes DARKER */
  --pmc-danger: #c0334a;
  --danger-soft-bg: #fdecef;
  --pmc-success: #2a6b2f;
  --success-soft-bg: #eef8ee;
  --pmc-warning: #8a6414;
  --warning-soft-bg: #fff8e6;
  --accent-soft-bg: #fdf1f7;
  --accent-soft-border: #f3c9de;
  --pmc-focus: #6d4fe0;
  --shadow-color: rgba(20, 20, 27, 0.14);
  --scrollbar-thumb: #c9c3d3;
  --toggle-track-off: #c9c3d3;
  color-scheme: light;
}

/* ---- Design tokens: DARK theme ----
   Same token NAMES as the light block above — every rule elsewhere in this
   file that reads one of them is automatically correct here too, with no
   separate override rule required. Values follow the batch's own explicit
   visual direction: deep charcoal app background, a slightly lighter
   charcoal for panels, another distinct step for elevated
   surfaces/modals/the open accordion card, and a step DARKER again for
   recessed input fields (a field should read as "sunken into" its panel,
   not as another panel floating on top of it) — background, panel, and
   input are all deliberately different tones, never the same flat black
   (this file's own task explicitly warned against that). Text uses a soft
   off-white rather than pure #fff (glare), and status colors are lightened
   versions of their light-theme counterparts so they stay clearly readable
   against a dark surface instead of reading as muddy dark-on-dark. */
:root[data-theme="dark"] {
  --app-bg: #14131a;
  --panel-bg: #1c1b23;
  --panel-elevated-bg: #242330;
  --input-bg: #121118;
  --pmc-text: #ece9f2;
  --pmc-text-muted: #9c99a8;
  --pmc-border: #3c3a46;
  --border-subtle: #2a2933;
  --pmc-pink-dark: #ff5fb8; /* DARK theme: hover/pressed goes LIGHTER, not darker, to stay visible */
  --pmc-danger: #ff6b81;
  --danger-soft-bg: rgba(255, 107, 129, 0.16);
  --pmc-success: #7ad986;
  --success-soft-bg: rgba(122, 217, 134, 0.14);
  --pmc-warning: #f0b846;
  --warning-soft-bg: rgba(240, 184, 70, 0.14);
  --accent-soft-bg: rgba(233, 30, 140, 0.16);
  --accent-soft-border: rgba(233, 30, 140, 0.45);
  --pmc-focus: #b79dff;
  --shadow-color: rgba(0, 0, 0, 0.55);
  --scrollbar-thumb: #46434f;
  --toggle-track-off: #3c3a46;
  color-scheme: dark;
}

* {
  box-sizing: border-box;
}

html, body {
  margin: 0;
  padding: 0;
  max-width: 100%;
}

/* Mobile/Touch UI QA batch root-cause fix: `overflow-x: hidden` was
   previously declared on BOTH html and body. Per the CSS overflow spec,
   declaring overflow-x as anything other than visible forces the computed
   overflow-y to `auto` too (even though it was never written) — so BOTH
   elements silently became scroll containers, even though the browser's
   real `document.scrollingElement` is (and always was) `html` alone; body's
   own "scroll box" never actually scrolls (its content is always exactly as
   tall as itself), it just sits there satisfying the CSS Positioned
   Layout spec's "nearest ancestor with a scroll box" rule for `position:
   sticky` — which made `.app-header` (a direct child of body) and, at the
   desktop breakpoint, `.workspace-preview` stick relative to body's own
   inert, never-scrolling box instead of the real page scroller, making
   `position: sticky` a complete no-op everywhere in the app, at every
   viewport width, not just mobile. Fixing it needs only ONE of the two
   ancestors to own overflow-x: hidden — html alone is both sufficient (any
   horizontal overflow from body's content is still clipped by html's own
   overflow-x:hidden, since body sits inside html) and correct (it matches
   the browser's actual scrollingElement), so body's copy is removed rather
   than html's. */
html {
  overflow-x: hidden;
}

body {
  font-family: "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  color: var(--pmc-text);
  background: var(--app-bg);
  line-height: 1.4;
}

/* Dark Editor Theme + Visual Polish batch: a short, deliberately narrow
   crossfade on the handful of highest-visual-area surfaces when the theme
   itself changes — NOT a general "animate everything" rule, and gated
   behind prefers-reduced-motion exactly like this file's existing
   .tray-item drag-transition guard, so a user who has asked for less motion
   gets an instant, un-animated theme switch instead. */
@media (prefers-reduced-motion: no-preference) {
  body,
  .app-header,
  .settings-section,
  .settings-subsection,
  .export-dialog,
  .field input,
  .field select,
  .btn {
    transition: background-color 160ms ease, border-color 160ms ease, color 160ms ease;
  }
}

h1, h2, h3 {
  margin: 0 0 0.5rem 0;
  line-height: 1.2;
}

button {
  font-family: inherit;
}

.visually-hidden-input {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* ---------- Buttons ---------- */
/* Three roles, unchanged from before this batch (Export/Save = primary,
   ordinary actions = secondary, destructive = danger) — real-browser
   review of every current button assignment found the existing 3-tier
   system already matches this batch's own requested hierarchy exactly
   (Export/Save Preset primary, Previous/Next/Restart/Cancel/Manage secondary,
   Delete danger), so no button's class was reassigned; only their
   token-driven colors changed. */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--touch-min);
  padding: 0.5rem 1.1rem;
  border-radius: var(--radius-md);
  border: 2px solid transparent;
  font-size: 1rem;
  font-weight: 600;
  cursor: pointer;
  text-align: center;
}

.btn:focus-visible {
  outline: 3px solid var(--pmc-focus);
  outline-offset: 2px;
}

.btn-primary {
  background: var(--pmc-pink);
  color: #fff;
}

.btn-primary:hover:not(:disabled) {
  background: var(--pmc-pink-dark);
}

.btn-secondary {
  background: var(--panel-elevated-bg);
  /* Found via real-browser review while converting this file to tokens:
     this previously read var(--pmc-ink) — always near-black — which was
     harmless on the light theme's white button surface but would have been
     completely unreadable on a dark theme's dark button surface. Every
     other button already used a theme-aware text color; this was the one
     latent exception. */
  color: var(--pmc-text);
  border-color: var(--pmc-border);
}

.btn-secondary:hover {
  border-color: var(--pmc-pink);
}

.btn-danger {
  background: var(--panel-elevated-bg);
  color: var(--pmc-danger);
  border-color: var(--pmc-danger);
}

.btn-danger:hover:not(:disabled) {
  background: var(--pmc-danger);
  color: #fff;
}

.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Dark Editor Theme + Visual Polish batch: 0.5 opacity over a dark button
   surface sits closer to the panel behind it than the same 0.5 does over a
   light surface — bumped slightly darker-theme-only so a disabled button
   stays clearly distinguishable from an enabled one without changing the
   light theme's already-fine appearance. */
:root[data-theme="dark"] .btn:disabled {
  opacity: 0.62;
}

/* Mobile Editor IA Redesign: found via real-browser testing — .btn's own
   `display: inline-flex` above is an AUTHOR rule, so per the CSS cascade it
   always wins over the browser's UA-stylesheet `[hidden] { display: none }`
   rule, regardless of selector specificity (the exact ".export-result[hidden]"
   precedent already documented further down this file) — without this,
   setting `.hidden = true` on any `<button class="btn ...">` (e.g. Update
   Preset/Manage Presets below) silently has no visual effect at all. */
.btn[hidden] {
  display: none;
}

/* ---------- Header ---------- */
/* The app header stays an intentionally dark "ink" bar in BOTH themes — it
   was already dark before this batch existed, so light mode already reads
   as a small dark strip atop a light page (a common, deliberate app-header
   convention), and it needed no color changes at all to "feel integrated"
   into the new dark theme; only the new theme-toggle button (below) is new
   here. */

.app-header {
  background: var(--pmc-ink);
  color: #fff;
  padding: 0.9rem 1rem;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  position: sticky;
  top: 0;
  z-index: 10;
}

.app-header-title h1 {
  font-size: 1.15rem;
  font-weight: 700;
}

.app-header-accent {
  color: var(--pmc-pink);
}

.app-header-subtitle {
  margin: 0;
  font-size: 0.75rem;
  color: #b9b9c4;
}

.app-header-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
}

/* Dark Editor Theme + Visual Polish batch: a compact icon-only toggle,
   deliberately NOT a .btn (a full-size labeled button would be the largest
   element in an already-tight mobile header row) — same 44px touch target,
   same focus-visible treatment, but sized/padded to sit comfortably next to
   Latest Videos/New Project/Export without pushing them to a second row any
   more than they already would be. Lives on the dark .app-header in both
   themes, so its own colors are theme-invariant (white icon, pink focus/
   hover) exactly like the rest of the header chrome around it. */
.theme-toggle-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--touch-min);
  height: var(--touch-min);
  padding: 0;
  border-radius: 999px;
  border: 2px solid rgba(255, 255, 255, 0.18);
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  flex: 0 0 auto;
}

.theme-toggle-btn:hover {
  border-color: var(--pmc-pink);
  background: rgba(255, 255, 255, 0.14);
}

.theme-toggle-btn:focus-visible {
  outline: 3px solid var(--pmc-pink);
  outline-offset: 2px;
}

.new-project-hint {
  flex-basis: 100%;
  margin: 0;
  font-size: 0.8rem;
  color: #ffd7e9;
}

/* Mobile/Touch UI QA batch: the header is sticky (now that the overflow bug
   above is fixed, it actually stays pinned), so on narrow phones its own
   height directly eats into the small usable viewport for the ENTIRE rest
   of the session, not just on first load. At 360-390px the unpatched header
   ran 185-207px tall (title wrapping to two lines, plus a decorative
   subtitle, plus three action buttons wrapping to a second row) — over a
   quarter of a typical phone's visible height, permanently. Trimmed here to
   a compact single-purpose bar: smaller title (fits on one line at these
   widths without changing the wordmark/colors/brand treatment itself),
   subtitle hidden (purely decorative tagline, not needed once the app is
   actually in use), tighter button padding/font-size (still >= 44px tall,
   only the horizontal padding and label size shrink) so the three actions
   are more likely to share one row. */
@media (max-width: 480px) {
  .app-header {
    padding: 0.6rem 0.75rem;
    gap: 0.4rem;
  }

  .app-header-title h1 {
    font-size: 0.95rem;
  }

  .app-header-subtitle {
    display: none;
  }

  .app-header-actions {
    gap: 0.4rem;
  }

  .app-header-actions .btn {
    padding: 0.4rem 0.7rem;
    font-size: 0.85rem;
  }
}

/* ---------- Layout ---------- */
/* Mobile-first: one column, preview first (DOM order), settings below in
   collapsible sections. At the desktop breakpoint, named grid areas move
   the preview to a sticky right-hand column regardless of source order —
   so mobile keeps its natural top-to-bottom reading order while desktop
   gets the settings-left/preview-right split. */

.visually-hidden-heading {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

.app-workspace {
  display: grid;
  grid-template-columns: 1fr;
  grid-template-areas: "preview" "settings";
  gap: 1rem;
  padding: 1rem;
  max-width: 1600px;
  margin: 0 auto;
}

.workspace-preview {
  grid-area: preview;
}

.workspace-settings {
  grid-area: settings;
  min-width: 0;
}

.preview-panel-inner {
  background: var(--panel-bg);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  padding: 1rem;
  box-shadow: 0 1px 3px var(--shadow-color);
}

@media (min-width: 1024px) {
  .app-workspace {
    grid-template-columns: minmax(320px, 38%) 1fr;
    grid-template-areas: "settings preview";
    align-items: start;
  }

  /* Mobile/Touch UI QA batch: this sticky rule was a structural no-op before
     the html/body double-overflow bug (see the html {overflow-x:hidden} rule
     near the top of this file) was fixed — nothing on desktop ever actually
     exercised it, so nobody had reason to notice `top: 1rem` doesn't account
     for the sticky HEADER's own height above it. Now that sticky genuinely
     works, that mismatch would be a real, visible bug: once scrolled, the
     preview column would settle 1rem from the true viewport top, sliding up
     UNDER/behind the header (which occupies roughly the first 76px) instead
     of resting just below it. `top`/`max-height` below use the header's real
     measured height at this breakpoint (76px, hardcoded rather than read via
     a CSS custom property — the header's size only ever changes at the
     narrower mobile breakpoint below 1024px, so this desktop-only value
     never needs to track it dynamically) plus the same 1rem gap the preview
     already sits at when the page is unscrolled, so sticking now settles it
     exactly where it started, with no jump and no overlap. */
  .workspace-preview {
    position: sticky;
    top: calc(76px + 1rem);
    max-height: calc(100vh - 76px - 2rem);
    overflow-y: auto;
  }

  .preview-panel-inner {
    padding: 1.25rem;
  }

  .preview-frame {
    /* Batch 6H root-cause fix: the previous rule (width:100%; max-width:520px;
       height:auto; max-height:72vh) let width resolve to a DEFINITE value
       (from width:100%/max-width) independently of the height limit
       (max-height:72vh). CSS's aspect-ratio auto-sizing only derives height
       FROM width when height is auto — it does not shrink width back down
       after height gets separately clamped by max-height. On viewports where
       72vh translated to less height than a 520px-wide 9:16 box needs, the
       box ended up wide-and-short instead of 1080:1920, and the canvas
       (always internally 1080x1920) was stretched non-uniformly to fill it —
       this was the actual source of the reported portrait "squeeze," not the
       photo-fitting math in compositor.js (audited and confirmed correct).
       Fixing it means width itself must already respect BOTH limits before
       aspect-ratio derives height, so height is only ever derived, never
       separately re-clamped: width is set to the smallest of (a) the
       container's available width, (b) the 520px cap, and (c) the width a
       1080:1920 box would have at exactly 72vh tall — so the resulting
       height (from aspect-ratio + height:auto) can never exceed 72vh either. */
    width: min(100%, 520px, calc(72vh * 1080 / 1920));
    height: auto;
  }
}

/* ---------- Settings form ---------- */

.field-group {
  border: none;
  padding: 0;
  margin: 0 0 1.25rem 0;
}

.field-group legend {
  font-weight: 700;
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--pmc-text-muted);
  padding: 0 0 0.5rem 0;
}

/* Dark Editor Theme + Visual Polish batch: a lightweight subheader used
   inside a few already-merged sections (Brand & Header: "Brand Profile" /
   "Header" / "Optional Details"; reused wherever a small internal label
   genuinely improves comprehension without a new nested card) — the small
   leading accent tick is the "small accent indicator + clear section name"
   treatment this batch's task asked for at the top-level accordion scale,
   scaled down for an in-body grouping label instead of a whole new card. */
.field-subheader {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 1.1rem 0 0.6rem 0;
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--pmc-text-muted);
}

.field-subheader::before {
  content: "";
  display: inline-block;
  width: 3px;
  height: 0.9rem;
  border-radius: 999px;
  background: var(--pmc-pink);
  flex: 0 0 auto;
}

.field-subheader:first-child {
  margin-top: 0;
}

.field {
  margin-bottom: 0.85rem;
}

.field label,
.field-static-label {
  display: block;
  font-size: 0.9rem;
  font-weight: 600;
  margin-bottom: 0.3rem;
}

.optional-tag {
  font-weight: 400;
  color: var(--pmc-text-muted);
  font-size: 0.8rem;
}

.field input[type="text"],
.field input[type="date"],
.field input[type="number"],
.field input[type="search"],
.field input[type="password"],
.field select {
  width: 100%;
  min-height: var(--touch-min);
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--pmc-border);
  border-radius: var(--radius-md);
  font-size: 1rem;
  background: var(--input-bg);
  color: var(--pmc-text);
}

.field select:disabled {
  background: var(--app-bg);
  color: var(--pmc-text-muted);
  cursor: not-allowed;
}

.field input:focus-visible,
.field select:focus-visible {
  outline: 2px solid var(--pmc-focus);
  outline-offset: 1px;
}

/* Dark Editor Theme + Visual Polish batch: Chrome/Edge's own autofill
   yellow-then-white background paints OVER an author background-color
   (the same "browser default beats a plain author rule that doesn't use
   this exact override" family of quirk this file already documents for
   `[hidden]` elsewhere) — without this, an autofilled Business
   name/Location/etc. field would show the OS's own bright autofill color
   islanded inside an otherwise-dark panel. The 1000px inset box-shadow
   trick repaints the field's interior in the current theme's own
   --input-bg before the browser gets a chance to render its autofill
   color, and -webkit-text-fill-color (not `color`, which Chrome ignores
   here) keeps the typed text itself legible against it. */
.field input:-webkit-autofill {
  box-shadow: 0 0 0 1000px var(--input-bg) inset;
  -webkit-text-fill-color: var(--pmc-text);
}

/* A short, always-visible explanation beneath a control — used by Template
   Animation's own intro copy and the (currently single-option) Animation
   Style selector. */
.field-hint {
  margin: 0.3rem 0 0 0;
  font-size: 0.8rem;
  color: var(--pmc-text-muted);
}

/* Output preset — informational card, not a disabled input. There is only
   one active preset in this MVP, so this is deliberately non-interactive. */

.preset-card {
  background: var(--accent-soft-bg);
  border: 1px solid var(--accent-soft-border);
  border-radius: var(--radius-md);
  padding: 0.65rem 0.75rem;
}

.preset-card-header {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 0.35rem;
}

.preset-badge {
  display: inline-block;
  background: var(--pmc-pink);
  color: #fff;
  font-size: 0.65rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  flex: 0 0 auto;
}

.preset-name {
  font-weight: 700;
  font-size: 0.95rem;
  color: var(--pmc-text);
}

.preset-detail {
  margin: 0 0 0.35rem 0;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--pmc-text);
}

.preset-note {
  margin: 0 0 0.45rem 0;
  font-size: 0.78rem;
  color: var(--pmc-text-muted);
}

.preset-future-note {
  margin: 0;
  padding-top: 0.4rem;
  border-top: 1px dashed var(--border-subtle);
  font-size: 0.72rem;
  font-style: italic;
  color: var(--pmc-text-muted);
}

/* Output Type segmented control — a real, keyboard-accessible radio group
   styled as two touch-friendly cards, replacing the old single-preset
   informational-only card. */

.output-mode-selector {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 0.6rem;
}

.output-mode-option {
  flex: 1 1 0;
}

.output-mode-card {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: var(--touch-min);
  padding: 0.5rem 0.75rem;
  border: 2px solid var(--pmc-border);
  border-radius: var(--radius-md);
  background: var(--input-bg);
  font-weight: 700;
  font-size: 0.95rem;
  color: var(--pmc-text);
  cursor: pointer;
  text-align: center;
}

.output-mode-option input:checked + .output-mode-card {
  border-color: var(--pmc-pink);
  background: var(--accent-soft-bg);
  color: var(--pmc-pink-dark);
}

.output-mode-option input:focus-visible + .output-mode-card {
  outline: 3px solid var(--pmc-focus);
  outline-offset: 2px;
}

.duration-control {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.duration-control input[type="range"] {
  flex: 1 1 auto;
  min-height: var(--touch-min);
}

.duration-control input[type="number"] {
  width: 4.5rem;
  flex: 0 0 auto;
}

.duration-unit {
  color: var(--pmc-text-muted);
  font-size: 0.85rem;
}

/* Toggle switch for Blurred Background.
   The whole row is one <label for="blur-toggle">, so clicking the title,
   hint, or switch all toggle the real checkbox (id/state unchanged). */

.toggle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  min-height: var(--touch-min);
  padding: 0.35rem 0;
  cursor: pointer;
}

.toggle-copy {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
  flex: 1 1 auto;
}

.toggle-title {
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--pmc-text);
}

.toggle-hint {
  font-size: 0.78rem;
  line-height: 1.35;
  color: var(--pmc-text-muted);
}

.toggle-switch {
  /* Anchors the absolutely-positioned checkbox below to this box instead of
     the document root — the previous version omitted this and the native
     checkbox rendered detached from the visible track. */
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  flex: 0 0 auto;
  min-height: var(--touch-min);
}

.toggle-input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
  margin: 0;
}

.toggle-track {
  width: 50px;
  height: 28px;
  background: var(--toggle-track-off);
  border-radius: 999px;
  position: relative;
  transition: background 0.15s ease;
  flex: 0 0 auto;
}

.toggle-thumb {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  /* Deliberately always white in both themes — matches the native-toggle
     convention every mobile OS already uses (the thumb itself never
     recolors with the surrounding theme, only the track does). */
  background: #fff;
  transition: transform 0.15s ease;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
}

.toggle-input:checked ~ .toggle-track {
  background: var(--pmc-pink);
}

.toggle-input:checked ~ .toggle-track .toggle-thumb {
  transform: translateX(22px);
}

.toggle-input:focus-visible ~ .toggle-track {
  outline: 3px solid var(--pmc-focus);
  outline-offset: 2px;
}

.toggle-state {
  min-width: 1.6rem;
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--pmc-text-muted);
  text-align: left;
}

.toggle-state::before {
  content: "Off";
}

.toggle-input:checked ~ .toggle-state::before {
  content: "On";
  color: var(--pmc-pink-dark);
}

.summary-row {
  display: flex;
  justify-content: space-between;
  padding: 0.3rem 0;
  font-size: 0.9rem;
  border-bottom: 1px dashed var(--border-subtle);
}

.summary-row:last-child {
  border-bottom: none;
}

.summary-label {
  color: var(--pmc-text-muted);
}

.summary-value {
  font-weight: 700;
}

/* Duration guidance always states its meaning in text (not color alone).
   Dark Editor Theme + Visual Polish batch: all four tiers now route through
   the shared status tokens (success/warning/danger) instead of their own
   hand-picked hex pairs — "ok" and "caution" intentionally share the warning
   tier (their wording, not their color, is what actually distinguishes
   them; see the comment on getDurationGuidance() in app/js/settings.js). */

.duration-guidance {
  margin: 0.5rem 0 0 0;
  padding: 0.4rem 0.6rem;
  border-radius: var(--radius-md);
  font-size: 0.85rem;
  font-weight: 700;
  text-align: center;
}

.duration-guidance-good {
  background: var(--success-soft-bg);
  color: var(--pmc-success);
}

.duration-guidance-ok {
  background: var(--warning-soft-bg);
  color: var(--pmc-warning);
}

.duration-guidance-caution {
  background: var(--warning-soft-bg);
  color: var(--pmc-warning);
}

.duration-guidance-long {
  background: var(--danger-soft-bg);
  color: var(--pmc-danger);
}

/* ---------- Preview panel ---------- */

.preview-meta-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-bottom: 0.5rem;
}

.preview-mode-badge {
  display: inline-block;
  background: var(--pmc-ink);
  color: #fff;
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 0.2rem 0.6rem;
  border-radius: 999px;
}

.preview-frame {
  position: relative;
  width: 100%;
  max-width: 340px;
  aspect-ratio: 1080 / 1920;
  margin: 0 auto 0.75rem auto;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--pmc-ink-soft);
}

.preview-canvas {
  /* Internal drawing resolution stays exactly 1080x1920 (set via the width/
     height attributes in HTML) — these rules only scale its display size. */
  display: block;
  width: 100%;
  height: 100%;
}

.preview-status {
  margin: 0 0 0.5rem 0;
  font-size: 0.85rem;
  color: var(--pmc-text-muted);
  text-align: center;
}

.preview-error {
  margin: 0 0 0.5rem 0;
  padding: 0.5rem 0.75rem;
  border-radius: var(--radius-md);
  background: var(--danger-soft-bg);
  color: var(--pmc-danger);
  font-size: 0.85rem;
  font-weight: 600;
  text-align: center;
}

.preview-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  justify-content: center;
  margin-bottom: 0.75rem;
}

.preview-controls .btn {
  flex: 1 1 70px;
  min-width: 70px;
  padding-left: 0.6rem;
  padding-right: 0.6rem;
}

.preview-controls .btn.is-playing {
  background: var(--pmc-pink-dark);
  color: #fff;
}

.preview-disclaimer {
  font-size: 0.8rem;
  color: var(--pmc-text-muted);
  background: var(--panel-elevated-bg);
  border-radius: var(--radius-md);
  padding: 0.6rem 0.75rem;
  margin: 0 0 1rem 0;
}

.preview-summary h3 {
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--pmc-text-muted);
}

.preview-summary dl {
  margin: 0;
}

.preview-summary-row {
  display: flex;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.25rem 0;
  border-bottom: 1px dashed var(--border-subtle);
  font-size: 0.9rem;
}

.preview-summary-row:last-child {
  border-bottom: none;
}

.preview-summary-row dt {
  color: var(--pmc-text-muted);
  font-weight: 600;
}

.preview-summary-row dd {
  margin: 0;
  font-weight: 700;
  text-align: right;
  overflow-wrap: anywhere;
}

.is-placeholder {
  color: var(--pmc-text-muted);
  font-weight: 400;
  font-style: italic;
}

/* Mobile Editor IA Redesign: the preview disclaimer paragraph and the Live
   Summary block are redundant once the user is actively operating the app —
   the same information now surfaces as per-section accordion summaries (see
   the "Collapsible settings sections" block below). Hidden via CSS only
   (never `hidden`-by-attribute) so the underlying elements/state are
   untouched and desktop — which keeps its current long-page workflow — is
   completely unaffected below this rule's 1024px cutoff (matches the
   existing .app-workspace desktop breakpoint, so "mobile" here means exactly
   the same width range the rest of this stylesheet already treats as
   single-column). */
/* Mobile Design Cleanup: found via real-browser testing — `.desktop-only-
   block` and `.design-template-card` are both single-class author rules
   with identical specificity, so without `!important` the cascade's
   source-order tiebreak silently let whichever rule happens to be defined
   LATER in this file win, regardless of which one is semantically meant to
   be the override — `.design-template-card`'s own `display: flex` (defined
   later in this file) was winning, leaving the decorative template card
   visible on mobile despite carrying this class. `!important` here is
   deliberate: this class's entire purpose is "unconditionally hidden on
   mobile no matter what display value the element itself declares," the
   same category of override `#section-event-details:not([open]) >
   .event-details-toolbar` already uses `!important` for elsewhere in this
   file, so a future element added to this class does not need to first
   discover and win the same specificity race. */
@media (max-width: 1023.98px) {
  .desktop-only-block {
    display: none !important;
  }
}

/* ---------- Import panel ---------- */

.dropzone {
  border: 2px dashed var(--pmc-border);
  border-radius: var(--radius-lg);
  padding: 1.25rem 1rem;
  text-align: center;
  transition: border-color 0.15s ease, background 0.15s ease;
}

.dropzone.is-dragover {
  border-color: var(--pmc-pink);
  background: var(--accent-soft-bg);
}

.dropzone-btn {
  margin-bottom: 0.6rem;
}

.dropzone-hint {
  margin: 0;
  font-size: 0.8rem;
  color: var(--pmc-text-muted);
}

.import-messages {
  min-height: 1.2rem;
  font-size: 0.85rem;
  margin-top: 0.6rem;
  color: var(--pmc-text-muted);
}

.import-messages.has-warning {
  color: var(--pmc-danger);
  font-weight: 600;
}

/* ---------- Tray ---------- */

.tray-header {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}

.tray-empty-state {
  color: var(--pmc-text-muted);
  font-size: 0.9rem;
  padding: 1rem;
  text-align: center;
  border: 1px dashed var(--border-subtle);
  border-radius: var(--radius-md);
}

.image-tray {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  /* Mobile/Touch UI QA batch: 150px was measured (via real-browser layout,
     not just arithmetic) to force a SINGLE oversized column at 360-390px
     phone widths — auto-fill only ever adds a new track once two full
     150px tracks plus the 12px gap between them (312px) fit the container,
     and a 360-390px phone's actual content width (page padding + this
     settings section's own padding subtracted) lands at ~291-306px, just
     under that. Naive "N*min + (N-1)*gap <= container" arithmetic suggested
     136px would already fit 2 columns at 360px, but real-browser
     measurement showed auto-fill still resolved to 1 column there (its
     actual track-count algorithm leaves less margin than that arithmetic
     implies) — 128px, verified in-browser at every target width from
     360-430px, is the value that actually produces 2 columns everywhere
     in that range, not merely computed to. Every card's thumbnail/order-
     number/filename/drag-handle/Move-Left/Right/Delete buttons remain
     comfortably legible at the resulting ~128-168px card width. 768px
     tablet width lands on 4 columns at this minimum (each ~163px, the same
     per-card size the 412px 2-column layout already uses) rather than
     being forced to some other count. */
  grid-template-columns: repeat(auto-fill, minmax(128px, 1fr));
  gap: 0.75rem;
  max-height: 60vh;
  overflow-y: auto;
}

.tray-item {
  /* Dark Editor Theme + Visual Polish batch: "cards should feel like photo
     thumbnails sitting on a dark editing tray" — softened the card's own
     outline to --border-subtle and leaned on --panel-elevated-bg (distinct
     from the tray/section's own --panel-bg behind it) to do the work of
     separating one card from its neighbors, rather than a hard --pmc-border
     line around every single card. */
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  padding: 0.5rem;
  background: var(--panel-elevated-bg);
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  position: relative;
  /* Final Pre-Production Visual Polish batch: one shared transition powers
     both the dragged card's own lift/settle (is-dragging below) and every
     displaced sibling's FLIP glide (app/js/tray.js's playFlip()), which
     relies on this transition already being present on every .tray-item so
     it only has to toggle `transform`/`transition:none` inline, never author
     the animation itself. ~150ms sits inside the requested 120-180ms
     "fast and responsive, not floaty" range. */
  transition: transform 150ms ease, box-shadow 150ms ease, background-color 150ms ease, border-color 150ms ease;
}

.tray-item.is-dragging {
  opacity: 0.5;
  /* Image Order Pointer Drag Fix batch: class-based (not just the handle's
     own :active pseudo-class, which browsers can drop once the pointer
     moves outside the element during a held-button drag) so the grabbing
     cursor and text-selection suppression hold for the ENTIRE card, for the
     whole gesture, regardless of exactly where the pointer currently is. */
  user-select: none;
  /* Final Pre-Production Visual Polish batch: "active card lifts slightly...
     subtle scale around ~1.02... slightly stronger shadow" — animates in/out
     via the base .tray-item transition above, since this class is only
     toggled (not itself given inline transforms), so grabbing and releasing
     both animate the same way. */
  transform: scale(1.02);
  box-shadow: 0 10px 22px var(--shadow-color);
  z-index: 2;
}

.tray-item.is-dragging .tray-drag-handle {
  cursor: grabbing;
}

/* Final Pre-Production Visual Polish batch: prefers-reduced-motion turns off
   the drag lift/settle and sibling FLIP glide — app/js/tray.js's playFlip()
   still sets/clears the exact same inline transform on the exact same
   elements either way, so the reorder mutation and its outcome are
   completely unaffected; only the transition that makes the change visible
   as motion is removed here, so displaced cards jump straight to their
   final position instead of gliding. */
@media (prefers-reduced-motion: reduce) {
  .tray-item {
    transition: none;
  }
}

/* Image Drag Reorder + Explicit Grid Hero batch: dragging now starts ONLY
   from this handle (Pointer Events — mouse/touch/pen through one path),
   never from anywhere else on the card, so the thumbnail/Move Left/Move
   Right/Delete controls and ordinary tray scrolling can never accidentally
   trigger a reorder. touch-action:none stops the browser's own touch-scroll
   gesture from competing with a touch drag, matching the same technique
   opener-settings.js's reposition frame already uses. */
.tray-drag-handle {
  align-self: center;
  width: 100%;
  min-height: var(--touch-min);
  border: 1px dashed var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--input-bg);
  color: var(--pmc-text-muted);
  font-size: 1.1rem;
  line-height: 1;
  cursor: grab;
  touch-action: none;
  user-select: none;
}

.tray-drag-handle:active {
  cursor: grabbing;
}

.tray-drag-handle:focus-visible {
  outline: 3px solid var(--pmc-focus);
  outline-offset: 2px;
}

.tray-item.is-selected {
  /* "Selected/Hero state should use the brand accent clearly but not
     overwhelm the photo" — a thin pink ring plus a tinted (not solid) card
     background, exactly as before this batch, just token-driven now. */
  border-color: var(--pmc-pink);
  background: var(--accent-soft-bg);
  box-shadow: 0 0 0 2px var(--pmc-pink);
}

.tray-thumb {
  display: block;
  position: relative;
  width: 100%;
  aspect-ratio: 1080 / 1920;
  border-radius: 6px;
  overflow: hidden;
  background: var(--input-bg);
  border: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
  min-height: var(--touch-min);
}

.tray-thumb:focus-visible {
  outline: 3px solid var(--pmc-focus);
  outline-offset: 2px;
}

.tray-selected-badge {
  position: absolute;
  bottom: 4px;
  right: 4px;
  background: var(--pmc-pink);
  color: #fff;
  font-size: 0.65rem;
  font-weight: 700;
  padding: 0.1rem 0.4rem;
  border-radius: 999px;
}

.tray-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.tray-index {
  position: absolute;
  top: 4px;
  left: 4px;
  background: rgba(20, 20, 27, 0.8);
  color: #fff;
  font-size: 0.75rem;
  font-weight: 700;
  padding: 0.1rem 0.4rem;
  border-radius: 999px;
}

.tray-info {
  min-width: 0;
}

.tray-filename {
  display: block;
  font-size: 0.75rem;
  color: var(--pmc-text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tray-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
}

.tray-btn {
  flex: 1 1 auto;
  min-height: var(--touch-min);
  min-width: 44px;
  padding: 0.3rem 0.5rem;
  font-size: 0.75rem;
  font-weight: 600;
  border-radius: var(--radius-md);
  border: 1px solid var(--pmc-border);
  background: var(--panel-elevated-bg);
  color: var(--pmc-text);
  cursor: pointer;
}

.tray-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.tray-btn-delete {
  color: var(--pmc-danger);
  border-color: var(--pmc-danger);
}

.tray-btn-delete:hover:not(:disabled) {
  background: var(--pmc-danger);
  color: #fff;
}

/* ---------- Export dialog ---------- */

.export-dialog-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(10, 10, 15, 0.6);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  z-index: 100;
}

.export-dialog-backdrop[hidden] {
  display: none;
}

.export-dialog {
  background: var(--panel-elevated-bg);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  padding: 1.25rem;
  max-width: 420px;
  width: 100%;
  max-height: 90vh;
  overflow-y: auto;
  box-shadow: 0 12px 32px var(--shadow-color);
}

.export-dialog-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}

.export-dialog-header h2 {
  margin: 0;
  font-size: 1.1rem;
}

.export-dialog-close {
  background: none;
  border: none;
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
  color: var(--pmc-text-muted);
  min-width: var(--touch-min);
  min-height: var(--touch-min);
}

.export-dialog-close:focus-visible {
  outline: 3px solid var(--pmc-focus);
  outline-offset: 2px;
}

.export-details {
  margin: 0 0 1rem 0;
}

.export-details-row {
  display: flex;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.3rem 0;
  border-bottom: 1px dashed var(--border-subtle);
  font-size: 0.88rem;
}

.export-details-row:last-child {
  border-bottom: none;
}

.export-details-row dt {
  color: var(--pmc-text-muted);
  font-weight: 600;
}

.export-details-row dd {
  margin: 0;
  font-weight: 700;
  text-align: right;
  overflow-wrap: anywhere;
}

.export-filename {
  font-family: monospace;
  font-size: 0.78rem;
  font-weight: 600;
}

.export-progress {
  margin-bottom: 1rem;
}

.export-progress-label {
  margin: 0 0 0.4rem 0;
  font-size: 0.85rem;
  color: var(--pmc-text-muted);
  text-align: center;
}

.export-progress-track {
  height: 10px;
  border-radius: 999px;
  background: var(--input-bg);
  overflow: hidden;
}

.export-progress-fill {
  height: 100%;
  width: 0%;
  background: var(--pmc-pink);
  transition: width 0.2s ease;
}

.export-progress-fill.is-indeterminate {
  width: 40%;
  animation: export-indeterminate 1.2s ease-in-out infinite;
}

@keyframes export-indeterminate {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(250%); }
}

.export-error {
  margin: 0 0 1rem 0;
  padding: 0.6rem 0.75rem;
  border-radius: var(--radius-md);
  background: var(--danger-soft-bg);
  color: var(--pmc-danger);
  font-size: 0.85rem;
  font-weight: 600;
}

/* Phase 5 (Image Quality Guard) — reuses the export dialog's own shell (no
   second/nested dialog); this block temporarily replaces the normal
   details/Create MP4 footer while state is QUALITY_WARNING, per this
   batch's own "do not add multiple nested dialogs" instruction. Warning
   tier (not danger) — never blocks export, so it should read as a
   heads-up, not a failure. */
.export-quality-warning {
  margin: 0 0 1rem 0;
  padding: 0.75rem 0.9rem;
  border-radius: var(--radius-md);
  background: var(--warning-soft-bg);
  border: 1px solid var(--pmc-warning);
}

.export-quality-warning-title {
  margin: 0 0 0.35rem 0;
  color: var(--pmc-warning);
  font-weight: 700;
  font-size: 0.95rem;
}

.export-quality-warning-text {
  margin: 0 0 0.75rem 0;
  color: var(--pmc-text);
  font-size: 0.85rem;
}

.export-result {
  margin-bottom: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  align-items: stretch;
}

/* Batch 6I root-cause fix: an author-stylesheet rule that sets `display` on
   an element ALWAYS wins over the browser's UA-stylesheet `[hidden] {
   display: none }` rule, regardless of selector specificity — origin is
   checked before specificity in the cascade, and author styles outrank UA
   styles. So `.export-result`'s own `display: flex` above was silently
   overriding the native `hidden` attribute: JS/HTML correctly set
   `hidden`, but the browser rendered it anyway. This exact pattern was
   already fixed for `.export-dialog-backdrop` elsewhere in this file
   (search `.export-dialog-backdrop[hidden]`) — it was simply never applied
   here, which is why "Your MP4 is ready" could appear before a real export
   ever completed. */
.export-result[hidden] {
  display: none;
}

.export-result-message {
  margin: 0;
  font-weight: 600;
  text-align: center;
}

/* Batch 6K: shown only when the MP4 itself succeeded but Latest Videos
   registration (thumbnail/metadata) did not — the video is still fully
   downloadable via the button right below this, this is purely an
   informational note, never an error-level color. */
.export-library-warning {
  margin: 0;
  padding: 0.5rem 0.65rem;
  border-radius: var(--radius-md);
  background: var(--warning-soft-bg);
  color: var(--pmc-warning);
  font-size: 0.82rem;
  font-weight: 600;
  text-align: center;
}

.export-dialog-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
}

.export-dialog-actions .btn {
  flex: 1 1 auto;
}

/* ---------- Adjust Grid Photos (Photo Grid Reveal focal repositioning) --- */
/* Reuses the export dialog's backdrop/dialog/header/actions classes above —
   same modal shell, same visual language — this section only adds the
   picker-grid and reposition-frame content specific to this dialog. */

.opener-focus-dialog {
  max-width: 480px;
}

.opener-focus-thumbnails {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: 0.75rem;
  margin: 0.75rem 0;
}

/* Image Drag Reorder + Explicit Grid Hero batch: the grid item is now this
   card (thumbnail button + Make Hero button as SIBLINGS) rather than the
   thumbnail button itself — a <button> cannot legally contain another
   <button>, and Make Hero needs its own independent click target that does
   not also open the reposition editor. */
.opener-focus-thumbnail-card {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.opener-focus-hero-btn {
  min-height: var(--touch-min);
  padding: 0.3rem 0.5rem;
  font-size: 0.72rem;
  font-weight: 700;
  border-radius: var(--radius-md);
  border: 1px solid var(--pmc-pink);
  background: var(--panel-elevated-bg);
  color: var(--pmc-pink);
  cursor: pointer;
}

.opener-focus-hero-btn:hover:not(:disabled) {
  background: var(--accent-soft-bg);
}

.opener-focus-hero-btn.is-hero {
  background: var(--pmc-pink);
  color: #fff;
  cursor: default;
  /* The "HERO ✓" state is a clear, positive designation, not a disabled
     control — override the default disabled dimming so it stays fully
     legible, matching the task's "must be immediately obvious" requirement. */
  opacity: 1;
}

.opener-focus-thumbnail {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.35rem;
  padding: 0.4rem;
  border: 2px solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--panel-elevated-bg);
  cursor: pointer;
  min-height: var(--touch-min);
}

.opener-focus-thumbnail:hover,
.opener-focus-thumbnail:focus-visible {
  border-color: var(--pmc-pink);
  outline: none;
}

.opener-focus-thumbnail-image-wrap {
  position: relative;
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: calc(var(--radius-md) - 4px);
  overflow: hidden;
  background: var(--pmc-ink);
}

.opener-focus-thumbnail-image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.opener-focus-thumbnail-badge {
  position: absolute;
  top: 4px;
  left: 4px;
  background: rgba(10, 10, 15, 0.72);
  color: #fff;
  font-size: 0.68rem;
  font-weight: 700;
  padding: 0.1rem 0.4rem;
  border-radius: 999px;
}

.opener-focus-thumbnail-adjusted-badge {
  position: absolute;
  top: 4px;
  right: 4px;
  background: var(--pmc-pink);
  color: #fff;
  font-size: 0.62rem;
  font-weight: 700;
  padding: 0.1rem 0.4rem;
  border-radius: 999px;
}

.opener-focus-thumbnail-label {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--pmc-text);
  text-align: center;
}

.opener-focus-editor-label {
  margin: 0 0 0.4rem 0;
  font-weight: 700;
  font-size: 1rem;
}

.opener-focus-frame {
  position: relative;
  width: 100%;
  max-width: 340px;
  margin: 0 auto 0.75rem auto;
  overflow: hidden;
  border-radius: var(--radius-md);
  background: var(--pmc-ink);
  border: 2px solid var(--pmc-pink);
  touch-action: none;
  cursor: grab;
  user-select: none;
}

.opener-focus-frame.is-dragging {
  cursor: grabbing;
}

/* Pan/Zoom/Fill/Fit batch: Fit-mode-only blurred backing, sized/positioned
   via the same computeFramePlacement() math the sharp foreground below
   uses (opener-settings.js) — approximates (not pixel-identical to) the
   Canvas renderer's overscanned blur, which remains the authoritative
   final look; this is an editor-preview aid only. z-index keeps it behind
   the sharp foreground image; `hidden` (toggled in JS) removes it entirely
   in Fill mode. */
.opener-focus-backing-image {
  position: absolute;
  left: 0;
  top: 0;
  max-width: none;
  pointer-events: none;
  z-index: 1;
  filter: blur(14px) brightness(0.72) saturate(65%);
}

.opener-focus-backing-image[hidden] {
  display: none;
}

.opener-focus-image {
  position: absolute;
  left: 0;
  top: 0;
  max-width: none;
  pointer-events: none;
  z-index: 2;
}

.opener-focus-frame-grid {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 3;
  background-image:
    linear-gradient(to right, rgba(255, 255, 255, 0.55) 1px, transparent 1px),
    linear-gradient(to right, rgba(255, 255, 255, 0.55) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(255, 255, 255, 0.55) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(255, 255, 255, 0.55) 1px, transparent 1px);
  background-position: 33.333% 0, 66.666% 0, 0 33.333%, 0 66.666%;
  background-size: 1px 100%, 1px 100%, 100% 1px, 100% 1px;
  background-repeat: no-repeat;
}

.opener-focus-mode-field,
.opener-focus-zoom-field {
  margin-top: 0.6rem;
}

/* ---------- Adjust Photo (ordinary slideshow slide framing) ------------- */
/* Phase 3 of the Per-Slide Photo Framing plan: reuses the export dialog's
   backdrop/dialog/header/actions shell, same visual language as Adjust Grid
   Photos above, but is intentionally its OWN set of classes (not shared with
   .opener-focus-*) — the two editors write to independent project-state maps
   (Grid's photoFocus vs. this feature's slideshowFraming) and must stay
   decoupled in markup/CSS too, not just in JS, so a future change to one
   modal's look never silently drags the other along with it. */

.adjust-photo-dialog {
  max-width: 420px;
}

.adjust-photo-meta-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.6rem;
}

.adjust-photo-filename {
  min-width: 0;
  font-size: 0.8rem;
  color: var(--pmc-text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.adjust-photo-mode-badge {
  flex: 0 0 auto;
  background: var(--pmc-pink);
  color: #fff;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
}

.adjust-photo-frame {
  position: relative;
  width: 100%;
  max-width: 320px;
  margin: 0 auto 0.75rem auto;
  overflow: hidden;
  border-radius: var(--radius-md);
  background: var(--pmc-ink);
  border: 2px solid var(--pmc-pink);
  touch-action: none;
  cursor: grab;
  user-select: none;
}

.adjust-photo-frame.is-dragging {
  cursor: grabbing;
}

.adjust-photo-backing-image {
  position: absolute;
  left: 0;
  top: 0;
  max-width: none;
  pointer-events: none;
  z-index: 1;
  filter: blur(14px) brightness(0.72) saturate(65%);
}

.adjust-photo-backing-image[hidden] {
  display: none;
}

.adjust-photo-image {
  position: absolute;
  left: 0;
  top: 0;
  max-width: none;
  pointer-events: none;
  z-index: 2;
}

.adjust-photo-frame-grid {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 3;
  background-image:
    linear-gradient(to right, rgba(255, 255, 255, 0.55) 1px, transparent 1px),
    linear-gradient(to right, rgba(255, 255, 255, 0.55) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(255, 255, 255, 0.55) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(255, 255, 255, 0.55) 1px, transparent 1px);
  background-position: 33.333% 0, 66.666% 0, 0 33.333%, 0 66.666%;
  background-size: 1px 100%, 1px 100%, 100% 1px, 100% 1px;
  background-repeat: no-repeat;
}

.adjust-photo-mode-field,
.adjust-photo-zoom-field {
  margin-top: 0.6rem;
}

/* Phase 5 (Image Quality Guard): the live indicator — updates immediately
   on every Fill/Fit/Zoom/Pan change (see adjust-photo-settings.js's own
   positionImage()/updateQualityIndicator()). Kept deliberately restrained
   ("do not make the normal mobile UI feel technical or cluttered"): one
   short primary line, one small secondary technical line, four color tiers
   reusing the SAME success/warning/danger tokens `.duration-guidance-*`
   already established elsewhere in this file (Caution and Low intentionally
   share the warning tier's color — their WORDING is what distinguishes
   them, matching that existing precedent's own "state the meaning in text,
   not color alone" discipline). */
.adjust-photo-quality-indicator {
  margin-top: 0.6rem;
  padding: 0.5rem 0.65rem;
  border-radius: var(--radius-md);
  background: var(--success-soft-bg);
}

.adjust-photo-quality-indicator[data-quality-level="caution"],
.adjust-photo-quality-indicator[data-quality-level="low"] {
  background: var(--warning-soft-bg);
}

.adjust-photo-quality-indicator[data-quality-level="severe"] {
  background: var(--danger-soft-bg);
}

.adjust-photo-quality-text {
  margin: 0;
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--pmc-success);
}

.adjust-photo-quality-indicator[data-quality-level="caution"] .adjust-photo-quality-text,
.adjust-photo-quality-indicator[data-quality-level="low"] .adjust-photo-quality-text {
  color: var(--pmc-warning);
}

.adjust-photo-quality-indicator[data-quality-level="severe"] .adjust-photo-quality-text {
  color: var(--pmc-danger);
}

.adjust-photo-quality-detail {
  margin: 0.2rem 0 0 0;
  font-size: 0.72rem;
  color: var(--pmc-text-muted);
}

/* ---------- Image Order tray: Adjust Photo button + Adjusted badge ------ */

.tray-adjust-row {
  display: flex;
}

.tray-adjust-btn {
  flex: 1 1 auto;
  min-height: var(--touch-min);
  padding: 0.3rem 0.5rem;
  font-size: 0.75rem;
  font-weight: 600;
  border-radius: var(--radius-md);
  border: 1px solid var(--pmc-pink);
  background: var(--panel-elevated-bg);
  color: var(--pmc-pink);
  cursor: pointer;
}

.tray-adjust-btn:hover {
  background: var(--accent-soft-bg);
}

.tray-adjusted-badge {
  position: absolute;
  top: 4px;
  right: 4px;
  background: var(--pmc-pink);
  color: #fff;
  font-size: 0.62rem;
  font-weight: 700;
  padding: 0.1rem 0.4rem;
  border-radius: 999px;
}

/* Phase 5 (Image Quality Guard): thumbBtn (`.tray-thumb`) is itself a
   <button>, so the quality badge below cannot be nested inside it without
   nesting one interactive control inside another — this wrap is the shared
   `position:relative` ancestor that lets the badge sit as thumbBtn's own
   SIBLING while still landing in the same visual corner over the
   thumbnail. Every other existing badge (index/selected/adjusted above)
   stays exactly where it already was, nested inside thumbBtn itself — they
   are plain non-interactive spans, so that was always valid. */
.tray-thumb-wrap {
  position: relative;
}

/* Shown ONLY at Low/Severe (see tray.js's own BADGE_LEVELS check) — never
   at Caution, matching the task's explicit "do not clutter every card"
   instruction. A real <button> (tapping it opens Adjust Photo for that
   photo) rather than a plain span, styled to match the pill shape of the
   other three badges above but bottom-left, the one corner none of them
   already occupy. */
.tray-quality-badge {
  position: absolute;
  bottom: 4px;
  left: 4px;
  border: none;
  font-size: 0.62rem;
  font-weight: 700;
  padding: 0.15rem 0.45rem;
  border-radius: 999px;
  cursor: pointer;
  line-height: 1.3;
}

.tray-quality-badge-low {
  background: var(--pmc-warning);
  color: #fff;
}

.tray-quality-badge-severe {
  background: var(--pmc-danger);
  color: #fff;
}

.tray-item.is-highlighted .tray-thumb {
  outline: 3px solid var(--pmc-pink);
  outline-offset: 2px;
}

/* ---------- Collapsible settings sections ---------- */
/* Every settings section (Event Details, Brand & Header, Design, Photos,
   Intro / Opener, Slideshow) uses this same <details>/<summary> pattern —
   native, accessible, and it never destroys form state or reinitializes a
   module on open/close since collapsing only hides content with `hidden`-
   by-attribute browser behavior, nothing is removed from the DOM. Brand &
   Header's own colors here only affect the *admin* interface — the
   White-Label Branding panel's controls only ever change the exported/
   preview canvas, never this chrome.

   Dark Editor Theme + Visual Polish batch: collapsed vs. expanded now reads
   as two visibly different states without any extra markup — a collapsed
   card sits at the quiet --panel-bg with a plain --border-subtle edge; the
   moment it opens, its background steps up to --panel-elevated-bg AND its
   left edge gains a solid accent bar, so "this section body visibly
   belongs to that header" (this batch's own wording) holds structurally,
   not just by proximity. */

.settings-section {
  margin-bottom: 1rem;
  background: var(--panel-bg);
  border: 1px solid var(--border-subtle);
  border-left: 3px solid transparent;
  border-radius: var(--radius-md);
  padding: 0.25rem 0.75rem 0.9rem 0.75rem;
  box-shadow: 0 1px 2px var(--shadow-color);
}

.settings-section[open] {
  background: var(--panel-elevated-bg);
  border-left-color: var(--pmc-pink);
}

.settings-section-summary {
  cursor: pointer;
  padding: 0.7rem 0.25rem;
  font-weight: 700;
  font-size: 0.95rem;
  color: var(--pmc-text);
  list-style: revert;
  min-height: var(--touch-min);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.settings-section[open] > .settings-section-summary {
  border-bottom: 1px solid var(--border-subtle);
  margin-bottom: 0.25rem;
}

/* Mobile Editor IA Redesign: a live, derived one-line summary of each
   section's current state ("Brunch • Floods • Aug 16, 2026",
   "PictureMeClubbing", "11 images") shown next to the section title —
   computed fresh from project-state/brand-state/etc. on every render (see
   main.js's renderSectionSummaries()), never a second copy of that data.
   This is what replaces the mobile Live Summary block: the same information
   is now visible right on each collapsed accordion card instead of a
   separate always-expanded panel under Preview. */
.settings-section-summary-title {
  white-space: nowrap;
}

.settings-section-summary-meta {
  font-weight: 400;
  font-size: 0.82rem;
  color: var(--pmc-text-muted);
  text-align: right;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.settings-section-summary:focus-visible {
  outline: 2px solid var(--pmc-focus);
  outline-offset: 2px;
}

.settings-section-body {
  padding-top: 0.4rem;
}

/* A <details> nested inside another <details> (Template Animation, inside
   Slideshow; Add Photos/Image Order, inside Photos) — same accessible
   native pattern, just visually more compact/recessed so it reads as a
   subsection rather than a sibling top-level section. Starts collapsed by
   default (no `open` attribute in the HTML) so the already-open parent
   section isn't overwhelmed with every control at once — Photos' own two
   subsections are the one exception (both start open — see index.html —
   since Add Photos/Image Order are the section's whole reason to exist).
   Dark Editor Theme + Visual Polish batch: recessed toward --app-bg rather
   than sitting at the same tone as its own open parent card, giving the
   three-tier "background -> card -> nested control" tonal separation this
   batch's task asked for, without a heavier border. */
.settings-subsection {
  margin: 0.75rem 0 0.85rem 0;
  background: var(--app-bg);
  border-color: var(--border-subtle);
  border-left-color: transparent;
  box-shadow: none;
}

.settings-subsection[open] {
  border-left-color: var(--pmc-pink);
}

.settings-subsection .settings-section-summary {
  font-size: 0.9rem;
  padding: 0.55rem 0.25rem;
  min-height: 40px;
}

.settings-subsection .settings-section-body .field:first-of-type {
  margin-top: 0.2rem;
}

/* Mobile Editor IA Redesign: on mobile/tablet (the same <1024px range the
   rest of this file already treats as "not desktop" — see the
   @media (min-width: 1024px) two-column rule above), every workflow section
   except Event Details stays hidden until Event Details has a valid event
   name, location, and date (see main.js's isEventDetailsComplete()/
   applyEventDetailsGate()) — a guided, one-step-at-a-time flow instead of one
   long settings page. `display: none` only (never the `hidden` attribute,
   never removed from the DOM) so no project-state/form value is ever lost by
   a section being temporarily gated — a later valid Event Details still
   finds every other field exactly as the user left it. Desktop is completely
   unaffected: this rule only exists inside the same <1024px media query the
   whole gating feature is scoped to, so `.is-gated` has zero effect there
   regardless of whether Event Details is complete. */
@media (max-width: 1023.98px) {
  .workspace-settings.is-gated .gated-section {
    display: none;
  }
}

/* Event Details' Load Preset control is a sibling of <summary>, not part of
   the collapsible .settings-section-body — the second rule below
   deliberately overrides the browser's own `details:not([open]) > :not(summary)
   { display: none }` UA rule (an AUTHOR rule always wins over a UA rule
   regardless of selector specificity, the exact "[hidden] vs. author
   display" precedent this file already documents for .export-result[hidden]
   above), so Load Preset stays reachable even while the Event Details card
   is collapsed. */
.event-details-toolbar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  padding: 0 0.25rem 0.6rem 0.25rem;
}

#section-event-details:not([open]) > .event-details-toolbar {
  display: flex !important;
}

.event-details-toolbar-label {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--pmc-text-muted);
  white-space: nowrap;
}

.event-details-toolbar select {
  flex: 1 1 auto;
  min-width: 0;
  min-height: var(--touch-min);
  padding: 0.4rem 0.5rem;
  border-radius: var(--radius-md);
  border: 1px solid var(--pmc-border);
  font-size: 0.9rem;
  background: var(--input-bg);
  color: var(--pmc-text);
}

.manage-presets-list {
  list-style: none;
  margin: 0.5rem 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.manage-presets-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.6rem;
  background: var(--input-bg);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
}

.manage-presets-row-name {
  flex: 1 1 auto;
  min-width: 0;
  font-weight: 600;
  font-size: 0.9rem;
  overflow-wrap: anywhere;
}

.manage-presets-row .btn {
  font-size: 0.82rem;
  padding: 0.4rem 0.6rem;
  min-height: 38px;
}

.brand-storage-error,
.brand-logo-error {
  background: var(--danger-soft-bg);
  color: var(--pmc-danger);
}

.brand-storage-error,
.brand-message,
.brand-logo-error,
.brand-contrast-warning {
  margin: 0 0 0.75rem 0;
  padding: 0.5rem 0.65rem;
  border-radius: var(--radius-md);
  font-size: 0.82rem;
  font-weight: 600;
}

.brand-contrast-warning {
  background: var(--warning-soft-bg);
  color: var(--pmc-warning);
}

.brand-message {
  background: var(--success-soft-bg);
  color: var(--pmc-success);
}

.brand-message.is-error {
  background: var(--danger-soft-bg);
  color: var(--pmc-danger);
}

.brand-header-mode {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.radio-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.9rem;
  font-weight: 600;
  min-height: var(--touch-min);
  cursor: pointer;
}

.radio-row input[type="radio"] {
  width: 20px;
  height: 20px;
  flex: 0 0 auto;
}

.brand-logo-field {
  border: 1px dashed var(--border-subtle);
  border-radius: var(--radius-md);
  padding: 0.65rem;
}

.brand-logo-filename {
  margin: 0.4rem 0;
  font-size: 0.82rem;
  color: var(--pmc-text-muted);
  overflow-wrap: anywhere;
}

/* ---------- Sponsor End Card thumbnail (Sponsor + Grid Timing batch) ---------- */
/* Reuses .brand-logo-field/.brand-logo-filename/.brand-actions/.brand-logo-error
   for the surrounding upload-control shell (see index.html) — only the
   thumbnail preview itself is new, sized/bordered consistently with the
   existing dark-theme token system rather than a one-off style. */
.sponsor-thumbnail-wrap {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 96px;
  height: 96px;
  border: 1px solid var(--pmc-border);
  border-radius: var(--radius-md);
  background: var(--input-bg);
  overflow: hidden;
  margin-bottom: 0.4rem;
}

.sponsor-thumbnail {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.sponsor-thumbnail-empty {
  font-size: 0.75rem;
  color: var(--pmc-text-muted);
}

.sponsor-upload-btn {
  display: inline-flex;
  margin-bottom: 0.5rem;
}

.brand-colors {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.brand-color-row {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: center;
  gap: 0.5rem;
}

.brand-color-label {
  font-size: 0.85rem;
  font-weight: 600;
  min-width: 0;
  overflow-wrap: anywhere;
}

/* Deliberately NOT theme-tinted beyond its own border — the swatch shown
   here is a real BRAND color used in the exported video, so its own fill
   must always show the actual chosen color, unaffected by the editor's own
   Light/Dark state. */
.brand-color-picker {
  width: 44px;
  height: 44px;
  padding: 2px;
  border: 1px solid var(--pmc-border);
  border-radius: var(--radius-md);
  background: none;
  cursor: pointer;
}

.brand-color-hex {
  width: 5.5rem;
  min-height: var(--touch-min);
  padding: 0.3rem 0.5rem;
  border: 1px solid var(--pmc-border);
  border-radius: var(--radius-md);
  background: var(--input-bg);
  color: var(--pmc-text);
  font-family: monospace;
  font-size: 0.85rem;
  text-transform: lowercase;
}

.brand-color-hex.is-invalid {
  border-color: var(--pmc-danger);
  color: var(--pmc-danger);
}

.brand-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.5rem;
}

.brand-actions .btn {
  flex: 1 1 auto;
}

/* ---------- Design Template selector (Batch 6L) ---------- */
/* Deliberately styled distinctly from .preset-card (Output Type, above) and
   from anything in the Brand & Header section — a Design Template selection
   and a Brand Profile selection are two separate concepts (see the
   field-hint text in index.html), and keeping their card treatments
   visually distinct reinforces that they are not the same control. Desktop-
   only per the Mobile Design Cleanup batch (see .desktop-only-block above);
   dark-mode token conversion here only matters for desktop, since mobile
   never renders this card at all. */

.design-template-card {
  display: flex;
  gap: 0.75rem;
  align-items: center;
  background: var(--app-bg);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  padding: 0.65rem 0.75rem;
  margin-top: 0.5rem;
}

.design-template-swatch {
  flex: 0 0 auto;
  width: 42px;
  height: 74px;
  border-radius: 6px;
  /* Purely decorative, theme-invariant by design (a generic colorful
     preview chip, not a real template/brand color). */
  background: linear-gradient(180deg, var(--pmc-pink) 0%, #7b2ff7 100%);
}

.design-template-info {
  min-width: 0;
}

.design-template-description {
  margin: 0 0 0.25rem 0;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--pmc-text);
}

.design-template-modes {
  margin: 0;
  font-size: 0.78rem;
  color: var(--pmc-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.design-template-fallback-notice {
  margin: 0.6rem 0 0 0;
  padding: 0.5rem 0.65rem;
  border-radius: var(--radius-md);
  background: var(--warning-soft-bg);
  color: var(--pmc-warning);
  font-size: 0.82rem;
  font-weight: 600;
}

/* ---------- Native form control polish ---------- */
/* Dark Editor Theme + Visual Polish batch. Two browser-native pieces this
   file could not previously touch at all:

   1) `accent-color` — a single standard CSS property that tells the browser
      to tint ITS OWN native checkbox/radio/range rendering (the little
      check glyph, the radio dot, a range thumb that isn't already fully
      custom-styled below) with the app's own accent, in both the OS light
      and OS dark rendering paths, with no per-control markup or JS. This is
      the one modern, genuinely low-risk way to keep a handful of controls
      this app leaves fully native (`.radio-row input[type="radio"]`,
      `input[type="checkbox"]` where one isn't wrapped in the custom
      `.toggle-*` markup) visually consistent with everything else, instead
      of looking like an unstyled OS control dropped into a themed page.

   2) A custom `<select>` chevron — `appearance: none` removes the browser's
      own OS-drawn arrow (which does not follow `color`/`background` at all
      in most browsers) and a small inline SVG chevron replaces it, using
      `currentColor` so it always matches the field's own --pmc-text-muted
      in both themes. The dropdown's OPEN, native OS popup list itself
      (the actual list of options once clicked) still cannot be restyled by
      any browser today — that remains genuinely native, OS-drawn UI in
      both this app and virtually every other web app; `color-scheme` on
      :root (see the token blocks above) is what lets Chrome/Edge render
      THAT native popup in a dark-appropriate palette automatically, which
      is the best any web page can currently do here. */

:root {
  accent-color: var(--pmc-pink);
}

.field select,
.event-details-toolbar select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M4 6l4 4 4-4' stroke='%236b6b78' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.6rem center;
  padding-right: 2rem;
}

:root[data-theme="dark"] .field select,
:root[data-theme="dark"] .event-details-toolbar select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M4 6l4 4 4-4' stroke='%239c99a8' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

/* ---------- Range sliders ---------- */
/* Dark Editor Theme + Visual Polish batch: every input[type=range] in this
   app (Logo/Header size, slide duration, Grid Load Speed, intro/final-slide
   durations, Adjust Grid Photos zoom) previously relied entirely on
   browser-default rendering, which on some platforms renders a nearly
   invisible thin gray track against a dark page. WebKit/Blink and Firefox
   require separate vendor-prefixed pseudo-elements (there is no unprefixed
   standard yet) for the track and thumb; both are styled identically here
   so the control looks and behaves the same across engines. Track height
   stays modest but the THUMB is deliberately oversized (28px) well past
   the visual track so it remains an easy, obvious touch target — this
   does not shrink the slider's existing full-width touch area
   (`.duration-control input[type=range] { min-height: var(--touch-min) }`
   above still reserves the full 44px tall hit region; only the visible
   thumb graphic is 28px). */

input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 28px;
  background: transparent;
  cursor: pointer;
}

input[type="range"]::-webkit-slider-runnable-track {
  height: 6px;
  border-radius: 999px;
  background: var(--pmc-border);
}

input[type="range"]::-moz-range-track {
  height: 6px;
  border-radius: 999px;
  background: var(--pmc-border);
}

input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 28px;
  height: 28px;
  margin-top: -11px;
  border-radius: 50%;
  background: var(--pmc-pink);
  border: 3px solid var(--panel-elevated-bg);
  box-shadow: 0 1px 4px var(--shadow-color);
}

input[type="range"]::-moz-range-thumb {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--pmc-pink);
  border: 3px solid var(--panel-elevated-bg);
  box-shadow: 0 1px 4px var(--shadow-color);
}

input[type="range"]:focus-visible {
  outline: none;
}

input[type="range"]:focus-visible::-webkit-slider-thumb {
  outline: 3px solid var(--pmc-focus);
  outline-offset: 2px;
}

input[type="range"]:focus-visible::-moz-range-thumb {
  outline: 3px solid var(--pmc-focus);
  outline-offset: 2px;
}

input[type="range"]:disabled {
  cursor: not-allowed;
  opacity: 0.5;
}

input[type="range"]:disabled::-webkit-slider-thumb {
  background: var(--pmc-text-muted);
}

input[type="range"]:disabled::-moz-range-thumb {
  background: var(--pmc-text-muted);
}

/* ---------- Custom scrollbars (WebKit/Blink; supported browsers only) --- */
/* Scoped to the handful of internally-scrolling containers this app
   actually has (the Image Order tray, any modal tall enough to scroll, the
   desktop sticky preview column) rather than a global `*` rule — a thin,
   theme-matched thumb instead of the OS's own light-gray default scrollbar
   sitting oddly on top of a dark panel. Firefox's own `scrollbar-color`
   equivalent is intentionally skipped: this batch's own task explicitly
   said not to spend significant effort on cross-browser scrollbar polish,
   and every current real-device target (Samsung Internet/Chrome, desktop
   Chrome/Edge) is Blink-based. */
.image-tray::-webkit-scrollbar,
.export-dialog::-webkit-scrollbar,
.workspace-preview::-webkit-scrollbar {
  width: 8px;
}

.image-tray::-webkit-scrollbar-track,
.export-dialog::-webkit-scrollbar-track,
.workspace-preview::-webkit-scrollbar-track {
  background: transparent;
}

.image-tray::-webkit-scrollbar-thumb,
.export-dialog::-webkit-scrollbar-thumb,
.workspace-preview::-webkit-scrollbar-thumb {
  background: var(--scrollbar-thumb);
  border-radius: 999px;
}

/* ---------- Latest Videos page (Batch 6K) ---------- */

.videos-page {
  max-width: 1600px;
  margin: 0 auto;
  padding: 1rem;
}

.videos-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1rem;
}

.videos-filter {
  display: flex;
  gap: 0.4rem;
  flex-wrap: wrap;
}

.videos-filter-btn {
  min-height: var(--touch-min);
  padding: 0.4rem 0.9rem;
  border-radius: 999px;
  border: 2px solid var(--pmc-border);
  background: var(--panel-elevated-bg);
  color: var(--pmc-text);
  font-weight: 700;
  font-size: 0.85rem;
  cursor: pointer;
}

.videos-filter-btn.is-active {
  border-color: var(--pmc-pink);
  background: var(--accent-soft-bg);
  color: var(--pmc-pink-dark);
}

.videos-filter-btn:focus-visible {
  outline: 3px solid var(--pmc-focus);
  outline-offset: 2px;
}

.videos-search-field {
  flex: 1 1 240px;
  min-width: 0;
  max-width: 420px;
}

.videos-search-field input {
  width: 100%;
  min-height: var(--touch-min);
  padding: 0.4rem 0.7rem;
  border: 1px solid var(--pmc-border);
  border-radius: var(--radius-md);
  font-size: 0.95rem;
  background: var(--input-bg);
  color: var(--pmc-text);
}

.videos-search-field input:focus-visible {
  outline: 2px solid var(--pmc-focus);
  outline-offset: 1px;
}

.videos-status {
  text-align: center;
  color: var(--pmc-text-muted);
  padding: 2rem 0;
}

.videos-error-state,
.videos-empty-state {
  text-align: center;
  padding: 2.5rem 1rem;
  color: var(--pmc-text-muted);
  background: var(--panel-bg);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
}

.videos-error-state p {
  margin: 0 0 0.75rem 0;
  color: var(--pmc-danger);
  font-weight: 600;
}

.videos-empty-state p {
  margin: 0;
  font-size: 1rem;
  line-height: 1.6;
}

.videos-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 1.25rem;
}

@media (max-width: 620px) {
  .videos-grid {
    grid-template-columns: 1fr;
  }
}

.video-card {
  background: var(--panel-bg);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  box-shadow: 0 1px 3px var(--shadow-color);
}

.video-card.is-highlighted {
  outline: 3px solid var(--pmc-pink);
  outline-offset: 2px;
}

.video-card-media {
  position: relative;
  width: 100%;
  aspect-ratio: 1080 / 1920;
  max-height: 70vh;
  background: var(--pmc-ink-soft);
}

.video-card-media video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  background: var(--pmc-ink-soft);
}

.video-card-badge {
  position: absolute;
  top: 0.5rem;
  left: 0.5rem;
  background: var(--pmc-pink);
  color: #fff;
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 0.2rem 0.6rem;
  border-radius: 999px;
  z-index: 1;
  pointer-events: none;
}

.video-card-body {
  padding: 0.85rem 1rem 1rem 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.video-card-title {
  margin: 0;
  font-size: 1rem;
  font-weight: 700;
  overflow-wrap: anywhere;
}

.video-card-meta {
  margin: 0;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.15rem 0.6rem;
  font-size: 0.82rem;
}

.video-card-meta dt {
  color: var(--pmc-text-muted);
  font-weight: 600;
}

.video-card-meta dd {
  margin: 0;
  overflow-wrap: anywhere;
}

.video-card-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.video-card-actions .btn {
  flex: 1 1 auto;
  font-size: 0.85rem;
  padding: 0.5rem 0.7rem;
}

.video-caption-field {
  border-top: 1px dashed var(--border-subtle);
  padding-top: 0.6rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.video-caption-label {
  font-size: 0.85rem;
  font-weight: 600;
}

.video-caption-textarea {
  width: 100%;
  min-height: 7.5rem;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--pmc-border);
  border-radius: var(--radius-md);
  font-size: 0.85rem;
  font-family: inherit;
  line-height: 1.4;
  resize: vertical;
  color: var(--pmc-text);
  background: var(--input-bg);
}

.video-caption-textarea:focus-visible {
  outline: 2px solid var(--pmc-focus);
  outline-offset: 1px;
}

.video-caption-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
}

.video-caption-toolbar .btn {
  font-size: 0.8rem;
  padding: 0.4rem 0.6rem;
  min-height: 38px;
}

.video-caption-count {
  margin-left: auto;
  font-size: 0.75rem;
  color: var(--pmc-text-muted);
}

.video-caption-status {
  margin: 0;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--pmc-success);
  min-height: 1.1rem;
}

.video-caption-status.is-error {
  color: var(--pmc-danger);
}

.video-share-status {
  margin: 0;
  font-size: 0.78rem;
  color: var(--pmc-text-muted);
  min-height: 1.1rem;
}

.video-delete-description {
  margin: 0 0 0.5rem 0;
  font-size: 0.9rem;
}

.video-delete-warning {
  margin: 0 0 1rem 0;
  font-size: 0.82rem;
  color: var(--pmc-text-muted);
}

.videos-toast {
  position: fixed;
  left: 50%;
  bottom: 1.25rem;
  transform: translateX(-50%);
  background: var(--pmc-ink);
  color: #fff;
  padding: 0.6rem 1.1rem;
  border-radius: 999px;
  font-size: 0.88rem;
  font-weight: 600;
  z-index: 200;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
}

/* Phase P2 (production hardening): the login page — a single centered
   card, reusing the same tokens/`.field`/`.btn` patterns the editor
   already uses so it reads as part of the same app, not a bolted-on
   separate page. Deliberately simple — no accordion sections, no preview
   panel, nothing beyond what a login form needs. */
.login-page-body {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  padding: 1.5rem;
}

.login-card {
  width: 100%;
  max-width: 360px;
  background: var(--panel-bg);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  padding: 1.75rem 1.5rem;
  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.25);
}

.login-card-header {
  text-align: center;
  margin-bottom: 1.5rem;
}

.login-card-header h1 {
  font-size: 1.3rem;
  margin: 0 0 0.2rem 0;
}

.login-card-header p {
  margin: 0;
  color: var(--pmc-text-muted);
  font-size: 0.85rem;
}

/* Large tap target (Part 11: mobile/S23) — noticeably taller than the
   editor's own compact fields, since this is the ONLY form on the page. */
.login-card .field input[type="text"],
.login-card .field input[type="password"] {
  min-height: 3rem;
  font-size: 1.05rem;
}

.login-card .btn-primary {
  width: 100%;
  min-height: 3rem;
  font-size: 1.05rem;
  margin-top: 0.5rem;
}

.login-error {
  margin: 0 0 1rem 0;
  padding: 0.6rem 0.75rem;
  border-radius: var(--radius-md);
  background: var(--danger-soft-bg);
  color: var(--pmc-danger);
  font-size: 0.85rem;
  font-weight: 600;
  text-align: center;
}
