/* VideoWorks — Build 9 design system, Build 10 editor.
   Tokens are the single source (research report §8.1): tiered-dark
   surfaces, ONE accent, semantic lane colours, system font only,
   elevation by lightness — never borders/shadows. Some Builds 1–8
   component styles still ride the legacy aliases (--panel,
   --border, --gold…) mapped onto the tokens. */
:root {
  /* surfaces — dark-only */
  --bg-0: #000000;
  --bg-1: #0E0E11;
  --surface-1: #17171B;
  --surface-2: #1F1F24;
  --surface-3: #2A2A31;
  --surface-4: #3A3A42;
  --hairline: rgba(255, 255, 255, 0.08);
  --text-1: #FFFFFF;
  --text-2: rgba(235, 235, 245, 0.62);
  --text-3: rgba(235, 235, 245, 0.34);
  /* the one accent */
  --accent: #4D7CFE;
  --accent-pressed: #3B68DB;
  --on-accent: #FFFFFF;
  --danger: #FF453A;
  --success: #30D158;
  --warning: #FFD60A;
  /* semantic lane colours — timeline only, never chrome */
  --track-video: #7D6BFF;
  --track-audio: #2FBF71;
  --track-text: #FFB340;
  --track-overlay: #2EC4C6;
  --track-effect: #FF6B9D;
  --playhead: #FFFFFF;
  --selection: #FFFFFF;
  /* type — system stack ONLY, no webfonts */
  --font: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", "Segoe UI", Inter, Roboto, sans-serif;
  --font-num: ui-monospace, "SF Mono", SFMono-Regular, Menlo, monospace;
  /* radii */
  --r-clip: 7px;
  --r-card: 12px;
  --r-chip: 10px;
  --r-sheet: 20px;
  --r-pill: 999px;
  /* spacing: 4pt base */
  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px; --s5: 24px; --s6: 32px;
  /* motion */
  --spring: cubic-bezier(.32, .72, .24, 1);
  --dur-sheet: 340ms;
  --dur-tool: 160ms;
  --dur-push: 320ms;
  /* legacy aliases (Builds 1–8 component styles ride these) */
  --bg: var(--bg-1);
  --panel: var(--surface-1);
  --panel-2: var(--surface-2);
  --border: var(--hairline);
  --text: var(--text-1);
  --muted: var(--text-2);
  --gold: #D4AF37;
  --green: #4CC38A;
  --amber: #E5A13D;
  --red: var(--danger);
  --radius: 14px;
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
html { height: 100%; }
body {
  background: var(--bg-1);
  color: var(--text-1);
  font: 16px/1.5 var(--font);
  -webkit-font-smoothing: antialiased;
}
/* App-shell discipline: the page itself never scrolls — home or
   editor. Each view owns its scroll regions; the editor is a
   locked 100dvh column whose stage is pinned (Build 10 law). */
body:has(> .app-home) { height: 100dvh; overflow: hidden; }
a { color: var(--accent); }
.muted { color: var(--text-2); }
button { font: inherit; color: inherit; }
input, select, textarea { font: inherit; }
/* The hidden attribute must always win: several components set an
   author display (grid/flex/block) that would otherwise override the
   UA's [hidden] rule and leave "hidden" elements painted — this is
   what kept the stage placeholder visible over playing video until
   Build 6 (found by the first full smoke run). */
[hidden] { display: none !important; }

/* ---------- Icons (one inline-SVG set, 24px, 1.8 stroke) ---------- */
.ic { width: 24px; height: 24px; flex: none; display: block; }
.ic-sm { width: 20px; height: 20px; }
button:active .ic, a:active .ic { transform: scale(0.96); }

/* ---------- Buttons ---------- */
.btn {
  appearance: none; border: 1px solid var(--hairline); background: var(--surface-2); color: var(--text-1);
  border-radius: 10px; padding: 10px 16px; cursor: pointer; min-height: 44px;
}
.btn:active { background: var(--surface-3); transform: scale(0.98); }
.btn-primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); font-weight: 700; }
.btn-primary:active { background: var(--accent-pressed); }
.btn-small { padding: 6px 12px; min-height: 44px; min-width: 44px; font-size: 14px; }
.btn-ghost { background: transparent; }
.btn-danger { color: var(--danger); }
.icon-btn {
  appearance: none; border: 0; background: transparent; color: var(--text-1);
  width: 44px; height: 44px; border-radius: var(--r-pill); display: grid; place-items: center;
  cursor: pointer; padding: 0;
}
.icon-btn:active { background: var(--surface-3); }
/* Build 48: one visible keyboard focus ring — the app accent
   on dark surfaces, everywhere. Controls with their own :focus
   rules (the sheet text fields) out-specify this and keep
   theirs. */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Build 48: range inputs get a real hit height (the drawn
   track stays slim and centred; the box is the target).
   Zoomctl/transport rules below out-specify this where their
   chrome rows cap the height. */
input[type="range"] { min-height: 28px; }

/* ---------- Home (app shell) ---------- */
.home { max-width: 860px; margin: 0 auto; padding: 20px 16px 40px; } /* legacy: story page */
.app-home {
  max-width: none; margin: 0; padding: 0; height: 100dvh;
  display: flex; flex-direction: column; background: var(--bg-1);
}
.app-bar {
  flex: none; display: flex; align-items: center; gap: var(--s2);
  padding: calc(var(--s2) + env(safe-area-inset-top)) var(--s4) var(--s2);
  background: rgba(14, 14, 17, 0.86); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  z-index: 5;
}
.app-bar .brand { display: flex; gap: 10px; align-items: center; flex: 1; min-width: 0; }
.brand-mark { border-radius: 9px; flex: none; }
.app-bar h1 { margin: 0; font-size: 20px; font-weight: 700; letter-spacing: 0.1px; line-height: 1.15; }
.app-bar .tagline { margin: 0; font-size: 12px; color: var(--text-3); line-height: 1.2; }
.app-bar-actions { display: flex; gap: var(--s1); flex: none; }

.create-block { flex: none; padding: var(--s3) var(--s4) var(--s2); }
.create-card {
  appearance: none; width: 100%; min-height: 64px; border: 0; border-radius: var(--r-card);
  background: var(--accent); color: var(--on-accent); cursor: pointer;
  display: flex; align-items: center; justify-content: center; gap: var(--s2);
  font-size: 17px; font-weight: 600; padding: 12px 16px;
}
.create-card:active { background: var(--accent-pressed); transform: scale(0.99); }
/* Build 58: the Reels section entry stacks under New project
   (same card, second action — no new layout, just a gap). */
.create-block .create-card + .create-card { margin-top: 8px; }
/* Build 58: the reels-mode marker in the editor topbar —
   descriptive label only, accent-tinted like the aspect pill. */
.reels-pill {
  flex: none; padding: 7px 12px; min-height: 44px; display: flex; align-items: center;
  background: var(--surface-3); color: var(--accent); border-radius: var(--r-pill);
  font-size: 13px; font-weight: 600;
}
/* Build 58: the safe-zone approximate note under the select. */
.safezone-note { margin: 6px 0 0; font-size: 12px; line-height: 1.45; }
/* Build 58: onboarding beats — plain paragraphs, bold lead-ins. */
.reels-lines p { margin: 0 0 10px; line-height: 1.5; }
.reels-lines p:last-child { margin-bottom: 0; }

.home-scroll { flex: 1; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain; }
.home-view { padding: var(--s3) var(--s4) calc(var(--s5) + env(safe-area-inset-bottom)); }
.view-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s3); margin: var(--s2) 0 var(--s3); }
.view-head h2 { margin: 0; font-size: 17px; font-weight: 600; }
.view-count { color: var(--text-3); font-size: 12px; }

/* project library grid */
.project-grid {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px 12px;
}
.project-card { position: relative; }
.card-main {
  appearance: none; border: 0; background: transparent; padding: 0; cursor: pointer;
  display: block; width: 100%; text-align: left; -webkit-tap-highlight-color: transparent;
}
.card-thumb {
  position: relative; display: block; aspect-ratio: 16 / 10; border-radius: var(--r-card);
  overflow: hidden; background: var(--surface-1);
}
.card-thumb.has-poster { background-size: cover; background-position: center; }
/* Build 39 (audit F6): the placeholder layer is opaque and
   covers the whole thumb — once a real poster lands it must
   retire, or the poster paints invisibly underneath it (the
   audit's home cards stayed blank-faced all session). */
.card-thumb.has-poster .card-thumb-ph { display: none; }
.card-thumb-ph {
  position: absolute; inset: 0; display: grid; place-items: center;
  background: linear-gradient(150deg, #23232e 0%, #17171f 55%, #101016 100%);
}
.card-thumb-ph i {
  display: block; border: 2px solid rgba(235, 235, 245, 0.4); border-radius: 4px;
}
.card-dur {
  position: absolute; right: 6px; bottom: 6px; z-index: 2;
  background: rgba(0, 0, 0, 0.62); color: #fff; font-family: var(--font-num);
  font-size: 11px; padding: 1px 6px; border-radius: var(--r-pill);
}
.card-more {
  position: absolute; top: 4px; right: 4px; z-index: 3;
  width: 44px; height: 44px; border: 0; border-radius: var(--r-pill);
  background: rgba(10, 10, 14, 0.5); color: #fff; display: grid; place-items: center; cursor: pointer;
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
}
.card-more:active { background: rgba(10, 10, 14, 0.78); }
.project-name {
  display: block; margin-top: 8px; font-size: 13.5px; font-weight: 500; color: var(--text-1);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.project-meta { display: block; font-size: 12px; color: var(--text-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Build 47: the card's quiet media-size line. */
.project-size { display: block; font-size: 11.5px; color: var(--text-2); opacity: 0.75; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* skeleton shimmer while the library loads */
.skel-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px 12px; }
.skel-thumb {
  aspect-ratio: 16 / 10; border-radius: var(--r-card); background: var(--surface-2);
  animation: skel-pulse 1.25s ease-in-out infinite alternate;
}
.skel-line { height: 12px; border-radius: 6px; background: var(--surface-2); margin-top: 10px; animation: skel-pulse 1.25s ease-in-out infinite alternate; }
.skel-line.short { width: 55%; }
@keyframes skel-pulse { from { opacity: 0.55; } to { opacity: 1; } }

/* empty state — designed, not a paragraph */
.projects-empty { text-align: center; padding: 56px 24px 40px; color: var(--text-2); }
.projects-empty .ic { width: 44px; height: 44px; margin: 0 auto 12px; color: var(--text-3); }
.projects-empty p { margin: 0 0 4px; font-size: 17px; font-weight: 600; color: var(--text-1); }
.projects-empty small { display: block; font-size: 13px; margin-bottom: 20px; }
.projects-empty .btn { min-width: 180px; }

/* ---------- Device view (diagnostics, restyled) ---------- */
.diag-verdict {
  display: flex; gap: 10px; align-items: flex-start;
  border-radius: var(--r-card); padding: 14px; font-weight: 600; background: var(--surface-1);
  font-size: 15px; margin-bottom: var(--s4);
}
.diag-dot { flex: none; width: 10px; height: 10px; border-radius: 50%; margin-top: 6px; background: var(--text-3); }
.diag-verdict[data-status="ready"] .diag-dot { background: var(--success); }
.diag-verdict[data-status="limited"] .diag-dot { background: var(--warning); }
.diag-verdict[data-status="no"] .diag-dot { background: var(--danger); }
.diag-intro { font-size: 13.5px; margin: 0 0 var(--s4); }
.diag-group { background: var(--surface-1); border-radius: var(--r-card); padding: 2px 14px; }
.diag-row { padding: 11px 0; }
.diag-row + .diag-row { border-top: 1px solid var(--hairline); }
.diag-row-head { display: flex; justify-content: space-between; align-items: center; gap: 10px; }
.diag-label { font-weight: 500; font-size: 15px; }
.diag-detail { margin: 3px 0 0; color: var(--text-2); font-size: 13px; }
.diag-running { color: var(--text-2); font-size: 14px; }
/* Build 44: the Device view's Storage section + install hint. */
.device-subhead { font-size: 17px; font-weight: 600; margin: var(--s5) 0 var(--s3); }
.storage-ask { padding: 10px 0 12px; }
/* Build 47: the ask row can hold two buttons now — it only
   collapses when every child is hidden. */
.storage-ask:empty, .storage-ask:has(> [hidden]):not(:has(> :not([hidden]))) { padding: 0; }
.storage-ask .chip-btn + .chip-btn { margin-left: 8px; }
.install-hint {
  display: flex; gap: 12px; align-items: flex-start;
  background: var(--surface-1); border: 1px solid var(--hairline);
  border-radius: var(--r-card); padding: 14px; margin: 0 0 var(--s4);
}
.install-hint[hidden] { display: none; }
.install-hint-ic { width: 26px; height: 26px; flex: none; color: var(--accent); margin-top: 2px; }
.install-hint-main { flex: 1; min-width: 0; }
.install-hint-title { margin: 0 0 3px; font-size: 15px; font-weight: 600; }
.install-hint-text { margin: 0; font-size: 13.5px; color: var(--text-2); }
.pill { font-size: 12px; font-weight: 700; border-radius: var(--r-pill); padding: 2px 10px; white-space: nowrap; }
.pill-ready { background: rgba(48, 209, 88, 0.16); color: var(--success); }
.pill-limited { background: rgba(255, 214, 10, 0.14); color: var(--warning); }
.pill-no { background: rgba(255, 69, 58, 0.16); color: var(--danger); }
.chip-btn {
  appearance: none; display: inline-flex; align-items: center; gap: 6px;
  border: 0; background: var(--surface-3); color: var(--text-1); cursor: pointer;
  border-radius: var(--r-pill); padding: 7px 12px; font-size: 13px; font-weight: 600; min-height: 44px;
}
/* Build 48 exception (documented): the editor's timeline
   toolbar is height-capped resting chrome — its height is the
   pinned stage's budget (Build 38 trims), so its chips keep
   the dense 36px row height. The phone equivalents of these
   toggles live in the More ribbon at the full 44px, and on
   desktop 36px still clears the 24px pointer floor (WCAG
   2.5.8 AA). */
#editor-view .tl-toolbar .chip-btn { min-height: 36px; }
.chip-btn:active { background: var(--surface-4); }
.chip-btn .ic { width: 16px; height: 16px; }
.build-chip {
  font-size: 11px; font-weight: 600; color: var(--text-2);
  background: var(--surface-3); border-radius: var(--r-pill); padding: 2px 8px; white-space: nowrap;
}
.home-foot {
  margin-top: var(--s6); padding-top: var(--s4); border-top: 1px solid var(--hairline);
  color: var(--text-3); font-size: 12.5px; display: flex; gap: 8px; flex-wrap: wrap; align-items: center;
}

/* ---------- Tab bar (phone bottom; desktop left rail) ---------- */
.tab-bar {
  flex: none; display: flex; align-items: stretch; z-index: 6;
  background: rgba(23, 23, 27, 0.88); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
  border-top: 1px solid var(--hairline);
  padding-bottom: env(safe-area-inset-bottom);
}
.tab-btn {
  appearance: none; border: 0; background: transparent; cursor: pointer; flex: 1;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  min-height: 52px; padding: 6px 0 4px; color: var(--text-2); font-size: 11px; font-weight: 500;
  -webkit-tap-highlight-color: transparent;
}
.tab-btn .ic { width: 25px; height: 25px; }
.tab-btn.is-active { color: var(--text-1); }
.tab-btn.is-active .ic { color: var(--accent); }
.tab-create {
  appearance: none; border: 0; background: transparent; cursor: pointer; flex: 1;
  display: flex; align-items: center; justify-content: center; -webkit-tap-highlight-color: transparent;
}
.tab-create-circle {
  width: 52px; height: 52px; border-radius: 50%; background: var(--accent); color: #fff;
  display: grid; place-items: center; margin-top: -14px;
  border: 4px solid var(--bg-1);
}
.tab-create:active .tab-create-circle { background: var(--accent-pressed); transform: scale(0.96); }
.tab-create-circle .ic { width: 26px; height: 26px; }

/* ---------- Sheets (grabber, spring, detent) ---------- */
.sheet-overlay {
  position: fixed; inset: 0; z-index: 90; display: flex; align-items: flex-end; justify-content: center;
  background: rgba(0, 0, 0, 0.45); opacity: 0; transition: opacity var(--dur-sheet) var(--spring);
}
.sheet-overlay.is-open { opacity: 1; }
.sheet {
  width: min(560px, 100%); background: var(--surface-2);
  border-radius: var(--r-sheet) var(--r-sheet) 0 0;
  padding: var(--s2) var(--s5) calc(var(--s4) + env(safe-area-inset-bottom));
  transform: translateY(103%); transition: transform var(--dur-sheet) var(--spring);
  max-height: 92dvh; overflow-y: auto;
}
.sheet-overlay.is-open .sheet { transform: translateY(0); }
.sheet-grabber {
  width: 36px; height: 5px; border-radius: var(--r-pill); background: var(--surface-4);
  margin: 2px auto 10px; flex: none;
}
.sheet-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); margin-bottom: var(--s3); }
.sheet-head h3 { margin: 0; font-size: 17px; font-weight: 600; }
.sheet-head .icon-btn { margin-right: -10px; }
.sheet-note { color: var(--text-2); font-size: 13.5px; margin: 0 0 var(--s3); word-break: break-word; }

/* Build 56: first-run sheet — stacked actions, quiet sample
   line. Buttons wear the normal 44px floor (unlike the stage
   card's compact buttons); the drag hint is desktop-only. */
.firstrun-actions { display: flex; flex-direction: column; gap: var(--s3); margin: var(--s4) 0; }
.firstrun-actions .btn { width: 100%; justify-content: center; }
.firstrun-drag { margin: 0 0 var(--s3); }
.firstrun-sample { display: block; width: 100%; padding: 12px; background: none; border: 0; color: var(--text-2); font-size: 14px; line-height: 1.4; cursor: pointer; }
.firstrun-sample:hover { color: var(--text-1); }
@media (pointer: coarse) { .firstrun-drag { display: none; } }

/* create-project sheet */
#create-sheet { min-height: 52dvh; }
.create-field { display: block; margin-bottom: var(--s4); }
.create-field > span { display: block; font-size: 13px; font-weight: 600; color: var(--text-2); margin-bottom: 6px; }
.create-field input[type="text"] {
  width: 100%; padding: 12px; border-radius: var(--r-chip); border: 1px solid var(--hairline);
  background: var(--surface-1); color: var(--text-1); font-size: 16px; appearance: none;
}
.create-field input[type="text"]:focus { outline: 2px solid var(--accent); outline-offset: 0; border-color: transparent; }
.aspect-cap { font-size: 13px; font-weight: 600; color: var(--text-2); margin: 0 0 6px; }
.aspect-tiles { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--s2); margin-bottom: var(--s5); }
.aspect-tile {
  display: grid; justify-items: center; gap: 5px; cursor: pointer;
  background: var(--surface-3); border-radius: var(--r-card); padding: 12px 4px 10px;
  border: 2px solid transparent;
}
.aspect-tile input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.aspect-tile .tile-shape { height: 30px; display: flex; align-items: center; justify-content: center; }
.aspect-tile .tile-shape i { display: block; border: 2px solid var(--text-2); border-radius: 3px; }
.aspect-tile .tile-name { font-size: 13px; font-weight: 600; }
.aspect-tile .tile-sub { font-size: 10.5px; color: var(--text-3); text-align: center; line-height: 1.25; }
.aspect-tile:has(input:checked) { border-color: var(--accent); background: var(--surface-4); }
.aspect-tile:has(input:checked) .tile-shape i { border-color: var(--accent); }
.aspect-tile:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Build 59: create-sheet template radios + picker rows — the
   sheet's own list language (name over a muted one-liner),
   nothing fancier, so a template never looks like footage. */
#create-template-options { display: grid; gap: 6px; margin-bottom: var(--s5); }
.template-radio { display: flex; gap: 10px; align-items: flex-start; padding: 10px 12px; border: 1px solid var(--hairline); border-radius: var(--r-chip); cursor: pointer; }
.template-radio:has(input:checked) { border-color: var(--accent); background: var(--surface-4); }
.template-radio input { margin-top: 2px; accent-color: var(--accent); }
.template-radio-text { display: grid; gap: 1px; }
.template-radio-name { font-size: 14.5px; font-weight: 600; }
.template-radio-sub { font-size: 12.5px; line-height: 1.3; }
.reel-template-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.reel-template-row { display: grid; gap: 6px; padding: 12px; border: 1px solid var(--hairline); border-radius: var(--r-chip); }
.reel-template-name { font-size: 15px; font-weight: 600; }
.reel-template-sub { font-size: 12.5px; line-height: 1.35; }
.reel-template-actions { display: flex; gap: 8px; flex-wrap: wrap; }
#create-submit { width: 100%; min-height: 52px; font-size: 16px; border-radius: var(--r-card); }

/* action sheet (project ⋯ / long-press) */
.action-rows { display: grid; gap: 2px; }
.action-row {
  appearance: none; border: 0; background: transparent; color: var(--text-1); cursor: pointer;
  display: flex; align-items: center; gap: var(--s3); width: 100%; min-height: 52px;
  padding: 6px var(--s2); border-radius: var(--r-chip); font-size: 16px; text-align: left;
}
.action-row:active { background: var(--surface-3); }
.action-row .ic { color: var(--text-2); }
.action-row.is-danger, .action-row.is-danger .ic { color: var(--danger); }
.sheet-cancel {
  appearance: none; width: 100%; min-height: 52px; margin-top: var(--s3);
  border: 0; border-radius: var(--r-card); background: var(--surface-3); color: var(--text-1);
  font-size: 16px; font-weight: 600; cursor: pointer;
}
.sheet-cancel:active { background: var(--surface-4); }
.rename-field input[type="text"] {
  width: 100%; padding: 12px; border-radius: var(--r-chip); border: 1px solid var(--hairline);
  background: var(--surface-1); color: var(--text-1); font-size: 16px; appearance: none; margin-bottom: var(--s4);
}
.rename-field input[type="text"]:focus { outline: 2px solid var(--accent); border-color: transparent; }

/* desktop home: tab bar becomes a left rail, grid unfolds */
@media (min-width: 1024px) {
  .app-home { flex-direction: row; }
  .home-main-col { flex: 1; min-width: 0; display: flex; flex-direction: column; }
  .tab-bar {
    order: -1; flex-direction: column; align-items: stretch; width: 88px; flex: none;
    border-top: 0; border-right: 1px solid var(--hairline); padding: calc(var(--s4) + env(safe-area-inset-top)) 0 var(--s4);
    justify-content: flex-start; gap: var(--s2);
  }
  .tab-btn { flex: none; min-height: 60px; }
  .tab-create { flex: none; padding: var(--s2) 0; }
  .tab-create-circle { margin-top: 0; }
  .project-grid, .skel-grid { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 24px 16px; }
  .home-view { padding-left: var(--s5); padding-right: var(--s5); }
  .create-block { padding-left: var(--s5); padding-right: var(--s5); }
}
@media (min-width: 761px) {
  .sheet-overlay { align-items: center; padding: 20px; }
  .sheet { border-radius: var(--r-sheet); }
  #create-sheet { min-height: 0; }
}

/* ---------- Story ---------- */
.story { max-width: 720px; }
.story h1 { font-size: 30px; margin: 10px 0 14px; }
.story p { margin: 0 0 14px; }
.story .gap { font-size: 18px; }
.site-footer { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; color: var(--muted); padding: 18px 4px; font-size: 14px; }

.home-main-col { flex: 1; min-height: 0; min-width: 0; display: flex; flex-direction: column; }
/* ---------- Editor (Build 10: app chrome on the token system) ----
   LAYOUT LAW (user-directed, the PhotoWorks rule): the stage is
   pinned — sized once from the resting chrome, centred in the
   largest area the screen offers, and it NEVER resizes, shifts or
   reflows when UI state changes. Only the zones below it move:
   ribbons overlay the lower zone (transport + timeline + dock),
   sheets rise from the bottom over the lower zones.
   Build 53 distinction: a DELIBERATE layout change is the one
   exception — dragging a panel border, moving the Layout
   slider, or collapsing a panel re-centres the stage at maximum
   size in the space that remains. That is the user resizing
   the room, not the UI jittering inside it. Build 54 adds the
   dock-height handle to the same exception list: dragging it
   resizes the stage to the room that remains, and nothing
   else (ribbon, sheet, playback, selection) moves the stage
   at a fixed dock height. */
.editor {
  display: flex; flex-direction: column; height: 100dvh; overflow: hidden;
  background: var(--bg-1);
}
/* top bar: back / name / aspect pill / the ONE Export pill */
.ed-topbar {
  order: 0; flex: none; display: flex; align-items: center; gap: var(--s2);
  padding: calc(6px + env(safe-area-inset-top)) 10px 6px;
  background: var(--surface-1); border-bottom: 1px solid var(--hairline); z-index: 5;
}
.editor-title {
  margin: 0; font-size: 17px; font-weight: 600; flex: 1; min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.aspect-pill {
  appearance: none; display: flex; align-items: center; gap: 3px; flex: none;
  background: var(--surface-3); color: var(--text-1); border: 0; cursor: pointer;
  border-radius: var(--r-pill); padding: 7px 8px 7px 12px; min-height: 44px;
  font-family: var(--font-num); font-size: 13px;
}
.aspect-pill:active { background: var(--surface-4); }
.aspect-pill .ic { color: var(--text-2); }
.export-pill {
  appearance: none; display: flex; align-items: center; gap: 6px; flex: none;
  background: var(--accent); color: var(--on-accent); border: 0; cursor: pointer;
  border-radius: var(--r-pill); padding: 8px 16px; min-height: 44px;
  font-size: 15px; font-weight: 600;
}
.export-pill:active { background: var(--accent-pressed); transform: scale(0.98); }
/* Build 53: the Layout button lives in the top bar on desktop
   only — on a phone the panels live in ribbons, so there is
   nothing to resize (the More ribbon's Layout row says so). */
.layout-btn { display: none; }
/* Build 53: panel collapse affordances are desktop-only too. */
.panel-collapse { display: none; }
.panel-rail { display: none; }
.panel-resize { display: none; }
.panel-head-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.panel-head-row h3 { margin: 0; }
/* Build 53: the layout sheet's controls. */
.layout-slider-row { display: flex; align-items: center; gap: 10px; margin: 4px 0 2px; }
.layout-slider-row label { font-size: 14px; font-weight: 600; flex: none; }
.layout-slider-row input[type="range"] { flex: 1; min-width: 0; accent-color: var(--accent); }
.layout-stop-name { font-size: 14px; font-weight: 600; color: var(--accent); flex: none; min-width: 76px; text-align: right; }
.layout-stops { display: flex; justify-content: space-between; font-size: 12px; color: var(--muted); margin: 0 0 10px; }
.layout-hint { margin: 0 0 14px; }
.layout-toggles { display: flex; gap: 8px; flex-wrap: wrap; margin: 0 0 12px; }

/* panels (desktop grid cells; on phone they live in ribbons) */
.panel { padding: 14px; background: var(--surface-1); overflow-y: auto; min-height: 0; }
.panel h3 { margin: 0 0 10px; font-size: 15px; }
.panel-assets { order: 3; }
.panel-inspector { order: 4; }
.panel-list { padding-left: 18px; margin: 0; }
.panel-section { border-bottom: 1px solid var(--hairline); padding-bottom: 12px; margin-bottom: 12px; }

/* the pinned stage: flexes to every remaining pixel, frame centred */
.stage-wrap {
  order: 1; flex: 1; min-height: 0; background: var(--bg-0);
  display: grid; place-items: center; padding: 10px;
  container-type: size;
}
.stage-frame {
  position: relative; background: #000; border-radius: 8px; overflow: hidden;
  width: min(100cqw, calc(100cqh * var(--ar, 0.5625)));
  aspect-ratio: var(--ars, 9 / 16);
}
@supports not (width: 1cqw) {
  .stage-frame { width: min(100%, calc((100dvh - 420px) * var(--ar, 0.5625))); min-width: 140px; }
}
.stage-placeholder { position: absolute; inset: 0; display: grid; place-items: center; padding: 6px; }
/* Build 38: the import workspace card — the stage's empty state
   is a drop card that names its job (dashed outline, upload
   icon, one headline, one line, the import button), in the
   media bin empty state's language. It lives INSIDE the pinned
   stage frame, so it can never move the stage box. */
.stage-import-card {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: 5px; width: min(96%, 300px); box-sizing: border-box;
  border: 1.5px dashed var(--border); border-radius: var(--r-card);
  background: rgba(23, 23, 27, 0.78); padding: 12px 10px;
}
.stage-import-card.is-dragover { border-color: var(--accent); background: rgba(77, 124, 254, 0.14); }
.stage-import-ic { width: 26px; height: 26px; color: var(--accent); }
.stage-import-title { margin: 0; font-size: 15px; font-weight: 700; line-height: 1.2; }
.stage-import-sub { margin: 0; font-size: 11.5px; line-height: 1.4; color: var(--text-2); }
.stage-import-card .btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-height: 38px; max-width: 100%; white-space: normal; text-align: center;
  font-size: 14px; padding: 7px 10px; line-height: 1.25;
}
/* Build 56: the sample action on the card is a quiet text
   action, not a fourth full button — as a .btn it wrapped to
   69px and pushed the card to 316.9px inside a 303px phone
   frame (measured), breaking the Build 38 fit rule. At text
   size the card fits again with room to spare. */
.stage-import-card .stage-import-sample {
  min-height: 0; padding: 4px 4px; font-size: 12.5px; line-height: 1.35;
  color: var(--text-2);
}
.stage-import-card .stage-import-sample:hover { color: var(--text-1); }
/* the card must FIT the frame it lives in (a phone 9:16 frame
   is ~290px tall in the test rig): with media waiting, the
   phone Record entry steps aside — it stays one tap away in
   the media panel — so the card keeps all its words and both
   remaining buttons inside the frame. */
.stage-import-card.has-media .stage-import-record { display: none; }
/* on a phone, a 16:9 frame is only ~210px tall: the card drops
   its supporting line there rather than spilling past the
   frame's edges */
@media (max-width: 1023.5px) {
  .stage-frame[data-aspect="16:9"] .stage-import-sub { display: none; }
  .stage-frame[data-aspect="16:9"] .stage-import-ic { width: 24px; height: 24px; }
}
/* the Record entry rides the card on phone only (desktop
   records from the media panel, as since Build 27). The
   selectors carry the card scope so they outrank the card's
   generic .btn display rule. */
.stage-import-card .stage-import-record { display: none; }
@media (max-width: 1023.5px) {
  .stage-import-card .stage-import-record { display: inline-flex; }
}

/* project frame controls (inspector section / Aspect ribbon) */
#aspect-controls { display: grid; gap: 10px; }
.aspect-switcher { display: flex; gap: 6px; flex-wrap: wrap; }
.aspect-chip {
  border: 1px solid var(--hairline); background: var(--surface-3); color: var(--text-1);
  border-radius: var(--r-pill); padding: 7px 14px; cursor: pointer; min-height: 44px;
  font-family: var(--font-num); font-size: 13px;
}
.aspect-chip.is-active { border-color: var(--accent); color: var(--accent); font-weight: 700; }
.safezone-control { display: flex; align-items: center; gap: 8px; font-size: 14px; color: var(--muted); }
.safezone-control select {
  background: var(--surface-3); color: var(--text-1); border: 1px solid var(--hairline);
  border-radius: 8px; padding: 8px 10px; min-height: 44px;
}

/* Build 29: Smart reframe controls (same #aspect-controls grid) */
.reframe-control { display: grid; gap: 8px; }
.reframe-row { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.reframe-track { display: inline-flex; align-items: center; gap: 7px; font-size: 14px; color: var(--muted); cursor: pointer; min-height: 38px; }
.reframe-track input { width: 18px; height: 18px; accent-color: var(--accent); }
.reframe-note { margin: 0; font-size: 13px; line-height: 1.45; }

.safezone-layer { position: absolute; inset: 0; pointer-events: none; }
/* Build 38: the guide is ONE thin outline in a quiet colour +
   one small chip that says "Safe area" in plain words. The
   filled bands and the filled green slab with its technical
   sentence are gone (they covered the picture and read as
   clutter — the user's verdict + audit F4). The chip sits
   inside the outline, which sits inside the frame, so it is
   fully inside the frame at any stage size; when the outline
   is too small to hold it, app.js omits the chip entirely. */
.sz-rect { position: absolute; border: 1.5px solid rgba(111, 207, 151, 0.8); border-radius: 3px; }
.sz-chip {
  position: absolute; top: 5px; left: 5px; max-width: calc(100% - 10px);
  background: rgba(8, 10, 9, 0.62); color: #e9f6ee; border-radius: 999px;
  font-size: 10.5px; font-weight: 600; line-height: 1.3; padding: 3px 8px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sz-chip small {
  display: block; font-size: 9.5px; font-weight: 500; opacity: 0.78;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.insp-row { display: flex; justify-content: space-between; gap: 12px; padding: 7px 0; border-top: 1px solid var(--border); font-size: 14px; }
.insp-k { color: var(--muted); flex: none; }
.insp-v { text-align: right; }

/* lower zone: transport + timeline + dock at resting heights;
   the ribbon overlays exactly this box and nothing above it */
.lower-zone {
  order: 2; flex: none; position: relative; min-height: 0;
  background: var(--surface-1); border-top: 1px solid var(--hairline);
  /* Build 54: the zone's height IS the dock height (js/layout.js
     law, set as --dock-h by applyLayout). The 72dvh guard keeps
     a very short viewport from squeezing the stage to nothing
     without touching the brief's clamps on normal viewports
     (72% of 800 px is 576 > the 520 px desktop max); the flex
     column below makes the lane stack the only part that
     bends when the height changes. */
  height: var(--dock-h);
  max-height: 72dvh;
  display: flex; flex-direction: column;
}
/* Build 54: the dock-height handle — an in-flow row at the
   zone's top edge (never straddling a border: Build 53
   lesson), quiet at rest, accent grip on hover/focus/drag.
   12px tall so it is a real target on phone and desktop. */
.dock-resize {
  flex: none; height: 12px; display: grid; place-items: center;
  cursor: ns-resize; touch-action: none; background: var(--surface-1);
}
.dock-resize-grip {
  width: 36px; height: 4px; border-radius: 2px; background: var(--border);
}
.dock-resize:hover .dock-resize-grip,
.dock-resize:focus-visible .dock-resize-grip,
.dock-resize.is-dragging .dock-resize-grip { background: var(--accent); }
.dock-resize:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.transport {
  flex: none;
  display: flex; align-items: center; gap: var(--s2);
  /* Build 48: vertical padding 4px -> 2px buys the taller
     44px transport targets below at the SAME row height —
     the row is resting chrome and its height is the pinned
     stage's budget. */
  padding: 2px 10px; min-height: 44px;
}
.tp-group { display: flex; align-items: center; gap: 2px; flex: none; }
.tp-btn { width: 44px; height: 44px; color: var(--text-2); }
.tp-btn:disabled { opacity: 0.35; }
.tp-btn[aria-pressed="true"] { color: var(--accent); }
.tp-btn[aria-pressed="true"]:active { background: var(--surface-3); }
.tp-play {
  appearance: none; display: flex; align-items: center; gap: 6px; flex: none;
  background: var(--surface-3); border: 0; color: var(--text-1); cursor: pointer;
  border-radius: var(--r-pill); padding: 7px 15px 7px 12px; min-height: 44px;
  font-weight: 600; font-size: 14px;
}
.tp-play:active { background: var(--surface-4); }
.tp-play:disabled { opacity: 0.45; cursor: default; }
.tp-play .ic { width: 22px; height: 22px; }
.tp-time { flex: 1; text-align: center; }
.tp-vol { display: flex; align-items: center; gap: 2px; }
.tp-vol input[type="range"] { width: 84px; accent-color: var(--accent); }

/* timeline — Build 54: the flex column inside the dock. The
   toolbar and note keep their heights; the wrap (and its
   scrollport) take whatever height remains. */
.timeline { background: var(--surface-1); padding: 2px 0 4px; flex: 1; min-height: 0; display: flex; flex-direction: column; }
.tl-toolbar {
  flex: none;
  display: flex; justify-content: space-between; align-items: center; gap: 8px;
  padding: 2px 10px 6px;
}
.tl-tools { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
/* Build 56 (amended): the timeline "+" keeps its words on desktop
   but shrinks to the glyph alone on narrow screens, so the phone
   toolbar stays one row (Ripple + "+" + zoom ≈ 344px at 390px
   wide; the words would wrap it to two rows and eat lane room). */
@media (max-width: 1023.5px) {
  #tl-add-media .tl-add-label { display: none; }
}
.tl-split-btn { display: inline-flex; align-items: center; gap: 6px; }
.tl-zoomctl { display: flex; align-items: center; gap: 2px; }
.tl-zoomctl input[type="range"] { width: 96px; accent-color: var(--accent); }
.tl-wrap { position: relative; flex: 1; min-height: 0; display: flex; flex-direction: column; }
/* Build 54: the lane stack takes the dock body's full height
   (flex: 1 inside the wrap). There is no max-height cap any
   more: the dock height itself is the budget (Build 38's cap
   existed because the zone height was content-driven; now it
   is set). The ruler and the beat strip stick to the top of
   the scrollport while the lanes scroll beneath them.
   box-sizing: border-box so the height includes the bottom
   padding. When the dock is tall enough, JS fills the lanes
   exactly (see .is-filling); when it is not, the stack
   scrolls inside its budget instead of squeezing the stage
   below its floor (audit F2). */
.tl-scroll {
  overflow: auto; padding: 0 12px 2px 0; position: relative;
  flex: 1; min-height: 0; box-sizing: border-box;
}
.tl-ruler {
  position: sticky; top: 0; z-index: 4; height: 20px; margin-left: 64px;
  background: var(--surface-1);
  font-size: 10.5px; color: var(--text-3); font-family: var(--font-num);
}
.tl-tick { position: absolute; top: 0; transform: translateX(2px); }
.tl-tick::before { content: ""; position: absolute; left: -2px; top: 12px; width: 1px; height: 6px; background: var(--border); }
/* Build 18: the beat-marker strip. Fixed height ALWAYS (empty or
   full) so toggling markers can never resize the timeline zone —
   the pinned stage above it stays pixel-identical (design law). */
.tl-beats { position: sticky; top: 20px; z-index: 3; height: 10px; margin-left: 64px; margin-top: 1px; background: var(--surface-1); }
.tl-beat-tick {
  position: absolute; bottom: 0; width: 2px; height: 6px; margin-left: -1px;
  border-radius: 1px; background: rgba(47, 191, 113, 0.35);
}
.tl-beat-tick.is-downbeat { height: 10px; background: rgba(47, 191, 113, 0.85); }
.tl-lane-row { display: flex; align-items: stretch; margin-top: 3px; }
.tl-track-label {
  flex: none; width: 52px; margin-left: 12px; font-size: 11px; color: var(--muted);
  display: flex; align-items: center; position: sticky; left: 0; z-index: 2; background: var(--surface-1);
}
.tl-track-label::before {
  content: ""; flex: none; width: 7px; height: 7px; border-radius: 50%;
  margin-right: 5px; background: var(--track-video);
}
.tl-row-text .tl-track-label::before { background: var(--track-text); }
.tl-row-overlay .tl-track-label::before { background: var(--track-overlay); }
.tl-row-audio .tl-track-label::before { background: var(--track-audio); }
.tl-row-captions .tl-track-label::before { background: #f2f2ed; }
.tl-row-captions .tl-track-label { font-size: 10px; letter-spacing: -0.2px; }
/* Build 57: --lane-h must be a registered property for the
   focus-height glide to animate: without @property, a custom
   property jumps instead of transitioning. inherits:true so
   the .tl-lane child sees the row's animated value; the
   44px initial matches the existing fallback everywhere. */
@property --lane-h { syntax: '<length>'; inherits: true; initial-value: 44px; }
.tl-lane { position: relative; background: var(--surface-2); border-radius: 6px; flex: none; height: calc(var(--lane-h, 44px) * var(--rows, 1)); }
/* Build 57: the focus glide runs ONLY while .is-focus-anim is
   on the scrollport (app.js adds it around a focus change),
   so dock drags and timeline re-renders stay instant. */
.tl-scroll.is-focus-anim .tl-lane-row { transition: --lane-h 180ms ease; }
@media (prefers-reduced-motion: reduce) {
  .tl-scroll.is-focus-anim .tl-lane-row { transition: none; }
}
/* Build 51 (visual sweep V1): --lane-h is the lane's single-row
   height and --rows the number of stacked sub-rows renderTimeline
   assigned (time-overlapping items used to share one row and paint
   over each other, garbling both labels). The lane grows by whole
   rows inside the timeline's own scroll — the pinned stage above
   never moves, the same contract as the Build 38 lane collapse. */
.tl-row-text { --lane-h: 28px; --clip-inset: 3px; }
.tl-row-overlay { --lane-h: 44px; }
.tl-row-video { --lane-h: 56px; }
.tl-row-audio { --lane-h: 36px; }
.tl-row-captions { --lane-h: 28px; --clip-inset: 3px; }
/* Build 38: an EMPTY auxiliary lane (no real items — app.js
   renderTimeline tags the row) collapses to a slim labelled
   strip and expands back when it gains an item; the Video
   lane always keeps its height. Placeholder clips inside a
   collapsed lane hide (they are samples, not content) — the
   strip itself stays as the lane's label and drop surface.
   This is where the phone stage gets its room back (F2). */
.tl-lane-row.tl-row-empty .tl-lane { height: 14px; }
/* Build 54: in fill mode an empty lane is a full row, not a
   strip — the void must not reappear as a collapsed lane.
   (The class stays, so placeholder clips in empty lanes
   still hide; only the height is restored to the calc.) */
.tl-scroll.is-filling .tl-lane-row.tl-row-empty .tl-lane { height: calc(var(--lane-h, 44px) * var(--rows, 1)); }
/* Build 57: a focused empty lane renders at its focus height
   even in scroll mode, instead of the 14px strip, so it stays
   a visible, usable row while focused. */
.tl-lane-row.is-focused.tl-row-empty .tl-lane { height: calc(var(--lane-h, 44px) * var(--rows, 1)); }
.tl-lane-row.tl-row-empty .tl-clip { display: none; }
.tl-lane-row.tl-row-empty .tl-track-label { font-size: 10.5px; line-height: 1.15; }
.tl-clip {
  position: absolute; top: 4px; bottom: 4px; border-radius: var(--r-clip);
  border: 1px solid rgba(125, 107, 255, 0.4); background: #2a2440;
  color: var(--text); font-size: 12px; padding: 0 8px; text-align: left;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer;
}
.tl-row-text .tl-clip { top: 3px; bottom: 3px; border-radius: var(--r-pill); font-size: 11px; }
.tl-clip-text { background: rgba(255, 179, 64, 0.16); border-color: rgba(255, 179, 64, 0.45); }
.tl-clip-overlay { background: rgba(46, 196, 198, 0.14); border-color: rgba(46, 196, 198, 0.45); }
.tl-clip-audio { background: rgba(47, 191, 113, 0.13); border-color: rgba(47, 191, 113, 0.4); padding-bottom: 13px; }
.tl-row-captions .tl-clip { top: 3px; bottom: 3px; border-radius: var(--r-pill); font-size: 11px; }
.tl-clip-caption { background: rgba(242, 242, 237, 0.13); border-color: rgba(242, 242, 237, 0.42); }
.tl-clip-caption .tl-clip-name { display: inline-flex; align-items: center; gap: 5px; }
.tl-clip-caption .ic { width: 13px; height: 13px; flex: none; }
/* Build 51 (V1): a stacked clip sits in its assigned sub-row —
   same box height as a lone clip, offset by whole lane rows. */
.tl-lane .tl-clip.is-stacked {
  top: calc(var(--clip-inset, 4px) + var(--subrow, 0) * var(--lane-h, 44px));
  bottom: auto;
  height: calc(var(--lane-h, 44px) - (2 * var(--clip-inset, 4px)));
}
.tl-clip.is-selected { border-color: var(--selection); box-shadow: 0 0 0 1.5px var(--selection); z-index: 2; }
.tl-add-tile {
  position: absolute; top: 4px; bottom: 4px; width: 44px; margin-left: 6px;
  display: grid; place-items: center; border: 0; border-radius: var(--r-clip);
  background: var(--surface-3); color: var(--text-2); cursor: pointer; padding: 0;
}
.tl-add-tile:active { background: var(--surface-4); }
/* Build 23: transition junctions on the Video lane. The band
   marks the overlap window the two clips share (a hatched blend
   zone over both clips' filmstrips); the chip sits on the
   junction — a quiet "+" when nothing is set, a filled pill
   naming the type + duration when one is. */
.tl-trans-band {
  position: absolute; top: 4px; bottom: 4px; z-index: 1; pointer-events: none;
  border-radius: 3px;
  background: repeating-linear-gradient(45deg, rgba(255,255,255,0.22) 0 5px, rgba(255,255,255,0.05) 5px 10px);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.25);
}
.tl-trans-chip {
  position: absolute; top: 50%; transform: translate(-50%, -50%); z-index: 4;
  display: inline-flex; align-items: center; gap: 3px;
  min-width: 22px; height: 22px; padding: 0 5px; justify-content: center;
  border-radius: var(--r-pill); border: 1px solid var(--hairline);
  background: var(--surface-3); color: var(--text-2);
  font-size: 10.5px; font-weight: 600; white-space: nowrap; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.tl-trans-chip .ic { width: 12px; height: 12px; flex: none; }
.tl-trans-chip.is-set {
  background: var(--accent); border-color: var(--accent); color: #fff;
  box-shadow: 0 1px 6px rgba(0,0,0,0.45);
}
.tl-trans-chip:active { filter: brightness(1.2); }
.tl-playhead {
  position: absolute; top: 0; bottom: 0; left: 50%; width: 2px; margin-left: -1px;
  background: var(--playhead); pointer-events: none; z-index: 3;
}
.tl-playhead::before {
  content: ""; position: absolute; top: 0; left: 50%; transform: translateX(-50%);
  border: 6px solid transparent; border-top: 8px solid var(--playhead);
}
.tl-note { padding: 2px 14px 6px; margin: 0; font-size: 13px; flex: none; }

/* contextual dock (phone) — icon + label, state machine in app.js */
.tool-dock {
  flex: none;
  background: var(--surface-2); border-top: 1px solid var(--hairline);
  padding: 6px 8px calc(6px + env(safe-area-inset-bottom));
}
/* Build 15: the clip dock carries ten actions now (Freeze +
   Reverse joined) — buttons keep a minimum width and the row
   scrolls sideways instead of crushing them (CapCut's answer). */
.dock-buttons { display: flex; gap: 2px; animation: dock-in var(--dur-tool) ease; overflow-x: auto; scrollbar-width: none; }
.dock-buttons::-webkit-scrollbar { display: none; }
@keyframes dock-in { from { opacity: 0.3; transform: translateY(3px); } to { opacity: 1; transform: none; } }
.dock-btn {
  appearance: none; flex: 1 0 44px; min-width: 0; min-height: 56px; padding: 5px 2px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  border: 0; border-radius: 10px; background: transparent; color: var(--text-2);
  font-size: 11px; font-weight: 500; cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.dock-btn .ic { width: 24px; height: 24px; }
.dock-btn:active { background: var(--surface-3); }
.dock-btn:disabled { opacity: 0.38; pointer-events: none; }
.dock-btn.is-active { color: var(--text-1); }
.dock-btn.is-active .ic { color: var(--accent); }
.dock-btn.is-danger, .dock-btn.is-danger .ic { color: var(--danger); }

/* ribbon: overlays the lower zone only — the stage never moves */
.ribbon {
  position: absolute; inset: 0; z-index: 6; display: flex; flex-direction: column;
  background: var(--surface-2); border-top: 1px solid var(--hairline);
  border-radius: 16px 16px 0 0; animation: ribbon-in var(--dur-sheet) var(--spring);
}
@keyframes ribbon-in { from { transform: translateY(16px); opacity: 0.3; } to { transform: none; opacity: 1; } }
.ribbon-head {
  flex: none; display: flex; align-items: center; justify-content: space-between;
  padding: 4px 6px 0 16px; min-height: 44px;
}
.ribbon-title { font-size: 13.5px; font-weight: 600; }
.ribbon-body {
  flex: 1; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch;
  padding: 2px 14px calc(10px + env(safe-area-inset-bottom));
}
.ribbon-hint { font-size: 13px; margin: 2px 0 10px; }
.ribbon-presets { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.ribbon-preset {
  appearance: none; display: flex; align-items: center; gap: 10px; cursor: pointer;
  background: var(--surface-3); border: 0; border-radius: var(--r-chip);
  color: var(--text-1); padding: 12px; min-height: 52px; font-size: 15px; font-weight: 500;
  text-align: left;
}
.ribbon-preset:active { background: var(--surface-4); }
.ribbon-preset .ic { color: var(--text-2); }
.ribbon-row-between { display: flex; align-items: center; justify-content: space-between; gap: 10px; min-height: 44px; }
.ribbon-row-label { font-size: 15px; font-weight: 500; }
.ribbon-subhead {
  font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.6px;
  color: var(--text-3); margin: 14px 0 2px;
}
.future-list { list-style: none; margin: 0; padding: 0; }
.future-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 9px 0; border-top: 1px solid var(--hairline);
  color: var(--text-2); font-size: 14.5px;
}
.build-badge {
  flex: none; font-size: 11px; font-weight: 600; color: var(--text-3);
  background: var(--surface-3); border-radius: var(--r-pill); padding: 2px 8px; white-space: nowrap;
}
/* one ribbon, one job: Trim hides Frame and vice versa */
#ribbon-body[data-show="trim"] .insp-frame { display: none; }
#ribbon-body[data-show="frame"] .insp-edit:not(.insp-frame),
#ribbon-body[data-show="frame"] .insp-row { display: none; }
/* Build 13: the Adjust ribbon shows only the colour section —
   Trim hides it too, and Frame already hides it via the
   :not(.insp-frame) rule above. */
#ribbon-body[data-show="trim"] .insp-color { display: none; }
#ribbon-body[data-show="adjust"] .insp-frame { display: none; }
/* Build 31: the Background section (.insp-bgfx) rides Adjust
   alongside Colour, so the :not() exempts it too. */
/* Build 32: the Faces section (.insp-facefx) rides Adjust too. */
/* Build 35: the Pose section (.insp-posefx) rides Adjust too. */
/* Build 40: the Glow section (.insp-glowfx) rides Adjust too. */
#ribbon-body[data-show="adjust"] .insp-edit:not(.insp-color):not(.insp-bgfx):not(.insp-facefx):not(.insp-posefx):not(.insp-glowfx),
#ribbon-body[data-show="adjust"] .insp-row { display: none; }
/* Build 14: the Speed ribbon shows only the speed section —
   Trim/Frame/Adjust hide it via their rules (and the ones below),
   and it hides everything else the same way. */
#ribbon-body[data-show="trim"] .insp-speed { display: none; }
#ribbon-body[data-show="adjust"] .insp-speed { display: none; }
#ribbon-body[data-show="speed"] .insp-frame { display: none; }
#ribbon-body[data-show="speed"] .insp-color { display: none; }
#ribbon-body[data-show="speed"] .insp-edit:not(.insp-speed),
#ribbon-body[data-show="speed"] .insp-row { display: none; }
/* Build 16: the clip Volume ribbon shows only the volume
   section — Trim hides it explicitly (its hides are
   section-by-section); Frame/Adjust/Speed hide it via their
   :not() rules. */
#ribbon-body[data-show="trim"] .insp-clipvol { display: none; }
#ribbon-body[data-show="clip-vol"] .insp-frame { display: none; }
#ribbon-body[data-show="clip-vol"] .insp-color { display: none; }
#ribbon-body[data-show="clip-vol"] .insp-speed { display: none; }
#ribbon-body[data-show="clip-vol"] .insp-edit:not(.insp-clipvol),
#ribbon-body[data-show="clip-vol"] .insp-row { display: none; }
/* Build 23: the Transition ribbon shows only the transition
   section — Frame/Adjust/Speed/Volume hide it via their :not()
   rules; Trim hides sections one by one, so it names this one;
   and the transition view hides the rest the same way. */
#ribbon-body[data-show="trim"] .insp-transition { display: none; }
#ribbon-body[data-show="transition"] .insp-frame { display: none; }
#ribbon-body[data-show="transition"] .insp-color { display: none; }
#ribbon-body[data-show="transition"] .insp-speed { display: none; }
#ribbon-body[data-show="transition"] .insp-clipvol { display: none; }
#ribbon-body[data-show="transition"] .insp-edit:not(.insp-transition),
#ribbon-body[data-show="transition"] .insp-row { display: none; }
/* Build 31: Trim hides sections one by one, so it names the
   Background section too; the other ribbons' :not() rules
   already cover it. */
#ribbon-body[data-show="trim"] .insp-bgfx { display: none; }
/* Build 32: Trim names the Faces section for the same reason. */
#ribbon-body[data-show="trim"] .insp-facefx { display: none; }
/* Build 35: Trim names the Pose section for the same reason. */
#ribbon-body[data-show="trim"] .insp-posefx { display: none; }
/* Build 40: Trim names the Glow section for the same reason. */
#ribbon-body[data-show="trim"] .insp-glowfx { display: none; }
/* Build 40: glow controls */
.glowfx-note { line-height: 1.45; }
/* Build 31: background-effect controls */
.insp-bgfx-modes { display: flex; flex-wrap: wrap; gap: 6px; flex: 1; }
.bgfx-chip {
  font: inherit; font-size: 13px; color: var(--text-2);
  background: var(--surface-3); border: 1px solid transparent;
  border-radius: var(--r-pill); padding: 6px 12px; cursor: pointer;
  /* Build 48: 44px touch floor for the fx chip family (these
     live in scrolling inspector/ribbon regions). */
  min-height: 44px; display: inline-flex; align-items: center;
}
.bgfx-chip.is-active { color: var(--text-1); border-color: var(--accent); background: var(--surface-2); }
.insp-bgfx-swatches { display: flex; align-items: center; gap: 8px; flex: 1; }
.bgfx-swatch {
  width: 26px; height: 26px; border-radius: 50%; flex: none;
  border: 2px solid var(--hairline); cursor: pointer; padding: 0;
}
.bgfx-swatch.is-active { border-color: var(--accent); }
.bgfx-color-input {
  width: 30px; height: 30px; flex: none; padding: 0; cursor: pointer;
  background: transparent; border: 1px solid var(--hairline); border-radius: 8px;
}
.bgfx-note { line-height: 1.45; }
/* Build 37: the background-picture picker */
.bgfx-pick { display: flex; flex-wrap: wrap; gap: 6px; flex: 1; }
.bgfx-pick-item {
  display: flex; align-items: center; gap: 8px; max-width: 100%;
  font: inherit; font-size: 13px; color: var(--text-2); text-align: left;
  background: var(--surface-3); border: 1px solid transparent;
  border-radius: var(--r-card); padding: 5px 10px 5px 5px; cursor: pointer;
}
.bgfx-pick-item.is-active { color: var(--text-1); border-color: var(--accent); background: var(--surface-2); }
.bgfx-pick-thumb {
  width: 34px; height: 34px; flex: none; border-radius: 7px;
  background-color: var(--surface-2); background-size: cover; background-position: center;
}
.bgfx-pick-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 180px; }
.bgfx-pick-note { line-height: 1.45; }
/* Build 32: face-privacy controls (the bgfx chip pattern) */
.insp-facefx-modes { display: flex; flex-wrap: wrap; gap: 6px; flex: 1; }
.facefx-chip {
  font: inherit; font-size: 13px; color: var(--text-2);
  background: var(--surface-3); border: 1px solid transparent;
  border-radius: var(--r-pill); padding: 6px 12px; cursor: pointer;
  min-height: 44px; display: inline-flex; align-items: center;
}
.facefx-chip.is-active { color: var(--text-1); border-color: var(--accent); background: var(--surface-2); }
.facefx-note { line-height: 1.45; }
/* Build 35: pose-effect controls (the facefx chip pattern) */
.insp-posefx-modes { display: flex; flex-wrap: wrap; gap: 6px; flex: 1; }
.posefx-chip {
  font: inherit; font-size: 13px; color: var(--text-2);
  background: var(--surface-3); border: 1px solid transparent;
  border-radius: var(--r-pill); padding: 6px 12px; cursor: pointer;
  min-height: 44px; display: inline-flex; align-items: center;
}
.posefx-chip.is-active { color: var(--text-1); border-color: var(--accent); background: var(--surface-2); }
.insp-posefx-swatches { display: flex; align-items: center; gap: 8px; flex: 1; }
.posefx-swatch {
  width: 26px; height: 26px; border-radius: 50%; flex: none;
  border: 2px solid var(--hairline); cursor: pointer; padding: 0;
}
.posefx-swatch.is-active { border-color: var(--accent); }
.posefx-color-input {
  width: 30px; height: 30px; flex: none; padding: 0; cursor: pointer;
  background: transparent; border: 1px solid var(--hairline); border-radius: 8px;
}
.posefx-note { line-height: 1.45; }
.insp-speed-chips { display: flex; flex-wrap: wrap; gap: 6px; flex: 1; }
.insp-speed-line { margin: 8px 0 0; }
/* Build 13: looks chips + per-slider reset in the colour section */
.insp-looks {
  display: flex; gap: 8px; overflow-x: auto; padding: 2px 0 4px;
  scrollbar-width: none; flex: 1;
}
.insp-looks::-webkit-scrollbar { display: none; }
.look-chip {
  flex: none; display: flex; flex-direction: column; align-items: center; gap: 5px;
  background: transparent; border: 0; padding: 2px; cursor: pointer;
  color: var(--text-2); font: inherit;
}
.look-thumb {
  width: 46px; height: 34px; border-radius: 8px; display: block;
  border: 2px solid transparent;
}
.look-chip.is-active .look-thumb { border-color: var(--accent); }
.look-chip.is-active .look-name { color: var(--text-1); }
.look-name { font-size: 11px; line-height: 1; }
.insp-color-reset {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  /* Build 48: the hit box is the 44px floor; the visible disc
     stays 30px — it is drawn on the icon itself (padded,
     surfaced, round) so the row reads exactly as before. */
  width: 44px; height: 44px; border-radius: var(--r-pill);
  background: transparent; border: 0; color: var(--text-2); cursor: pointer;
}
.insp-color-reset .ic { width: 15px; height: 15px; padding: 7.5px; background: var(--surface-3); border-radius: 50%; }
/* Build 11: the overlay inspector splits the same way — the
   Position ribbon hides the trim group, the Trim ribbon hides
   the position group. */
#ribbon-body[data-show="overlay-pos"] .insp-ov-trim { display: none; }
#ribbon-body[data-show="overlay-trim"] .insp-ov-pos,
#ribbon-body[data-show="overlay-trim"] .insp-row { display: none; }
/* Build 16: the overlay Volume group shows only in the Volume
   ribbon; Position/Trim hide it, and it hides them. */
#ribbon-body[data-show="overlay-pos"] .insp-ov-vol { display: none; }
#ribbon-body[data-show="overlay-trim"] .insp-ov-vol { display: none; }
/* Build 20: the Chroma key section rides the Position ribbon. */
#ribbon-body[data-show="overlay-trim"] .insp-ov-chroma,
#ribbon-body[data-show="overlay-vol"] .insp-ov-chroma { display: none; }
/* Build 28: the Mask section shows only in its own ribbon;
   the Mask ribbon hides every other overlay group. */
#ribbon-body[data-show="overlay-pos"] .insp-ov-mask,
#ribbon-body[data-show="overlay-trim"] .insp-ov-mask,
#ribbon-body[data-show="overlay-vol"] .insp-ov-mask { display: none; }
#ribbon-body[data-show="overlay-mask"] .insp-ov-pos,
#ribbon-body[data-show="overlay-mask"] .insp-ov-trim,
#ribbon-body[data-show="overlay-mask"] .insp-ov-vol,
#ribbon-body[data-show="overlay-mask"] .insp-ov-chroma,
#ribbon-body[data-show="overlay-mask"] .insp-row { display: none; }
/* Build 28: mask shape / angle chips wrap like the other
   grouped chip rows. */
.insp-ov-mask .insp-text-row { flex-wrap: wrap; gap: 6px; }
/* Build 33: the Follow section rides the Position ribbon (it is
   a position behaviour); Trim / Volume / Mask ribbons hide it. */
#ribbon-body[data-show="overlay-trim"] .insp-ov-follow,
#ribbon-body[data-show="overlay-vol"] .insp-ov-follow,
#ribbon-body[data-show="overlay-mask"] .insp-ov-follow { display: none; }
.insp-ov-follow .insp-text-row { flex-wrap: wrap; gap: 6px; }
/* Build 34: the Track section rides the Position ribbon the
   same way (it writes position keyframes). */
#ribbon-body[data-show="overlay-trim"] .insp-ov-track,
#ribbon-body[data-show="overlay-vol"] .insp-ov-track,
#ribbon-body[data-show="overlay-mask"] .insp-ov-track { display: none; }
/* Build 37: the Remove background section rides the Position
   group like Chroma — hidden in the trim / volume / mask views. */
#ribbon-body[data-show="overlay-trim"] .insp-ov-removebg,
#ribbon-body[data-show="overlay-vol"] .insp-ov-removebg,
#ribbon-body[data-show="overlay-mask"] .insp-ov-removebg { display: none; }
/* Build 34: the tap-to-track banner — a prompt / progress /
   result bar across the bottom of the lower zone (the ribbon's
   neighbourhood), so the pinned stage never moves while a
   track is armed, running, or reporting. */
.track-banner {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 7;
  display: flex; align-items: center; gap: 10px;
  background: var(--surface-2); border-top: 1px solid var(--hairline);
  padding: 10px 14px calc(10px + env(safe-area-inset-bottom));
}
.track-banner[hidden] { display: none; }
.track-banner-main { flex: 1; min-width: 0; }
.track-banner-text { margin: 0; font-size: 13.5px; line-height: 1.45; }
.track-bar {
  height: 4px; border-radius: 2px; background: var(--surface-3);
  margin-top: 8px; overflow: hidden;
}
.track-bar-fill {
  height: 100%; width: 0%; background: var(--accent);
  transition: width 120ms linear;
}
/* Build 33: built-in sticker tiles at the top of the media bin —
   the add-overlay surface alongside the imported media rows. */
.media-stickers-row { list-style: none; padding: 2px 0 8px; }
.media-stickers-cap {
  display: block; font-size: 11.5px; color: var(--text-2); padding: 2px 0 6px;
}
.media-stickers { display: flex; gap: 8px; flex-wrap: wrap; }
.media-sticker-tile {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  min-width: 76px; padding: 8px 10px; cursor: pointer;
  background: var(--surface-3); border: 1px solid var(--hairline);
  border-radius: 10px; color: var(--text-2); font-size: 11.5px;
}
/* Build 51 (visual sweep V2): the glyphs sit on a light swatch
   plate in the picker — the Sunglasses art is near-black linework
   (#15171C) and vanished on the dark tile; Star and Heart read as
   swatches on the same plate, so the row stays one component.
   Picker only: the stage and export draw the raw art, unchanged. */
.media-sticker-tile svg { width: 42px; height: auto; display: block; background: #EAE7DE; border-radius: 9px; padding: 5px 6px; }
/* Build 20: chroma section bits — key-colour swatches, the
   custom colour well, and the eyedropper pick button. */
.insp-chroma-swatches { align-items: center; gap: 8px; }
.insp-chroma-swatch {
  width: 28px; height: 28px; border-radius: 50%; flex: none;
  border: 2px solid rgba(255, 255, 255, 0.25); padding: 0; cursor: pointer;
}
.insp-chroma-swatch.is-active { border-color: var(--accent); }
.insp-chroma-color {
  width: 40px; height: 28px; flex: none; padding: 0;
  border: 1px solid var(--hairline); border-radius: 8px; background: transparent;
}
#ribbon-body[data-show="overlay-vol"] .insp-ov-pos,
#ribbon-body[data-show="overlay-vol"] .insp-ov-trim,
#ribbon-body[data-show="overlay-vol"] .insp-row { display: none; }
/* Build 12: the audio inspector splits the same way — the Volume
   ribbon hides the trim group, the Trim ribbon hides the volume
   group + info rows. */
#ribbon-body[data-show="audio-vol"] .insp-au-trim { display: none; }
#ribbon-body[data-show="audio-trim"] .insp-au-vol,
#ribbon-body[data-show="audio-trim"] .insp-row { display: none; }
/* on phone the dock owns clip/text actions; the ribbon shows controls */
.ribbon .insp-edit-actions { display: none; }
/* inside a ribbon the EDIT controls lead; the info rows follow
   (on desktop the inspector keeps its rows-first reading order) */
.ribbon #inspector-body { display: flex; flex-direction: column; }
.ribbon .insp-edit { order: -1; }
/* panels restyled when they live inside the ribbon */
.ribbon .panel-assets .panel-head h2 { display: none; } /* the ribbon title already says it */
.ribbon .panel-assets {
  display: block; background: transparent; padding: 2px 0 0; overflow: visible; border: 0;
}
.ribbon #aspect-controls { gap: 12px; }
.ribbon .safezone-control { justify-content: space-between; }
.ribbon .safezone-control select { flex: 1; }

/* editor sheets (import / export) on the Build 9 sheet component */
.ed-sheet-overlay { z-index: 95; }
.ed-sheet .import-sheet { border: 0; background: transparent; padding: 0; margin: 0; }

/* phone editor (<1024): panels hide, title hides, note stays out
   of the compact resting chrome. Build 12: the Audio lane is a
   real lane now, so it shows on phone too (Build 10 hid the
   then-placeholder row). */
@media (max-width: 1023.5px) {
  .panel-assets, .panel-inspector { display: none; }
  .editor-title { display: none; }
  .aspect-pill { margin: 0 auto; }
  .tl-note { display: none; }
  .tl-split-btn { display: none; } /* the dock owns Split on phone */
  /* Build 18: the More ribbon owns the beat toggles on phone —
     two more chips here could wrap the toolbar and resize the
     resting chrome (and with it the pinned stage) per state. */
  #tl-beats-markers, #tl-snap-beat { display: none; }
  /* the transport must FIT the phone — an overflowing row makes
     the whole editor horizontally scrollable and shifts the
     pinned stage. No volume slider on phone (the mute toggle
     stays; loudness is the device's job, as in the native apps). */
  .transport { gap: var(--s1); padding-left: 8px; padding-right: 8px; }
  .tp-vol input[type="range"] { display: none; }
  .tp-play { padding: 7px 11px; }
  .tp-time { font-size: 12.5px; min-width: 0; }
  /* Build 38 (audit F16): at long totals the mono readout ran
     under the loop button — a touch smaller and tighter on
     narrow screens lets "0:03.3 / 0:25.6" fit its slot.
     Build 51 (visual sweep V3): still not enough — the measured
     ink (121.9px) beat the shrunken slot (103.2px) and the loop
     glyph painted over the last digit. The phone Play pill goes
     icon-only (its aria-label still says Play/Pause, as in the
     reference editors) and the readout drops to 11px, tighter —
     the freed pill width lands in the readout's flex slot, and
     every in-scope total (<= 15:00.0) now clears the loop glyph
     with room (measured, visual sweep Round 2). The text format
     itself is unchanged — one format on every screen. */
  #tp-play-label { display: none; }
  .tp-time { font-size: 11px; letter-spacing: -0.4px; }
  /* Build 38 (audit F2): resting-chrome trims so the pinned
     stage gets real room on a phone — the top bar, transport,
     timeline toolbar, dock and stage padding each give back a
     few pixels; the lane collapse above does the heavy lift.
     The stage is sized from THIS resting chrome, so every
     pixel saved here is a pixel of preview. */
  .ed-topbar { padding-top: calc(5px + env(safe-area-inset-top)); padding-bottom: 5px; }
  .stage-wrap { padding: 8px; }
  /* Build 48: 3px -> 1px buys the 44px transport targets at
     the same phone row height (see the base .transport note). */
  .transport { padding-top: 1px; padding-bottom: 1px; }
  .timeline { padding-top: 0; padding-bottom: 2px; }
  #editor-view .tl-toolbar { padding: 2px 10px 4px; }
  /* Build 48: the zoom pair grows to a 44px-wide target; its
     height stays at the toolbar's 36px cap (the documented
     pinned-chrome exception — the slider between them is the
     same control at the same height). */
  .tl-zoomctl .tp-btn { width: 44px; height: 36px; }
  .tl-zoomctl input[type="range"] { height: 36px; min-height: 36px; margin-top: 0; margin-bottom: 0; }
  .tool-dock { padding-top: 4px; padding-bottom: calc(4px + env(safe-area-inset-bottom)); }
  .dock-btn { min-height: 44px; padding: 3px 2px; }
  .dock-btn .ic { width: 20px; height: 20px; }
  #editor-view .tl-tools .btn-small { min-height: 32px; padding-top: 4px; padding-bottom: 4px; }
}

/* desktop editor (>=1024): the same system unfolded — left media,
   centre stage, right inspector, lower zone full width */
@media (min-width: 1024px) {
  .editor {
    display: grid;
    /* Build 53: panel widths are variables, set live by the
       layout code (js/layout.js) — defaults are the widths
       the app shipped with for 52 builds. */
    grid-template-columns: var(--panel-l, 288px) minmax(0, 1fr) var(--panel-r, 300px);
    grid-template-rows: auto minmax(0, 1fr) auto;
    grid-template-areas: "top top top" "assets stage inspector" "lower lower lower";
  }
  .layout-btn {
    display: inline-flex; align-items: center; gap: 6px; flex: none;
    appearance: none; background: var(--surface-3); color: var(--text-1);
    border: 0; cursor: pointer; border-radius: var(--r-pill);
    padding: 7px 12px; min-height: 44px; font-size: 14px; font-weight: 600;
  }
  .layout-btn:active { background: var(--surface-4); }
  .editor .panel { position: relative; }
  .panel-collapse { display: inline-flex; }
  /* Build 53: the drag handles ride the panels' inner edges —
     a 12px grab strip fully INSIDE the panel, with a 2px accent
     line drawn exactly on the border that only appears on
     hover, focus or drag, so the chrome stays quiet at rest.
     (A first cut straddled the border; the panel's scroll
     overflow clipped the outer half and the strip's centre sat
     exactly on the panel edge, where hit-testing is ambiguous
     — the left handle never engaged. Inset handles engage.) */
  .panel-resize {
    display: block; position: absolute; top: 0; bottom: 0; width: 12px;
    z-index: 3; cursor: ew-resize; touch-action: none;
  }
  .panel-assets .panel-resize { right: 0; }
  .panel-inspector .panel-resize { left: 0; }
  .panel-resize::after {
    content: ''; position: absolute; top: 0; bottom: 0;
    width: 2px; background: transparent;
  }
  .panel-assets .panel-resize::after { right: 0; }
  .panel-inspector .panel-resize::after { left: 0; }
  .panel-resize:hover::after, .panel-resize:focus-visible::after,
  .panel-resize.is-dragging::after { background: var(--accent); }
  .panel-resize:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
  /* Build 53: a collapsed panel is a 44px rail holding only its
     expand button. Everything else in the panel hides — the
     grid column itself shrinks via the --panel-l/--panel-r
     variable the layout code sets. */
  .panel.is-collapsed { padding: 8px 0; overflow: hidden; }
  .panel.is-collapsed > :not(.panel-rail) { display: none !important; }
  .panel.is-collapsed .panel-rail {
    display: grid; place-items: center; width: 44px; height: 44px;
    margin: 0 auto; padding: 0; appearance: none; background: transparent;
    color: var(--text-1); border: 0; border-radius: var(--r-pill); cursor: pointer;
  }
  .panel.is-collapsed .panel-rail:active { background: var(--surface-3); }
  .ed-topbar { grid-area: top; }
  .panel-assets { grid-area: assets; border-right: 1px solid var(--hairline); }
  .stage-wrap { grid-area: stage; padding: 16px; }
  .panel-inspector { grid-area: inspector; border-left: 1px solid var(--hairline); }
  .lower-zone { grid-area: lower; }
  .tool-dock { display: none; }
  .tl-row-text { --lane-h: 32px; }
  .tl-row-overlay { --lane-h: 48px; }
  .tl-row-video { --lane-h: 64px; }
  .tl-row-audio { --lane-h: 40px; }
  .tl-row-captions { --lane-h: 32px; }
  /* Build 54: no desktop scroll cap any more — the dock height
     is the budget on both form factors (see .tl-scroll). */
  .transport { padding-left: 14px; padding-right: 14px; }
}
@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* ---------- Build 2: media bin + import sheet ---------- */
.media-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; flex-wrap: wrap; gap: 8px; }
/* Build 38 (audit F5/F11): the actions row WRAPS inside the
   288px desktop panel instead of clipping the Import button
   to a sliver, and in the phone ribbon the panel's own
   "Media" heading hides — the ribbon already titles it. */
.ribbon .media-head h3 { display: none; }
/* Build 38: the bin's empty state — the stage import card's
   language (icon + one line + the import button), not a
   plain paragraph. */
.media-empty {
  display: flex; flex-direction: column; align-items: flex-start; gap: 8px;
  border: 1px dashed var(--border); border-radius: 10px;
  padding: 14px 12px; margin-bottom: 12px;
}
.media-empty[hidden] { display: none; }
.media-empty .ic { width: 22px; height: 22px; color: var(--accent); }
.media-empty p { margin: 0; font-size: 13.5px; line-height: 1.45; color: var(--text-2); }
.media-head h3 { margin: 0; }
.media-drop {
  border: 1px dashed var(--border); border-radius: 10px; padding: 12px; margin-bottom: 12px;
}
.media-drop p { margin: 0 0 6px; }
.media-drop p:last-child { margin-bottom: 0; font-size: 13px; }
.panel-assets.is-dragover { outline: 2px dashed var(--accent); outline-offset: -6px; }
/* Build 38: the brief flash when the stage card's Browse
   media button points a desktop user at the pinned panel. */
.panel-assets.is-flash { outline: 2px solid var(--accent); outline-offset: -2px; }
.panel-assets.is-dragover .media-drop { border-color: var(--accent); }

.media-list { list-style: none; margin: 0 0 10px; padding: 0; display: grid; gap: 8px; }
.media-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  background: var(--panel-2); border: 1px solid var(--border); border-radius: 10px; padding: 8px;
}
.media-row-unsupported { border-color: var(--amber); }
.media-main {
  /* Build 39 (audit F8): a 120px flex basis — the row's wrap
     breaker now wraps the ACTION BUTTONS (order 2, below) onto
     the next line instead of shrinking this block toward zero;
     the audit measured it at 21px with the metadata overstruck
     by a badge. flex-grow still lets it claim the free width. */
  flex: 1 1 120px; min-width: 0; text-align: left; background: transparent; border: 0; color: var(--text);
  cursor: pointer; padding: 4px; display: grid; gap: 2px;
}
.media-main:disabled { cursor: default; opacity: 0.75; }
.media-name { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.media-meta { color: var(--muted); font-size: 12.5px; }
/* Build 39 (audit F8): the badges' own chip row — full width,
   directly under the name/meta line, never beside or on top of
   the metadata. Actions (order 2) sit on the name line when
   they fit and wrap as a group when they don't; the status
   line and cap keep the last lines (orders 4/5). */
.media-badges {
  order: 3; flex-basis: 100%; display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
}
.media-badges:empty { display: none; }
.media-row > .media-act, .media-row > .media-del { order: 2; }
.media-badge {
  font-size: 11px; font-weight: 700; border-radius: 999px; padding: 2px 9px; white-space: nowrap;
  border: 1px solid var(--border); color: var(--muted);
}
.media-badge-normal { border-color: var(--green); color: var(--green); }
.media-badge-proxy-only { border-color: var(--amber); color: var(--amber); }
.media-badge-segmented { border-color: var(--gold); color: var(--gold); }
.media-cap { order: 5; flex-basis: 100%; margin: 0; font-size: 12.5px; }
.media-note { margin: 0 0 14px; font-size: 13.5px; color: var(--gold); }
.media-note:empty { display: none; }

.import-sheet {
  border: 1px solid var(--surface-3); border-radius: 10px; padding: 12px; margin-bottom: 12px;
  background: var(--panel-2);
}
.sheet-title { font-weight: 700; margin: 0 0 6px; word-break: break-word; }
.sheet-facts { margin: 0 0 6px; font-size: 13.5px; }
.sheet-why { margin: 0 0 10px; font-size: 13.5px; }
/* Build 44: the import space guard speaks in the warning voice. */
.sheet-warn { color: var(--warning); }
.sheet-modes { display: grid; gap: 8px; margin-bottom: 12px; }
.sheet-mode {
  display: flex; gap: 10px; align-items: flex-start; border: 1px solid var(--border);
  border-radius: 10px; padding: 9px 10px; cursor: pointer; background: var(--panel);
}
.sheet-mode input { accent-color: var(--accent); margin-top: 3px; }
.sheet-mode small { color: var(--muted); }
.sheet-actions { display: flex; gap: 8px; }
.sheet-phase { margin: 0 0 8px; font-size: 13.5px; }
.progress { height: 8px; border-radius: 999px; background: var(--panel); overflow: hidden; margin-bottom: 10px; }
.progress-bar { height: 100%; width: 0; background: var(--accent); border-radius: 999px; }
.import-status:empty { display: none; }

.tl-clip-real { background: #2f4a2b; border-color: #4a7a3f; }

/* ---------- Build 3: proxies, filmstrips, waveforms, storage ---------- */
.media-thumb {
  flex: none; width: 64px; height: 48px; border-radius: 6px; border: 1px solid var(--border);
  background-color: #23232e; background-size: cover; background-position: center;
}
.media-badge-master { border-color: var(--gold); color: var(--gold); }
/* Build 12: the audio-only badge rides the audio lane's green. */
.media-badge-audio { border-color: var(--track-audio); color: var(--track-audio); }
/* Build 30: the photo badge rides the overlay lane's teal. */
.media-badge-photo { border-color: var(--track-overlay); color: var(--track-overlay); }
.media-badge-reversed { border-color: var(--track-overlay); color: var(--track-overlay); }
.media-badge-beats {
  border-color: var(--track-audio); color: var(--track-audio);
  display: inline-flex; align-items: center; gap: 4px;
}
.media-badge-beats .ic { width: 12px; height: 12px; }
/* Build 15: a freeze clip's name carries a small snowflake; the
   clip itself keeps the video lane colour (it IS video — a still
   of it), with a slightly cooler cast from the icon. */
.tl-clip-freeze .tl-clip-name .ic { width: 12px; height: 12px; vertical-align: -2px; margin-right: 3px; }
/* Build 30: a still clip keeps the real-clip body (its photo
   tiles the block like a freeze's frame) with a teal edge so a
   photo reads differently from footage at a glance. */
.tl-clip-still { border-color: var(--track-overlay); }
.media-proxy { order: 4; flex-basis: 100%; margin: 0; font-size: 12.5px; }
.media-proxy-progress { order: 4; flex-basis: 100%; margin: 0; }
.storage-line { font-size: 12.5px; margin: 0 0 14px; }
.storage-line:empty { display: none; }

.tl-clip { overflow: hidden; }
.tl-clip-name {
  position: relative; z-index: 1; display: block; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; pointer-events: none;
}
.tl-clip-strip .tl-clip-name {
  background: rgba(5, 5, 9, 0.62); border-radius: 3px; padding: 0 4px;
  margin: 0 -2px;
}
.tl-wave {
  position: absolute; left: 0; right: 0; bottom: 0; width: 100%; height: 12px;
  pointer-events: none;
}
.tl-clip-real { padding-bottom: 13px; }

/* ---------- Build 4: preview playback ---------- */
.stage-media { position: absolute; inset: 0; }
.stage-video {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: contain; background: #000;
}
/* Build 8: blurred-backdrop mirror deck (positioned by the same
   framing adapter as the real decks; clicks belong to the decks
   above it, never to the mirror). */
.stage-video-bg { pointer-events: none; filter: blur(18px) brightness(0.7); background: transparent; }
/* Build 30: the still layer — an <img> positioned by the same
   framing adapter as the decks (inline geometry + object-fit
   override); clicks belong to the stage, never to the photo.
   The -bg twin's blur filter is composed inline by the app
   (blur + the clip's colour grade), like the mirror deck's. */
.stage-still, .stage-still-bg { pointer-events: none; background: transparent; }
/* Build 31: the background-effect preview canvas — covers the
   whole stage frame (the pinned box never moves; only this
   picture layer swaps), display-only like the still layer. */
.stage-bgfx {
  position: absolute; inset: 0; width: 100%; height: 100%;
  pointer-events: none; background: #000;
}
/* Build 11: the picture-in-picture deck — positioned/sized by the
   js/overlay.js CSS adapter (centre-based %, like the main decks),
   above them and below the text layer; the selected overlay gets
   a white outline, and dragging it moves the overlay. */
.stage-video-overlay {
  position: absolute; object-fit: fill; background: transparent;
  border-radius: 4px; touch-action: none;
}
.stage-video-overlay.is-selected { outline: 2px solid #fff; outline-offset: 1px; cursor: grab; }
.stage-video-overlay.is-selected:active { cursor: grabbing; }
/* Build 20: the WebGL chroma display canvas — same rect and
   selected outline as the deck it mirrors, but display-only:
   the (transparent) video above it owns every pointer event. */
.stage-canvas-overlay { pointer-events: none; background: transparent; }
.stage-canvas-overlay.is-selected { cursor: default; }
/* Build 33: the built-in sticker layer — an inline SVG element
   positioned/sized by the same CSS adapter as the overlay deck
   (centre-based %), above the decks and below the text layer.
   Display-only: a sticker is placed by the Position sliders or
   by the face it follows, never by a stage drag. */
.stage-sticker-overlay {
  position: absolute; background: transparent; pointer-events: none;
}
.stage-sticker-overlay.is-selected { outline: 2px solid #fff; outline-offset: 1px; border-radius: 4px; }
/* Build 12: the audio-lane decks are never displayed — they only
   sound. (An item's source may be a video file; its picture must
   never appear.) */
.stage-audio-deck { display: none !important; }
/* Build 38: warnings left the picture. The silent-proxy badge
   and the stage error card are gone from the stage; both now
   render in the lower zone's banner (next rule), above the
   transport, where they can never cover the video (audit F3). */
.stage-banner {
  position: absolute; top: 0; left: 0; right: 0; z-index: 5;
  display: flex; align-items: center; gap: 8px;
  padding: 7px 8px 7px 12px; font-size: 13px; line-height: 1.35;
  background: #2c2110; color: #f6ddab; border-bottom: 1px solid var(--amber);
  /* The row overlays the transport's top edge (so the pinned
     stage never moves when it comes and goes) — it must not
     EAT the transport's taps: the strip itself is click-through
     and only its ✕ takes pointer events. */
  pointer-events: none;
}
.stage-banner[hidden] { display: none; }
.stage-banner.is-error { background: #31120d; color: #ffd9d3; border-bottom-color: var(--red); }
.stage-banner-ic { flex: none; width: 16px; height: 16px; }
.stage-banner-text { flex: 1 1 auto; min-width: 0; }
.stage-banner-x {
  flex: none; width: 32px; height: 32px; display: grid; place-items: center;
  background: none; border: 0; border-radius: 8px; color: inherit; cursor: pointer;
  pointer-events: auto;
}
/* Build 10: .transport layout lives in the editor block above
   (fixed height — it is part of the pinned-stage resting chrome). */
.transport input:disabled { opacity: 0.45; cursor: default; }
.tp-time { color: var(--muted); font-family: var(--font-num); font-size: 14px; white-space: nowrap; }
.tp-loop[aria-pressed="true"] { border-color: var(--accent); color: var(--accent); font-weight: 700; }
.tp-vol { display: flex; align-items: center; gap: 6px; }
.tp-vol input[type="range"] { width: 92px; accent-color: var(--accent); }
.now-playing {
  border: 1px solid var(--surface-3); border-radius: 10px; background: var(--panel-2);
  padding: 9px 11px; margin-bottom: 12px; font-size: 13.5px;
}
.now-playing .np-label { color: var(--accent); font-weight: 700; display: block; font-size: 12px; text-transform: uppercase; letter-spacing: 0.4px; }
.now-playing .np-name { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.now-playing .np-time { color: var(--muted); font-family: var(--font-num); }
.tl-clip.is-playing { border-color: var(--green); box-shadow: 0 0 0 1px var(--green); }
.tl-clip-missing { border-color: var(--red) !important; opacity: 0.6; }
.tl-wrap.is-scrubbing { cursor: grabbing; }

/* ---------- Build 5: timeline editing ---------- */
.tl-toolbar {
  display: flex; justify-content: space-between; align-items: center; gap: 10px;
  /* Build 48: this later rule is the effective padding — its
     bottom goes 8px -> 4px to absorb the 44px zoom/split
     targets at the same toolbar (and pinned-stage) height. */
  flex-wrap: wrap; padding: 2px 14px 4px;
}
.tl-tools { display: flex; gap: 6px; flex-wrap: wrap; }
.tl-tools .btn:disabled { opacity: 0.45; cursor: default; }
#tl-ripple[aria-pressed="true"] { border-color: var(--accent); color: var(--accent); font-weight: 700; }
.tl-zoomctl { display: flex; align-items: center; gap: 6px; }
.tl-zoomctl input[type="range"] { width: 110px; accent-color: var(--accent); }
.tl-scroll { position: relative; }
#tl-snapline {
  position: absolute; top: 0; bottom: 0; width: 2px; margin-left: -1px;
  background: var(--success); z-index: 4; pointer-events: none;
}
#tl-dragtip {
  position: absolute; top: 0; transform: translateX(-50%); z-index: 5;
  background: rgba(5, 5, 9, 0.88); border: 1px solid var(--surface-3); border-radius: 999px;
  color: var(--text); font-size: 11px; padding: 1px 8px; white-space: nowrap;
  pointer-events: none; font-family: var(--font-num);
}
.tl-clip { touch-action: none; }
.tl-clip.is-dragging { opacity: 0.85; z-index: 3; cursor: grabbing; }
.tl-trim {
  position: absolute; top: 0; bottom: 0; width: 11px; z-index: 2;
  cursor: ew-resize; touch-action: none;
}
.tl-trim-l { left: 0; border-radius: 5px 0 0 5px; }
.tl-trim-r { right: 0; border-radius: 0 5px 5px 0; }
.tl-trim::after {
  content: ""; position: absolute; top: 20%; bottom: 20%; width: 3px;
  border-radius: 2px; background: rgba(212, 175, 55, 0.55);
}
.tl-trim-l::after { left: 2px; }
.tl-trim-r::after { right: 2px; }
.tl-trim:hover::after, .tl-clip.is-dragging .tl-trim::after { background: var(--gold); }
.insp-edit { border-top: 1px solid var(--border); margin-top: 10px; padding-top: 10px; }
.insp-edit-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.insp-edit-field { display: grid; gap: 4px; font-size: 12.5px; color: var(--muted); }
.insp-edit-field input {
  width: 100%; padding: 8px 10px; border-radius: 8px; border: 1px solid var(--border);
  background: var(--panel-2); color: var(--text); font-size: 15px; font-family: var(--font-num);
  min-height: 44px; /* Build 48: touch floor for inspector fields */
}
.insp-edit-hint { font-size: 12.5px; margin: 8px 0 0; }
.insp-edit-actions { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 10px; }

/* ---------- Build 6: text & titles ---------- */
.stage-text-layer { position: absolute; inset: 0; pointer-events: none; z-index: 2; }
.stage-text {
  position: absolute; left: 6%; right: 6%; margin: 0; padding: 0.12em 0.3em;
  white-space: pre-wrap; overflow-wrap: break-word; line-height: 1.18;
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.65), 0 0 2px rgba(0, 0, 0, 0.8);
  pointer-events: none; border-radius: 4px;
}
.stage-text.bg-box, .stage-text.bg-highlight { text-shadow: none; }
.stage-text.bg-highlight { border-radius: 6px; }
.stage-text.align-left { text-align: left; }
.stage-text.is-selected {
  pointer-events: auto; cursor: grab; touch-action: none;
  outline: 2px dashed var(--gold); outline-offset: 2px;
}
.stage-text.is-selected:active { cursor: grabbing; }

.text-add-row { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
.text-hint { font-size: 12.5px; margin: 0 0 8px; }
.text-list { list-style: none; margin: 0 0 14px; padding: 0; display: grid; gap: 6px; }
.text-row-btn {
  display: flex; justify-content: space-between; align-items: center; gap: 10px; width: 100%;
  background: var(--panel-2); border: 1px solid var(--border); border-radius: 10px;
  padding: 8px 10px; color: var(--text); cursor: pointer; text-align: left; min-height: 40px;
}
.text-row-btn.is-selected { border-color: var(--gold); box-shadow: 0 0 0 1px var(--gold); }
.text-row-name { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.text-row-time { color: var(--muted); font-size: 12.5px; font-family: var(--font-num); white-space: nowrap; }

.insp-text-area {
  width: 100%; padding: 9px 10px; border-radius: 8px; border: 1px solid var(--border);
  background: var(--panel-2); color: var(--text); font: inherit; font-size: 15px; resize: vertical;
}
.insp-text-group { margin-top: 12px; }
.insp-text-cap { display: block; font-size: 12.5px; color: var(--muted); margin-bottom: 6px; }
.insp-text-row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.insp-text-size { flex: 1; min-width: 90px; accent-color: var(--accent); }
.insp-text-val { font-size: 13px; color: var(--muted); font-family: var(--font-num); min-width: 38px; text-align: right; }
/* Build 38 (audit F9): THE standard note style — plain
   sentences in panels and ribbons render left-aligned in the
   system font, never in the slider-readout's mono/right style
   (builders had been stuffing sentences into .insp-text-val
   spans, which set them in monospace, flush right). Numeric
   readouts keep .insp-text-val; sentences use this. */
.insp-note { display: block; font-size: 13px; line-height: 1.45; color: var(--muted); text-align: left; margin-top: 6px; }
/* the same note treatment inside a section title row (beside
   the heading, where a block margin would misalign it) */
.insp-note-inline { font-size: 12.5px; line-height: 1.4; color: var(--muted); text-align: left; flex: 1 1 auto; }
.insp-toggle.is-active { border-color: var(--accent); color: var(--accent); font-weight: 700; }
.swatch {
  width: 26px; height: 26px; border-radius: 50%; border: 2px solid var(--border);
  padding: 0; cursor: pointer; flex: none;
}
.swatch.is-active { border-color: var(--gold); box-shadow: 0 0 0 1px var(--gold); }
.insp-text-color {
  width: 34px; height: 30px; padding: 0; border: 1px solid var(--border);
  border-radius: 8px; background: var(--panel-2); cursor: pointer;
}
.dock-sub-btn.is-live { border-style: solid; color: var(--text); }

/* ---------- Build 7: export sheet ---------- */
/* Build 10: the export sheet rides the Build 9 .sheet component
   (grabber + spring); the legacy centred card is gone. */
.sheet-head h3 { margin: 0; }
.export-presets { display: grid; gap: 8px; margin-bottom: 10px; }
.export-preset {
  display: flex; gap: 10px; align-items: flex-start; cursor: pointer;
  background: var(--panel-2); border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px;
}
.export-preset.is-selected { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.export-preset input { margin-top: 3px; accent-color: var(--accent); }
.export-preset-body { display: grid; gap: 1px; }
.export-preset-name { font-weight: 700; }
/* Build 38 (audit F9): preset spec lines are sentences, not
   numbers — system font like the rest of the sheet. */
.export-preset-meta { font-size: 13px; }
.export-preset-blurb { font-size: 12.5px; }
.export-fps-row { display: flex; align-items: center; gap: 10px; margin: 4px 0 10px; }
/* Build 17: the loudness note under the normalize toggle. */
.export-norm-note { margin: 0 0 10px; font-size: 12.5px; line-height: 1.45; }
/* Build 38 (audit F5): the control WRAPS as a group — a
   four-preset row (Classic / Bold pop / Minimal / Karaoke)
   no longer clips its last chip against the panel edge; the
   buttons share each row evenly instead. */
.seg-control { display: inline-flex; flex-wrap: wrap; max-width: 100%; border: 1px solid var(--border); border-radius: 18px; overflow: hidden; }
.seg-btn { background: var(--panel-2); color: var(--muted); border: 0; padding: 6px 14px; cursor: pointer; font: inherit; font-size: 13.5px; min-height: 44px; flex: 1 1 auto; white-space: nowrap; }
.seg-btn.is-active { background: var(--accent); color: var(--on-accent); font-weight: 700; }
/* Build 60: the Reels captions picker — one full-width button
   per preset (label + when-to-use line), all four sharing one
   height rhythm and one active treatment, mirroring seg-btn. */
.reel-cap-list { display: grid; gap: 6px; margin-top: 6px; }
.reel-cap-btn { display: grid; gap: 2px; width: 100%; min-height: 44px; padding: 8px 12px; text-align: left; background: var(--panel-2); color: var(--text); border: 1px solid var(--border); border-radius: 12px; cursor: pointer; font: inherit; }
.reel-cap-btn.is-active { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
.reel-cap-btn.is-active .reel-cap-blurb { color: var(--on-accent); opacity: 0.85; }
.reel-cap-label { font-size: 13.5px; font-weight: 700; }
.reel-cap-blurb { font-size: 12px; line-height: 1.4; }
/* Build 47: in batch mode a picked format carries a check in
   front of its name (the viewed format keeps .is-active). */
.seg-btn.is-checked::before { content: "✓ "; font-weight: 700; }
.seg-btn.is-checked:not(.is-active) { color: var(--text); box-shadow: inset 0 0 0 1.5px var(--accent); }
/* Build 47: the batch queue — one row per format (name, the
   priced summary, a status chip), in the setup preview and
   live in the progress view. */
.export-queue { display: flex; flex-direction: column; gap: 6px; margin: 10px 0 2px; }
.export-queue-row { display: flex; align-items: baseline; gap: 8px; background: var(--panel-2); border: 1px solid var(--border); border-radius: 10px; padding: 8px 10px; }
.export-queue-name { font-weight: 600; font-size: 13.5px; white-space: nowrap; }
.export-queue-meta { flex: 1 1 auto; font-size: 12px; min-width: 0; }
.batch-status { font-size: 11.5px; font-weight: 700; white-space: nowrap; border-radius: 999px; padding: 2px 8px; background: var(--panel); color: var(--muted); border: 1px solid var(--border); }
.batch-status-running { color: var(--on-accent); background: var(--accent); border-color: var(--accent); }
.batch-status-done { color: #2fbf71; border-color: #2fbf71; }
.batch-status-failed { color: var(--danger); border-color: var(--danger); }
.export-batch-file { font-weight: 600; margin: 14px 0 0; }
.export-bar { height: 10px; border-radius: 999px; background: var(--panel-2); border: 1px solid var(--border); overflow: hidden; margin: 8px 0; }
.export-bar-fill { height: 100%; width: 0; background: var(--accent); transition: width 0.2s linear; }
.export-progress-label { font-weight: 600; margin: 4px 0 0; }
/* Build 51 (visual sweep V5): while a render runs, the export
   sheet's close is disabled — measured full-white at opacity 1,
   indistinguishable from enabled. Disabled must look disabled,
   the same dim the transport's disabled buttons wear. */
#export-close:disabled { opacity: .35; }
.export-result-head { font-weight: 700; font-size: 17px; margin: 2px 0 4px; }
.export-note { color: var(--amber); font-size: 13.5px; }
.export-error-text { color: var(--red); }
.export-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
.export-list { list-style: none; margin: 0 0 14px; padding: 0; display: grid; gap: 6px; }
.export-row {
  display: flex; align-items: center; gap: 8px;
  background: var(--panel-2); border: 1px solid var(--border); border-radius: 10px; padding: 8px 10px;
}
.export-row-main { flex: 1; min-width: 0; display: grid; gap: 1px; }
.export-row-name { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.export-row-meta { font-size: 12.5px; font-family: var(--font-num); }
/* Build 41: the format badge on export rows (MP4 / WebM / GIF /
   M4A / WAV), and a seg button a browser capability disables
   (WebM/M4A explain themselves in the note under the row). */
.export-format-badge {
  display: inline-block; font-size: 11px; font-weight: 700; letter-spacing: 0.04em;
  color: var(--accent); border: 1px solid var(--accent); border-radius: 999px;
  padding: 1px 7px; margin-left: 6px; vertical-align: 1px; font-family: var(--font-num);
}
.seg-btn:disabled { opacity: 0.45; cursor: not-allowed; }

.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* ---------- Build 10 overrides (win over Builds 2–8 sections) --
   The gold selection language is retired everywhere: selection is
   white, snapping is the accent, lane colours are the tokens. */
.tl-clip-real { background: #2a2440; border-color: rgba(125, 107, 255, 0.4); }
#tl-snapline { background: var(--accent); }
.tl-trim::after { background: rgba(255, 255, 255, 0.55); }
.tl-trim:hover::after, .tl-clip.is-dragging .tl-trim::after,
.tl-clip.is-selected .tl-trim::after { background: var(--selection); }
/* ---------- Build 19: keyframe diamonds + Animate sections ----
   Diamonds sit centred on their key moment inside the lane clip
   (gold, the app's keyframe colour); the hit box is a little
   larger than the drawn diamond. The Animate section reuses the
   inspector group/row language. */
.tl-key {
  position: absolute; top: 50%; width: 15px; height: 15px; z-index: 3;
  transform: translate(-50%, -50%); cursor: pointer; touch-action: none;
}
.tl-key::after {
  content: ""; position: absolute; left: 50%; top: 50%; width: 8px; height: 8px;
  transform: translate(-50%, -50%) rotate(45deg);
  background: var(--gold); border: 1px solid rgba(0, 0, 0, 0.55); border-radius: 1.5px;
}
.insp-anim-readout { margin: 0 0 8px; font-size: 13px; color: var(--text); font-family: var(--font-num); }
.insp-anim-actions { flex-wrap: wrap; }
.insp-anim-ease { align-items: center; flex-wrap: wrap; margin-top: 8px; }
.insp-anim-ease-cap { font-size: 12.5px; color: var(--muted); margin-right: 2px; }
.stage-text.is-selected { outline-color: var(--selection); }
.text-row-btn.is-selected { border-color: var(--selection); box-shadow: 0 0 0 1px var(--selection); }
.swatch.is-active { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }

/* ---------- Build 21: captions ----------
   The stage band mirrors js/captions.js captionBlockLayout:
   centred, max 88% wide, bottom edge 9% above the frame bottom
   (above the safe-zone guide) or 4% from the top; font size is
   set inline from the frame height (S/M/L = 4.2/5.5/7.2%). */
.stage-caption-layer { position: absolute; inset: 0; pointer-events: none; z-index: 1; }
.stage-caption {
  position: absolute; left: 50%; transform: translateX(-50%);
  max-width: 88%; width: max-content; margin: 0; text-align: center;
  white-space: pre-line; overflow-wrap: break-word; line-height: 1.22;
  font-weight: 600; color: #fff; pointer-events: none;
}
.stage-caption.is-bottom { bottom: 9%; }
.stage-caption.is-top { top: 4%; }
.stage-caption.bg-pill { background: rgba(0, 0, 0, 0.7); border-radius: 0.18em; padding: 0.14em 0.32em; }
.stage-caption.bg-outline { text-shadow: -1px 0 2px rgba(0,0,0,0.85), 1px 0 2px rgba(0,0,0,0.85), 0 -1px 2px rgba(0,0,0,0.85), 0 1px 3px rgba(0,0,0,0.9); }
.stage-caption.bg-none { text-shadow: 0 1px 6px rgba(0, 0, 0, 0.65), 0 0 2px rgba(0, 0, 0, 0.8); }
.stage-caption.is-bold { font-weight: 800; }
/* Build 26: karaoke word spans — sung words take the highlight
   colour, the word being spoken adds a soft highlight pill
   (colours arrive as CSS vars set from the project style). */
.stage-caption .cap-w { border-radius: 0.18em; padding: 0 0.07em; }
.stage-caption .cap-w.is-sung { color: var(--karaoke, #4D7CFE); }
.stage-caption .cap-w.is-now { color: var(--karaoke, #4D7CFE); background: var(--karaoke-bg, rgba(77, 124, 254, 0.35)); }
.cap-swatch-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* Captions panel (media panel section + the phone Captions
   ribbon share one builder in app.js). */
.cap-count { font-size: 13px; margin: 0 0 10px; }
.cap-group { margin-top: 12px; }
.cap-group:first-child { margin-top: 0; }
.cap-cap { display: block; font-size: 12.5px; color: var(--muted); margin-bottom: 6px; }
.cap-row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.cap-hint { font-size: 12.5px; margin: 8px 0 0; }
.cap-auto-btn { width: 100%; min-height: 44px; }
.cap-job { margin-top: 10px; }
.cap-job-label { font-size: 13px; margin: 0 0 6px; }
.cap-bar { height: 6px; border-radius: 999px; background: var(--surface-3); overflow: hidden; }
.cap-bar-fill { height: 100%; width: 0%; background: var(--accent); border-radius: 999px; transition: width 160ms ease; }
.cap-job-choices { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
.cap-job-choices[hidden] { display: none; }

/* Cleanup panel (Build 22 — media panel section + the phone
   Cleanup ribbon share one builder in app.js). */
.clean-slider-row { display: flex; align-items: center; gap: 10px; margin-top: 10px; flex-wrap: wrap; }
.clean-lab { font-size: 13px; flex: 0 0 auto; }
.clean-slider-row input[type="range"] { flex: 1 1 auto; min-width: 0; }
.clean-val { font-size: 13px; font-variant-numeric: tabular-nums; min-width: 44px; text-align: right; }
.clean-ends { display: flex; justify-content: space-between; font-size: 11.5px; margin-top: 2px; }
.clean-step { min-width: 44px; min-height: 44px; }
#cleanup-panel .btn-primary, #ribbon-cleanup .btn-primary { width: 100%; min-height: 44px; margin-top: 8px; }
#cleanup-panel .cap-group + .cap-group, #ribbon-cleanup .cap-group + .cap-group { margin-top: 18px; }

/* More ribbon: the Cleanup tool row (Build 22). */
.more-tool-row {
  display: flex; align-items: center; gap: 12px; width: 100%;
  min-height: 56px; padding: 8px 4px; margin-bottom: 6px;
  background: none; border: none; border-bottom: 1px solid var(--surface-3);
  color: var(--text); font: inherit; text-align: left; cursor: pointer;
}
.more-tool-row .ic { width: 22px; height: 22px; flex: 0 0 auto; }
.more-tool-text { display: flex; flex-direction: column; gap: 1px; flex: 1 1 auto; }
.more-tool-label { font-size: 15px; font-weight: 600; }
.more-tool-sub { font-size: 12.5px; }
.more-tool-chev { width: 18px !important; height: 18px !important; opacity: 0.6; }

/* ---------- Build 24: reels kit — cover picker + hook starters --
   The export sheet's Cover row mirrors the export rows; the
   cover thumb is a fixed slot (the render's own aspect varies
   with the project, object-fit keeps the slot honest). The
   cover sheet scrubs over the lower zone like every sheet —
   the stage above it stays pinned and shows the picked frame. */
.cover-row {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  background: var(--panel-2); border: 1px solid var(--border);
  border-radius: 10px; padding: 8px 10px; margin-bottom: 10px;
}
.cover-thumb {
  width: 44px; height: 60px; object-fit: cover; flex: 0 0 auto;
  border-radius: 6px; background: var(--surface-3);
}
.cover-thumb-ph { display: grid; place-items: center; color: var(--muted); }
.cover-row-main { flex: 1 1 140px; min-width: 0; display: grid; gap: 1px; }
.cover-row-name { font-weight: 600; }
.cover-row-meta { font-size: 12.5px; }
.cover-scrub-row { display: flex; align-items: center; gap: 8px; margin: 12px 0; }
.cover-scrub-row input[type="range"] { flex: 1 1 auto; min-width: 0; }
/* Build 36: the stabilize sheet's smoothness row (the banner
   reuses .track-banner wholesale — no new banner styles). */
.stab-smooth-row { display: flex; align-items: center; gap: 10px; margin: 14px 0 4px; }
.stab-smooth-row input[type="range"] { flex: 1 1 auto; min-width: 0; }
.stab-smooth-label { font-weight: 600; white-space: nowrap; }
.stab-smooth-val { font-family: var(--font-num); font-size: 13px; white-space: nowrap; min-width: 34px; text-align: right; }
#stab-run { width: 100%; min-height: 48px; margin-top: 10px; }
.cover-time { font-family: var(--font-num); font-size: 13px; white-space: nowrap; }
#cover-use { width: 100%; min-height: 48px; }
/* Build 61: grid-crop previews in the cover sheet (reels
   projects only). Each frame is a centre crop of the saved
   cover at that shape — object-fit: cover on a centred
   image IS the centre crop, so no second renderer exists.
   One row, one label style, one gap rhythm; boxes share
   the same width so the shapes themselves are the
   comparison, not the sizes. */
#cover-grid-previews { margin-top: 14px; border-top: 1px solid var(--border); padding-top: 10px; }
.grid-crop-title { margin: 0 0 6px; font-size: 14px; }
.grid-crop-row { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 10px; }
.grid-crop-box { margin: 0; flex: 1 1 104px; max-width: 150px; display: grid; gap: 6px; }
.grid-crop-frame { width: 100%; overflow: hidden; border-radius: 8px; background: var(--surface-3); }
.grid-crop-frame img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: center; }
.grid-crop-box figcaption { font-size: 12px; line-height: 1.35; color: var(--muted); }
.hook-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 6px 0 10px; }
.hook-chip {
  background: var(--panel-2); border: 1px solid var(--border);
  border-radius: 999px; padding: 7px 12px; min-height: 44px;
  font: inherit; font-size: 13.5px; color: var(--text); cursor: pointer;
}
.hook-chip:active { background: var(--surface-4); }

/* ---------------- Build 25: templates ----------------
   Slot clips read as placeholders: the video lane's violet,
   dashed, translucent — clearly not footage yet. The stage
   slot card is an overlay layer like the placeholder/error
   cards (it never changes the stage box: DESIGN LAW). */
.tl-clip-slot {
  background: rgba(125, 107, 255, 0.14);
  border: 1.5px dashed rgba(125, 107, 255, 0.75);
  color: var(--text-1);
}
.tl-clip-slot.is-selected {
  border-color: var(--accent);
  box-shadow: 0 0 0 1.5px var(--accent);
}
.stage-slot {
  position: absolute; inset: 0; display: grid; place-items: center;
  padding: 12px; pointer-events: none; z-index: 3;
}
.stage-slot[hidden] { display: none; }
/* Build 38: a small CENTRED card — icon, "Empty slot", one
   short line — replacing the sentence-and-duration chip that
   could squeeze against the frame's edges. */
.stage-slot-card {
  border: 1.5px dashed rgba(125, 107, 255, 0.8); border-radius: var(--r-card);
  background: rgba(14, 14, 17, 0.94); color: var(--text-1);
  width: min(80%, 200px); box-sizing: border-box;
  padding: 12px 10px; text-align: center;
}
.stage-slot-card .ic { width: 18px; height: 18px; color: #b7aaff; margin-bottom: 3px; }
.stage-slot-title { margin: 0 0 2px; font-size: 12.5px; font-weight: 700; line-height: 1.25; }
.stage-slot-line { margin: 0; font-size: 11px; color: var(--text-2); line-height: 1.3; }

/* Home: the Templates section under the project grid. */
.templates-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin: 28px 0 12px;
}
.templates-head h3 { margin: 0; font-size: 17px; font-weight: 600; }
.templates-empty { text-align: center; padding: 20px 24px 8px; color: var(--text-2); }
.templates-empty p { margin: 0 0 4px; font-size: 15px; font-weight: 600; color: var(--text-1); }
.templates-empty small { display: block; font-size: 13px; }
.template-actions { display: flex; gap: 6px; margin-top: 8px; flex-wrap: wrap; }
.card-thumb-template i { border-style: dashed; border-color: rgba(125, 107, 255, 0.8); }

/* Editor: the armed slot-fill hint above the media list. */
.media-fill-hint {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  background: rgba(77, 124, 254, 0.14); border: 1px solid rgba(77, 124, 254, 0.55);
  border-radius: var(--r-card); padding: 8px 10px; margin: 0 0 10px;
  font-size: 13px; color: var(--text-1);
}
.media-fill-hint[hidden] { display: none; }

/* ---------- Build 27: teleprompter camera ---------- */
.media-head-actions { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.media-head-actions .btn { display: inline-flex; align-items: center; gap: 5px; }

.record-view {
  position: fixed; inset: 0; z-index: 120; background: #000;
  display: flex; flex-direction: column;
  padding-top: env(safe-area-inset-top);
}
.record-view[hidden] { display: none !important; }
.rec-stage { position: relative; flex: 1 1 auto; min-height: 0; overflow: hidden; background: #000; }
.rec-src { display: none; }
.rec-canvas, .rec-playback {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: contain; background: #000;
}
.rec-script-band {
  position: absolute; left: 0; right: 0; bottom: 0; height: 46%;
  overflow: hidden; pointer-events: none;
  background: linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,0.55) 22%, rgba(0,0,0,0.72));
}
.rec-script-scroll {
  max-width: 560px; margin: 0 auto; padding: 44px 22px 64px;
  color: #fff; text-align: center; white-space: pre-wrap; word-break: break-word;
  font-weight: 600; line-height: 1.45; text-shadow: 0 1px 8px rgba(0,0,0,0.8);
  will-change: transform;
}
.rec-script-band[data-size="S"] .rec-script-scroll { font-size: 19px; }
.rec-script-band[data-size="M"] .rec-script-scroll { font-size: 24px; }
.rec-script-band[data-size="L"] .rec-script-scroll { font-size: 31px; }
.rec-count {
  position: absolute; inset: 0; display: grid; place-items: center;
  font-size: 120px; font-weight: 800; color: #fff; pointer-events: none;
  text-shadow: 0 4px 40px rgba(0,0,0,0.75); font-family: var(--font-num);
}
.rec-topbar {
  position: absolute; top: 0; left: 0; right: 0; z-index: 4;
  display: flex; align-items: flex-start; justify-content: space-between;
  padding: 10px 12px;
}
.rec-top-btn {
  background: rgba(20, 20, 24, 0.65); color: #fff; border: 1px solid rgba(255,255,255,0.18);
  backdrop-filter: blur(6px);
}
.rec-timer {
  display: inline-flex; align-items: center; gap: 7px;
  background: rgba(20, 20, 24, 0.65); border: 1px solid rgba(255,255,255,0.18);
  color: #fff; border-radius: var(--r-pill); padding: 7px 13px;
  font-family: var(--font-num); font-size: 14px; backdrop-filter: blur(6px);
}
.rec-timer.is-late { border-color: var(--danger); color: #ffb3ae; }
.rec-dot {
  width: 10px; height: 10px; border-radius: 50%; background: var(--danger);
  display: inline-block; flex: none; animation: rec-blink 1.2s ease-in-out infinite;
}
.rec-dot-btn { background: #fff; animation: none; }
@keyframes rec-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }
.rec-controls {
  flex: none; background: var(--bg-1); border-top: 1px solid var(--hairline);
  padding: var(--s3) var(--s5) calc(var(--s4) + env(safe-area-inset-bottom));
  max-height: 52dvh; overflow-y: auto;
}
.rec-state { display: flex; flex-direction: column; gap: 10px; }
.rec-state[hidden] { display: none !important; }
.rec-script-field { margin-bottom: 0; }
.rec-script-field textarea {
  width: 100%; padding: 12px; border-radius: var(--r-chip); border: 1px solid var(--hairline);
  background: var(--surface-1); color: var(--text-1); font-size: 16px; appearance: none;
  font-family: inherit; line-height: 1.4; resize: vertical; box-sizing: border-box;
}
.rec-script-field textarea:focus { outline: 2px solid var(--accent); outline-offset: 0; border-color: transparent; }
.rec-row { display: flex; align-items: center; gap: 12px; min-height: 40px; }
.rec-row-label { flex: 0 0 128px; font-size: 13.5px; font-weight: 600; color: var(--text-2); }
.rec-row input[type="range"] { flex: 1; }
.rec-speed-label { flex: 0 0 64px; text-align: right; font-size: 13px; color: var(--text-2); }
.rec-main-btn { width: 100%; min-height: 50px; display: inline-flex; align-items: center; justify-content: center; gap: 8px; font-size: 16px; }
#rec-state-rec { flex-direction: row; flex-wrap: wrap; }
#rec-state-rec .rec-main-btn { flex: 1 1 140px; width: auto; }
#rec-state-rec .rec-note { flex: 1 1 100%; margin: 0; }
.rec-note { margin: 0; font-size: 13px; }
.rec-review-note { margin: 0; font-size: 15px; font-weight: 600; }
.rec-review-actions { display: flex; gap: 8px; }
.rec-review-actions .rec-main-btn { flex: 1; width: auto; }
.rec-error-title { margin: 0; font-size: 16px; font-weight: 700; color: var(--danger); }

/* Build 42: project files — the two home import chips sit side
   by side; the save sheet's buttons go full-width, stacked. */
.templates-head .chip-btn + .chip-btn { margin-left: 8px; }
#projfile-sheet .btn { width: 100%; }
#projfile-sheet #projfile-cancel { margin-top: 8px; }
#projfile-sheet .progress { margin: 4px 0 12px; }

/* Build 43: desktop file handling — the Home drop veil, the
   shuttle indicator and the shortcuts sheet's key rows.
   The veil never intercepts (pointer-events:none); the
   shuttle badge lives in the transport row, below the stage. */
.home-drop-veil {
  position: fixed; inset: 0; z-index: 90;
  display: flex; align-items: center; justify-content: center;
  background: rgba(8, 8, 12, 0.72); pointer-events: none; padding: 24px;
}
.home-drop-card {
  border: 2px dashed var(--accent); border-radius: var(--r-sheet);
  background: var(--surface-1); color: var(--text-1);
  padding: 36px 44px; text-align: center; max-width: 440px;
}
.home-drop-card .ic { width: 34px; height: 34px; color: var(--accent); }
.home-drop-card p { margin: 10px 0 4px; font-size: 21px; font-weight: 700; }
.home-drop-card small { color: var(--text-2); font-size: 13.5px; line-height: 1.45; display: block; }
.tp-shuttle {
  color: var(--accent); font-family: var(--font-num); font-size: 13px;
  font-weight: 700; white-space: nowrap; letter-spacing: 0.02em;
}
.shortcuts-list { display: flex; flex-direction: column; margin: 4px 0 14px; }
.shortcut-row {
  display: flex; align-items: baseline; gap: 16px;
  padding: 9px 2px; border-bottom: 1px solid var(--hairline);
}
.shortcut-row:last-child { border-bottom: none; }
.shortcut-keys {
  flex: 0 0 168px; font-family: var(--font-num); font-size: 13.5px;
  font-weight: 700; color: var(--text-1); white-space: nowrap;
}
.shortcut-label { flex: 1; font-size: 14.5px; color: var(--text-2); }
#shortcuts-sheet .btn { width: 100%; }
@media (max-width: 560px) {
  .shortcut-keys { flex-basis: 128px; font-size: 12.5px; }
}
/* Build 49: the features sheet's grouped plain list — group
   heads in the ribbon-subhead register, one hairline-ruled
   line per feature, real <ul> markup. */
.features-head {
  font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.6px;
  color: var(--text-3); margin: 16px 0 2px;
}
.features-list { list-style: none; margin: 0; padding: 0; }
.features-list li {
  padding: 8px 2px; border-bottom: 1px solid var(--hairline);
  font-size: 14.5px; line-height: 1.45; color: var(--text-2);
}
.features-list li:last-child { border-bottom: none; }
#features-sheet .btn { width: 100%; margin-top: 14px; }
