/* ─── Extra label fonts (bundled locally, not from Google Fonts) ─── */
@font-face { font-family: 'Varsity'; src: url('/fonts/Varsity.ttf') format('truetype'); font-display: swap; }
@font-face { font-family: 'Space Age'; src: url('/fonts/SpaceAge.ttf') format('truetype'); font-display: swap; }
@font-face { font-family: 'Orange Juice'; src: url('/fonts/OrangeJuice.ttf') format('truetype'); font-display: swap; }
@font-face { font-family: 'Hanging Letters'; src: url('/fonts/HangingLetters.ttf') format('truetype'); font-display: swap; }
@font-face { font-family: 'Adult Swim'; src: url('/fonts/AdultSwim.otf') format('opentype'); font-display: swap; }

:root {
  --bg-primary: #ffffff;
  --bg-secondary: #f4f4f6;
  --bg-tertiary: #eaeaef;
  --text-primary: #0b1220;
  --text-secondary: #525c70;
  /* Signature accent: blue (was green until 2026-09-21; green is still one tap away via
     the accent toggle). --on-accent is the text/icon color for anything FILLED with the
     accent: white here, but dark navy in the dark theme, where the accent is a lighter
     blue so it also reads as text on the navy background. No single blue passes contrast
     both as a fill under white text and as text on navy. */
  --accent: #2563eb;
  --accent-hover: #1d4ed8;
  --accent-soft: #e8f0ff;
  --on-accent: #ffffff;
  /* Complementary warm accent (green's near-opposite on the wheel) — used sparingly, on
     marketing pages only, as a second color pop against all the green/white/gray so
     those pages don't read as one flat monochrome wash. Never used in the app UI itself,
     which stays strictly one-accent. */
  --accent-warm: #e0562e;
  --accent-warm-soft: #fdeee9;
  --success: #16a34a;
  --danger: #dc2626;
  --danger-hover: #b91c1c;
  --border: #e3e3e8;
  --border-strong: #0e0e12;
  --glass-bg: #ffffff;
  --glass-border: #e3e3e8;
  --glass-shadow: 0 1px 2px rgba(14, 14, 18, 0.04);
  /* Small and mostly-uniform on purpose — square/flat, not the bubbly rounded-corner
     treatment that reads as generic AI-tool chrome. --radius-full is a deliberate
     exception for things that are round by nature (status dot, pill badges), not a
     softer version of the square scale. */
  --radius-sm: 4px;
  --radius-md: 6px;
  --radius-lg: 8px;
  --radius-full: 999px;
  /* Flat: real elevation comes from a border, not a blurred shadow. What's left here is
     just enough to lift the one focal element (the live canvas) off the page. */
  --shadow-xs: none;
  --shadow-sm: none;
  --shadow-md: 0 1px 3px rgba(14, 14, 18, 0.10);
  --shadow-lg: 0 2px 10px rgba(14, 14, 18, 0.10);
  --border-radius: var(--radius-md);
  --transition: 0.15s cubic-bezier(0.4, 0, 0.2, 1);
  --sidebar-w: 380px;
  --header-h: 76px;
  --bottom-nav-h: 0px;
  /* Inter: the shipped default everywhere (wordmark, hero, body copy) — same brief as
     the system-Helvetica default this replaced (neutral, confidence-reads-as-competence,
     not a "creative coder" display face making a personality statement), but as an
     actual loaded webfont rather than a system-font stack: "Helvetica Neue" silently
     degrades to Arial on Windows/Android, which reads noticeably less polished than
     what a Mac visitor sees from the exact same CSS. Inter renders identically
     everywhere, which is what a hero headline in particular needs to look deliberate
     rather than incidental. 'Space Grotesk'/Helvetica (still available as the explicit
     "technical"/"helvetica" states below) and the other named looks are all one tap
     away via the font-cycle toggle. */
  /* Inter Tight (a real, distinct Google Font — narrower metrics/tighter default tracking
     than plain Inter, not just Inter at a different weight) for headlines, plain Inter for
     body — the pairing most modern SaaS marketing sites use for exactly the reason it
     works here: a headline in the SAME face as the paragraph under it reads as "the same
     text, just bigger," while a distinct (but clearly related, same designer/superfamily)
     display face is what actually creates hierarchy. Falls back to plain Inter, not
     straight to Helvetica, if Inter Tight's stylesheet is ever slow/blocked — the two are
     close enough in shape that this is a graceful size-only degradation, not a visibly
     different font swap. */
  --font-display: 'Inter Tight', 'Inter', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  --font-body: 'Inter', 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

/* ─── UI Theme (Light/Dark) + Font toggle ───
   Both driven by a `data-theme`/`data-font` attribute on <html>, set by the small
   toggle control fixed near the bottom of the viewport (see .theme-font-toggle) and
   persisted to localStorage. Dark redefines the same tokens every component already
   draws from (--bg-*/--text-*/--border/--glass-*) rather than a parallel set of dark-
   specific classes, so it reaches everywhere those tokens are already used without
   needing every component re-touched. `prefers-color-scheme` supplies the default when
   no explicit choice has been made yet; an explicit light/dark choice always wins over
   it either way. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg-primary: #000000;
    --bg-secondary: #0e1117;
    --bg-tertiary: #161b24;
    --text-primary: #f2f5fa;
    --text-secondary: #bcc4d3;
    --accent: #5b9dff;
    --accent-hover: #86b6ff;
    --accent-soft: #101c33;
    --on-accent: #00060f;
    --accent-warm: #ff7a4d;
    --accent-warm-soft: #3a1f16;
    --border: #232936;
    --border-strong: #f2f5fa;
    --glass-bg: #05070b;
    --glass-border: #1e222c;
    --glass-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
  }
}
:root[data-theme="dark"] {
  --bg-primary: #000000;
  --bg-secondary: #0e1117;
  --bg-tertiary: #161b24;
  --text-primary: #f2f5fa;
  --text-secondary: #bcc4d3;
  --accent: #5b9dff;
  --accent-hover: #86b6ff;
  --accent-soft: #101c33;
  --on-accent: #00060f;
  --accent-warm: #ff7a4d;
  --accent-warm-soft: #3a1f16;
  --border: #232936;
  --border-strong: #f2f5fa;
  --glass-bg: #05070b;
  --glass-border: #1e222c;
  --glass-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}
/* Curated UI chrome font pairings — "Helvetica" (system font, zero network cost) is the
   shipped default at bare :root above; restated explicitly here too so an explicit
   data-font="helvetica" (always set by index.html's inline head script from now on, even
   for a first-time visitor with no saved preference — see the comment there) resolves
   identically either way. The rest are from families already loaded for the in-canvas
   label fonts (see the Google Fonts <link> in index.html), zero extra network cost too. */
:root[data-font="helvetica"] { --font-display: 'Helvetica Neue', Helvetica, Arial, sans-serif; --font-body: 'Helvetica Neue', Helvetica, Arial, sans-serif; }
:root[data-font="technical"] { --font-display: 'Space Grotesk', sans-serif; --font-body: 'JetBrains Mono', monospace; }
:root[data-font="modern"] { --font-display: 'Montserrat', 'Poppins', sans-serif; --font-body: 'Poppins', sans-serif; }
:root[data-font="classic"] { --font-display: 'Playfair Display', Georgia, serif; --font-body: 'Inter', sans-serif; }

/* Curated accent-color themes — opt-in only, via the same floating toggle as light/dark
   and font (see .theme-font-toggle / #accentToggleBtn). Green stays the shipped DEFAULT
   for the reason documented on --accent above (a considered, specific choice, not a
   generic template color) — this isn't reversing that, it's letting a visitor who wants
   a different accent have one without touching the app's own baseline identity. Each
   theme only ever redefines --accent/--accent-hover/--accent-soft, nothing else: no new
   radii, shadows, or gradients sneak in through here, so the flat/square/border-elevation
   language stays identical no matter which color is active. --accent-soft is derived via
   color-mix() against --bg-primary (already used elsewhere in this file, e.g.
   .floating-play-overlay) rather than hardcoded per theme, so it automatically tracks
   whichever of light/dark is active instead of needing its own light+dark pair per theme
   the way the base green tokens do. --success/--danger are semantic, not brand colors —
   deliberately left untouched by every theme here. */
:root[data-accent="green"] {
  --accent: #16a34a;
  --accent-hover: #15803d;
  --accent-soft: color-mix(in srgb, #16a34a 16%, var(--bg-primary));
  --on-accent: #ffffff;
}
:root[data-accent="violet"] {
  --accent: #7c3aed;
  --accent-hover: #6d28d9;
  --on-accent: #ffffff;
  --accent-soft: color-mix(in srgb, #7c3aed 16%, var(--bg-primary));
}
:root[data-accent="orange"] {
  /* Reuses the exact hue already established as this app's own "second color" (see
     --accent-warm above) rather than inventing an unrelated orange from scratch. */
  --accent: #e0562e;
  --accent-hover: #c2431f;
  --on-accent: #ffffff;
  --accent-soft: color-mix(in srgb, #e0562e 16%, var(--bg-primary));
}
:root[data-accent="mono"] {
  /* The genuinely "no added color" option — a neutral graphite rather than tracking
     --text-primary directly, which would put near-white-on-near-white filled buttons
     underneath white button text in dark mode. */
  --accent: #52525b;
  --accent-hover: #3f3f46;
  --on-accent: #ffffff;
  --accent-soft: color-mix(in srgb, #52525b 16%, var(--bg-primary));
}

* { box-sizing: border-box; margin: 0; padding: 0; }
/* One consistent keyboard-focus ring app-wide, in the app's own accent color instead of
   each browser's own default (blue in Chrome, a different blue in Firefox, etc.) — most
   buttons/cards/pills here never had any focus styling of their own at all, just falling
   back to whatever the browser draws by default, which still worked but looked like a
   foreign element dropped onto an otherwise fully custom-styled UI. :focus-visible (not
   :focus) so this only ever shows for keyboard/switch-device navigation, never a mouse or
   touch tap — exactly the "was this reached by keyboard" signal a11y guidance calls for,
   not a change to how anything looks when clicked. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

html, body {
  /* Stops iOS Safari's elastic rubber-band bounce at the page level — with the whole
     layout already fixed to 100dvh, that bounce only ever revealed blank space behind
     the app rather than scrolling to real content, which read as the page glitching. */
  overscroll-behavior-y: none;
}

body {
  font-family: var(--font-body);
  background-color: var(--bg-primary);
  color: var(--text-primary);
  min-height: 100dvh;
  overflow-x: hidden;
  -webkit-tap-highlight-color: transparent;
  transition: background-color var(--transition), color var(--transition);
}

.glass-panel {
  background: var(--glass-bg);
  border: 1px solid var(--glass-border);
}

/* ─── Layout ─── */
.app-container {
  display: grid;
  grid-template-columns: 1fr var(--sidebar-w);
  grid-template-rows: var(--header-h) 1fr;
  height: 100dvh;
}

/* A brief, one-time entrance on first load — header settles in first, video area and
   sidebar follow just behind it. Guarded behind prefers-reduced-motion so anyone who's
   opted out of motion at the OS level never sees it. */
@media (prefers-reduced-motion: no-preference) {
  @keyframes appEnter { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
  header { animation: appEnter 0.45s ease-out both; }
  .video-area { animation: appEnter 0.5s 0.08s ease-out both; }
  /* .sidebar deliberately excluded — on mobile it's a bottom sheet whose own transform
     (translateY(100%) closed / translateY(0) open, further down this file) would get
     briefly overridden by this animation's own transform values, flashing the sheet into
     view on load regardless of its actual open/closed state. */
}

/* ─── Header ─── */
header {
  grid-column: 1 / -1;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 24px;
  padding: 0 24px;
  z-index: 100;
  border-bottom: 1px solid var(--glass-border);
  background: var(--bg-primary);
  height: var(--header-h);
  box-shadow: var(--shadow-xs);
  position: relative;
}
/* A plain 1px border read as an ordinary panel edge rather than the app's own header —
   this thin accent line ties it back to the brand color without adding any real weight
   or competing with the (deliberately minimal) flat design elsewhere. Desktop only: the
   compact mobile header has tighter things to spend a pixel of visual weight on. */
@media (min-width: 769px) {
  header::after {
    content: '';
    position: absolute;
    left: 0; right: 0; bottom: -1px;
    height: 2px;
    background: linear-gradient(to right, var(--accent) 0%, var(--accent) 240px, transparent 240px);
  }
}

.header-content { display: flex; align-items: center; gap: 12px; }
.header-logo {
  width: 38px; height: 38px; flex-shrink: 0;
  /* Soft accent glow under the tile: the one place the flat/border-only elevation rule
     bends, since the mark is the brand's anchor and should sit slightly forward. */
  filter: drop-shadow(0 4px 10px color-mix(in srgb, var(--accent) 35%, transparent));
}
/* Title row stays its own line; the tagline and Free/No Signup/Runs Locally badges
   that used to stack in three separate rows (each with its own margin-top hack) now
   share one row via wrap — denser, and reads as one connected byline instead of a
   list of afterthoughts trailing under the wordmark. */
.header-content > div { display: flex; flex-wrap: wrap; align-items: center; column-gap: 10px; row-gap: 3px; }
/* Wordmark font follows the same --font-display token as the hero headline/site-wordmark
   elsewhere (see the UI font toggle, .header-content h1's font-family flows from
   whichever of technical/modern/classic is active) — bold and heavy regardless of which,
   just no longer locked to the display-serif "classic" look by default. */
.header-content h1 { flex-basis: 100%; font-family: var(--font-display); font-size: 1.6rem; font-weight: 800; letter-spacing: -0.035em; line-height: 1; color: var(--text-primary); }
/* Two-tone wordmark: "Exo" in the accent so the name carries the brand color even
   where the logo tile isn't shown (and ties the word to the mark beside it). */
.brand-wordmark span { color: var(--accent); }
.header-content p { font-size: 0.78rem; color: var(--text-secondary); }
.header-badges { display: flex; gap: 6px; }
.header-badges span {
  font-size: 0.62rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 3px 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  color: var(--text-secondary);
}

/* Real on-page links to the SEO content/FAQ sections below the fold and the repo —
   desktop only (see mobile override): a compact header has no room for these, and the
   mobile drawer already surfaces settings, so they'd just be clutter there. */
.header-nav { display: flex; align-items: center; gap: 4px; }
.header-nav a {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text-secondary);
  text-decoration: none;
  padding: 7px 12px;
  border-radius: var(--radius-full);
  transition: background-color var(--transition), color var(--transition);
}
.header-nav a:hover { background: var(--accent-soft); color: var(--accent-hover); }
/* Scroll-spy active state (home.html only, see its own script) — same pill treatment as
   :hover so a resting active link doesn't look like a different, competing style. */
.header-nav a.nav-active { background: var(--accent-soft); color: var(--accent-hover); }
/* The one nav link that isn't "go read a section on this same page" but "leave this page
   and start using the product" — worth a harder visual break from the rest of the nav so
   it reads as the nav's own primary action, not just the fourth item in a row. */
.home-header .header-nav a.nav-cta {
  background: var(--accent);
  color: var(--on-accent);
  margin-left: 4px;
}
.home-header .header-nav a.nav-cta:hover { background: var(--accent-hover); color: var(--on-accent); }

.status-indicator {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.75rem;
  font-weight: 600;
  padding: 5px 11px;
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  background: var(--bg-secondary);
  color: var(--text-secondary);
  white-space: nowrap;
}
.status-indicator::before {
  content: '';
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--text-secondary);
  flex-shrink: 0;
}
.status-indicator.ready {
  background: color-mix(in srgb, var(--success) 12%, var(--bg-primary));
  color: color-mix(in srgb, var(--success) 70%, var(--text-primary));
  border-color: color-mix(in srgb, var(--success) 35%, var(--bg-primary));
}
.status-indicator.ready::before { background: var(--success); box-shadow: 0 0 0 3px color-mix(in srgb, var(--success) 22%, transparent); }

/* hamburger – mobile only */
#sidebarToggle {
  display: none;
  background: none;
  border: 1px solid var(--border);
  width: 40px;
  height: 40px;
  cursor: pointer;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  flex-shrink: 0;
}
#sidebarToggle span {
  display: block;
  width: 20px;
  height: 2px;
  background: var(--text-primary);
  transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.15s ease;
}
/* Hamburger-to-X morph on open — previously this button never reflected open/closed
   state at all (three static bars regardless), the one interactive control in the app
   with zero feedback of its own despite virtually everything else here (mode cards, play
   buttons, quick-bar pills) already having a tuned transition. Middle bar fades out
   rather than collapsing to a dot mid-rotation (cleaner than the classic "squeeze to
   center" variant), outer two rotate into an X anchored on their own center. */
#sidebarToggle.active span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
#sidebarToggle.active span:nth-child(2) { opacity: 0; transform: scaleX(0); }
#sidebarToggle.active span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* ─── Video area ─── */
.video-area {
  grid-column: 1 / 2;
  grid-row: 2 / 3;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  padding: 16px;
  overflow: hidden;
  /* A flat fill here read as dead/unfinished space, especially on a wide monitor where
     the centered canvas card is nowhere near big enough to fill it (see .is-empty's own
     comment below — same problem, still visible past laptop width). A faint dot grid
     gives the empty area a "this is a workspace" identity instead, the same visual
     language graphics/motion tools (including TouchDesigner itself) use for their canvas
     background — while staying subtle enough not to compete with the actual content. */
  background-color: var(--bg-secondary);
  background-image: radial-gradient(rgba(14, 14, 18, 0.09) 1.4px, transparent 1.4px);
  background-size: 28px 28px;
  position: relative;
  gap: 10px;
}

/* Desktop Photo Booth strip (#desktopPhotoBoothRow) — a normal-flow row under the canvas,
   NOT the same visual as the mobile version's translucent glass-over-video pills
   (.mq-pb-pill lives entirely inside a mobile-only media query, styled for a compact
   absolute overlay on top of a phone's live picture) — this sits in the page's own flow
   with room to breathe, so it uses this app's regular button/pill language instead
   (var(--bg-secondary)/var(--border), matching .mode-card and friends) rather than the
   glass/blur treatment that context needed. Hidden below the mobile breakpoint
   unconditionally — that width already has its own, different-looking equivalent. */
.desktop-photobooth-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  padding: 0 4px;
}
@media (max-width: 768px) {
  .desktop-photobooth-row { display: none !important; }
}

/* Desktop home for the Clip Editor — see the long comment on #desktopClipEditorDock in
   index.html for why this exists (previously only reachable via Source tab > scroll >
   Trim & Edit, with zero visual connection to the video it edits). Empty/invisible by
   default: `.video-area` normally centers its (shorter) content vertically inside a
   fixed-height grid cell with `overflow:hidden` — appending a tall panel there directly
   would either get clipped or squeeze the video's own computed size. Instead, opening the
   dock only ever ADDS a class to `.video-area` that switches it to top-aligned + scrollable
   (`getWrapperBudget()` in main.js measures `.video-area`'s own fixed box, not its
   scrollable content, so the canvas's computed size is completely unaffected either way) —
   the video stays exactly the size it always was; scrolling the area is what reveals the
   editor sitting below it, same as scrolling down to a timeline in a real NLE. */
.desktop-clip-editor-dock { display: none; }
@media (min-width: 769px) {
  .video-area.has-desktop-editor {
    justify-content: flex-start;
    overflow-y: auto;
  }
  .desktop-clip-editor-dock.is-open {
    display: block;
    width: 100%;
    max-width: 900px;
    flex: 0 0 auto;
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    padding: 16px 18px;
    margin: 4px 0 16px;
  }
  /* The panel's own tabs/timeline were sized for a ~300px sidebar column — stretched to
     this dock's full (much wider) width, a couple of its children benefit from an explicit
     wider cap so they don't look stranded in the extra room; everything else (buttons,
     sliders) already just fills whatever width its flex/grid container gives it. */
  .desktop-clip-editor-dock .kf-timeline { min-height: 160px; }
}
@media (max-width: 768px) {
  .desktop-clip-editor-dock { display: none !important; }
}
.pb-pill-desktop {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 8px 14px;
  background: var(--bg-primary);
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text-primary);
  font-family: inherit;
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
  transition: background var(--transition), border-color var(--transition), color var(--transition), transform 0.1s ease;
}
.pb-pill-desktop .icon { width: 16px; height: 16px; }
.pb-pill-desktop:hover { background: var(--bg-secondary); border-color: var(--text-secondary); }
.pb-pill-desktop:active { transform: scale(0.96); }
.pb-pill-desktop.active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

.canvas-wrapper {
  position: relative;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  overflow: hidden;
  display: flex;
  justify-content: center;
  /* .video-area is itself a column flex container, so this wrapper is a flex ITEM along
     that axis too — and flex items default to flex-shrink:1 regardless of their own
     explicit width/height. That default is exactly what silently squeezed the video
     shorter than fitCanvasWrapper() had just computed for it the moment the desktop Clip
     Editor dock (a sibling in the same flex column, see .desktop-clip-editor-dock) had any
     real height to ask for — confirmed by comparing the wrapper's actual rendered rect
     against its own untouched inline style, which still said the original (taller) number.
     flex-shrink:0 makes the video the one thing in this column that's never a candidate
     for that squeeze; .video-area's own overflow-y:auto (added alongside the dock) is what
     handles any resulting overflow now, via scrolling instead of shrinking the video. */
  flex-shrink: 0;
  /* Was unset (stretch) — a replaced element with its own intrinsic ratio can't actually
     stretch to fill the cross axis, so it instead sits flush at the cross-start edge:
     the top, for this row-direction flex. Harmless for anything close to the wrapper's
     own aspect ratio, but a landscape/widescreen source renders as a short strip pinned
     to the very top of the wrapper's much taller box (its fixed height is viewport-
     derived on mobile, not content-derived) — exactly where .mobile-quick-style-bar's
     stacked absolute-positioned rows (Mode + Tool + Effects) already live, so a wide
     video's whole preview could end up sitting behind that chrome instead of centered
     in the open space below it. */
  align-items: center;
  max-width: 100%;
  max-height: 100%;
  /* Before a video/webcam/photo loads, the canvas has no intrinsic size beyond its
     300x150 default — give the wrapper a floor so the empty-state prompt has room
     to lay out instead of being clipped to that tiny box. */
  min-width: min(360px, 100%);
  min-height: min(240px, 100%);
  background: var(--bg-primary);
}
.floating-play-overlay {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) scale(0.78);
  width: 64px;
  height: 64px;
  border-radius: 50%;
  background: rgba(10, 10, 14, 0.45);
  border: 1.5px solid color-mix(in srgb, var(--accent) 40%, rgba(255, 255, 255, 0.18));
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow: 0 8px 26px rgba(0, 0, 0, 0.3), inset 0 0 0 1px rgba(255, 255, 255, 0.05);
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.25s ease, transform 0.32s cubic-bezier(0.34, 1.56, 0.64, 1);
  z-index: 5;
}
.floating-play-overlay.visible { opacity: 1; transform: translate(-50%, -50%) scale(1); }
.floating-play-overlay svg {
  width: 24px; height: 24px;
  color: var(--accent);
  filter: drop-shadow(0 0 7px color-mix(in srgb, var(--accent) 65%, transparent));
}

/* Before anything is loaded there's no aspect ratio to respect yet, so let the prompt
   claim a much more generous, deliberately-sized area instead of shrinking to the
   300x150 canvas default's floor — that read as a small lost box in a sea of empty
   background on anything wider than a laptop. Reverts to the floor above the moment a
   real video/webcam/photo gives the wrapper an actual aspect ratio to size around. */
.canvas-wrapper.is-empty {
  width: min(920px, 94%);
  height: min(700px, 86dvh);
}
/* Past laptop width, 920px still leaves a huge flat margin on either side of the empty-
   state card (a 1920px-wide screen has over 900px of unused space in this column alone)
   — let it grow further on genuinely wide displays instead of stalling at a width tuned
   for a much smaller viewport. */
@media (min-width: 1500px) {
  .canvas-wrapper.is-empty { width: min(1180px, 80%); height: min(760px, 82dvh); }
}

/* Once a video/webcam/photo is loaded, the wrapper previously had no explicit size of
   its own on desktop (only .is-empty above does) — a flex container with no size just
   shrink-wraps its content, so it hugged whatever tiny CSS size the canvas happened to
   render at. That canvas size is its raw *processing* resolution (canvas.width/height,
   set in updateCanvasSize() and capped by the Resolution setting — 640px wide by
   default), not a display size, so on any desktop screen bigger than that the preview
   sat in a small box with a wide empty margin around it, worst at low Resolution
   settings on a big monitor. Giving the loaded wrapper the same generous footprint the
   empty-state card already claims (mirrored at the same breakpoints below), combined
   with canvas's own width:100% further down, lets CSS scale the preview up to fill
   that space while object-fit:contain keeps the real aspect ratio — display size and
   processing resolution are decoupled the same way a <video> element's playback
   resolution already is from its on-screen size. Mobile already had this exact fix
   (see canvas.width:100% under the max-width:768px block below); this is its desktop
   counterpart. */
/* Plain px caps only, no percentage/dvh component — fitCanvasWrapper() in main.js is
   what actually fits this box to the loaded video's own aspect ratio, and it already
   measures .video-area's real available space directly (so it can never overflow the
   container on its own). A percentage-based max-width/max-height here would just clamp
   *against* that already-correct JS-computed size a second time — a max-width:94% ends
   up being 94% of .video-area's width, not 94% of whatever JS decided, silently shrinking
   the box below what JS asked for. These two numbers are also hardcoded into JS's
   `getWrapperBudget()` (its hardW/hardH) — keep both in sync if either changes. These
   rules are mainly a fallback for the instant before JS runs / if it's ever skipped. */
.canvas-wrapper:not(.is-empty) {
  width: auto;
  height: auto;
  max-width: 1280px;
  max-height: 900px;
  /* Overrides the empty-state card's var(--bg-primary) (white in light theme, the
     default) with a fixed near-black once real video content is expected here. The
     canvas itself is transparent until its first frame actually paints — a video file
     load has a real gap for that (decode, Smart Calibrate's seeks, toggleVideoPlayback
     resolving) longer than a webcam's near-instant stream — and during that gap the
     wrapper's own background was showing straight through as a stark white flash over
     what a viewer expects to look like a video player. Every real video player (this
     app's own light theme included) keeps its screen black regardless of UI theme for
     exactly this reason — it's showing camera/video output, not themed chrome. */
  background: #000;
}
@media (min-width: 1500px) {
  .canvas-wrapper:not(.is-empty) { max-width: 1600px; max-height: 1000px; }
}

canvas {
  display: block;
  width: 100%;
  max-width: 100%;
  max-height: calc(100dvh - var(--header-h) - 32px);
  object-fit: contain;
  /* Was `none`, which blocks EVERY native touch gesture starting on the canvas —
     including vertical page scroll. That's most of the screen once a video is loaded, so
     a swipe-to-scroll attempt starting there (the natural place a thumb lands) silently
     did nothing. `pan-y` still lets the browser handle vertical scroll natively; it just
     leaves horizontal panning to JS (the swipe-to-switch-mode gesture below, which only
     ever calls preventDefault() on touchend once it's decided a gesture was horizontal —
     that explicit call still overrides this regardless). Zone-drawing's own touchmove
     handler already scopes its preventDefault() to only while actively drawing, so it
     keeps working unchanged either way. */
  touch-action: pan-y;
}
/* `display:none` used to hide this — but several mobile browsers (notably Android
   Chrome/WebView under CPU pressure, which recording reliably creates) throttle or
   fully pause decoding a display:none video element to save power/battery. Once that
   kicks in mid-recording, every ctx.drawImage(video,...) call in the tracking pipeline
   keeps redrawing whatever was left in the decoder's last surface — on the affected
   devices that's a blank near-white frame, not the last real frame — which reads as a
   pale wash settling over the live picture partway through a take. Keeping the element
   in the render tree (zero size, fully transparent, off to the side) instead of removing
   it from layout keeps it classified as "visible" for decode-throttling purposes while
   staying completely invisible and inert on screen. */
video { position: absolute; width: 1px; height: 1px; opacity: 0; overflow: hidden; pointer-events: none; }

/* ─── Full Screen ───
   Real :fullscreen (desktop browsers, Android Chrome) gets the browser's own UA
   fullscreen sizing for free — these rules just correct the canvas's own max-height,
   which is otherwise still `calc(100dvh - header-h - 32px)` (see the base `canvas` rule
   above), a budget that's meaningless once the header it's subtracting is no longer on
   screen at all. iOS Safari has no Fullscreen API for arbitrary elements, so
   toggleFullscreen() in main.js falls back to this same .fake-fullscreen class there —
   a plain position:fixed cover, styled identically so the two paths look the same. */
.canvas-wrapper:fullscreen,
.canvas-wrapper:-webkit-full-screen,
.canvas-wrapper.fake-fullscreen {
  width: 100vw;
  height: 100dvh;
  max-width: 100vw;
  max-height: 100dvh;
  border-radius: 0;
}
.canvas-wrapper.fake-fullscreen {
  position: fixed;
  inset: 0;
  z-index: 200;
  margin: 0;
}
.canvas-wrapper:fullscreen canvas,
.canvas-wrapper:-webkit-full-screen canvas,
.canvas-wrapper.fake-fullscreen canvas {
  max-height: 100dvh;
  max-width: 100vw;
}

/* ─── Loading Overlay ─── */
.loading-overlay {
  position: absolute;
  inset: 0;
  /* Fixed dark scrim, not tied to var(--bg-primary) — the app's default (unthemed)
     state IS light, where --bg-primary is #ffffff, so blending 92% toward that was
     still a near-solid white wash over the video every single time this showed (page
     load, every mode switch's first model download, every video-file upload). This
     overlay sits over VIDEO content, not UI chrome, so it gets the same fixed-dark
     treatment as .canvas-wrapper's own background — consistent regardless of light/
     dark theme, like every real video player's loading/buffering state. */
  background: rgba(10, 10, 14, 0.82);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  z-index: 10;
  animation: loading-fade-in 0.2s ease;
  /* Covers the dismiss direction — showLoading()/hideLoading() in main.js toggle opacity
     between 1 and 0 (display:none only follows 250ms later, once this has had time to
     actually play) instead of the old instant display:none pop-away. */
  transition: opacity 0.25s ease;
}
@keyframes loading-fade-in { from { opacity: 0; } to { opacity: 1; } }
/* Two rings (one fast/thin, one slow/thick, opposite directions) instead of one bare
   ring — reads as "actively working," not just a static circle with a moving notch,
   and the accent-colored inner ring ties it to the rest of the app's one signature
   color instead of a generic gray spinner. */
.spinner {
  position: relative;
  width: 44px; height: 44px;
  margin-bottom: 16px;
}
.spinner::before, .spinner::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 3px solid transparent;
}
.spinner::before {
  border-top-color: var(--accent);
  border-right-color: var(--accent);
  animation: spin 0.9s cubic-bezier(0.5, 0.1, 0.5, 0.9) infinite;
}
.spinner::after {
  inset: 8px;
  /* Fixed light color, not var(--border-strong) — that token flips to near-black in
     light theme (the app's default), which would go nearly invisible against this
     overlay's now-always-dark background. */
  border-bottom-color: #f2f2f5;
  opacity: 0.25;
  animation: spin-reverse 1.6s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
@keyframes spin-reverse { to { transform: rotate(-360deg); } }
.progress-bar-container {
  width: 200px; height: 6px;
  /* Fixed translucent-white track, not var(--bg-secondary)/var(--border) — both are
     light-toned in light theme and would nearly disappear against this overlay's
     always-dark background. */
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: var(--radius-full);
  margin-top: 12px;
  overflow: hidden;
}
/* There's no real byte/model-load progress signal available from any of the sources
   this bar covers (CDN <script> tags, TFJS model.load() calls) — an indeterminate
   sweep is an honest "still working" signal, where the old static 0%-forever bar
   (width was declared but never once assigned by any JS) just looked broken/stalled. */
.progress-bar {
  width: 40%; height: 100%;
  background: linear-gradient(90deg, var(--accent), var(--accent-hover));
  border-radius: var(--radius-full);
  animation: progress-indeterminate 1.1s ease-in-out infinite;
}
#loadingText {
  font-weight: 600;
  letter-spacing: 0.01em;
  /* Fixed light color — inherited var(--text-primary) is near-black in light theme
     (the app's default), illegible against this overlay's now-always-dark background. */
  color: #f2f2f5;
}

/* ─── Persistent transport bar. Always visible on mobile (below); on desktop it's a
   hover-reveal overlay on the video itself (further down) — previously a desktop user
   had to look away to the sidebar's Playback section for anything beyond play/pause
   (click-to-toggle already worked on the canvas), which is exactly the "glance at the
   video, not the settings panel" control a scrub bar is for. Reuses the same element
   and JS wiring as the mobile bar rather than duplicating a second set of controls. ─── */
.mobile-player-bar { display: none; }
/* Webcam-only capture controls swapped into the scrub/time's spot in the transport bar —
   see setWebcamTransportMode() in main.js, which toggles .is-webcam on #mobilePlayerBar.
   Sizing/spacing for the buttons themselves lives with .mp-play-btn per breakpoint below;
   this shared rule just handles which half of the bar is visible. */
.mp-webcam-actions { display: none; align-items: center; gap: 8px; flex-shrink: 0; }
.mobile-player-bar.is-webcam .mp-scrub,
.mobile-player-bar.is-webcam .mp-time { display: none; }
.mobile-player-bar.is-webcam .mp-webcam-actions { display: flex; }
/* Same landscape-phone union as .mobile-quick-style-bar above — width-only 768px missed
   a landscape phone (commonly 800px+ wide), which combined with the desktop block further
   down being hover/focus-within-gated (no real ":hover" on a touchscreen) meant transport
   controls were completely unreachable in that orientation, not just cosmetically off. */
@media (max-width: 768px), (max-width: 900px) and (max-height: 500px) and (orientation: landscape) {
  .mobile-player-bar {
    display: flex;
    align-items: center;
    gap: 8px;
    position: absolute;
    left: 8px; right: 8px; bottom: 8px;
    padding: 6px 12px 6px 6px;
    background: rgba(0,0,0,0.55);
    backdrop-filter: blur(8px);
    border-radius: 999px;
    box-shadow: 0 4px 16px rgba(0,0,0,0.25);
    z-index: 8;
  }
  .mp-play-btn {
    flex-shrink: 0;
    width: 32px; height: 32px;
    border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    background: rgba(255,255,255,0.14);
    border: none; padding: 0;
    color: #fff;
    cursor: pointer;
    /* Previously had no transition/active state at all — a tap registered instantly with
       zero tactile feedback, which is what actually read as "clunky" rather than any
       real input lag. */
    transition: transform 0.12s ease, background-color 0.12s ease;
  }
  .mp-play-btn:active:not(:disabled) { transform: scale(0.86); background: rgba(255,255,255,0.28); }
  .mp-play-btn:disabled { opacity: 0.4; }
  .mp-play-btn .icon { width: 18px; height: 18px; transition: transform 0.16s cubic-bezier(0.34, 1.56, 0.64, 1); }
  .mp-play-btn .icon.icon-pop { transform: scale(1.25); }
  .mp-scrub {
    --progress: 0%;
    flex: 1;
    -webkit-appearance: none;
    appearance: none;
    /* The element's own box IS the touch hit target for a native range input — a finger
       has nowhere near a mouse cursor's precision, so the old 4px-tall element (matching
       the visual track exactly) was close to un-grabbable on a real phone. Kept the thin
       *visual* track by painting it as a short centered strip instead of the full box,
       while the actual hit area grows to a real touch target. */
    height: 32px;
    background: linear-gradient(to right, #fff var(--progress), rgba(255,255,255,0.28) var(--progress));
    background-size: 100% 4px;
    background-position: center;
    background-repeat: no-repeat;
  }
  .mp-scrub::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 20px; height: 20px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 4px rgba(0,0,0,0.4);
  }
  .mp-scrub::-moz-range-thumb {
    width: 20px; height: 20px;
    border: none;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 4px rgba(0,0,0,0.4);
  }
  .mp-time {
    flex-shrink: 0;
    color: rgba(255,255,255,0.9);
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.66rem;
    white-space: nowrap;
  }
  .mp-icon-btn {
    flex-shrink: 0;
    width: 32px; height: 32px;
    border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    background: rgba(255,255,255,0.14);
    border: none; padding: 0;
    color: #fff;
    cursor: pointer;
    transition: transform 0.12s ease, background-color 0.12s ease;
  }
  .mp-icon-btn:active:not(:disabled) { transform: scale(0.86); background: rgba(255,255,255,0.28); }
  .mp-icon-btn:disabled { opacity: 0.4; }
  .mp-icon-btn .icon { width: 16px; height: 16px; }
  /* Recording state — same red-pulse language as the mobile quick-bar's own Record
     button (.qb-recording), so a viewer sees the same "this is live" signal wherever
     they happen to be looking, canvas or bottom bar. */
  .mp-record-btn.mp-recording { background: var(--danger); animation: qbRecordPulse 1.4s ease-in-out infinite; }
}

/* ─── Desktop hover transport bar — same #mobilePlayerBar element/JS, just revealed on
   canvas hover/focus instead of being permanently on-screen. Sits hidden (display:none,
   matching the base rule above) until a pointer is actually over the video, then fades
   in; hideEmptyState()/showEmptyState() still control whether it's eligible to show at
   all via the element's own inline display style (see main.js). ─── */
@media (min-width: 769px) {
  .canvas-wrapper:hover .mobile-player-bar,
  .canvas-wrapper:focus-within .mobile-player-bar {
    display: flex;
  }
  .mobile-player-bar {
    align-items: center;
    gap: 10px;
    position: absolute;
    left: 16px; right: 16px; bottom: 16px;
    padding: 8px 16px 8px 8px;
    background: rgba(0,0,0,0.6);
    backdrop-filter: blur(10px);
    border-radius: 999px;
    box-shadow: 0 6px 20px rgba(0,0,0,0.25);
    z-index: 8;
    opacity: 0;
    transform: translateY(4px);
    animation: fadeInTransport 0.18s ease forwards;
  }
  @keyframes fadeInTransport { to { opacity: 1; transform: translateY(0); } }
  .mp-play-btn {
    flex-shrink: 0;
    width: 30px; height: 30px;
    border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    background: rgba(255,255,255,0.14);
    border: none; padding: 0;
    color: #fff;
    cursor: pointer;
    transition: background 0.12s ease, transform 0.12s ease;
  }
  .mp-play-btn:hover:not(:disabled) { background: rgba(255,255,255,0.24); }
  .mp-play-btn:active:not(:disabled) { transform: scale(0.86); }
  .mp-play-btn:disabled { opacity: 0.4; }
  .mp-play-btn .icon { width: 16px; height: 16px; transition: transform 0.16s cubic-bezier(0.34, 1.56, 0.64, 1); }
  .mp-play-btn .icon.icon-pop { transform: scale(1.25); }
  .mp-scrub {
    --progress: 0%;
    flex: 1;
    -webkit-appearance: none;
    appearance: none;
    height: 4px;
    border-radius: 2px;
    background: linear-gradient(to right, #fff var(--progress), rgba(255,255,255,0.28) var(--progress));
    cursor: pointer;
  }
  .mp-scrub::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 13px; height: 13px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 4px rgba(0,0,0,0.4);
    transition: transform 0.1s ease;
  }
  .mp-scrub:hover::-webkit-slider-thumb { transform: scale(1.2); }
  .mp-scrub::-moz-range-thumb {
    width: 13px; height: 13px;
    border: none;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 4px rgba(0,0,0,0.4);
  }
  .mp-time {
    flex-shrink: 0;
    color: rgba(255,255,255,0.9);
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.7rem;
    white-space: nowrap;
  }
  .mp-icon-btn {
    flex-shrink: 0;
    width: 30px; height: 30px;
    border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    background: rgba(255,255,255,0.14);
    border: none; padding: 0;
    color: #fff;
    cursor: pointer;
    transition: background 0.12s ease, transform 0.12s ease;
  }
  .mp-icon-btn:hover:not(:disabled) { background: rgba(255,255,255,0.24); }
  .mp-icon-btn:active:not(:disabled) { transform: scale(0.86); }
  .mp-icon-btn:disabled { opacity: 0.4; }
  .mp-icon-btn .icon { width: 15px; height: 15px; }
  .mp-record-btn.mp-recording { background: var(--danger); animation: qbRecordPulse 1.4s ease-in-out infinite; }
}

/* ─── Mobile persistent quick-style bar (Mode / Style / Line Width / Fill / Color) —
   hidden on desktop, where the sidebar's own Tracking Configuration + General Settings
   cover this. Modeled on a TikTok/Instagram capture-screen tray rather than a boxed
   settings panel: no single bounding card, just two free-floating rows of pill controls
   directly over the video so the feed itself stays the focus. Row 1 is Mode — a
   horizontally scrolling strip of icon pills (the same visual language as TikTok's
   effect tray: circular icon, label below, active one filled solid white). Row 2 is a
   slimmer tool row for Style/Line Width/Fill/Color, the things tweaked less often than
   Mode but still worth reaching without opening the sheet. ─── */
.mobile-quick-style-bar { display: none; }
/* Union with the same landscape-phone condition `.mobile-mode-tabs` already gets special-
   cased for further down (max-width:900px + max-height:500px + orientation:landscape) —
   without it, this whole bar (Mode/Style/Photo Booth, all of it) silently vanished the
   moment a phone rotated sideways, since a landscape phone's width commonly clears 768px
   (844px on an iPhone 14/15, 926px on the Pro Max) even though it's exactly the same touch
   device the portrait rules were written for. The follow-up landscape-only block further
   down trims pill sizing to fit the far shorter height this orientation leaves. */
@media (max-width: 768px), (max-width: 900px) and (max-height: 500px) and (orientation: landscape) {
  /* Two rows, ~90px total. This was four stacked rows and ~217px — over 40% of the
     preview box on a phone — because Row 2's four controls, Row 2b's two effect pills,
     and Row 2b's full-width texture select each took a line of their own. Both tool rows
     are now one sideways-scrolling strip (see .mq-tool-row below) and every pill lost its
     stacked icon-over-label layout, which is where the rest of the height came from. */
  .mobile-quick-style-bar {
    display: flex;
    flex-direction: column;
    gap: 6px;
    position: absolute;
    left: 0; right: 0; top: 0;
    padding-top: 6px;
    z-index: 8;
    pointer-events: none;
  }
  /* Soft top scrim (the same trick TikTok/Instagram's own camera chrome uses) so the
     pills read clearly over any footage — bright sky, white walls, anything — without
     leaning entirely on each pill's own glass background for contrast. Sits behind
     everything in the bar via negative z-index within the bar's own stacking context,
     never intercepts taps (pointer-events: none), and fades out well past row 2 so it
     blends into the video instead of ending on a hard edge. */
  .mobile-quick-style-bar::before {
    content: "";
    position: absolute;
    inset: 0 0 -32px 0;
    background: linear-gradient(to bottom, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0.24) 60%, transparent 100%);
    z-index: -1;
    pointer-events: none;
  }
  .mobile-quick-style-bar > * { pointer-events: auto; }

  /* ── Row 1: Mode — horizontally scrollable icon-pill strip ── */
  .mq-mode-row {
    display: flex;
    gap: 8px;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x proximity;
    scrollbar-width: none;
    padding: 3px 10px 2px;
    mask-image: linear-gradient(to right, transparent 0, black 16px, black calc(100% - 16px), transparent 100%);
    -webkit-mask-image: linear-gradient(to right, transparent 0, black 16px, black calc(100% - 16px), transparent 100%);
  }
  .mq-mode-row::-webkit-scrollbar { display: none; }
  /* Icon beside label rather than stacked above it — the same shape the landscape block
     further down already used, worth ~18px per row in portrait too. The labels stay
     (unlike landscape, which drops them): nine unlabelled glyphs is a guessing game for
     a first-time visitor, and this row scrolls sideways anyway, so the extra width the
     labels cost is width this row already has to spare. */
  .mq-mode-row .mode-tab.mq-mode-pill {
    scroll-snap-align: center;
    flex: 0 0 auto;
    flex-direction: row;
    gap: 6px;
    min-width: 0;
    min-height: 36px;
    padding: 0 13px;
    background: rgba(255,255,255,0.1);
    backdrop-filter: blur(10px) saturate(1.3);
    -webkit-backdrop-filter: blur(10px) saturate(1.3);
    border: 1px solid rgba(255,255,255,0.18);
    border-radius: 999px;
    color: rgba(255,255,255,0.92);
    text-transform: none;
    letter-spacing: 0;
    font-size: 0.68rem;
    font-weight: 600;
    white-space: nowrap;
    box-shadow: 0 2px 8px rgba(0,0,0,0.22), inset 0 1px 0 rgba(255,255,255,0.1);
    transition: background-color 0.18s ease, color 0.18s ease, border-color 0.18s ease,
                transform 0.24s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.18s ease;
  }
  .mq-mode-row .mode-tab.mq-mode-pill .icon { width: 18px; height: 18px; transition: transform 0.24s cubic-bezier(0.34, 1.56, 0.64, 1); }
  .mq-mode-row .mode-tab.mq-mode-pill:active { transform: scale(0.92); }
  .mq-mode-row .mode-tab.mq-mode-pill.active {
    background: #fff;
    color: #0a0a0a;
    border-color: #fff;
    font-weight: 800;
    transform: translateY(-1px) scale(1.05);
    box-shadow: 0 4px 16px rgba(0,0,0,0.4);
  }
  .mq-mode-row .mode-tab.mq-mode-pill.active .icon { transform: scale(1.08); }
  .mq-mode-row .mode-tab.mq-mode-pill.active:active { transform: translateY(-1px) scale(0.97); }

  /* ── Row 2: Style / Line Width / Fill / Color / Ghost Trails / Step Print / Texture ──
     One sideways-scrolling strip carrying what used to be two stacked rows. Scrolls the
     same way (and with the same edge mask) as the mode row above it, so the gesture is
     already established by the time anyone reaches this row. Children are `flex: 0 0 auto`
     — in an overflowing scroller, growing/shrinking to share a line is exactly the
     behavior that made the texture select wrap to its own row in the first place. */
  .mq-tool-row {
    display: flex;
    align-items: center;
    gap: 7px;
    padding: 0 10px;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    mask-image: linear-gradient(to right, transparent 0, black 16px, black calc(100% - 16px), transparent 100%);
    -webkit-mask-image: linear-gradient(to right, transparent 0, black 16px, black calc(100% - 16px), transparent 100%);
  }
  .mq-tool-row::-webkit-scrollbar { display: none; }
  .mq-tool-row > * { flex: 0 0 auto; }
  /* Splits the row into its three loose groups (pick a look → toggle an effect → tune
     shape rendering) — previously eight-plus controls in a row with no visual grouping at
     all, which read as one undifferentiated strip rather than related clusters. Purely a
     divider: zero width impact worth mentioning, `aria-hidden` since it carries no
     information a screen reader needs. */
  .mq-divider { width: 1px; align-self: stretch; margin: 6px 1px; background: rgba(255,255,255,0.16); }
  .mq-select.mq-style-select,
  .mq-select.mq-fill-style-select,
  .mq-select.mq-aspect-select,
  .mq-linewidth-wrap,
  .mq-effect-pill,
  .mq-pill {
    box-shadow: 0 2px 8px rgba(0,0,0,0.22), inset 0 1px 0 rgba(255,255,255,0.1);
    transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease,
                transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.18s ease;
  }
  /* Same equal-specificity cascade-order issue as .mq-effect-pill[hidden] above — needs
     its own explicit rule to actually win over the unconditional width/display further
     down, since hidden-by-default is how syncMqAspectSelect() (main.js) keeps this out
     of the way for every mode except Squash, the only one Output Aspect applies to. */
  .mq-select.mq-aspect-select[hidden] { display: none; }
  /* Squash mode runs no detection, so the tracking-only controls on this row (Style, Line
     Width, Fill Shapes, Color, Fill Texture) would do nothing — Squash's own controls take
     their place (see syncMqSquashControls() in main.js). !important because several of
     these carry their own later, equal-or-higher-specificity display rules. */
  .mobile-quick-style-bar.mq-squash-active .mq-track-only { display: none !important; }
  /* Squash's own tap-to-cycle pills: deliberately smaller than the effect pills next to
     them — they sit on top of the footage, show a short value, and get tapped repeatedly,
     so they read better as a compact row than as five more full-size buttons. */
  .mq-mini-pill {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    flex: 0 0 auto;
    height: 30px;
    min-height: 30px;
    padding: 0 10px;
    border: 1px solid rgba(255, 255, 255, 0.16);
    border-radius: 999px;
    background: rgba(12, 18, 32, 0.66);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    color: #fff;
    font-family: inherit;
    font-size: 0.68rem;
    font-weight: 650;
    letter-spacing: 0.01em;
    white-space: nowrap;
    cursor: pointer;
    transition: background-color var(--transition), border-color var(--transition), transform 0.12s ease;
  }
  .mq-mini-pill:active { transform: scale(0.94); }
  .mq-mini-pill[hidden] { display: none; }
  .mq-mini-icon { width: 13px; height: 13px; opacity: 0.75; flex-shrink: 0; }
  .mq-mini-label { font-variant-numeric: tabular-nums; }
  /* Anything but the default reads as "this is doing something" without a second glance. */
  .mq-mini-pill[data-value]:not([data-value="original"]):not([data-value="center"]):not([data-value="stretch"]) {
    border-color: color-mix(in srgb, var(--accent) 60%, transparent);
    background: color-mix(in srgb, var(--accent) 24%, rgba(12, 18, 32, 0.66));
  }
  .mq-select.mq-style-select,
  .mq-select.mq-fill-style-select,
  .mq-select.mq-aspect-select {
    /* Fixed widths rather than flex-sizing: the row scrolls now, so there's no line to
       share and no truncation that varies by screen width — the thing the old
       `flex-basis:100%` wrap was working around. 160px (was 140px, confirmed via a real
       screenshot truncating to "Boundin...") is sized for the DEFAULT option in each
       select ("Bounding Box", "Solid Fill", "Original") to actually fit — Visual Style's
       longer option names ("Interlinked (Starburst)", 23 characters) still ellipsize
       past this width, which is accepted rather than chased: fitting the longest of ~30
       style names would need a pill wide enough to dominate the whole scrollable row for
       a case that only affects a handful of the more exotic styles. */
    width: 160px;
    min-height: 36px;
    height: 36px;
    background: rgba(255,255,255,0.1);
    backdrop-filter: blur(10px) saturate(1.3);
    -webkit-backdrop-filter: blur(10px) saturate(1.3);
    color: #fff;
    border: 1px solid rgba(255,255,255,0.18);
    border-radius: 999px;
    padding: 0 14px;
    /* Same 16px-minimum rule as .dropdown/.text-input above — this select was at
       0.78rem (12.5px), well under the threshold that stops iOS Safari zooming the
       page on focus. */
    font-size: 16px;
    font-weight: 600;
    line-height: 1.2;
    text-overflow: ellipsis;
    white-space: nowrap;
    overflow: hidden;
  }
  .mq-select option { color: #000; }
  /* Ghost Trails / Step Printing quick toggles, sized to their label rather than the
     fixed circle Fill/Color use — neither has a universally-recognizable glyph the way
     fill/color do, so the text has to stay. */
  .mq-effect-pill {
    display: flex;
    align-items: center;
    gap: 5px;
    /* 36px, matching every other control in this row. Below the usual 44px touch floor,
       which is a deliberate trade here and only here: these sit ON TOP of the video, so
       every px of pill is a px of the user's footage hidden behind chrome, and they're
       wide targets (a labelled pill, not an icon) with generous horizontal spacing —
       the dimension a thumb actually misses on. The bottom nav, the sidebar toggles and
       the undo toast all still hold the full 44px, since none of them cover content. */
    min-height: 36px;
    height: 36px;
    padding: 0 13px;
    background: rgba(255,255,255,0.1);
    backdrop-filter: blur(10px) saturate(1.3);
    -webkit-backdrop-filter: blur(10px) saturate(1.3);
    color: rgba(255,255,255,0.85);
    border: 1px solid rgba(255,255,255,0.18);
    border-radius: 999px;
    cursor: pointer;
    font-size: 0.72rem;
    font-weight: 600;
    white-space: nowrap;
  }
  /* Higher specificity than the plain `display:flex` above is needed to actually win —
     [hidden] and .mq-effect-pill are both single-selector rules of equal specificity, so
     without this the later, unconditional `display:flex` rule would keep rendering
     mqConnectToggle even while its `hidden` attribute is set (see syncMqConnectToggle()
     in main.js, which hides it for None/Squash). */
  .mq-effect-pill[hidden] { display: none; }
  .mq-effect-pill:active { transform: scale(0.92); }
  .mq-effect-pill[aria-pressed="true"] {
    background: #fff;
    color: #0a0a0a;
    border-color: #fff;
    box-shadow: 0 2px 10px rgba(0,0,0,0.3);
  }
  /* The three levels have to be tellable apart at a glance on top of moving footage, so
     they escalate in weight rather than being three shades of the same white: Subtle is a
     translucent chip, Light the solid white above, Heavy the accent. Must stay after the
     [aria-pressed="true"] rule above: equal specificity, so source order decides. */
  /* Faint (2026-09-23): below Subtle — just an outline and the level badge. */
  .mq-effect-pill[data-level="faint"] {
    background: rgba(255,255,255,0.12);
    border-color: rgba(255,255,255,0.5);
    color: #fff;
    box-shadow: none;
  }
  .mq-effect-pill[data-level="subtle"] {
    background: rgba(255,255,255,0.28);
    border-color: rgba(255,255,255,0.65);
    color: #fff;
    box-shadow: none;
  }
  /* Medium sits between Light (solid white) and Heavy (full accent): white tinted toward
     the accent, with an accent edge. */
  .mq-effect-pill[data-level="medium"] {
    background: color-mix(in srgb, var(--accent) 38%, #ffffff);
    border-color: var(--accent);
    color: #06101f;
  }
  .mq-effect-pill[data-level="heavy"] {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--on-accent);
  }
  /* The level badge, not the effect name — deliberately smaller and quieter, so the pill
     still reads as "Ghost Trails" first and "which amount" second. */
  .mq-level {
    font-size: 0.64rem;
    font-weight: 800;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    opacity: 0.82;
  }
  .mq-effect-pill .mq-tool-icon { color: inherit; }
  .mq-linewidth-wrap {
    width: 104px;
    display: flex;
    align-items: center;
    gap: 7px;
    min-height: 36px;
    height: 36px;
    background: rgba(255,255,255,0.1);
    backdrop-filter: blur(10px) saturate(1.3);
    -webkit-backdrop-filter: blur(10px) saturate(1.3);
    border: 1px solid rgba(255,255,255,0.18);
    border-radius: 999px;
    padding: 0 13px;
  }
  .mq-tool-icon { width: 15px; height: 15px; flex-shrink: 0; color: rgba(255,255,255,0.7); }
  .mq-linewidth {
    flex: 1 1 auto;
    min-width: 0;
    min-height: 24px;
    -webkit-appearance: none;
    appearance: none;
    height: 3px;
    background: rgba(255,255,255,0.3);
    border-radius: 2px;
  }
  .mq-linewidth::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 18px; height: 18px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 4px rgba(0,0,0,0.5);
    transition: transform 0.15s cubic-bezier(0.34, 1.56, 0.64, 1);
  }
  .mq-linewidth:active::-webkit-slider-thumb { transform: scale(1.15); }
  .mq-linewidth::-moz-range-thumb {
    width: 18px; height: 18px;
    border: none;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 4px rgba(0,0,0,0.5);
  }
  /* Fill Shapes has no other quick-access anywhere — normally 2 tabs deep in the sheet
     (Style > General Settings) despite being one of the most common things to flip while
     experimenting. */
  .mq-pill {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    background: rgba(255,255,255,0.1);
    backdrop-filter: blur(10px) saturate(1.3);
    -webkit-backdrop-filter: blur(10px) saturate(1.3);
    color: rgba(255,255,255,0.8);
    border: 1px solid rgba(255,255,255,0.18);
    border-radius: 50%;
    cursor: pointer;
  }
  .mq-pill:active { transform: scale(0.88); }
  .mq-fill-toggle .mq-tool-icon { width: 16px; height: 16px; color: inherit; }
  .mq-fill-toggle[aria-pressed="true"] {
    background: #fff;
    color: #0a0a0a;
    border-color: #fff;
    box-shadow: 0 2px 10px rgba(0,0,0,0.3);
  }
  /* A thin conic-gradient ring (the same visual shorthand Instagram uses for "there's
     something to open here") around the swatch, since this is the one control in the
     bar that's inherently about color — a plain border didn't hint at that the way the
     rest of the bar's monochrome pills correctly do for everything else. */
  .mq-color-wrap {
    flex: 0 0 auto;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    padding: 2.5px;
    display: block;
    background: conic-gradient(from 180deg, #f9ce34, #ee2a7b, #6228d7, #00c8ff, #f9ce34);
    box-shadow: 0 2px 8px rgba(0,0,0,0.28);
    transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
  }
  .mq-color-wrap:active { transform: scale(0.88); }
  /* Same equal-specificity cascade-order trap as .mq-effect-pill[hidden]/.control-group[hidden]
     elsewhere in this file — .mq-color-wrap's own unconditional `display: block` above is an
     author rule and wins a tie against the `hidden` attribute's UA-stylesheet `display: none`
     regardless of source order, so #mqConnectColorWrap's `hidden` (see syncMqConnectToggle() in
     main.js, which hides it alongside #mqConnectToggle on modes with nothing to connect) would
     silently do nothing without this. */
  .mq-color-wrap[hidden] { display: none; }
  .mq-color {
    width: 100%;
    height: 100%;
    padding: 0;
    border: 2px solid rgba(10,10,10,0.55);
    border-radius: 50%;
    background: none;
  }
  .mq-color::-webkit-color-swatch-wrapper { padding: 0; border-radius: 50%; overflow: hidden; }
  .mq-color::-webkit-color-swatch { border: none; border-radius: 50%; }
  .mq-color::-moz-color-swatch { border: none; border-radius: 50%; }

  /* ── Row 3: Photo Booth — hidden by default, shown only for a live webcam (see the
     isWebcam-driven show/hide in main.js). Same icon-pill visual language as the Mode
     row above (same min-size, same active-state pop), just a different accent color on
     the active pill — pink/violet rather than plain white — so a glance is enough to
     tell "you're browsing filters" apart from "you're picking a tracking mode", even
     though both rows sit right on top of each other. ── */
  .mq-photobooth-row { display: none; }
  .mq-mode-row.mq-photobooth-row {
    display: none; /* JS flips this to flex only while isWebcam is true */
  }
  /* Same icon-beside-label, 36px shape as the mode pills above — this row only appears in
     webcam mode, but when it does it's a *third* row of chrome over the live picture,
     which is the worst place to spend the height. */
  .mq-pb-pill {
    scroll-snap-align: center;
    flex: 0 0 auto;
    display: inline-flex;
    flex-direction: row;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-width: 0;
    min-height: 36px;
    padding: 0 13px;
    background: rgba(255,255,255,0.1);
    backdrop-filter: blur(10px) saturate(1.3);
    -webkit-backdrop-filter: blur(10px) saturate(1.3);
    border: 1px solid rgba(255,255,255,0.18);
    border-radius: 999px;
    color: rgba(255,255,255,0.92);
    font-family: inherit;
    font-size: 0.68rem;
    font-weight: 600;
    white-space: nowrap;
    cursor: pointer;
    box-shadow: 0 2px 8px rgba(0,0,0,0.22), inset 0 1px 0 rgba(255,255,255,0.1);
    transition: background-color 0.18s ease, color 0.18s ease, border-color 0.18s ease,
                transform 0.24s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.18s ease;
  }
  .mq-pb-pill .icon { width: 18px; height: 18px; transition: transform 0.24s cubic-bezier(0.34, 1.56, 0.64, 1); }
  .mq-pb-pill:active { transform: scale(0.92); }
  .mq-pb-pill.active {
    background: linear-gradient(135deg, #ee2a7b, #6228d7);
    color: #fff;
    border-color: transparent;
    font-weight: 800;
    transform: translateY(-1px) scale(1.05);
    box-shadow: 0 4px 16px rgba(150,20,120,0.45);
  }
  .mq-pb-pill.active .icon { transform: scale(1.08); }
  .mq-pb-pill.active:active { transform: translateY(-1px) scale(0.97); }
}

/* ─── Empty State (no video/webcam/photo loaded yet) ─── */
.empty-state {
  position: absolute;
  inset: 0;
  background: var(--bg-primary);
  display: flex;
  flex-direction: column;
  /* `safe center` (not plain `center`): on a short mobile viewport this content is
     TALLER than the box, and plain `center` splits that overflow evenly above AND below
     — confirmed via a real Playwright measurement that the headline rendered ~87px above
     the visible top with `scrollTop` already pinned at its minimum (0), i.e. permanently
     unreachable no matter how the user scrolled, not just initially off-screen. `safe`
     falls back to start-alignment specifically when content overflows, so the excess
     always lands at the bottom (reachable via the `overflow-y:auto` below) instead of
     split, and has zero effect at all on a viewport tall enough to fit everything, where
     this still centers exactly as before. */
  justify-content: safe center;
  align-items: center;
  text-align: center;
  padding: 24px;
  gap: 4px;
  z-index: 5;
  min-width: 320px;
  min-height: 240px;
  overflow-y: auto;
  transition: background-color 0.15s ease, outline-color 0.15s ease, opacity 0.25s ease;
}
/* Scroll-for-more affordance, see the comment above this element's markup in index.html.
   Absolutely positioned over .canvas-wrapper at a fixed bottom-right corner (independent
   of #emptyState's own internal scroll position) rather than in-flow/sticky inside that
   scrolling content — a sticky version was tried first and, confirmed via screenshot,
   ended up visually sitting on top of the bottom mode card's icon instead of reading as
   its own separate hint. pointer-events:none so it can never block a real tap on whatever
   card/link happens to be underneath it. Static (no bounce) by default so
   prefers-reduced-motion users still get the affordance itself, just not the motion. */
.empty-state-scroll-hint {
  position: absolute;
  right: 14px;
  bottom: 14px;
  z-index: 6;
  display: none;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
}
.empty-state-scroll-hint svg { width: 16px; height: 16px; }
.empty-state-scroll-hint.has-overflow { display: flex; opacity: 0.9; }
.empty-state-scroll-hint.faded { opacity: 0; }
@media (prefers-reduced-motion: no-preference) {
  .empty-state-scroll-hint.has-overflow { animation: emptyStateScrollHintBob 1.6s ease-in-out infinite; }
}
@keyframes emptyStateScrollHintBob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(4px); } }
/* Same affordance, for the mobile settings sheet — see #settingsScrollHint's own markup
   comment in index.html. Declared globally (not inside the mobile-only media query the
   sheet's own peek/expand rules live in) specifically so `display:none` is this element's
   real, unconditional default — the JS that adds `.has-overflow` already only ever runs
   while `.sidebar.open` is set, which desktop's permanent side-panel sidebar never gets
   (only the mobile hamburger toggles it), so this naturally never shows on desktop without
   needing its own width check too; the first version of this rule lived only inside the
   mobile media query, which meant desktop had NO rule for it at all and silently fell back
   to the browser's plain `display:block` default for a bare <div> — confirmed via
   getComputedStyle before this fix, not assumed. Fixed to the viewport (not sticky/
   absolute inside .sidebar's own scrolling content, the same overlap mistake
   .empty-state-scroll-hint's first version made) at a corner matching where the sheet's
   bottom edge always sits regardless of its internal scroll offset. */
.settings-scroll-hint {
  position: fixed;
  right: 10px;
  bottom: calc(var(--bottom-nav-h, 60px) + 10px);
  z-index: 95;
  display: none;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
}
.settings-scroll-hint svg { width: 15px; height: 15px; }
.settings-scroll-hint.has-overflow { display: flex; opacity: 0.9; }
.settings-scroll-hint.faded { opacity: 0; }
@media (prefers-reduced-motion: no-preference) {
  .settings-scroll-hint.has-overflow { animation: emptyStateScrollHintBob 1.6s ease-in-out infinite; }
}
/* Was just a barely-there background-color shift (var(--bg-secondary), one shade off
   var(--bg-primary) — the same low-contrast gap the mobile Use Webcam button had) — a
   drag actually in progress got almost no acknowledgment at all, nothing like the
   dashed-border-plus-overlay convention every other drag-and-drop target on the web
   already uses, which is what actually reads as "yes, drop it here" rather than a
   maybe-it-changed shift someone has to look for. outline (not border) so the accent
   ring doesn't shift any content by changing the box model, inset so it reads as
   "this whole card is the target" rather than hugging just the outer edge. */
.empty-state.drag-active {
  background: var(--accent-soft);
  outline: 2px dashed var(--accent);
  outline-offset: -14px;
}
/* Covers (rather than trying to dim) the content underneath — the empty state's own
   entrance animation (see further down) sets opacity via @keyframes with fill-mode:both,
   which keeps outranking a plain opacity rule on the same elements for as long as it
   still technically applies, so a same-property "just fade the children out" rule here
   could silently never take visible effect depending on animation state. A solid-enough
   overlay reads clearly either way, regardless of what's happening underneath it. */
.empty-state.drag-active::after {
  content: "Drop to Upload";
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--bg-primary) 88%, transparent);
  font-family: var(--font-display);
  font-size: clamp(1.3rem, 3.5vw, 1.9rem);
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--accent);
  pointer-events: none;
}
/* loadVideoFile()/loadPhotoFile() have always accepted a drop mid-session too (see the
   comment above canvasWrapperEl in main.js — "works even before the state is empty")
   but the drag feedback above only ever lived on #emptyState, which is display:none the
   moment a source is loaded — dragging a replacement video over a playing preview gave
   no visual sign a drop would do anything. Scoped to :not(.is-empty) so this never
   double-draws with the block above while the empty-state prompt is still showing. */
.canvas-wrapper:not(.is-empty).drag-active { outline: 2px dashed var(--accent); outline-offset: -14px; }
.canvas-wrapper:not(.is-empty).drag-active::after {
  content: "Drop to Replace Video";
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--bg-primary) 70%, transparent);
  font-family: var(--font-display);
  font-size: clamp(1.1rem, 3vw, 1.6rem);
  font-weight: 800;
  letter-spacing: -0.01em;
  color: var(--accent);
  pointer-events: none;
  z-index: 9;
}
/* Bold display serif, matching the header wordmark — this is still the functional
   "drop a file here" prompt, just given the same typographic weight as the rest of the
   app's editorial, high-contrast identity instead of reading as a plain system label. */
.empty-state-title { font-family: var(--font-display); font-size: clamp(1.7rem, 4vw, 2.5rem); font-weight: 900; letter-spacing: -0.02em; line-height: 1.05; margin-bottom: 6px; }
/* One phrase in the accent color — the actual differentiator (everything else on this
   headline is table-stakes for any tracking tool; running entirely in the visitor's own
   browser, nothing uploaded, is the specific claim worth a visitor's eye landing on
   first). Color, not a gradient or a weight bump on top of the already-900 heading —
   consistent with the "one confident accent, not a effects-laden template" language the
   rest of the page's flat/bordered surfaces already use (see --accent's own comment). */
.hero-title-accent { color: var(--accent); }
.empty-state-sub { font-size: 0.82rem; color: var(--text-secondary); max-width: 560px; margin-left: auto; margin-right: auto; line-height: 1.5; }
/* Drag-and-drop (and Ctrl/Cmd+V paste, which needs a keyboard) are both dead interactions
   on a touch-primary device — no drag/dragover/drop event ever fires from a finger, and
   there's no keyboard shortcut to invoke. Advertising them anyway wasted a line of this
   already-cramped empty-state screen on a touch phone/tablet for an impossible action;
   `pointer: coarse` (true for the device's primary input, unlike a width-based check —
   a narrow desktop browser window stays `pointer: fine` and correctly keeps this hint,
   since drag/paste both still work fine there). */
@media (pointer: coarse) {
  .drag-paste-hint { display: none; }
}
.empty-state-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; margin: 16px 0; }
.empty-state-sample-btn[disabled] { opacity: 0.7; cursor: progress; }
/* Hero stat row — a quick-scan proof strip (bold number + label, thin divider between
   each) under the primary CTAs, the pattern most SaaS product pages lead with before any
   feature explanation. Numbers are real counts (tracking modes, visual styles), not
   filler, and stay accurate as long as they're updated alongside those lists. */
.hero-stats {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0;
  margin: 22px 0 4px;
}
.hero-stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 0 20px;
  border-left: 1px solid var(--border);
}
.hero-stat:first-child { border-left: none; }
.hero-stat-num { font-family: var(--font-display); font-size: 1.5rem; font-weight: 700; line-height: 1; color: var(--text-primary); }
.hero-stat-label { font-size: 0.66rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-secondary); }
@media (max-width: 480px) {
  /* Tight enough at this width that the default 20px side padding would force a wrap —
     a wrapped row's leading item would then pick up a stray left divider from the
     :first-child rule above, which only accounts for a single row. Shrinking padding
     keeps all 4 stats on one line instead. */
  .hero-stat { padding: 0 10px; }
  .hero-stat-num { font-size: 1.25rem; }
  .hero-stat-label { font-size: 0.6rem; }
}
.empty-state-link { text-decoration: underline; cursor: pointer; }
.empty-state-features { margin-top: 26px; padding-top: 22px; border-top: 1px solid var(--border); max-width: 720px; width: 100%; }
.empty-state-features-title { font-size: 0.76rem; color: var(--text-secondary); margin-bottom: 14px; }

/* This is the very first thing most visitors see, and it used to just appear fully
   formed the instant initSystem() flips its display to flex — a plain CSS animation
   (not JS-driven) is enough since that display:none-to-flex flip is exactly what
   (re)starts it, same mechanism .loading-overlay's own fade-in already relies on.
   Staggered by direct child instead of all at once — title, then copy, then the actions
   people actually came here to tap, reading top-to-bottom the way eyes already scan the
   page — with the mode cards cascading in individually a beat after their own section
   arrives, instead of popping in as one flat block. */
@media (prefers-reduced-motion: no-preference) {
  @keyframes emptyStateFadeUp { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: translateY(0); } }
  .empty-state > * { animation: emptyStateFadeUp 0.55s cubic-bezier(0.16, 1, 0.3, 1) both; }
  .empty-state > *:nth-child(1) { animation-delay: 0.02s; }
  .empty-state > *:nth-child(2) { animation-delay: 0.08s; }
  .empty-state > *:nth-child(3) { animation-delay: 0.14s; }
  .empty-state > *:nth-child(4) { animation-delay: 0.18s; }
  .empty-state > *:nth-child(5) { animation-delay: 0.22s; }
  .empty-state > *:nth-child(6) { animation-delay: 0.28s; }
  .empty-state > *:nth-child(7) { animation-delay: 0.34s; }
  .mode-card-grid .mode-card { animation: emptyStateFadeUp 0.45s cubic-bezier(0.16, 1, 0.3, 1) both; }
  .mode-card-grid .mode-card:nth-child(1) { animation-delay: 0.36s; }
  .mode-card-grid .mode-card:nth-child(2) { animation-delay: 0.39s; }
  .mode-card-grid .mode-card:nth-child(3) { animation-delay: 0.42s; }
  .mode-card-grid .mode-card:nth-child(4) { animation-delay: 0.45s; }
  .mode-card-grid .mode-card:nth-child(5) { animation-delay: 0.48s; }
  .mode-card-grid .mode-card:nth-child(6) { animation-delay: 0.51s; }
  .mode-card-grid .mode-card:nth-child(n+7) { animation-delay: 0.54s; }
}

/* ─── Mode Cards — clickable, set Tracking Mode ahead of loading a source ─── */
.mode-card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  gap: 8px;
}
.mode-card {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 14px 10px;
  background: var(--bg-primary);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text-primary);
  font-family: inherit;
  cursor: pointer;
  text-align: center;
  transition: background var(--transition), border-color var(--transition), box-shadow var(--transition), transform 0.1s ease;
}
.mode-card:hover { background: var(--bg-secondary); border-color: var(--text-secondary); box-shadow: var(--shadow-xs); }
.mode-card:active { transform: scale(0.98); }
.mode-card .icon { width: 22px; height: 22px; margin: 0 0 2px; }
.mode-card-name { font-size: 0.82rem; font-weight: 700; }
.mode-card-desc { font-size: 0.68rem; color: var(--text-secondary); line-height: 1.3; }
/* Number-key hint (1–9, same order as ALL_MODES) — surfaces the existing keyboard
   shortcut, which previously had no on-screen indication anywhere in the UI. */
.mode-card-key {
  position: absolute;
  top: 5px;
  right: 6px;
  font-size: 0.62rem;
  font-weight: 700;
  color: var(--text-secondary);
  opacity: 0.6;
  line-height: 1;
}
.mode-card.active .mode-card-key { color: var(--accent); opacity: 0.85; }
/* Reflects whichever mode is currently selected (via Tracking Mode elsewhere too, not
   just a click here) so this never silently disagrees with the rest of the app. Was a
   solid accent-green fill — the single most saturated thing on an otherwise white/gray/
   black screen, which is exactly what read as loud/messy rather than "selected." A soft
   tint + accent border reads just as clearly as the active card without the whole tile
   turning into a block of color. */
.mode-card.active { background: var(--accent-soft); border-color: var(--accent); color: var(--text-primary); box-shadow: none; }
.mode-card.active .mode-card-name { color: var(--accent); }
.mode-card.active .mode-card-desc { color: var(--text-secondary); }
/* Full-width separator ahead of None/Squash — those two run no detection at all, which
   is easy to mistake for a bug ("why isn't it tracking anything?") if they just look
   like two more mode cards in the same grid. */
.mode-card-divider {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 4px 0 0;
  font-size: 0.64rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-secondary);
}
.mode-card-divider::before, .mode-card-divider::after { content: ""; flex: 1; height: 1px; background: var(--border); }

/* Was one flat sentence running all 9 links together with no grouping — grouped into
   labeled categories (Alternatives / Effects / Workflow) instead, each its own row, so
   a visitor scanning for "does this cover X" can tell the shape of what's here at a
   glance rather than reading a run-on line of link text. */
.empty-state-guides {
  margin-top: 18px;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px 28px;
  font-size: 0.72rem;
}
.empty-state-guides-group {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 10px;
}
.empty-state-guides-label {
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 0.62rem;
  color: var(--text-secondary);
  opacity: 0.7;
}
.empty-state-guides a { color: var(--text-secondary); text-decoration: underline; text-underline-offset: 2px; }
.empty-state-guides a:hover { color: var(--text-primary); }
@media (max-width: 640px) {
  .empty-state-guides { justify-content: flex-start; text-align: left; }
}

/* ─── Sidebar ─── */
.sidebar {
  grid-column: 2 / 3;
  grid-row: 2 / 3;
  border-left: 1px solid var(--glass-border);
  padding: 14px;
  overflow-y: auto;
  overflow-x: hidden;
  display: flex;
  flex-direction: column;
  gap: 14px;
  background: var(--bg-primary);
  -webkit-overflow-scrolling: touch;
  /* Without this, dragging past the top/bottom of the settings list on mobile "chains"
     the scroll into the page behind it — on the bottom-sheet layout that meant the
     canvas/video area visibly shifted or bounced while just scrolling the sheet. */
  overscroll-behavior-y: contain;
}

.control-section {
  display: flex;
  flex-direction: column;
  gap: 9px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border);
}
.control-section:last-child { border-bottom: none; }

.control-section h3 {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  font-weight: 700;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.control-section h3::after {
  content: '\2212'; /* − */
  font-size: 0.95rem;
  font-weight: 400;
  opacity: 0.6;
}
.control-section.collapsed h3::after { content: '+'; }
/* !important: some sections (Audio Reactivity) toggle a child's display via inline
   style from JS — collapsing the whole section must still win over that. */
.control-section.collapsed > *:not(h3) { display: none !important; }

.control-group { display: flex; flex-direction: column; gap: 5px; }
/* Same equal-specificity cascade-order trap as .mq-effect-pill[hidden] elsewhere in this
   file: the `hidden` attribute's UA-stylesheet `display:none` and this class's own
   `display:flex` are equal specificity (one attribute selector, one class selector), and
   an author rule always wins a tie against a user-agent one regardless of which the
   browser parsed first — so toggling `.hidden = true` on a `.control-group` element (see
   setSubtitlesGenerateProgress()/renderSubtitlesTranscriptEditor() in main.js, confirmed
   broken via a real Playwright screenshot: the progress panel stayed visible after
   `.hidden = true`) silently did nothing without this override. */
.control-group[hidden] { display: none; }
.row-group { flex-direction: row; gap: 8px; }
.label-row { display: flex; justify-content: space-between; align-items: center; }

/* Record/Stop reads as one dynamic control rather than two adjacent buttons (an
   IG/TikTok capture button morphs in place, it doesn't sit next to a second, mostly-
   dimmed button) — whichever one is disabled is hidden outright instead of just faded,
   so the other fills the full row on its own. Stop picks up the same red pulse the
   always-visible mobile quick-bar's own Record button already uses while live, so the
   two read as the same "recording" state no matter which one is on screen. */
.record-cta-row #startRecordBtn,
.record-cta-row #stopRecordBtn {
  flex: 1 1 100%;
  border-radius: 999px;
  min-height: 46px;
  font-weight: 700;
  font-size: 0.92rem;
}
/* Stop only ever shows while actually recording. Start shows the rest of the time,
   including dimmed-but-present before a source is loaded (so the row never goes fully
   blank the way hiding both disabled buttons would) — but hides once Stop is live,
   via :has() rather than mirroring Start's own :disabled state, since Start is *also*
   disabled while recording and a plain :disabled rule can't tell those two "disabled"
   states apart. */
.record-cta-row #stopRecordBtn:disabled { display: none; }
.record-cta-row #startRecordBtn:has(~ #stopRecordBtn:not(:disabled)) { display: none; }
.record-cta-row #stopRecordBtn:not(:disabled) {
  background: var(--danger);
  border-color: var(--danger);
  color: #fff;
  animation: qbRecordPulse 1.4s ease-in-out infinite;
}

label { font-size: 0.82rem; font-weight: 600; }

.value-display {
  font-size: 0.7rem;
  font-family: 'JetBrains Mono', monospace;
  font-weight: 600;
  color: var(--text-secondary);
  background: var(--bg-tertiary);
  padding: 2px 6px;
  border-radius: var(--radius-sm);
}
/* Every slider's readout is tap/click-to-edit (see bindDisplay in main.js) — a thin
   <input type=range> track is genuinely hard to land an exact value on with a finger,
   especially the threshold/sensitivity sliders that get fiddled with constantly while
   tuning tracking live. Dotted underline signals "this is editable" without looking
   like a button. */
.value-display-editable { cursor: pointer; border-bottom: 1px dotted var(--text-secondary); }
.value-display-editable:hover, .value-display-editable:focus-visible { color: var(--text-primary); border-color: var(--text-primary); outline: none; }
.value-edit-input {
  width: 4.5em;
  font: inherit;
  font-family: 'JetBrains Mono', monospace;
  color: var(--text-primary);
  background: var(--bg-primary);
  border: 1px solid var(--accent);
  border-radius: var(--radius-sm);
  padding: 1px 4px;
  -moz-appearance: textfield;
}
.value-edit-input::-webkit-outer-spin-button, .value-edit-input::-webkit-inner-spin-button { margin: 0; }

/* ─── Icons ─── */
.icon {
  width: 14px;
  height: 14px;
  vertical-align: -2px;
  margin-right: 4px;
  flex-shrink: 0;
  display: inline-block;
}
.icon-lg { width: 20px; height: 20px; margin-right: 0; }
/* The sidebar's Play/Pause button (togglePlayBtn) gets popIcon()'s icon-pop class
   toggled the same as every other play/pause control (see popIcon() in main.js), but
   until now had no matching CSS — the shape swap landed as an instant, lifeless cut
   here while the floating transport bar's identical toggle visibly popped. */
.play-pause-btn .icon { transition: transform 0.16s cubic-bezier(0.34, 1.56, 0.64, 1); }
.play-pause-btn .icon.icon-pop { transform: scale(1.3); }
h3 .icon { width: 15px; height: 15px; margin-right: 5px; }
.seo-card h4 .icon { width: 18px; height: 18px; margin-right: 6px; color: var(--accent, #00ffcc); }

/* ─── Buttons ─── */
/* Compact, rounded, one confident accent color for anything primary — the previous
   version (sharp corners, uppercase+tracked labels, black-or-white-only fills) read as
   a zine/brutalist poster more than a piece of software. Sentence case + a real accent
   hue + softened elevation is most of the gap between that and how Premiere/CapCut/
   Figma actually render a button. */
.btn {
  padding: 9px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-primary);
  color: var(--text-primary);
  font-family: inherit;
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  text-decoration: none; /* some buttons are <a> tags (the SEO pages' CTAs) — no link underline */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  text-align: center;
  flex: 1;
  min-height: 38px; /* touch target restored to 44px under the mobile breakpoint below */
  user-select: none;
  -webkit-user-select: none;
  transition: background-color var(--transition), border-color var(--transition), box-shadow var(--transition), transform 0.1s ease;
}
.btn:active:not(:disabled) { transform: scale(0.98); }
/* Same equal-specificity cascade-order trap as .mq-effect-pill[hidden]/.control-group[hidden]
   elsewhere in this file: `.btn`'s own unconditional `display: inline-flex` ties the
   `hidden` attribute's UA-stylesheet `display:none` in specificity, and an author rule
   always wins that tie regardless of source order — so `cancelBtn.hidden = false` (the
   runBatchExport() progress-UI setup in main.js, meant to REVEAL #batchExportCancelBtn
   for the run's duration) never had any visible effect either way: confirmed via
   getComputedStyle that the button rendered `display: flex` even with hidden="true"
   already sitting in the markup at page load, before any batch export had even started. */
.btn[hidden] { display: none; }
.primary-btn { background: var(--accent); border-color: var(--accent); color: var(--on-accent); box-shadow: var(--shadow-xs); }
.primary-btn:hover:not(:disabled) { background: var(--accent-hover); border-color: var(--accent-hover); box-shadow: var(--shadow-sm); }
.secondary-btn { background: var(--bg-secondary); border-color: transparent; }
.pose-region-btn.active { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
#soloGhostPickBtn.active { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.secondary-btn:hover:not(:disabled) { background: var(--bg-tertiary); }

/* Webcam is the instant, one-tap path on desktop, so it gets the filled/primary look
   there — but mobile browsers prompt for camera permission and often hand back a rear
   camera by default, a rockier first tap than picking an existing video, so Upload Video
   takes the primary slot below the mobile breakpoint instead. Same two buttons, the
   filled emphasis just swaps sides. */
.desktop-primary-btn { background: var(--accent); border-color: var(--accent); color: var(--on-accent); box-shadow: var(--shadow-xs); }
.desktop-primary-btn:hover:not(:disabled) { background: var(--accent-hover); border-color: var(--accent-hover); box-shadow: var(--shadow-sm); }
.mobile-primary-btn { background: var(--bg-secondary); border-color: transparent; }
.mobile-primary-btn:hover:not(:disabled) { background: var(--bg-tertiary); }
@media (max-width: 768px) {
  .desktop-primary-btn { background: var(--bg-secondary); border-color: transparent; color: var(--text-primary); box-shadow: none; }
  .desktop-primary-btn:hover:not(:disabled) { background: var(--bg-tertiary); }
  .mobile-primary-btn { background: var(--accent); border-color: var(--accent); color: var(--on-accent); box-shadow: var(--shadow-xs); }
  .mobile-primary-btn:hover:not(:disabled) { background: var(--accent-hover); border-color: var(--accent-hover); }
}
.danger-btn { background: var(--danger); border-color: var(--danger); color: #fff; }
.danger-btn:hover:not(:disabled) { background: var(--danger-hover); border-color: var(--danger-hover); }
.icon-btn { background: var(--bg-secondary); border-color: transparent; flex: 0 0 auto; width: 40px; }
.icon-btn:hover:not(:disabled) { background: var(--bg-tertiary); }
.btn:disabled { opacity: 0.4; cursor: not-allowed; }
.btn:active:not(:disabled) { opacity: 0.7; }

input[type="file"] { display: none; }
.file-upload-label { margin: 0; line-height: 1.2; display: flex; align-items: center; justify-content: center; }

/* ─── Inputs ─── */
.dropdown, .text-input {
  width: 100%;
  padding: 9px 12px;
  background: var(--bg-primary);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text-primary);
  font-family: inherit;
  font-size: 0.82rem;
  min-height: 38px;
  transition: border-color var(--transition), box-shadow var(--transition);
}
.dropdown:focus, .text-input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.text-center { text-align: center; }
/* Generated-captions review list (subtitlesTranscriptList, populated by
   renderSubtitlesTranscriptEditor() in main.js) — one row per cue, a fixed-width
   monospace timestamp so the column of times stays aligned regardless of digit count,
   plus a .text-input for the caption text itself. Capped height + scroll since a full
   video's transcript can run to dozens of cues, and this sits inside an already-tall
   settings sidebar. */
.subtitles-transcript-list { max-height: 260px; overflow-y: auto; margin-top: 8px; display: flex; flex-direction: column; gap: 6px; }
.subtitles-transcript-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
/* Second line of each row: editable Start/End (previously the timestamp button was the
   only way to even see a line's timing, and it wasn't editable at all — fixing a caption
   that's a beat early/late meant re-generating or hand-editing the .srt outside the app).
   Forced onto its own line (flex-basis 100%) and indented past the time column above it
   so it reads as that row's own detail, not a new row. */
.subtitles-transcript-meta { flex: 1 0 100%; display: flex; align-items: center; gap: 6px; padding-left: 52px; }
.subtitles-transcript-meta label { display: flex; align-items: center; gap: 4px; font-size: 0.68rem; color: var(--text-secondary); }
.subtitles-transcript-timing { width: 52px; font-family: 'JetBrains Mono', monospace; font-size: 0.68rem; padding: 2px 4px; min-height: 22px; text-align: center; }
/* Flagged once a line shows more text than is comfortably readable in its own on-screen
   duration (see CPS_WARN_THRESHOLD in main.js) — a real subtitle-quality problem imported
   captions and ASR output both produce often (Whisper especially, whenever a chunk's
   timestamp comes out too short for what it transcribed). */
.subtitles-cps-badge {
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--danger);
  background: color-mix(in srgb, var(--danger) 14%, transparent);
  border-radius: var(--radius-sm);
  padding: 2px 6px;
  margin-left: auto;
}
.subtitles-shift-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.subtitles-shift-row .text-input { flex: 0 0 auto; }
/* Quick-pick caption colors sitting under the full picker — yellow especially, which is
   the classic film/DVD caption color and the one people actually ask for by name. */
.subtitle-swatches { display: flex; gap: 6px; margin-top: 8px; }
.subtitle-swatch {
  width: 24px;
  height: 24px;
  padding: 0;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--sw);
  cursor: pointer;
  transition: transform var(--transition), border-color var(--transition), box-shadow var(--transition);
}
.subtitle-swatch:hover { transform: translateY(-1px); border-color: var(--text-secondary); }
.subtitle-swatch.is-active {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-soft);
}

/* A <button> rather than the <span> this used to be — it seeks the video to the line.
   Deliberately keeps the plain timestamp look (no button chrome) so the list still reads
   as a transcript, with the affordance carried by hover/focus instead. */
.subtitles-transcript-row .subtitles-transcript-time {
  flex: 0 0 auto;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.68rem;
  color: var(--text-secondary);
  width: 44px;
  text-align: right;
  background: none;
  border: none;
  padding: 2px 0;
  cursor: pointer;
  border-radius: var(--radius-sm);
  transition: color var(--transition), background-color var(--transition);
}
.subtitles-transcript-row .subtitles-transcript-time:hover,
.subtitles-transcript-row .subtitles-transcript-time:focus-visible {
  color: var(--accent);
  background: var(--accent-soft);
}
.subtitles-transcript-row .text-input { flex: 1; min-height: 30px; padding: 5px 8px; font-size: 0.78rem; }
/* A <textarea> now (see autoGrowTextarea() in main.js) so a caption can actually hold a
   real line break, not just visually wrap — resize handle off (height tracks content via
   JS, a manual drag-resize would fight that), no internal scrollbar since it's always
   grown tall enough to show everything, and font/line-height explicitly matched to the
   sibling .text-input elsewhere in this app rather than a textarea's browser-default
   monospace-ish rendering. */
.subtitles-transcript-textarea {
  resize: none;
  overflow: hidden;
  line-height: 1.35;
  font-family: inherit;
}
.subtitles-transcript-merge {
  flex: 0 0 auto;
  font-size: 0.66rem;
  font-weight: 600;
  padding: 3px 7px;
  background: none;
  border: 1px solid var(--border);
  color: var(--text-secondary);
  border-radius: var(--radius-sm);
  cursor: pointer;
  /* Not its own margin-left:auto — the Fast badge right before it already has one, and two
     auto margins on adjacent flex items split the free space BETWEEN them instead of
     bunching both at the edge together, which read as a stray gap. Riding along right
     after the badge (or right after End, when there's no badge to push it) is the simpler,
     still-reasonable result. */
  transition: color var(--transition), border-color var(--transition), background-color var(--transition);
}
.subtitles-transcript-merge:hover, .subtitles-transcript-merge:focus-visible {
  color: var(--accent);
  border-color: var(--accent);
  background: var(--accent-soft);
}
.subtitles-transcript-row .subtitles-transcript-del {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  line-height: 1;
  font-size: 0.95rem;
  background: none;
  border: none;
  color: var(--text-secondary);
  cursor: pointer;
  border-radius: var(--radius-sm);
  opacity: 0.55;
  transition: opacity var(--transition), color var(--transition), background-color var(--transition);
}
.subtitles-transcript-row .subtitles-transcript-del:hover,
.subtitles-transcript-row .subtitles-transcript-del:focus-visible {
  opacity: 1;
  color: var(--danger);
  background: color-mix(in srgb, var(--danger) 12%, transparent);
}
/* Whichever line is on screen right now. The accent bar on the left edge is what carries
   it at a glance while scrolling a long transcript. */
.subtitles-transcript-row.is-active .text-input {
  border-color: var(--accent);
  box-shadow: inset 2px 0 0 var(--accent);
}
.subtitles-transcript-row.is-active .subtitles-transcript-time { color: var(--accent); font-weight: 700; }

/* ─── Sliders ─── */
/* Filled progress track (--range-fill set per-slider by initSliderFill() in main.js,
   0-100, kept live on every 'input') — every slider used to be a flat, featureless bar
   with no visual read on where the current value sits relative to its range until you
   spotted the thumb itself; the threshold/sensitivity sliders in particular get dragged
   constantly while live-tuning, so that ambiguity was worth fixing everywhere at once
   rather than one control at a time. */
.slider {
  -webkit-appearance: none;
  width: 100%;
  height: 24px; /* generous invisible drag/tap hit-area — the visible track is the thinner
    ::-webkit-slider-runnable-track below, so this stays a clean thin line to look at while
    still being comfortable to land a finger on, the actual point of a bigger box here. */
  background: transparent;
  outline: none;
  cursor: pointer;
  display: block;
}
.slider::-webkit-slider-runnable-track {
  height: 6px;
  border-radius: var(--radius-full);
  background: linear-gradient(to right, var(--accent) var(--range-fill, 50%), var(--border) var(--range-fill, 50%));
}
.slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 17px; height: 17px; /* bigger for touch */
  background: var(--accent);
  cursor: pointer;
  border: 2px solid var(--bg-primary);
  border-radius: 50%;
  box-shadow: var(--shadow-sm);
  transition: transform 0.1s ease;
  margin-top: -5.5px; /* centers the 17px thumb on the 6px runnable-track */
}
.slider::-webkit-slider-thumb:hover { transform: scale(1.15); }
.slider:active::-webkit-slider-thumb { transform: scale(1.25); }
.slider::-moz-range-track { height: 6px; border-radius: var(--radius-full); background: var(--border); }
.slider::-moz-range-progress { height: 6px; border-radius: var(--radius-full); background: var(--accent); }
.slider::-moz-range-thumb {
  width: 17px; height: 17px;
  background: var(--accent);
  border: 2px solid var(--bg-primary);
  border-radius: 50%;
  cursor: pointer;
  box-shadow: var(--shadow-sm);
}

/* ─── Clip Editor (Trim / Overlay / Adjust sub-tabs) ─── */
.clip-editor-tabs { display: flex; gap: 4px; margin-bottom: 10px; }
.clip-editor-tab {
  flex: 1 1 auto;
  padding: 6px 4px;
  background: var(--bg-secondary);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: 0.68rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-secondary);
  cursor: pointer;
  transition: background-color var(--transition), color var(--transition), border-color var(--transition);
}
.clip-editor-tab:hover { background: var(--bg-tertiary); color: var(--text-primary); }
.clip-editor-tab.active { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
.clip-editor-pane { display: none; }
.clip-editor-pane.active { display: block; }

/* ─── Trim Bar (in/out point editor) ───
   A dedicated custom track rather than an overlay on top of the native `.slider` range
   input — that input's internal track geometry isn't something CSS can reliably target
   (it varies by browser/OS), so positioning a second layer precisely against it would be
   fragile. A standalone div-based bar sizes and hit-tests exactly how this code expects,
   consistently everywhere. */
.trim-bar {
  position: relative;
  height: 28px;
  background: var(--bg-secondary);
  border: 1px solid var(--border);
  margin-top: 4px;
  cursor: pointer;
  user-select: none;
  touch-action: none;
}
/* Filmstrip of real sampled frames sits behind everything else in the bar — actual
   content to judge the trim range against instead of a featureless gray strip. */
.trim-filmstrip {
  position: absolute;
  inset: 0;
  display: flex;
  overflow: hidden;
  pointer-events: none;
}
.trim-filmstrip img {
  flex: 1 1 0%;
  height: 100%;
  width: 0; /* flex-basis drives even width regardless of each thumb's own aspect ratio */
  object-fit: cover;
  display: block;
  opacity: 0.8;
}
.trim-bar-fill {
  position: absolute;
  top: 0; bottom: 0;
  /* Translucent (not the old solid --accent-soft) so the filmstrip thumbnails stay
     visible underneath — a solid fill would otherwise hide the exact frames the
     selected range covers, defeating the point of having them. */
  background: color-mix(in srgb, var(--accent) 30%, transparent);
  border-left: 2px solid var(--accent);
  border-right: 2px solid var(--accent);
  pointer-events: none;
}
.trim-handle {
  position: absolute;
  top: -1px; bottom: -1px;
  width: 12px;
  margin-left: -6px;
  background: var(--accent);
  cursor: ew-resize;
  touch-action: none;
  z-index: 2;
  transition: background-color 0.12s ease, transform 0.1s ease;
}
.trim-handle::after {
  content: '';
  position: absolute;
  top: 50%; left: 50%;
  width: 2px; height: 10px;
  background: rgba(255,255,255,0.7);
  transform: translate(-50%, -50%);
}
.trim-handle:hover, .trim-handle.dragging { background: var(--accent-hover); }
/* A grabbed handle otherwise looks identical to an idle one apart from the color
   change above — this is the same "confirm the touch registered" cue every other
   draggable/tappable control in the app already gives (toggle-controls, buttons'
   :active scale, etc.), just missing here until now. */
.trim-handle.dragging { transform: scaleX(1.3); }
/* Desktop-only compact size — the In/Out fields used to hardcode this same 11px via an
   inline style, which (being inline) couldn't be overridden by the mobile 16px
   iOS-zoom-prevention rule below no matter how the cascade was ordered. Moving it here
   keeps desktop's compact look while letting mobile win on phones. */
.trim-time-input { font-size: 11px; padding: 4px 6px; }
.trim-playhead {
  position: absolute;
  top: 0; bottom: 0;
  width: 2px;
  background: var(--text-primary);
  pointer-events: none;
  z-index: 1;
}

/* ─── Color Picker ─── */
.color-picker {
  -webkit-appearance: none;
  width: 44px; height: 36px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: none;
  cursor: pointer;
  padding: 0;
  overflow: hidden;
}
.color-picker::-webkit-color-swatch-wrapper { padding: 0; }
.color-picker::-webkit-color-swatch { border: none; }

/* ─── Palette ─── */
.palette-container { display: flex; gap: 8px; flex-wrap: wrap; padding: 2px 0; }
.palette-swatch {
  width: 30px; height: 30px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  border: 1px solid var(--border);
  flex-shrink: 0;
}
.palette-swatch:hover, .palette-swatch:active { border: 2px solid var(--accent); }
.palette-swatch.selected {
  border: 2px solid var(--accent);
  box-shadow: 0 0 0 2px var(--accent-soft);
}

/* ─── Toggles ─── */
.toggles-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 7px;
  margin-top: 4px;
}
.toggle-control {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  font-size: 0.78rem;
  font-weight: 600;
  min-height: 32px;
  user-select: none;
  -webkit-user-select: none;
}
.toggle-control input { display: none; }
/* Squared off completely (0 radius, not --radius-sm's 4px) — a deliberately sharper
   corner than buttons/panels get, so the checkbox itself reads as a distinct, precise
   control rather than a smaller button. The checkmark below (a rotated two-sided border,
   not a filled dot) is the same flat/no-gradient technique the rest of the UI already
   commits to — a solid check glyph reads as more "designed" than a plain color swap. */
.control-indicator {
  width: 16px; height: 16px;
  background: var(--bg-primary);
  border: 1px solid var(--border);
  border-radius: 0;
  position: relative;
  flex-shrink: 0;
  transition: background-color var(--transition), border-color var(--transition);
}
.toggle-control:hover .control-indicator { border-color: var(--accent); }
.control-indicator::after {
  content: '';
  position: absolute;
  top: 2px; left: 5px;
  width: 4px; height: 8px;
  border: solid #fff;
  border-width: 0 2px 2px 0;
  transform: rotate(45deg) scale(0);
  transform-origin: center;
  opacity: 0;
  transition: transform 0.12s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.1s ease;
}
.toggle-control input:checked ~ .control-indicator { background: var(--accent); border-color: var(--accent); }
.toggle-control input:checked ~ .control-indicator::after { transform: rotate(45deg) scale(1); opacity: 1; }
/* A one-shot expanding ring the instant a toggle switches ON — the checkmark's own pop-in
   above already sells "this just changed", but nothing previously acknowledged the action
   itself the way a physical switch's own click/give does. Keyed off :checked so it plays
   exactly once per activation (the browser (re)starts a CSS animation whenever the
   selector it's attached to newly matches) and never on page load for an already-checked
   default, since matching on load doesn't count as "newly matching". Off state gets no
   equivalent flourish on purpose — a pulse on *every* toggle in both directions reads as
   restless rather than responsive. */
@media (prefers-reduced-motion: no-preference) {
  .toggle-control input:checked ~ .control-indicator { animation: toggleActivatePulse 0.45s ease-out; }
}
@keyframes toggleActivatePulse {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 45%, transparent); }
  100% { box-shadow: 0 0 0 9px color-mix(in srgb, var(--accent) 0%, transparent); }
}

/* ─── Smart Reveal — dependent settings collapse away instead of sitting there inert ─── */
/* The cap is only a fallback: initControlReveals() sizes the OPEN state to its own content,
   because a fixed number clipped whatever reveal outgrew it (the VHS controls are ~1400px
   since the OSD options landed, and everything past 600px simply vanished). */
.control-reveal {
  overflow: hidden;
  max-height: 3200px;
  opacity: 1;
  transition: max-height 0.35s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.22s ease, margin 0.35s cubic-bezier(0.4, 0, 0.2, 1);
}
.control-reveal.collapsed {
  /* !important so no future per-block ID rule (there is one: #horrorControlsBlock) can
     out-specify the collapsed state and leave invisible-but-space-occupying dead height
     in the panel — exactly the 577px gap that sat under Post FX Amount until 2026-09-30. */
  max-height: 0 !important;
  opacity: 0;
  margin-top: 0 !important;
  margin-bottom: 0 !important;
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
  .control-reveal { transition: none; }
  .control-reveal.collapsed { display: none; }
}

.dynamic-section { display: none; }
.dynamic-section.active { display: flex; }
/* Switching Tracking Mode used to hard-cut straight to the new mode's settings panel —
   a keyframe `animation` (not `transition`) is what actually fades it in, since a
   transition needs an already-rendered starting state to animate from and this element
   goes display:none -> flex in the very same class toggle, same appEnter guard as the
   page's own load-in above. */
@media (prefers-reduced-motion: no-preference) {
  @keyframes dynamicSectionEnter { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
  .dynamic-section.active { animation: dynamicSectionEnter 0.22s ease-out both; }
}

.hint-text { font-size: 0.72rem; color: var(--text-secondary); display: block; }
.hint-text.hint-warning { color: var(--danger); }
/* Several of these run past 500+ characters — on mobile especially, a single toggle's
   documentation could eat most of the visible screen. Clamped to 2 lines by default with
   a tap-to-expand toggle (see main.js); short hint-text never gets the .hint-clamped
   class at all, so this only ever affects the genuinely long ones. */
.hint-text.hint-clamped {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.hint-text.hint-clamped.expanded {
  -webkit-line-clamp: unset;
  overflow: visible;
}
.hint-toggle {
  display: block;
  background: none;
  border: none;
  padding: 3px 0 0;
  margin: 0;
  font-size: 0.68rem;
  font-weight: 700;
  color: var(--accent);
  cursor: pointer;
  text-align: left;
}
.hint-toggle:hover { text-decoration: underline; }
.loaded-file-info {
  font-size: 0.72rem;
  font-family: 'JetBrains Mono', monospace;
  color: var(--text-primary);
  background: var(--bg-secondary);
  border: 1px solid var(--border);
  padding: 6px 8px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.mt-3 { margin-top: 10px; }
.playback-controls { display: flex; flex-direction: row; }
.record-controls { display: flex; flex-direction: row; }

/* ─── Sidebar overlay (mobile) ─── */
.sidebar-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.4);
  z-index: 89;
}
.sidebar-overlay.open { display: block; }

/* ─── Photo analyse overlay result ─── */
#photoResultBar {
  display: none;
  position: absolute;
  bottom: 0; left: 0; right: 0;
  background: rgba(0,0,0,0.8);
  border-top: 2px solid var(--accent);
  color: #fff;
  font-size: 0.78rem;
  font-family: 'JetBrains Mono', monospace;
  padding: 6px 12px;
  z-index: 20;
  text-align: center;
}
/* Plays once, right when this first switches from display:none to visible — restyling a
   style/mode setting afterward just updates the text in place (display stays 'block' the
   whole time; see renderPhotoOverlay()), so this never replays as a distracting flash on
   every single tweak, only the initial "here's what was found" reveal. */
@media (prefers-reduced-motion: no-preference) {
  #photoResultBar { animation: photoResultBarIn 0.3s ease; }
}
@keyframes photoResultBarIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }

/* ─── Tablet ─── */
/* Between the fixed 380px desktop sidebar and the mobile drawer breakpoint, a tablet
   (portrait iPad, small laptop window) was still getting the full 380px sidebar carved
   out of a much narrower viewport, leaving the canvas cramped. Narrow it down instead
   of jumping straight to drawer mode — there's still enough width for a side-by-side layout. */
@media (min-width: 769px) and (max-width: 1100px) {
  :root { --sidebar-w: 300px; }
  .control-section { padding: 14px; }
  .header-nav { display: none; }
}

/* ─── Short desktop viewports ─── */
/* Every size above (--header-h, the hero/empty-state box, mode-card padding) was tuned
   against a tall laptop screen. Common Windows laptop panels run 1366x768 / 1536x864 —
   noticeably shorter than the ~900px+ logical height a MacBook reports — so on those,
   the same fixed 84px header plus a near-700px-tall empty-state hero eats most of the
   viewport and reads as the whole app being oversized/"zoomed in" rather than laid out
   to fit. Trim the same chrome down at reduced viewport height, regardless of width. */
@media (min-width: 769px) and (max-height: 820px) {
  :root { --header-h: 64px; }
  .header-content h1 { font-size: 1.5rem; }
  .header-content p { font-size: 0.72rem; }
  .empty-state-title { font-size: clamp(1.35rem, 3vw, 1.9rem); }
  .empty-state-features { margin-top: 16px; padding-top: 14px; }
  .canvas-wrapper.is-empty { height: min(520px, 78dvh); }
  /* Plain px cap (not min(...,78dvh)) — fitCanvasWrapper()'s own vh<=820 branch in
     main.js is what actually enforces this budget; see the comment on the base
     :not(.is-empty) rule above for why a percentage/dvh value here would double-shrink. */
  .canvas-wrapper:not(.is-empty) { max-height: 520px; }
  .mode-card { padding: 10px 8px; }
}

/* ─── Mobile landscape ─── */
/* A phone rotated to landscape has plenty of width but very little height — the fixed
   header + bottom quick-bar can eat a third of the screen and squeeze the canvas down
   to a sliver. Shrink both chrome elements and drop the quickbar's text labels. */
@media (max-width: 900px) and (max-height: 500px) and (orientation: landscape) {
  /* !important: the general mobile block below also sets --header-h (for plain
     portrait), and since this query can overlap with that one (e.g. a phone at
     667x375 landscape matches both), source order alone would let the later block
     win and undo this compact override right when height is most cramped. */
  /* Landscape rotates which edge the notch/Dynamic Island sits against — inset-top is
     usually ~0 here, inset-left/right is where the real safe area now is. */
  :root {
    --header-h: calc(44px + env(safe-area-inset-top)) !important;
    --bottom-nav-h: calc(44px + env(safe-area-inset-bottom)) !important;
  }
  header { padding-top: env(safe-area-inset-top); padding-left: max(12px, env(safe-area-inset-left)); padding-right: max(12px, env(safe-area-inset-right)); }
  .header-content p:not(.site-wordmark), .header-badges { display: none; }
  .mobile-quickbar { height: var(--bottom-nav-h); padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); }
  .qb-btn { min-height: var(--bottom-nav-h); font-size: 16px; }
  .qb-btn span { display: none; }
  /* Landscape has width to spare but almost no height, so this reasserts the compact
     horizontal-scroll strip (the portrait block below switches to a wrapped grid, which
     would cost 3 full rows of precious vertical space here). !important as this block
     can overlap with the general mobile block below (e.g. a phone at 667x375 landscape
     matches both, same as the --header-h override above) — same specificity, so without
     it source order alone would let the later block win and silently undo this exactly
     when height is most cramped. Verified via headless Chrome at 667x375: without
     !important this computed to flex-wrap:wrap/display:grid anyway (portrait's rule
     winning); with it, the strip stays a single scrollable row as intended. */
  .mobile-mode-tabs { display: flex !important; flex-wrap: nowrap !important; overflow-x: auto !important; -webkit-overflow-scrolling: touch; mask-image: linear-gradient(to right, black 92%, transparent 100%); -webkit-mask-image: linear-gradient(to right, black 92%, transparent 100%); }
  .mobile-mode-tabs .mode-tab { padding: 6px 12px; min-height: 34px; border-right: 1px solid var(--border) !important; border-bottom: none !important; border-radius: 0; }
  .mobile-mode-tabs .mode-tab:last-child { border-right: none !important; }

  /* Quick-style bar (Mode/Style/Photo Booth) — the portrait sizing above (54px-tall
     pills, 3 stacked rows) alone would eat most of a landscape phone's ~340px of
     remaining height once the trimmed header/quick-bar above are accounted for. Row
     height comes down across the board, and pill labels drop to icon-only (title
     attribute still carries the name for anyone using a mouse) — recognizable icons
     without the text underneath read fine at this size, the same trade the bottom
     quick-bar already makes via `.qb-btn span { display:none }` two rules up. */
  .mobile-quick-style-bar { gap: 5px; padding-top: 4px; }
  .mq-mode-row, .mq-photobooth-row { padding: 3px 8px 1px; gap: 6px; }
  .mq-mode-row .mode-tab.mq-mode-pill,
  .mq-pb-pill {
    flex-direction: row;
    min-width: 0;
    min-height: 32px;
    padding: 0 10px;
    border-radius: 999px;
  }
  .mq-mode-row .mode-tab.mq-mode-pill span,
  .mq-pb-pill span { display: none; }
  .mq-tool-row { padding: 0 8px; gap: 6px; }
  .mq-select.mq-style-select, .mq-linewidth-wrap, .mq-pill, .mq-color-wrap { min-height: 32px; height: 32px; width: 32px; }
  .mq-select.mq-style-select { width: 128px; }
  .mq-linewidth-wrap { width: 96px; }
  /* This landscape height budget is already fully spent on Mode + the main tool row —
     Ghost Trails/Step Print/Fill Texture are conveniences, not core controls, so they
     drop rather than lengthen the row further on a screen this short. Still reachable via
     Style > General Settings as before. Targets the three controls directly now that they
     share the one `.mq-tool-row` rather than living in their own `.mq-effects-row`. */
  .mq-effect-item { display: none; }
}

/* ─── Mobile ─── */
@media (max-width: 768px) {
  :root {
    --sidebar-w: 0px;
    /* The extra env() term reserves room for the status bar / Dynamic Island —
       viewport-fit=cover (see index.html) tells iOS to draw the page underneath that
       area rather than leave it blank, so without this the header's actual content
       (logo/title) was vertically centered as if it had the full 56px to itself,
       landing it right under/behind the notch instead of below it. */
    --header-h: calc(56px + env(safe-area-inset-top));
    --bottom-nav-h: calc(56px + env(safe-area-inset-bottom));
  }

  .app-container {
    grid-template-columns: 1fr;
    grid-template-rows: var(--header-h) 1fr;
    height: 100dvh;
  }

  header { padding: 0 12px; padding-top: env(safe-area-inset-top); }
  .header-content p:not(.site-wordmark), .header-badges { display: none; }
  .header-nav { display: none; }
  .header-logo { width: 32px; height: 32px; }
  .header-content { gap: 10px; }
  .header-content h1 { font-size: 1.35rem; }
  /* "System Ready" was a wide mint pill competing with the wordmark for the header's
     tiny width; on a phone the dot alone carries the state (full text stays as the
     accessible name and on desktop). */
  .status-indicator.ready { font-size: 0; gap: 0; padding: 7px; }

  #sidebarToggle { display: flex; }

  /* Video fills full screen below header */
  .video-area {
    grid-column: 1;
    grid-row: 2;
    padding: 8px;
    /* .mobile-quickbar is position:fixed, so it doesn't consume grid-row space here —
       .video-area otherwise extends the full remaining viewport height and, since it
       centers its content, only lets half of any reserved bottom space actually land
       below the canvas. Padding it out explicitly reserves the real space so centering
       can't leak into the area the fixed nav bar covers. */
    padding-bottom: var(--bottom-nav-h);
  }

  /* Canvas's `width: 100%` needs an ancestor with a definite (non-shrink-to-fit)
     width to resolve against — otherwise it falls back to the ~300x150 canvas
     default and the preview renders tiny with empty space around it. */
  /* Overrides .canvas-wrapper:not(.is-empty)'s desktop `height: min(920px, 700px, 86dvh)`
     rule, which otherwise still applies here and is oblivious to --header-h/--bottom-nav-h
     — on a portrait video (the common case for phone-shot footage) that 86dvh box gets
     centered inside .video-area and its bottom edge lands underneath the fixed
     .mobile-quickbar, clipping the last ~40-90px of the preview behind it. */
  /* --mq-bar-h / --mp-bar-h are the measured heights of the two bars that float on top
     of the canvas (.mobile-quick-style-bar at the top, .mobile-player-bar at the bottom),
     published by syncMobileChromeInsets() in main.js. Padding the wrapper by them means
     the flex centering below happens inside the *clear* band between the two bars rather
     than the full box, so a canvas tall enough to fill the wrapper is no longer partly
     hidden underneath them. Both bars are position:absolute, and an absolutely positioned
     element resolves its offsets against its container's PADDING box — so this padding
     moves the canvas without moving either bar.

     Widescreen footage in most modes is short enough to sit in the clear band already and
     is unaffected. Squash's Portrait 9:16 output is the case that made this necessary: it
     makes the canvas as tall as the wrapper, and the top third of the video disappeared
     behind the style bar with its base behind the transport bar. */
  .canvas-wrapper,
  .canvas-wrapper:not(.is-empty) {
    width: 100%;
    /* 100% of .video-area, which ALREADY reserves the bottom nav as its own padding. The
       old formula subtracted the nav a second time, so the preview lost ~60px twice: a
       9:16 clip ended up at 224x399 inside a 393x604 area, less than half the space. */
    height: 100%;
    padding-top: var(--mq-bar-h, 0px);
    /* --sheet-cover: how far the open settings sheet reaches up over the stage (see
       syncMobileChromeInsets), so the picture fits in the part that's still visible. */
    padding-bottom: max(var(--mp-bar-h, 0px), var(--sheet-cover, 0px));
    transition: padding-bottom 0.3s cubic-bezier(0.2, 0.8, 0.2, 1);
  }

  canvas {
    /* Percentage, not the wrapper's own height formula repeated — 100% here resolves
       against the wrapper's content box, so it already accounts for the padding above
       and stays correct as the bars change height (Photo Booth's extra row on webcam,
       a different mode, a rotated phone) without either value being restated. */
    max-height: 100%;
    width: 100%;
  }

  /* Settings slide up as a bottom sheet, not a full-screen drawer — this leaves the
     top of the canvas visible underneath so a toggle's effect on the live preview is
     visible the moment it's tapped, instead of the video being fully hidden behind
     the panel until it's closed.

     CapCut-style peek/expand: opening the sheet shows only the category tab row
     (height:auto — with every section hidden below, it naturally shrinks to just the
     tabs) so the video/photo stays almost fully visible. Tapping a category is what
     expands the panel to show that category's controls, capped well under half the
     screen; tapping the same category again collapses back to the tab row. */
  .sidebar {
    grid-column: unset;
    grid-row: unset;
    position: fixed;
    left: 0; right: 0; bottom: var(--bottom-nav-h); top: auto;
    width: 100%;
    height: auto;
    max-height: calc(100dvh - var(--header-h) - var(--bottom-nav-h) - 16px);
    z-index: 90;
    transform: translateY(100%);
    transition: transform 0.22s ease;
    border-left: none;
    border-top: 1px solid var(--border);
    border-radius: 16px 16px 0 0;
    padding-top: 20px;
    box-shadow: 0 -4px 20px rgba(0,0,0,0.15);
  }
  .sidebar.open {
    transform: translateY(0);
  }
  .sidebar.content-expanded {
    /* Trimmed from 42dvh so the live preview keeps clearly more than half the screen
       while a settings category is open — seeing the effect of a toggle in real time
       matters more than avoiding a bit of scrolling inside the sheet. */
    max-height: min(36dvh, calc(100dvh - var(--header-h) - var(--bottom-nav-h) - 16px));
  }
  /* Peek state: everything except the drag handle + tab rows is hidden, regardless of
     which category is "active" — .content-expanded is what reveals that category's
     sections (via the data-active-tab rule above, unaffected by this). The Blob/Motion/
     Face/Object/Pose mode-tabs only belong to the Tracking category, so they stay
     hidden even when expanded unless Tracking is the active one. */
  .sidebar:not(.content-expanded) .control-section,
  .sidebar:not(.content-expanded) .mobile-mode-tabs,
  .sidebar.content-expanded:not([data-active-tab="tracking"]) .mobile-mode-tabs {
    display: none !important;
  }
  /* Drag-handle affordance signalling the panel can be dismissed */
  .sidebar::before {
    content: '';
    position: absolute;
    top: 8px; left: 50%;
    transform: translateX(-50%);
    width: 36px; height: 4px;
    border-radius: 2px;
    background: var(--border);
    opacity: 0.3;
  }

  /* Transparent tap-to-close layer — no dark scrim, so the video stays fully
     visible above the sheet instead of being dimmed out while adjusting settings. */
  .sidebar-overlay { background: transparent; }

  /* Bigger touch targets on mobile — desktop's 38px compact height drops back to the
     accessible 44px minimum here. */
  .btn { font-size: 0.88rem; padding: 13px 12px; min-height: 44px; }
  /* iOS Safari auto-zooms the whole page in whenever a focused text input/select
     computes to under 16px — 0.88rem (14.1px at the default 16px root) was tripping
     this on every single dropdown and text field in the sidebar, so tapping *any*
     control zoomed the page and left the user to manually pinch back out. 16px is the
     documented minimum WebKit checks; below it there's no way to opt out per-element. */
  .dropdown, .text-input { font-size: 16px; min-height: 44px; }
  /* A more specific selector wins over the plain .text-input/.dropdown rule above
     regardless of source order, so each of these quietly escaped the iOS-zoom fix and
     kept tapping into the same bug on a phone: the trim In/Out fields (previously an
     inline font-size, now .trim-time-input — see its desktop rule), the subtitle
     transcript editor's per-line text field, and the keyframe editor's time/value/track
     inputs. All are real typing fields in the Clip Editor, not read-only labels. */
  .trim-time-input,
  .subtitles-transcript-row .text-input,
  .kf-field .text-input, .kf-field .dropdown { font-size: 16px; min-height: 44px; }
  /* .kf-toolbar .kf-add-select's own 16px override lives further down, next to its base
     rule (line ~4380) — same (0,2,0) specificity as this block's selectors, so putting it
     here would silently lose to that later, unconditional rule regardless of viewport;
     confirmed by testing computed font-size in a real browser, not just reading the
     cascade by eye. */
  /* Same fix again for the per-cue Start/End fields and the Shift-all-timing field added
     alongside them — both are typed-into text inputs too, just narrower ones. Width grows
     a little to keep a 16px "12.3" readable in the box rather than clipped. */
  .subtitles-transcript-timing { font-size: 16px; min-height: 40px; width: 64px; }
  #subtitlesShiftInput { font-size: 16px; min-height: 40px; }
  .slider::-webkit-slider-thumb { width: 24px; height: 24px; margin-top: -9px; }
  /* Same iOS zoom-on-focus issue as .dropdown/.text-input above, plus a bigger tap
     target on the readout itself now that it's an editable control, not just a label. */
  .value-edit-input { font-size: 16px; min-height: 28px; }
  /* Landing a finger on 2-3 characters ("8%", "0.5") was fiddly at the old 22px box —
     rather than visually inflate the little value pill itself (it sits inline next to a
     small label; a full 44px-tall box there would read oversized against it), an
     invisible ::before extends the actual hit area out to a real 44px tap target while
     the pill keeps its original compact appearance. */
  .value-display-editable { min-height: 22px; display: inline-flex; align-items: center; position: relative; }
  .value-display-editable::before { content: ''; position: absolute; inset: -11px -6px; }

  /* This row is the always-visible strip in the collapsed "peek" sheet, so it's the one
     control a mobile user taps most often — bring it up near the ~44px touch-target
     minimum (the base rule's 8px padding + 0.72rem text lands well under that). */
  .sidebar-tab { padding: 11px 6px; min-height: 44px; font-size: 0.7rem; }
  .tab-icon { width: 17px; height: 17px; }
  .clip-editor-tab { padding: 10px 4px; min-height: 40px; font-size: 0.72rem; }

  /* The section accordion headers are the sole way to open 9 of the sidebar's ~16
     sections, tapped at least as often as the category tabs above — but unlike every
     other interactive control in this file, they'd never gotten a mobile touch-target
     pass at all (still the desktop's ~18px-tall text row). */
  .control-section h3 { min-height: 44px; padding: 4px 0; }

  /* Palette swatches, and a visible "this one's picked" state to go with the bigger
     target — previously the only feedback was a transient :hover/:active press effect,
     so once your finger lifted there was no lasting sign of which color was active. */
  .palette-swatch { width: 44px; height: 44px; }

  /* Color pickers stayed at the desktop 44x36 size on mobile — width cleared the 44px
     touch-target minimum but height didn't, and there was no room reserved for it since
     the picker just sits at its own width inside a flex label-row. Square it up to a
     full 44x44 tap target and give the row breathing room so it doesn't crowd its label
     at the narrower widths that box now needs. */
  .color-picker { width: 44px; height: 44px; }
  .label-row:has(.color-picker) { gap: 10px; }

  /* Toggles single column on narrow screens */
  .toggles-grid { grid-template-columns: 1fr 1fr; gap: 12px 8px; }
  /* The label itself is already the full tap target (input is display:none, click
     anywhere on the row toggles it) — the actual friction was that the visible checkbox
     stayed a tiny 16px square regardless of screen size (hard to read state at a glance)
     with zero feedback on tap, so a press didn't visually confirm it had registered. */
  .toggle-control { min-height: 44px; font-size: 0.82rem; gap: 10px; }
  .toggle-control .control-indicator { width: 22px; height: 22px; }
  .toggle-control .control-indicator::after { top: 3px; left: 7px; width: 5px; height: 10px; border-width: 0 2.5px 2.5px 0; }
  .toggle-control:active .control-indicator { background: var(--bg-secondary); transform: scale(0.9); transition: transform 0.1s ease; }

  /* Empty state actions stack full-width for easy tapping. Visual order flips to match
     which one is actually styled as primary here — Upload Video (.mobile-primary-btn)
     gets the accent fill on mobile a few rules below, but was still sitting second/below
     Use Webcam (.desktop-primary-btn, demoted to gray here) in the DOM, so the stack's
     top slot and its strongest visual weight used to point at two different buttons. */
  .empty-state-actions { flex-direction: column; width: 100%; max-width: 280px; }
  .empty-state-actions .btn { width: 100%; }
  .empty-state-actions .mobile-primary-btn { order: -1; }
  .mode-card-grid { grid-template-columns: repeat(3, 1fr); gap: 6px; }
  /* Ten modes over three columns leaves one card alone on the last row — let it take the
     whole row instead of sitting in the left third. */
  .mode-card-grid .mode-card:last-child:nth-child(3n+1) { grid-column: 1 / -1; }
  .mode-card { padding: 10px 4px; }
  .mode-card-desc { display: none; } /* three-across at this width only fits name + icon */
  /* Tighter rhythm end to end — .empty-state's own defaults (16px action margin, 22px
     stat-row margin, 26px/22px features margin+padding) were sized for the roomier
     desktop card and left the mobile screen feeling like several sparse full-height
     sections stacked up rather than one compact, scannable page. */
  .empty-state { padding: 20px 16px; gap: 2px; }
  .empty-state-sub { font-size: 0.78rem; }
  .empty-state-actions { margin: 12px 0; gap: 8px; }
  .hero-stats { margin: 14px 0 2px; }
  .empty-state-features { margin-top: 16px; padding-top: 14px; }
  /* Use Webcam (.desktop-primary-btn, demoted to a plain fill on mobile so Upload Video
     reads as the one primary action) had no border at all here — background var(--bg-
     secondary) against the page's own var(--bg-primary) is a difference of one shade,
     easy to read as "not actually a button" rather than a secondary one, dark theme
     especially (now the shipped default) where that gap compresses further than the
     same two tokens do in light. A real border — the same one every other bordered
     control in the app already uses — fixes that without making it look primary. */
  .desktop-primary-btn { border-color: var(--border); }
  /* The floating theme/font toggle is meant to sit over the live canvas ("reachable
     regardless of which sidebar tab is open" — see its own comment below), not over the
     empty state's scrollable marketing content, which it has no fixed relationship to:
     depending on scroll position it could land on the mode-card grid, the stats row, or
     anything else, and in the mode-card-grid's case visibly buried a card underneath it.
     Hidden for the whole time there's nothing loaded yet; reappears once a source is. */
  .app-container:has(.canvas-wrapper.is-empty) .theme-font-toggle { display: none; }

  /* Preset buttons (Pro Presets + More Looks) and LUT swatches measured at ~32px/26px
     tall on the desktop-sized padding — under the 44px minimum everywhere else in this
     block, and the one most tapped now that More Looks/LUT swatches put a whole gallery
     of them one burger-menu tap away instead of behind a ?preset= link. */
  .preset-btn { min-height: 44px; padding: 10px 10px; font-size: 0.78rem; }
  .lut-swatch { min-height: 44px; font-size: 0.76rem; }

  /* Trim/clip editor's in/out handles were a 12px-wide, 28px-tall target at every screen
     size — fine for a mouse cursor, close to un-grabbable precisely with a finger on the
     exact frame someone's trying to cut to. Bar grows to a real touch-friendly height and
     each handle roughly doubles in width; the visual center-line indicator (::after)
     stays fixed so a wider handle doesn't read as a chunkier visual bar than it needs to,
     just a bigger grab zone around that same thin line. */
  .trim-bar { height: 40px; }
  .trim-handle { width: 22px; margin-left: -11px; }
}

/* ─── Mobile bottom quick-action bar ─── */
/* One-time nudge toward the Settings tab — see settingsNudge in main.js for when this
   actually shows (once, the first time a source loads, mobile only). Roughly aligned
   above the Settings button (the last of 6 evenly-flexed items in .mobile-quickbar) via a
   fixed right offset rather than measuring the button's real position — simpler, and
   close enough for a pointer bubble that only needs to read as "down there," not pixel-
   perfect alignment with a button whose own width already varies by screen size.

   That is the desktop/no-source position. On a phone with a source loaded this bubble is
   lifted clear of the transport bar it used to cover, and above the picture when the preset
   dock is up — see the two `body.has-source ... .settings-nudge` overrides beside
   #undoToast's own mobile rules below. */
.settings-nudge {
  display: none;
  position: fixed;
  right: 6px;
  bottom: calc(var(--bottom-nav-h, 60px) + 10px);
  z-index: 98;
  align-items: center;
  gap: 8px;
  max-width: min(240px, calc(100vw - 24px));
  background: var(--accent);
  color: var(--on-accent);
  padding: 10px 8px 10px 14px;
  border-radius: 10px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35);
  font-size: 0.78rem;
  font-weight: 600;
  line-height: 1.3;
  opacity: 0;
  transform: translateY(8px) scale(0.96);
  transition: opacity 0.25s ease, transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.settings-nudge.visible { display: flex; }
.settings-nudge.visible.in { opacity: 1; transform: translateY(0) scale(1); }
.settings-nudge::after {
  content: '';
  position: absolute;
  bottom: -6px;
  right: 22px;
  width: 12px; height: 12px;
  background: var(--accent);
  transform: rotate(45deg);
}
.settings-nudge button {
  flex-shrink: 0;
  background: rgba(255, 255, 255, 0.2);
  border: none;
  color: #fff;
  width: 20px; height: 20px;
  border-radius: 50%;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}
.mobile-quickbar {
  display: none; /* desktop: hidden */
  grid-column: 1 / -1;
  grid-row: 3;
  position: fixed;
  bottom: 0; left: 0; right: 0;
  height: 60px;
  padding-bottom: env(safe-area-inset-bottom);
  background: var(--bg-primary);
  border-top: 1px solid var(--border);
  z-index: 95;
  flex-direction: row;
  align-items: stretch;
  box-sizing: content-box;
}
.qb-btn {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  background: none;
  border: none;
  border-right: 1px solid var(--border);
  cursor: pointer;
  font-family: inherit;
  font-size: 18px;
  color: var(--text-secondary);
  padding: 4px 0;
  min-height: 60px;
  user-select: none;
  -webkit-user-select: none;
}
.qb-btn:last-child { border-right: none; }
.qb-btn span { font-size: 9px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; }
.qb-btn:active { background: var(--bg-secondary); }
.qb-btn.qb-active { color: var(--accent); background: var(--accent-soft); }
.qb-btn:disabled { opacity: 0.3; cursor: not-allowed; }
/* Recording state on the mobile quick-bar's Record button — this bar is the one thing
   still on screen once the settings sheet (where the old Record button lived) is closed,
   so it's the only realistic place left to notice a recording is still running. */
.qb-btn.qb-recording { color: #fff; background: var(--danger); border-color: var(--danger); animation: qbRecordPulse 1.4s ease-in-out infinite; }
@keyframes qbRecordPulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.72; } }
/* A finished mobile export needs one more real tap to actually deliver (Web Share/
   window.open both require fresh user activation an async encoder callback doesn't
   carry) — this is that tap target, on the one button guaranteed to still be on screen.
   Green (not the recording red) and a distinct icon so "still recording" and "ready to
   save" never read as the same state mid-glance. */
.qb-btn.qb-save-ready { color: #fff; background: var(--success); border-color: var(--success); animation: qbRecordPulse 1.4s ease-in-out infinite; }
.qb-btn.qb-save-ready .icon { background: transparent; }
/* Splits the bar into "pick a source" (Video/Camera/Photo) vs. "act on it"
   (Save/Record/Settings) — a clearly wider, muted gap reads as a real group break next
   to the thin 1px hairlines .qb-btn already draws between same-group buttons. */
.qb-divider { flex: 0 0 12px; background: var(--bg-secondary); border-left: 1px solid var(--border); border-right: 1px solid var(--border); pointer-events: none; }
/* Record is the one action this whole app is built around capturing — every other
   .qb-btn reads identically at rest, which is exactly what made a 7-icon row feel like
   an undifferentiated wall. A filled accent circle behind its icon (camera-app shutter
   convention) gives it the visual weight the others deliberately don't have, without
   needing new copy or moving it out of the row. */
.qb-btn-primary .icon { background: var(--accent); color: var(--on-accent); border-radius: 50%; padding: 5px; box-sizing: content-box; }
.qb-btn-primary.qb-recording .icon { background: transparent; }

/* ─── Tracking-mode quick switcher ─── */
/* Shown right below the Source/Tracking/Style/Advanced category tabs, on every screen
   size, regardless of which category is active — switching Blob/Motion/Face/Object/
   Pose/Person used to require opening the Tracking category first and finding the Mode
   dropdown further down; this puts it one click away at all times. Mobile's media query
   below restyles this back into its original edge-to-edge bordered strip. */
.mobile-mode-tabs {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  flex-shrink: 0;
  gap: 6px;
  margin: 0 0 16px 0;
  padding: 0;
}
.mobile-mode-tabs::-webkit-scrollbar { display: none; }
.mode-tab {
  flex: 1 1 auto;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  padding: 8px 10px;
  background: var(--bg-secondary);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  cursor: pointer;
  color: var(--text-secondary);
  white-space: nowrap;
  min-height: 32px;
  transition: background-color var(--transition), color var(--transition), border-color var(--transition);
}
.mode-tab:hover { background: var(--bg-tertiary); color: var(--text-primary); }
.mode-tab.active { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
.mode-tab .icon { width: 13px; height: 13px; flex-shrink: 0; }
/* Same press-N hint as the empty-state's mode-card-grid, now visible for the rest of the
   session too (that grid disappears once a video loads) — this strip is the only mode
   picker left on screen at that point, so it's the one place the shortcut still needs
   to be discoverable from. */
.mode-tab-key { font-size: 0.6rem; opacity: 0.55; font-weight: 700; }
.mode-tab.active .mode-tab-key { opacity: 0.8; }
/* None/Squash run no detection at all — a thin leading rule marks them off from the
   detection modes so the strip doesn't read as 9 interchangeable options. */
.mode-tab-effect-only { margin-left: 4px; }
.mode-tab-effect-only:first-of-type { position: relative; }
.mode-tab-effect-only:first-of-type::before {
  content: "";
  position: absolute;
  left: -5px;
  top: 15%;
  bottom: 15%;
  width: 1px;
  background: var(--border);
}

/* ─── Settings search ─── */
.sidebar-search-wrap {
  position: relative;
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 12px;
  flex-shrink: 0;
}
.sidebar-reset-btn {
  flex-shrink: 0;
  width: 34px; height: 34px;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--border);
  background: var(--bg-secondary);
  color: var(--text-secondary);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: border-color var(--transition), color var(--transition), background-color var(--transition);
}
.sidebar-reset-btn:hover { color: var(--danger); border-color: var(--danger); }
.sidebar-reset-btn .icon { width: 15px; height: 15px; }
.sidebar-search-icon {
  position: absolute;
  left: 10px;
  top: 50%;
  transform: translateY(-50%);
  width: 15px;
  height: 15px;
  color: var(--text-secondary);
  pointer-events: none;
}
.sidebar-search-input {
  flex: 1;
  min-width: 0;
  padding: 9px 10px 9px 32px;
  border: 1px solid var(--border);
  background: var(--bg-secondary);
  color: var(--text-primary);
  font-family: inherit;
  font-size: 0.82rem;
  border-radius: var(--radius-sm);
  transition: border-color var(--transition), box-shadow var(--transition), background-color var(--transition);
}
.sidebar-search-input:focus {
  outline: none;
  background: var(--bg-primary);
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.sidebar-search-input::placeholder { color: var(--text-secondary); }
/* Native search-input clear (x) button — restyle to match the rest of the flat/mono UI
   instead of the browser's default round gray one. */
.sidebar-search-input::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
  width: 14px; height: 14px;
  margin-right: -2px;
  cursor: pointer;
  background: currentColor;
  color: var(--text-secondary);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'/%3E%3Cline x1='6' y1='6' x2='18' y2='18'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'/%3E%3Cline x1='6' y1='6' x2='18' y2='18'/%3E%3C/svg%3E") center / contain no-repeat;
}

/* While a search query is active: every category's sections show at once (instead of
   just the active tab's) and collapsed sections are force-expanded via JS removing
   .collapsed — individual non-matching toggles/control-groups are hidden by .search-hide,
   and a section with zero surviving matches hides itself via .search-section-hide. The
   `html` prefix and stacked classes below are there purely to out-specificity the
   tab-visibility rule further down (also !important) regardless of source order — the
   :not([data-tab=...]) in that rule counts as one more selector part than a bare class,
   so matching it needed the extra weight rather than an equal-specificity tie. */
html .sidebar.search-active[data-active-tab] .control-section {
  display: block !important;
}
.sidebar.search-active .dynamic-section {
  display: flex !important;
}
html .sidebar.search-active[data-active-tab] .control-section.search-section-hide {
  display: none !important;
}
.search-hide {
  display: none !important;
}
mark.search-mark {
  background: var(--text-primary);
  color: var(--bg-primary);
  border-radius: 2px;
  padding: 0 1px;
}
.sidebar-search-no-results {
  display: none;
  font-size: 0.8rem;
  color: var(--text-secondary);
  padding: 16px 4px;
  text-align: center;
}
.sidebar.search-active .sidebar-search-no-results.visible {
  display: block;
}

/* ─── Sidebar category tabs (Source / Tracking / Style / Advanced) ─── */
/* The sidebar has ~16 collapsible sections. On desktop (always-visible sidebar) these
   tabs mean only one category's sections render at a time instead of one long scroll.
   On mobile they double as the CapCut-style "always-visible tool row": the sheet opens
   collapsed to just this strip so the video/photo stays visible, and picking a category
   is what expands the panel — see the peek/expand rules below. */
.sidebar-tabs {
  display: flex;
  flex-wrap: nowrap;
  gap: 6px;
  margin-bottom: 16px;
}
.sidebar-tab {
  /* Icon stacked above label rather than side-by-side: at the sidebar's ~330px content
     width split 4 ways, icon and text competing for the same row got tight enough to
     force ellipsis on "Tracking"/"Advanced" even on a full-width desktop sidebar.
     Stacking only needs the wider of the two, not their sum, so it fits at every width
     this sidebar ever renders at without shrinking the font into illegibility. */
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  padding: 8px 3px;
  background: var(--bg-secondary);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: 0.6rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.01em;
  cursor: pointer;
  color: var(--text-secondary);
  white-space: nowrap;
  overflow: hidden;
  transition: background-color var(--transition), color var(--transition), border-color var(--transition), transform 0.1s ease;
}
.sidebar-tab span { overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
.tab-icon { width: 17px; height: 17px; flex-shrink: 0; }
.sidebar-tab:hover { background: var(--bg-tertiary); color: var(--text-primary); }
.sidebar-tab:active { transform: scale(0.96); }
.sidebar-tab.active { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
.sidebar-tab.active:hover { background: var(--accent-hover); color: var(--on-accent); }

/* Narrow desktop window (sidebar drops to 300px at the 1100px breakpoint below) — the
   stacked icon/label layout already fits comfortably here, just trim the icon a touch
   further to keep the row visually tight rather than dominant.
   .sidebar-tab's font-size/padding here (not just .tab-icon) is the actual fix for the
   5 category tabs (Source/Tracking/Style/Export/Advanced) ellipsis-truncating
   ("SOUR...", "TRACK...") at this width — the 380px->300px sidebar narrowing further up
   this file never carried a matching adjustment for them. Same values the ≤768px phone
   rule already uses for the identical problem at an even tighter width. Deliberately
   placed here rather than alongside that narrowing rule: this file defines the base
   .sidebar-tab { font-size: 0.6rem } rule AFTER that point, and since a later same-
   specificity rule wins regardless of which one sits inside a media query, an override
   placed before the base rule silently loses to it. This block is already positioned
   after the base rule, which is why only .tab-icon (below) used to actually take effect
   here. */
@media (min-width: 769px) and (max-width: 1100px) {
  .tab-icon { width: 15px; height: 15px; }
  /* Slightly smaller than the ≤768px phone value (0.58rem) — that rule was tuned for a
     full-width bottom-sheet layout; this 300px sidebar column is narrower than that, so
     reusing its exact number still left "Tracking"/"Advanced" a couple px short. */
  .sidebar-tab { font-size: 0.52rem; padding: 7px 0; letter-spacing: 0; }
}

/* Hide every section outside the active tab's group. Sections that stay visible are
   left completely untouched, so their own collapse-on-click and (for the per-mode
   Blob/Motion/Face/Object/Pose panels) .active-class visibility keep working as-is. */
.sidebar[data-active-tab="source"] .control-section:not([data-tab="source"]),
.sidebar[data-active-tab="tracking"] .control-section:not([data-tab="tracking"]),
.sidebar[data-active-tab="style"] .control-section:not([data-tab="style"]),
.sidebar[data-active-tab="export"] .control-section:not([data-tab="export"]),
.sidebar[data-active-tab="audio"] .control-section:not([data-tab="audio"]),
.sidebar[data-active-tab="applied"] .control-section:not([data-tab="applied"]) {
  display: none !important;
}
/* Switching categories (or, on mobile, expanding the sheet) used to just snap the new
   section's content into place with zero transition — display:none can't itself be
   transitioned, but a display:none-to-visible element still (re)plays any animation
   that's unconditionally applied to it, same trick .loading-overlay's own fade-in
   already relies on. Matches every .control-section (the hidden ones included) but only
   ever visibly plays for whichever one(s) just actually became displayed. */
@media (prefers-reduced-motion: no-preference) {
  .control-section[data-tab] { animation: controlSectionFadeIn 0.22s ease; }
}
@keyframes controlSectionFadeIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }

/* ─── Footer ─── */
.site-footer {
  background: var(--text-primary);
  color: var(--bg-primary);
  padding: 48px 24px 24px;
}
.footer-inner {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr;
  gap: 40px;
  max-width: 1200px;
  margin: 0 auto;
}
.footer-brand strong { font-size: 1rem; display: block; margin-bottom: 8px; }
.footer-brand p { font-size: 0.8rem; opacity: 0.7; line-height: 1.6; }
.footer-links h3, .footer-guides h3 { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 12px; opacity: 0.5; }
.footer-links ul { list-style: none; display: flex; flex-direction: column; gap: 7px; }
.footer-links li { font-size: 0.82rem; opacity: 0.8; }
.footer-links a, .footer-guides a { color: var(--bg-primary); text-decoration: none; transition: opacity var(--transition); }
/* Both these links' li parent already sits at opacity:0.8 (the muted-footer-text look) —
   opacity compounds with an ancestor's, so hovering needs 1 here (full brightness against
   that dimmed parent), not a lower number, to actually read as a highlight instead of a
   further fade. */
.footer-links a:hover, .footer-guides a:hover { text-decoration: underline; opacity: 1; }
/* All ~50 guide pages, grouped under 4 topic headings instead of one flat alphabetical-ish
   list — same links (nothing dropped, so internal-linking/crawl coverage to every page is
   unchanged), just organized by what they actually are (a tracking mode vs. a visual
   effect vs. a use case vs. an export/platform guide) instead of one undifferentiated wall
   of ~50 anchors a visitor has to read every single one of to find anything. */
.footer-guides {
  max-width: 1200px;
  margin: 32px auto 0;
  padding-top: 32px;
  border-top: 1px solid rgba(255,255,255,0.15);
}
.footer-guides-groups {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 28px;
}
.footer-guides-group h4 { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 12px; opacity: 0.5; font-weight: 600; }
.footer-guides-group ul { list-style: none; display: flex; flex-direction: column; gap: 7px; }
.footer-guides li { font-size: 0.78rem; opacity: 0.8; break-inside: avoid; margin-bottom: 0; }
.footer-bottom {
  border-top: 1px solid rgba(255,255,255,0.15);
  margin-top: 40px;
  padding-top: 16px;
  text-align: center;
  font-size: 0.75rem;
  opacity: 0.5;
}

@media (max-width: 768px) {
  /* iPhones with a home indicator report viewport-fit=cover's safe area here — without
     it, the quickbar/bottom-sheet sit flush with the edge and the indicator overlaps
     the buttons. */
  :root { --bottom-nav-h: calc(60px + env(safe-area-inset-bottom)); }
  body { padding-bottom: var(--bottom-nav-h); }
  
  .mobile-quickbar { display: flex; }

  /* 5 category tabs (Source/Tracking/Style/Export/Advanced) at phone widths need to
     shrink a bit tighter than desktop to avoid the label ellipsis-truncating. */
  .sidebar-tabs { gap: 4px; }
  .sidebar-tab { font-size: 0.58rem; padding: 7px 2px; letter-spacing: 0.01em; }
  .sidebar-tab .tab-icon { width: 15px; height: 15px; }

  .mobile-mode-tabs {
    display: grid;
    /* 9 modes (Blob/Motion/Face/Object/Pose/Person/Hand/None/Squash) as a 3-column
       grid instead of one scrolling row — the previous horizontal-scroll strip (still
       used in the landscape override above, where height is the scarce dimension) was
       silently hiding Person/Hand/None/Squash off the right edge with only a fading
       mask as a hint to keep swiping; every mode is now visible and reachable in one
       glance without a gesture. */
    grid-template-columns: repeat(3, 1fr);
    border: none;
    border-bottom: 1px solid var(--border);
    margin: -16px -16px 8px -16px;
    gap: 0;
  }
  .mobile-mode-tabs .mode-tab {
    border: none;
    border-right: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
    border-radius: 0;
    min-height: 44px;
  }
  .mobile-mode-tabs .mode-tab:nth-child(3n) { border-right: none; }
  .mobile-mode-tabs .mode-tab:nth-last-child(-n+3) { border-bottom: none; }
  /* Grid cells here are border-divided and margin-free by design (see above) — the
     flex-row-only divider treatment would just misalign this layout. */
  .mobile-mode-tabs .mode-tab-effect-only { margin-left: 0; }
  .mobile-mode-tabs .mode-tab-effect-only:first-of-type::before { display: none; }

  /* Sidebar needs padding for quickbar */
  .sidebar { padding-bottom: calc(var(--bottom-nav-h) + 16px); }

  /* Footer stacks on mobile */
  .site-footer { padding: 32px 16px 80px; }
  .footer-inner { grid-template-columns: 1fr; gap: 28px; }
  .footer-guides-groups { grid-template-columns: repeat(2, 1fr); gap: 24px 20px; }
}

@media (max-width: 400px) {
  .toggles-grid { grid-template-columns: 1fr; }
  /* Was gated behind the 350px query below — but at 375-400px, each of the 2 footer
     columns is only ~161px wide, and longer guide titles ("Free Browser Motion Capture
     (Mocap Alternative)") were already wrapping 3+ lines there, well before actually
     hitting the narrower 350px phones. Single column reads better than that much wrap,
     worth the extra scroll. */
  .footer-guides-groups { grid-template-columns: 1fr; }
}

/* Genuinely tiny/older phones (iPhone SE 1st-gen and similar, 320-350px) — the header
   title + logo + status badge + hamburger no longer all fit on one line at the normal
   mobile sizes, wrapping the title onto a second line that overlaps the badge. */
@media (max-width: 350px) {
  .header-content h1 { font-size: 1.1rem; }
  .header-content { gap: 8px; }
  .header-logo { width: 26px; height: 26px; }
  .status-indicator { font-size: 0.65rem; padding: 3px 6px; }
  /* 7 quick-bar buttons (Video/Camera/Photo/Play/Save/Record/Settings) start reading as
     cramped below the same width the header title starts wrapping at — icon-only (same
     treatment the short-landscape query already uses) keeps every button comfortably
     tappable. Left alone above this breakpoint: tested down to 375px with labels on and
     they're genuinely fine, so hiding them any earlier would cost more than it saves. */
  .qb-btn span { display: none; }
}

/* ─── Pro Presets Grid ─── */
.preset-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 6px;
  margin-top: 4px;
}
.preset-btn {
  background: var(--bg-secondary);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  padding: 6px 8px;
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
  text-align: left;
  transition: background var(--transition), color var(--transition), border-color var(--transition);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.preset-btn:hover {
  background: var(--accent-soft);
  color: var(--accent-hover);
  border-color: var(--accent);
}

/* ─── Live Stats Bar — sits below the canvas (not overlaid on top of it), off by default ─── */
.hud-bar {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px;
  max-width: 100%;
  flex-shrink: 0;
}
.hud-item {
  background: rgba(0, 0, 0, 0.75);
  backdrop-filter: blur(4px);
  border: 1px solid rgba(255, 255, 255, 0.2);
  padding: 3px 8px;
  border-radius: 3px;
  display: flex;
  flex-direction: column;
  color: #ffffff;
  font-family: 'JetBrains Mono', monospace;
}
.hud-label {
  font-size: 8px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  opacity: 0.65;
}
.hud-val {
  font-size: 11px;
  font-weight: 700;
  color: #00ffcc;
}

/* ─── Undo Toast ─── */
#undoToast {
  /* Deliberately small: a one-line pill, not a card. It reports something that already
     happened (Smart Calibrate, a preset, a reset) and offers a way back; it shouldn't
     cover the controls someone is about to use. Long messages ellipsize (full text is
     the title/accessible name) instead of wrapping into a tall box. */
  position: fixed;
  left: 50%;
  bottom: 20px;
  transform: translate(-50%, 8px);
  display: flex;
  align-items: center;
  gap: 2px;
  max-width: min(440px, calc(100vw - 24px));
  padding: 4px 4px 4px 14px;
  background: color-mix(in srgb, var(--bg-tertiary) 92%, transparent);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  color: var(--text-primary);
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 12.5px;
  line-height: 1.2;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
  z-index: 200;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s ease, transform 0.18s ease;
}
#undoToast.visible { opacity: 1; transform: translate(-50%, 0); pointer-events: auto; }
#undoToastMsg {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding-right: 6px;
}
#undoToast button {
  flex-shrink: 0;
  background: none;
  border: none;
  color: var(--accent);
  font: inherit;
  font-weight: 700;
  min-height: 32px;
  padding: 0 10px;
  border-radius: 999px;
  cursor: pointer;
}
#undoToast button:hover { background: var(--accent-soft); }
#undoToast #undoToastClose { color: var(--text-secondary); font-size: 16px; font-weight: 500; padding: 0 10px; }
@media (max-width: 768px) {
  /* Sits just above the bottom bar, clear of the mode/tool rows at the top of the video. */
  #undoToast { bottom: calc(var(--bottom-nav-h, 60px) + 10px); }
  #undoToast button { min-height: 36px; }
}

/* ─── Audio Spectrum Box ─── */
.audio-spectrum-container {
  background: #000000;
  border: 1px solid var(--border);
  padding: 4px;
  display: flex;
  flex-direction: column;
  align-items: center;
}
#audioSpectrumCanvas {
  display: block;
  width: 100%;
  height: 36px;
}
.audio-spectrum-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  padding-top: 4px;
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--text-secondary);
}
.audio-spectrum-legend span { display: flex; align-items: center; gap: 4px; }
.audio-spectrum-legend i { width: 8px; height: 8px; border-radius: 2px; display: inline-block; }


/* ─── SEO Content & FAQ Section ─── */
.seo-content-section {
  max-width: 1200px;
  margin: 24px auto 0;
  padding: 32px 24px;
  border-top: 1px solid var(--border);
  background: var(--bg-primary);
}
/* Was 1.3rem/700 with no font-family — identical size to the h3s beneath it (see that
   rule's own comment: same widespread issue, this is the other half of it), so the
   section read as one flat wall of same-weight text with no top-level anchor. Bumped to
   clearly outrank every h3 on the page and switched to --font-display so it matches the
   hero h1's treatment instead of falling back to plain body text. */
.seo-container article h2 {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 3vw, 2.1rem);
  font-weight: 800;
  line-height: 1.15;
  margin-bottom: 14px;
  letter-spacing: -0.02em;
}
.seo-container article p {
  font-size: 0.88rem;
  line-height: 1.6;
  color: var(--text-secondary);
  margin-bottom: 16px;
}
/* The lead paragraph right under the h2 — same size as every card blurb further down
   otherwise, with nothing to mark it as the section's opening statement. */
.seo-container article h2 + p {
  font-size: 1rem;
  line-height: 1.65;
  color: var(--text-primary);
}
/* Same gap as .lp-section-head h2 above (see its own comment) — reaches further here:
   this is the shared "Why This Guide" / "Frequently Asked Questions" heading used on
   home.html AND all 54 individual guide pages (.seo-content-section/.seo-container),
   so it was the single most widely-repeated undersized/undesigned heading on the whole
   site, not just a home.html-only gap. */
.seo-container article h3 {
  font-family: var(--font-display);
  font-size: 1.3rem;
  font-weight: 800;
  margin: 22px 0 12px;
  letter-spacing: -0.015em;
}
.seo-kicker {
  display: inline-block;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent-hover);
  background: var(--accent-soft);
  border-radius: var(--radius-full);
  padding: 4px 12px;
  margin-top: 20px;
}
.seo-kicker + h3 { margin-top: 8px; }
.seo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 14px;
  margin-bottom: 24px;
}
.seo-card {
  background: var(--bg-primary);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 16px;
  box-shadow: var(--shadow-xs);
}
.seo-card h4 {
  font-size: 0.88rem;
  font-weight: 700;
  margin-bottom: 6px;
}
.seo-card h4 a { color: var(--text-primary); text-decoration: none; }
.seo-card h4 a:hover { color: var(--accent); }
.seo-card p {
  font-size: 0.78rem;
  margin-bottom: 0;
  line-height: 1.5;
}
/* home.html's "Dedicated Guides" grid has exactly 18 cards — .seo-grid's shared
   auto-fit(minmax(240px,1fr)) happens to fit 4 per row at this container's width, which
   strands the last row at 2 cards flush left with empty space beside them. Scoped to this
   one page's #about-app instance (not the shared .seo-grid class, since other pages using
   it have different card counts auto-fit already handles fine) and only above the mobile
   breakpoint, where .seo-grid's own responsive stacking already takes over anyway. */
@media (min-width: 769px) {
  #about-app .seo-grid { grid-template-columns: repeat(3, 1fr); }
  /* The "Core Features" grid above it now has 7 cards (a Hand & Finger Tracking card
     was added after this rule was first written for a since-outgrown 6-card count) — at
     3 columns that stranded the 7th card alone in its own row with two empty column
     tracks visibly beside it. 4 columns leaves only one empty slot in a fuller second
     row instead of two in a near-empty one. */
  #about-app .seo-grid-features { grid-template-columns: repeat(4, 1fr); }
}

/* FAQ Accordion */
.seo-faq-accordion {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.seo-faq-accordion details {
  background: var(--bg-primary);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 14px 16px;
  transition: background-color var(--transition), border-color var(--transition), box-shadow var(--transition);
}
.seo-faq-accordion details:hover { border-color: var(--text-secondary); }
.seo-faq-accordion details[open] {
  background: var(--bg-primary);
  border-color: var(--accent);
  box-shadow: var(--shadow-xs);
}
.seo-faq-accordion summary {
  font-size: 0.88rem;
  font-weight: 600;
  cursor: pointer;
  user-select: none;
  list-style: none;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
}
.seo-faq-accordion summary::after {
  content: '+';
  font-size: 1.2rem;
  font-weight: 400;
  color: var(--text-secondary);
  flex-shrink: 0;
}
.seo-faq-accordion details[open] summary::after {
  content: '−';
  color: var(--accent);
}
.seo-faq-accordion details p {
  margin-top: 10px;
  margin-bottom: 0;
  font-size: 0.82rem;
  line-height: 1.65;
}

/* ─── SEO Landing Page Hero (shared across every /guide page) ───
   Was previously duplicated as an identical inline <style> block on all 8 pages;
   consolidated here so it applies to every current and future guide page automatically
   and only needs to change in one place. */
.site-wordmark { font-family: var(--font-display); font-size: 1.2rem; font-weight: 900; letter-spacing: -0.02em; }

.lp-breadcrumb { padding: 10px 24px; border-bottom: 1px solid var(--glass-border); }
.lp-breadcrumb ol { list-style: none; display: flex; gap: 6px; font-size: 0.78rem; color: var(--text-secondary); flex-wrap: wrap; }
.lp-breadcrumb li:not(:last-child)::after { content: '/'; margin-left: 6px; opacity: 0.5; }
.lp-breadcrumb a { color: var(--text-secondary); text-decoration: none; }
.lp-breadcrumb a:hover { color: var(--accent); }
.lp-breadcrumb li[aria-current="page"] { color: var(--text-primary); font-weight: 600; }

.lp-hero { max-width: 900px; margin: 0 auto; padding: 64px 24px 44px; text-align: center; position: relative; isolation: isolate; }
/* Full-bleed dot-grid behind the hero only — the "considered SaaS launch page" backdrop
   this genre (Linear/Vercel/Attio) uses instead of a stock photo or color-gradient mesh.
   Kept monochrome (var(--border), the same hairline gray already used for card borders)
   and faded out via mask rather than tinted, so it adds depth without reintroducing the
   "colorful gradient blob" look the flat/square system above was deliberately built to
   avoid. z-index:-1 + isolation:isolate on .lp-hero keeps it contained to this section's
   own stacking context, so it can never paint over the sticky header or a later section. */
.lp-hero::before {
  content: '';
  position: absolute;
  top: -1px; left: 50%; width: 100vw; height: calc(100% + 40px);
  transform: translateX(-50%);
  z-index: -1;
  background-image: radial-gradient(var(--border) 1px, transparent 1px);
  background-size: 24px 24px;
  -webkit-mask-image: radial-gradient(ellipse 55% 55% at 50% 10%, black 0%, transparent 72%);
  mask-image: radial-gradient(ellipse 55% 55% at 50% 10%, black 0%, transparent 72%);
  pointer-events: none;
}
.lp-kicker {
  display: inline-flex; align-items: center; gap: 7px; font-family: 'JetBrains Mono', monospace; font-size: 0.7rem;
  font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--accent-hover); background: var(--accent-soft); border-radius: var(--radius-full); padding: 5px 14px; margin-bottom: 22px;
}
.lp-kicker::before {
  content: ''; width: 6px; height: 6px; border-radius: 50%; background: var(--accent); flex-shrink: 0;
}
@media (prefers-reduced-motion: no-preference) {
  .lp-kicker::before { animation: heroDemoPulse 1.8s ease-in-out infinite; }
}
/* Was a flat solid-fill square, identical across all 54 guide pages bar the swapped-in
   SVG glyph — a two-tone diagonal fill plus a crisp 1px inner highlight along the top
   edge reads as a considered mark rather than a plain colored box, while staying inside
   the site's flat/no-blur system (an INSET line, not a drop shadow — see the --shadow-*
   comment at the top of this file on why blurred elevation is deliberately avoided here). */
.lp-icon-wrap {
  width: 64px; height: 64px; margin: 0 auto 22px; display: flex; align-items: center;
  justify-content: center; border: 1px solid var(--border); border-radius: var(--radius-lg);
  background: linear-gradient(155deg, var(--accent-soft) 0%, color-mix(in srgb, var(--accent-soft) 45%, var(--bg-primary)) 100%);
  color: var(--accent);
  box-shadow: var(--shadow-xs), inset 0 1px 0 color-mix(in srgb, white 30%, transparent);
}
/* Applied (alongside the already-existing .lp-kicker-warm on the same page's kicker
   pill) to every guide page EXCEPT the 7 "TRACKING MODE —" ones, which keep the default
   green — the categorization already exists in each page's own kicker text (see
   scripts/tag-warm-guides pattern used to apply this), this just gives it a second,
   consistent visual expression instead of every one of the 50+ guide pages using
   identical green regardless of topic. Deliberately still only two hues total (green +
   this one warm accent), matching the --accent-warm token's own "used sparingly, never
   a rainbow" comment above — not a per-category color wheel. */
.lp-icon-wrap.lp-icon-warm {
  background: linear-gradient(155deg, var(--accent-warm-soft) 0%, color-mix(in srgb, var(--accent-warm-soft) 45%, var(--bg-primary)) 100%);
  color: var(--accent-warm);
}
.lp-hero .lp-icon { width: 26px; height: 26px; margin: 0; }
.lp-hero h1 {
  /* Was clamp(1.7rem, 4.4vw, 2.7rem) — landed noticeably smaller than the equivalent
     hero headline on the Linear/Vercel/Attio-style sites this page's own backdrop
     comment (above) already names as the reference. Bigger + tighter tracking is the
     actual lever for that "confident" read, not more decoration around it. */
  font-family: var(--font-display); font-size: clamp(2rem, 5.4vw, 3.6rem);
  font-weight: 900; letter-spacing: -0.03em; line-height: 1.1; margin-bottom: 18px;
}
.lp-hero p { font-size: 1rem; line-height: 1.6; color: var(--text-secondary); max-width: 640px; margin: 0 auto 26px; }
.lp-cta { display: inline-flex; gap: 12px; flex-wrap: wrap; justify-content: center; margin-bottom: 22px; }
.lp-cta .btn { flex: 0 0 auto; padding: 14px 28px; }
/* The hero's primary CTA is the one conversion target on the whole page — same exception
   the live canvas already gets (see --shadow-md/lg comment above: "just enough to lift
   the one focal element off the page"), extended to the single most important button
   rather than to buttons generally. */
.lp-hero .lp-cta .primary-btn { transition: background-color var(--transition), border-color var(--transition), box-shadow 0.2s cubic-bezier(0.16, 1, 0.3, 1), transform 0.2s cubic-bezier(0.16, 1, 0.3, 1); }
.lp-hero .lp-cta .primary-btn:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 10px 24px -10px color-mix(in srgb, var(--accent) 55%, transparent); }
.lp-hero .lp-cta .primary-btn .btn-arrow { display: inline-block; margin-left: 7px; transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1); }
.lp-hero .lp-cta .primary-btn:hover:not(:disabled) .btn-arrow { transform: translateX(3px); }
.lp-badges { display: flex; gap: 6px; justify-content: center; flex-wrap: wrap; }

.lp-back { max-width: 1200px; margin: 0 auto; padding: 0 24px; }
.lp-back a { font-size: 0.82rem; color: var(--text-secondary); text-decoration: none; }
.lp-back a:hover { color: var(--accent); }

@media (max-width: 600px) {
  .lp-hero { padding: 36px 16px 28px; }
  .lp-breadcrumb { padding: 8px 16px; }
  /* Native-app-style top bar on a phone: logo + one primary action, not four competing
     targets crammed (and wrapping to a second row) into a header this narrow — the
     Modes/Styles/FAQ jump links stay one scroll away regardless, and the footer still
     lists every guide page for anyone who wants a direct link. */
  .lp-nav-jump { display: none; }
  .home-header .header-nav a.nav-cta { padding: 8px 16px; font-size: 0.82rem; }
  /* Full-width stacked CTAs read as a native mobile action sheet; side-by-side pills at
     this width either wrap awkwardly or force each button's label to truncate/shrink. */
  .lp-hero .lp-cta { display: flex; flex-direction: column; width: 100%; }
  .lp-hero .lp-cta .btn { width: 100%; padding: 13px 20px; }
}

/* Numbered feature cards, applied to the SEO feature grids on the homepage and every
   guide page. Hover used to fully invert to solid black (same language as
   .mode-card.active) — swapped for a lighter accent-tinted lift, since a dozen-plus
   cards flashing to solid black on every mouse pass over a marketing section read as
   noisy rather than premium; the active/selected app controls elsewhere keep the
   full-invert treatment where it actually communicates "selected". */
.seo-card {
  position: relative;
  counter-increment: seo-card;
  transition: background-color var(--transition), border-color var(--transition), box-shadow var(--transition), transform 0.15s ease;
}
.seo-grid { counter-reset: seo-card; }
.seo-card::before {
  content: counter(seo-card, decimal-leading-zero);
  display: block;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  color: var(--accent);
  margin-bottom: 8px;
  opacity: 0.6;
}
.seo-card:hover { background: var(--accent-soft); border-color: var(--accent); box-shadow: var(--shadow-sm); transform: translateY(-2px); }

/* ─── Mobile Touch UX Enhancements ─── */
.mobile-quickbar {
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}
.qb-btn {
  min-height: 48px;
}

/* ─── Reusable progress bar (Export / Render Mode / Smart Calibrate) ───
   One shared visual so "how far along is this" reads the same whether it's a live
   recording, an offline render, or a calibration sampling pass — a plain text status
   line (the only feedback these had before) doesn't communicate rate/scale at a glance
   the way a filling bar does. */
.progress-track-wrap { margin: 6px 0 2px; }
.progress-track {
  width: 100%;
  height: 8px;
  border-radius: 4px;
  background: var(--bg-secondary);
  border: 1px solid var(--border);
  overflow: hidden;
}
.progress-fill {
  height: 100%;
  width: 0%;
  background: linear-gradient(90deg, var(--accent), var(--accent-hover));
  border-radius: 4px;
  transition: width 0.25s ease;
}
.progress-fill.indeterminate {
  width: 35% !important;
  animation: progress-indeterminate 1.1s ease-in-out infinite;
}
@keyframes progress-indeterminate {
  0% { margin-left: -35%; }
  100% { margin-left: 100%; }
}
.progress-meta {
  display: flex;
  justify-content: space-between;
  font-size: 0.72rem;
  font-family: 'JetBrains Mono', monospace;
  color: var(--text-secondary);
  margin-top: 4px;
}

/* Mobile export/record progress strip — pinned just above the bottom quick-bar (the
   Settings sheet where the full progress bar above lives is closed for the whole
   export on mobile) so there's still a persistent, glanceable sense of "how far along"
   beyond just the qbRecordLabel percentage text. */
.qb-progress-bar {
  display: none;
  position: fixed;
  left: 0; right: 0;
  bottom: calc(var(--bottom-nav-h, 60px));
  height: 3px;
  background: rgba(0,0,0,0.15);
  z-index: 96;
}
.qb-progress-bar.active { display: block; }
.qb-progress-fill {
  height: 100%;
  width: 0%;
  background: linear-gradient(90deg, var(--accent), var(--accent-hover));
  transition: width 0.25s ease;
}
@media (min-width: 769px) {
  .qb-progress-bar { display: none !important; }
}

/* ─── Theme + UI font toggle ───
   Fixed near the bottom-right of the viewport rather than tucked into a settings tab —
   it's a whole-page preference (like a reader's dark-mode toggle), not a tracking or
   export setting, so it stays reachable regardless of which sidebar tab is open or
   whether the sidebar is even open at all. Sits above the mobile quick-bar on mobile so
   the two never overlap. */
.theme-font-toggle {
  position: fixed;
  right: 12px;
  bottom: calc(var(--bottom-nav-h, 0px) + 12px);
  z-index: 97;
  display: flex;
  gap: 6px;
  transition: bottom var(--transition);
}
.tf-btn {
  width: 38px;
  height: 38px;
  border-radius: var(--radius-full);
  background: var(--glass-bg);
  border: 1px solid var(--glass-border);
  color: var(--text-primary);
  box-shadow: var(--shadow-md);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  font-family: inherit;
  font-weight: 700;
  font-size: 0.8rem;
  transition: background-color var(--transition), border-color var(--transition), transform 0.1s ease;
}
.tf-btn:hover { border-color: var(--accent); color: var(--accent); }
.tf-btn:active { transform: scale(0.9); }
.tf-btn .icon { margin: 0; width: 18px; height: 18px; }
/* The one filled dot in #accentToggleBtn's palette icon stays accent-colored at all
   times (not just on :hover like the rest of the icon/button) — a resting, at-a-glance
   preview of whichever accent theme is currently active, the same way the light/dark
   toggle right next to it already shows a sun or a moon rather than one neutral icon for
   both states. The other three (empty) circles stay plain currentColor so the swatch
   reads as "one colored dot among neutral ones," not a fully accent-tinted icon that
   would fight the sun/moon icon's own neutral resting color beside it. */
#accentToggleBtn .icon circle[fill] { fill: var(--accent); }
@media (max-width: 768px) {
  .tf-btn { width: 44px; height: 44px; }
  .tf-btn .icon { width: 20px; height: 20px; }
  /* This floating widget sits above the settings sheet in stacking order (z-index 97
     vs. the sheet's 90) so it can stay reachable while the sheet is collapsed to just
     its peek row — but once a category is expanded, that same fixed screen position
     lands squarely on top of real controls (e.g. the Person/Hand/None/Squash row of
     the Tracking mode-tab grid), blocking taps on whatever scrolled underneath it.
     Hiding it for the duration is a one-tap cost (close the sheet to reach it) against
     silently unreachable controls every time. */
  body.sidebar-sheet-expanded .theme-font-toggle { display: none; }
}

/* ─── Export confirm modal ───
   Re-asks quality/render settings at the moment of export, rather than only ever
   surfacing them if the user happened to open the Export tab beforehand — mirrors the
   settings sheet's own glass-panel look so it doesn't feel like a foreign browser
   dialog dropped on top of the app. */
.export-confirm-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.55);
  z-index: 210;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.export-confirm-overlay.open { display: flex; }
.export-confirm-modal {
  background: var(--bg-primary);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: var(--shadow-lg, 0 20px 60px rgba(0,0,0,0.35));
  width: 100%;
  max-width: 420px;
  max-height: 88vh;
  overflow-y: auto;
  padding: 18px 20px 20px;
}
.export-confirm-modal h3 { margin: 0 0 4px; }
.export-confirm-modal .control-group { margin-top: 12px; }
.export-confirm-actions {
  display: flex;
  gap: 10px;
  margin-top: 18px;
}
.export-confirm-actions .btn { flex: 1; }
.export-confirm-summary-row {
  display: flex;
  justify-content: space-between;
  font-size: 0.78rem;
  padding: 3px 0;
  border-bottom: 1px dashed var(--border);
}
.export-confirm-summary-row span:first-child { color: var(--text-secondary); }

/* ─── Keyboard shortcuts help modal ─── shares .export-confirm-overlay/-modal's overlay
   chrome (same dialog treatment app-wide, no reason for a second one) but needs its own
   list layout for the key/description rows themselves. */
.shortcuts-list { margin-top: 14px; display: flex; flex-direction: column; gap: 2px; }
.shortcuts-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 0;
  border-bottom: 1px dashed var(--border);
  font-size: 0.82rem;
}
.shortcuts-row:last-child { border-bottom: none; }
.shortcuts-row span { color: var(--text-secondary); }
.shortcuts-row kbd {
  flex-shrink: 0;
  min-width: 26px;
  padding: 3px 7px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--bg-secondary);
  box-shadow: 0 1px 0 var(--border-strong);
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.74rem;
  font-weight: 700;
  text-align: center;
  color: var(--text-primary);
}

/* ─── "More Looks" preset grid + LUT quick-swatches (Style tab) ───
   Pro Presets above only ever showed the 7 hand-picked ones; the other ~18 presets
   (spiderwebHands, matrixRain, etc.) previously existed only as ?preset= deep-links
   from SEO landing pages, with no way to reach them from inside the app itself —
   including from the burger/settings menu on mobile. This surfaces all of them plus
   one-tap LUT swatches in the same reachable place. */
.more-looks-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 6px;
}
.lut-swatch-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.lut-swatch {
  flex: 1 1 30%;
  min-width: 80px;
  font-size: 0.72rem;
  padding: 6px 4px;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--bg-secondary);
  color: var(--text-primary);
  cursor: pointer;
  text-align: center;
}
.lut-swatch.active { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); font-weight: 600; }
/* Hover preview: the look is on the canvas right now but not committed — dashed accent ring
   rather than the solid one a real selection gets, so "trying" and "chosen" never look alike. */
.lut-swatch.is-previewing .lut-swatch-preview { outline: 2px dashed var(--accent); outline-offset: 1px; }
.lut-swatch.is-previewing .lut-swatch-name { color: var(--text-primary); }
.lut-swatch-hint { margin: 0 0 6px; }
/* Two copies, one shown at a time — a mouse can hover, a touchscreen can't, so which
   gesture this hint should even describe depends on the input the visitor actually has.
   Previously the hint was hidden outright on a coarse pointer rather than corrected,
   because there was no touch equivalent for it to describe yet — now there is (long-press
   to preview, see the pointerdown branch in main.js wiring the LUT swatch row). */
.lut-swatch-hint-coarse { display: none; }
@media (pointer: coarse) {
  .lut-swatch-hint-fine { display: none; }
  .lut-swatch-hint-coarse { display: block; }
}

/* ─── Buffering indicator ───
   Shown while the loaded video is stalled waiting on data (seeking, slow decode) so a
   frozen frame during playback reads as "loading", not "broken". */
.buffering-indicator {
  display: none;
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: 42px; height: 42px;
  border-radius: 50%;
  border: 3px solid rgba(255,255,255,0.25);
  border-top-color: #fff;
  animation: buffering-spin 0.8s linear infinite;
  pointer-events: none;
  z-index: 15;
}
.buffering-indicator.active { display: block; }
@keyframes buffering-spin { to { transform: translate(-50%, -50%) rotate(360deg); } }

/* ─── Landing-page mobile header nav ───
   The main app hides .header-nav below 1100px (tablet) / 768px (mobile) because that space
   is needed for the sidebar hamburger + status badge inside a fixed-height, fixed-viewport
   header. The 12 static guide pages (public/*.html) share the same header markup/CSS but
   have no hamburger, no sidebar, and no fixed-viewport layout to protect — hiding their nav
   left mobile visitors with zero way to navigate anywhere except scrolling to the footer.
   `.lp-page-header`/`.lp-header-nav` (added only on those pages) opt back into a visible,
   wrapping nav here. Placed at the end of the file so it wins the cascade over every earlier
   `.header-nav { display: none }` rule at equal specificity, regardless of which breakpoint
   block matches. */
@media (max-width: 1100px) {
  .lp-page-header {
    height: auto;
    min-height: var(--header-h);
    flex-wrap: wrap;
    row-gap: 8px;
    padding-top: 10px;
    padding-bottom: 10px;
  }
  .lp-header-nav {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 16px;
    font-size: 0.8rem;
  }
}

/* ─── Homepage (public/home.html) ───
   The root landing page is deliberately a static, no-JS-required page (no video/canvas,
   no OpenCV.js) — these are its own card/chip styles, separate from the in-app .seo-card
   grid, so a "look" can show its accent color without loading any real preview media. */
.lp-section { max-width: 1200px; margin: 0 auto; padding: 8px 24px 44px; }
.lp-section-head { text-align: center; max-width: 640px; margin: 0 auto 26px; }
/* Was plain browser-default h2 sizing (no font-family override at all — inherited
   whatever --font-body happened to be, at an unremarkable ~1.4rem/700) while the hero H1
   two sections up gets the full --font-display/tight-tracking treatment — every section
   below the fold read as a size down in importance from that mismatch alone. Matching
   weight/tracking (not matching SIZE — a section head should still read as one level
   below the hero) is what makes the hierarchy feel deliberate scrolling down the page
   instead of the hero being the one "designed" moment on an otherwise default-styled page. */
.lp-section-head h2 { font-family: var(--font-display); font-size: 1.75rem; font-weight: 800; letter-spacing: -0.02em; line-height: 1.15; margin-bottom: 10px; }
.lp-section-head p { font-size: 0.92rem; color: var(--text-secondary); line-height: 1.6; }

.how-steps { display: flex; align-items: flex-start; justify-content: center; gap: 20px; max-width: 1000px; margin: 0 auto; }
.how-step { flex: 1 1 0; text-align: center; max-width: 260px; }
.how-step-num {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: var(--radius-full);
  background: var(--accent-soft); color: var(--accent); border: 1px solid var(--border);
  font-family: var(--font-display); font-weight: 700; font-size: 0.95rem;
  margin-bottom: 12px;
}
.how-step h3 { font-size: 0.98rem; font-weight: 700; margin-bottom: 6px; letter-spacing: -0.01em; }
.how-step p { font-size: 0.82rem; color: var(--text-secondary); line-height: 1.55; }
.how-step-arrow { color: var(--border); font-size: 1.3rem; padding-top: 6px; flex: 0 0 auto; }
@media (max-width: 700px) {
  .how-steps { flex-direction: column; align-items: center; gap: 22px; }
  .how-step-arrow { transform: rotate(90deg); padding: 0; }
}

/* Scoped under .lp-section (the marketing homepage's own section wrapper, never used by
   the app's index.html) — .mode-card-grid/.mode-card are ALSO the class names the app's
   own empty-state "choose a tracking mode" screen uses (style.css:1098-1121), and these
   two component styles were colliding: same class, no ancestor scoping, so whichever was
   declared later in this shared stylesheet (this block) silently won the cascade for any
   shared property on BOTH pages — e.g. the app's empty-state grid was inheriting this
   block's minmax(210px)/gap:14px instead of its own tuned minmax(130px)/gap:8px, and
   .mode-card's `display:flex` (needed for its centered icon/name/desc column layout) was
   losing to this block's `display:block`. Scoping this block to `.lp-section` fixes both
   without touching the app's own rules at all. */
.lp-section .mode-card-grid, .lp-section .style-card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 14px;
}
.lp-section .mode-card, .lp-section .style-card {
  display: block;
  background: var(--bg-primary);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 16px;
  text-decoration: none;
  color: inherit;
  transition: border-color var(--transition), transform var(--transition), box-shadow var(--transition);
}
.lp-section .mode-card:hover, .lp-section .style-card:hover { border-color: var(--accent); transform: translateY(-2px); box-shadow: var(--shadow-sm); }
.lp-section .mode-card .icon { width: 22px; height: 22px; color: var(--accent); margin-bottom: 10px; }
.lp-section .mode-card strong, .lp-section .style-card strong { display: block; font-size: 0.92rem; margin-bottom: 4px; }
.lp-section .mode-card span, .lp-section .style-card span { font-size: 0.78rem; color: var(--text-secondary); line-height: 1.5; }
/* (.style-card's swatch dot and 3px top border were replaced by the card's own render —
   see the Visual styles block near the end of this file.) */

.style-chip-groups { display: flex; flex-direction: column; gap: 16px; margin: 4px 0 8px; }
.style-chip-group h5 { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-secondary); margin-bottom: 8px; }
.style-chip-row { display: flex; flex-wrap: wrap; gap: 7px; }
.style-chip {
  font-size: 0.76rem;
  padding: 5px 11px;
  border-radius: var(--radius-full);
  border: 1px solid var(--border);
  background: var(--bg-secondary);
  color: var(--text-secondary);
  transition: background-color var(--transition), border-color var(--transition), color var(--transition), transform 0.15s ease;
}
/* These are inert text, not links into the app — a hover response still matters so a
   33-chip wall doesn't read as a static spec dump, but only a tint/lift, never a pointer
   cursor that promises a click will do something. */
.style-chip:hover {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--text-primary);
  transform: translateY(-1px);
}

@media (max-width: 700px) {
  .lp-section { padding: 4px 16px 30px; }
  /* At a 320px viewport (288px content width after this section's own 16px padding),
     two 140px tracks + a 10px gap need 290px — 2px over — so auto-fit was silently
     dropping to a single column on the smallest still-supported phones, turning the
     28-card style grid into one long ~1400px-tall stack instead of 2 columns. 128px
     leaves enough room to spare even at 320px, and only ever adds columns (never removes
     any) at every wider width in this range. */
  .lp-section .mode-card-grid, .lp-section .style-card-grid { grid-template-columns: repeat(auto-fit, minmax(128px, 1fr)); gap: 10px; }
  .lp-section .mode-card, .lp-section .style-card { padding: 13px; }
}

/* Sticky header — plain on load, gains a hairline shadow + slight blur once the page has
   scrolled (toggled via .is-scrolled on the header, see home.html's own small script). A
   static header reads fine at the very top where it sits flush against the hero; once
   content is scrolling underneath it, an unstyled bar and the page content look like two
   unrelated flat planes touching by accident rather than one surface floating over another. */
.lp-page-header.home-header {
  position: sticky;
  top: 0;
  z-index: 20;
  transition: box-shadow 0.2s ease, backdrop-filter 0.2s ease;
}
/* Header joins the hero's own load-in cascade (see .hero-in below) instead of just
   appearing pre-rendered — the "whole page came alive at once" first impression falls
   apart if the bar above it was the one static element. The brand accent line
   (header::after, desktop-only) gets its own quick wipe-in on top of that so it reads
   as drawn, not just faded. */
@media (prefers-reduced-motion: no-preference) {
  .home-header { opacity: 0; animation: headerSlideIn 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards; }
}
@keyframes headerSlideIn { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } }
@media (min-width: 769px) and (prefers-reduced-motion: no-preference) {
  .home-header::after { animation: headerAccentGrow 0.8s cubic-bezier(0.16, 1, 0.3, 1) 0.25s backwards; }
}
@keyframes headerAccentGrow { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0 0 0 0); } }
/* Anchor-nav targets need to clear the sticky header's own height, or a jump lands with
   the section's heading hidden directly underneath the bar. --header-h already exists as
   the app's own header-height token; reused here since this header is the same height. */
#modes, #styles, #about-app, #guides, #faq { scroll-margin-top: calc(var(--header-h) + 12px); }
@media (prefers-reduced-motion: no-preference) {
  html:has(.home-header) { scroll-behavior: smooth; }
}
.lp-page-header.home-header.is-scrolled {
  box-shadow: 0 1px 0 var(--border), var(--shadow-sm);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  background: color-mix(in srgb, var(--glass-bg) 92%, transparent);
}

/* Hero live demo — a small, real canvas animation of the tracking engine's own visual
   language (colored blob + bounding box + label), not a stock gradient-mesh hero image.
   Purely decorative/illustrative (not the real OpenCV pipeline — that only exists once
   main.js loads at /app/*), but drawn with the exact same shapes a real tracked object
   gets on the actual canvas, so it reads as "this is what happens" rather than generic
   motion-for-motion's-sake. */
.hero-demo-wrap { max-width: 640px; margin: 40px auto 0; }
.hero-demo-panel {
  position: relative;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--bg-secondary);
  overflow: hidden;
  box-shadow: var(--shadow-md);
}
/* Frames the live canvas as a real product window (the "browser chrome" motif every
   Linear/Vercel/Raycast demo screenshot uses) instead of a bare floating rectangle —
   traffic-light dots read as decorative UI chrome, not as functional controls, so no
   button semantics/labels are needed on them. */
.hero-demo-chrome {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 12px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-primary);
}
.hero-demo-dots { display: flex; gap: 6px; flex-shrink: 0; }
.hero-demo-dots span { width: 9px; height: 9px; border-radius: 50%; display: block; }
.hero-demo-dots span:nth-child(1) { background: #ff5f57; }
.hero-demo-dots span:nth-child(2) { background: #febc2e; }
.hero-demo-dots span:nth-child(3) { background: #28c840; }
.hero-demo-chrome-label {
  font-family: 'JetBrains Mono', monospace; font-size: 0.68rem; color: var(--text-secondary);
  flex: 1; text-align: center; margin-right: 25px; /* balances the dots' width so the label optically centers */
}
.hero-demo-panel canvas { display: block; width: 100%; height: auto; aspect-ratio: 16 / 9; }
.hero-demo-badge {
  position: absolute;
  left: 12px; bottom: 12px;
  display: flex;
  align-items: center;
  gap: 7px;
  background: rgba(14, 14, 18, 0.72);
  color: #fff;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  padding: 6px 10px;
  border-radius: var(--radius-full);
  backdrop-filter: blur(6px);
}
.hero-demo-dot { width: 6px; height: 6px; border-radius: 50%; background: #ff4444; flex-shrink: 0; }
@media (prefers-reduced-motion: no-preference) {
  .hero-demo-dot { animation: heroDemoPulse 1.4s ease-in-out infinite; }
}
@keyframes heroDemoPulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
/* A small, honest credibility line under the demo mockup — real underlying tech, not
   fabricated "trusted by" logos this project has no basis to show. */
.hero-stack-line {
  text-align: center;
  font-size: 0.72rem;
  color: var(--text-secondary);
  margin: 14px 0 0;
  letter-spacing: 0.01em;
}

/* Nav: smooth in-page jumps that land below the sticky header instead of under it, plus
   a live "which section am I looking at" indicator (.active, toggled by home.html's own
   IntersectionObserver) — small, standard wayfinding polish rather than a new pattern. */
html:has(#homeHeader) { scroll-behavior: smooth; }
#modes, #styles, #faq, #about-app, #guides { scroll-margin-top: calc(var(--header-h, 76px) + 12px); }
.home-header .header-nav a { position: relative; }
.home-header .header-nav a.active { color: var(--accent); }
.home-header .header-nav a.active::after {
  content: '';
  position: absolute;
  left: 10px; right: 10px; bottom: 2px;
  height: 2px;
  background: var(--accent);
  border-radius: var(--radius-full);
}

/* Scroll reveal — a plain, restrained fade+rise, not a slide-in-from-every-direction
   showcase. Starts already visible (opacity:1) so anything above the fold or a
   reduced-motion visitor never has to wait on JS/an observer to see content; JS only
   adds .reveal-armed (which drops opacity to 0) once it's confirmed both an observer AND
   motion are actually wanted, then .in-view flips each element back in as it scrolls up. */
.reveal { transform: translateY(0); opacity: 1; }
@media (prefers-reduced-motion: no-preference) {
  .reveal.reveal-armed {
    opacity: 0;
    transform: translateY(16px);
    transition: opacity 0.5s ease, transform 0.5s cubic-bezier(0.22, 1, 0.36, 1);
  }
  .reveal.reveal-armed.in-view { opacity: 1; transform: translateY(0); }

  /* Card grids cascade in one card at a time instead of arriving as a single flat block —
     the extra bit of "the page is alive" motion premium SaaS feature grids (Linear's own
     included) use. The grid itself no longer moves as a unit (overridden back to visible/
     static via the extra class below, which outranks .reveal.reveal-armed's specificity);
     each card gets its own fade+rise on a short per-index delay instead, capped after the
     8th card so a 28-card style grid doesn't take over a second to finish appearing. */
  .mode-card-grid.reveal.reveal-armed,
  .style-card-grid.reveal.reveal-armed { opacity: 1; transform: none; }
  .mode-card-grid.reveal .mode-card,
  .style-card-grid.reveal .style-card {
    opacity: 0;
    transform: translateY(14px);
    transition: opacity 0.45s ease, transform 0.45s cubic-bezier(0.22, 1, 0.36, 1);
  }
  .mode-card-grid.reveal.in-view .mode-card,
  .style-card-grid.reveal.in-view .style-card { opacity: 1; transform: translateY(0); }
  .mode-card-grid .mode-card:nth-child(2), .style-card-grid .style-card:nth-child(2) { transition-delay: 0.04s; }
  .mode-card-grid .mode-card:nth-child(3), .style-card-grid .style-card:nth-child(3) { transition-delay: 0.08s; }
  .mode-card-grid .mode-card:nth-child(4), .style-card-grid .style-card:nth-child(4) { transition-delay: 0.12s; }
  .mode-card-grid .mode-card:nth-child(5), .style-card-grid .style-card:nth-child(5) { transition-delay: 0.16s; }
  .mode-card-grid .mode-card:nth-child(6), .style-card-grid .style-card:nth-child(6) { transition-delay: 0.2s; }
  .mode-card-grid .mode-card:nth-child(7), .style-card-grid .style-card:nth-child(7) { transition-delay: 0.24s; }
  .mode-card-grid .mode-card:nth-child(8), .style-card-grid .style-card:nth-child(8) { transition-delay: 0.28s; }
  .mode-card-grid .mode-card:nth-child(n+9), .style-card-grid .style-card:nth-child(n+9) { transition-delay: 0.32s; }

  /* Same per-card cascade as the mode/style grids above, applied to the "Why Blob Track
     Pro" feature grid — it and the FAQ accordion right below it were the only sections on
     this page with no .reveal treatment at all, popping in flat while everything above
     them on the same page already got this fade+rise. */
  .seo-grid.reveal.reveal-armed { opacity: 1; transform: none; }
  .seo-grid.reveal .seo-card {
    opacity: 0;
    transform: translateY(14px);
    transition: opacity 0.45s ease, transform 0.45s cubic-bezier(0.22, 1, 0.36, 1);
  }
  .seo-grid.reveal.in-view .seo-card { opacity: 1; transform: translateY(0); }
  .seo-grid .seo-card:nth-child(2) { transition-delay: 0.04s; }
  .seo-grid .seo-card:nth-child(3) { transition-delay: 0.08s; }
  .seo-grid .seo-card:nth-child(4) { transition-delay: 0.12s; }
  .seo-grid .seo-card:nth-child(5) { transition-delay: 0.16s; }
  .seo-grid .seo-card:nth-child(6) { transition-delay: 0.2s; }
}

/* Hero load-in — a staggered fade+rise the instant the page paints, the first-impression
   "the whole page just came alive" motion the launch pages this is modeled on (Linear,
   Vercel, YC-batch company sites) all share. Deliberately separate from .reveal above:
   .reveal is scroll-triggered for content that starts off-screen, but everything here is
   already inside the initial viewport — animating it off an IntersectionObserver would
   either fire instantly anyway (redundant machinery) or, worse, not fire until the
   observer's first tick and cause a visible pop-in flash. A plain CSS animation with a
   per-element delay needs neither JS nor an observer to guarantee it runs exactly once,
   on load, in order. */
@media (prefers-reduced-motion: no-preference) {
  .hero-in { opacity: 0; animation: heroFadeUp 0.7s cubic-bezier(0.16, 1, 0.3, 1) forwards; }
  .hero-in-1 { animation-delay: 0.05s; }
  .hero-in-2 { animation-delay: 0.13s; }
  .hero-in-3 { animation-delay: 0.21s; }
  .hero-in-4 { animation-delay: 0.29s; }
  .hero-in-5 { animation-delay: 0.37s; }
  .hero-in-6 { animation-delay: 0.45s; }
  .hero-in-7 { animation-delay: 0.53s; }
}
@keyframes heroFadeUp {
  from { opacity: 0; transform: translateY(16px) scale(0.985); filter: blur(4px); }
  to { opacity: 1; transform: translateY(0) scale(1); filter: blur(0); }
}

/* ─── Marketing-page color pass ───
   The white/green/gray system reads clean but flat across a long scroll of mostly-white
   sections — this adds color in the two ways that don't fight the flat/square system:
   a soft full-bleed gradient wash (green + its warm complement, var(--accent-warm)) behind
   the one section that's already inherently the most colorful (28 style cards, each with
   their own --card-accent), and a light tint of each card's own accent color instead of a
   flat white card body. Nothing here is a new UI pattern, just more of the color that was
   already sitting unused in --card-accent.
*/
#styles { position: relative; isolation: isolate; padding-top: 36px; padding-bottom: 40px; }
#styles::before {
  content: '';
  position: absolute;
  top: 0; left: 50%; width: 100vw; height: 100%;
  transform: translateX(-50%);
  z-index: -1;
  background: linear-gradient(160deg, var(--accent-soft) 0%, var(--bg-primary) 42%, var(--bg-primary) 58%, var(--accent-warm-soft) 100%);
}
.style-card {
  background: color-mix(in srgb, var(--card-accent, var(--accent)) 5%, var(--bg-primary));
}
.style-card:hover { background: color-mix(in srgb, var(--card-accent, var(--accent)) 11%, var(--bg-primary)); }

/* A second kicker color (warm, not the default green/accent-soft) so a page with more
   than one kicker chip reads as two distinct callouts rather than the same pill repeated. */
.lp-kicker.lp-kicker-warm { color: var(--accent-warm); background: var(--accent-warm-soft); }
.lp-kicker.lp-kicker-warm::before { background: var(--accent-warm); }

/* Mode-card icons get a small colored badge instead of a bare line icon floating on
   white — cycles through green/warm/a couple of the style palette's other hues so the
   "9 Tracking Modes" grid isn't the one all-monochrome block on the page. Colors chosen
   independently of each mode's own meaning (there's no "blob is green" convention to
   preserve), purely for rhythm across the 9 cards. */
/* .lp-section-scoped — bare .mode-card .icon would otherwise also match (and visibly
   break, with an unwanted padded/colored badge) the app's own empty-state mode-card
   icons, which have their own plain bare-line-icon rule at style.css:1121. See the
   .lp-section .mode-card-grid comment further up for the full explanation of why this
   whole component pair needs the ancestor scope in this shared stylesheet. */
.lp-section .mode-card .icon {
  width: 22px; height: 22px; padding: 7px; margin: 0 0 10px;
  border-radius: var(--radius-md);
  background: var(--icon-tint, var(--accent-soft));
  color: var(--icon-color, var(--accent));
  box-sizing: content-box;
}
.lp-section .mode-card:nth-of-type(2) .icon { --icon-tint: var(--accent-warm-soft); --icon-color: var(--accent-warm); }
.lp-section .mode-card:nth-of-type(4) .icon { --icon-tint: #eaf2ff; --icon-color: #2563eb; }
.lp-section .mode-card:nth-of-type(6) .icon { --icon-tint: var(--accent-warm-soft); --icon-color: var(--accent-warm); }
.lp-section .mode-card:nth-of-type(8) .icon { --icon-tint: #eaf2ff; --icon-color: #2563eb; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .lp-section .mode-card:nth-of-type(4) .icon,
  :root:not([data-theme="light"]) .lp-section .mode-card:nth-of-type(8) .icon { --icon-tint: #14233d; --icon-color: #60a5fa; }
}
:root[data-theme="dark"] .lp-section .mode-card:nth-of-type(4) .icon,
:root[data-theme="dark"] .lp-section .mode-card:nth-of-type(8) .icon { --icon-tint: #14233d; --icon-color: #60a5fa; }

/* ─── Themed display fonts for the "visual style" SEO pages only ───
   Applied via a class on that page's own <h1> (see the curated list this was scripted
   against) — reuses fonts already loaded for the in-app label-font toggle (zero extra
   network cost) rather than fetching anything new. Scoped to .lp-hero h1 so it can never
   leak into the shared header/footer/nav wordmark elsewhere on the same page. */
.lp-hero h1.lp-h1-scifi { font-family: 'Space Age', var(--font-display); letter-spacing: 0.01em; text-transform: uppercase; font-size: clamp(1.4rem, 3.6vw, 2.3rem); }
.lp-hero h1.lp-h1-mono { font-family: 'JetBrains Mono', monospace; letter-spacing: -0.01em; text-transform: uppercase; }
.lp-hero h1.lp-h1-noir { font-family: 'Playfair Display', Georgia, serif; font-style: italic; letter-spacing: 0; }
.lp-hero h1.lp-h1-glitch {
  font-family: 'JetBrains Mono', monospace; letter-spacing: -0.01em;
  text-shadow: -2px 0 #ff2a6d, 2px 0 #05d9e8;
}


/* ─────────────────────────────────────────────────────────────────────────────
   Cinematic LUT browser, Color Adjustments, Keyframes (2026-09-12)
   ───────────────────────────────────────────────────────────────────────────── */

/* LUT browser — replaces the flat row of 11 text buttons. With 33 built-in looks a
   wrapping grid of text labels would push the rest of the preset screen off the
   bottom, so it's a single horizontally-scrolling strip of rendered thumbnails
   (each one the actual grade applied to a sample image, or the loaded footage),
   filtered by category chips above it. */
.lut-swatch-filters { display: flex; gap: 4px; flex-wrap: wrap; margin-bottom: 8px; }
.lut-filter-chip {
  font: 600 0.68rem/1 var(--font-body, inherit);
  padding: 5px 9px;
  border-radius: var(--radius-full);
  border: 1px solid var(--border);
  background: var(--bg-primary);
  color: var(--text-secondary);
  cursor: pointer;
  transition: background-color var(--transition), color var(--transition), border-color var(--transition);
}
.lut-filter-chip:hover { color: var(--text-primary); border-color: var(--text-secondary); }
.lut-filter-chip.active { background: var(--text-primary); color: var(--bg-primary); border-color: var(--text-primary); }
.lut-swatch-row {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 92px;
  gap: 8px;
  overflow-x: auto;
  padding: 2px 2px 10px;
  scroll-snap-type: x proximity;
  scrollbar-width: thin;
}
.lut-swatch {
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--text-secondary);
  font-size: 0.68rem;
  font-weight: 500;
  text-align: left;
  cursor: pointer;
  scroll-snap-align: start;
}
.lut-swatch-preview {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 10;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--bg-tertiary) center / cover no-repeat;
  transition: border-color var(--transition), box-shadow var(--transition), transform 0.2s cubic-bezier(0.16, 1, 0.3, 1);
}
.lut-swatch-preview.is-none {
  background-image: repeating-linear-gradient(135deg, var(--bg-secondary) 0 6px, var(--bg-tertiary) 6px 12px);
  display: flex; align-items: center; justify-content: center;
  color: var(--text-secondary); font-size: 0.66rem; font-weight: 600;
}
.lut-swatch-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.2; }
.lut-swatch:hover .lut-swatch-preview { border-color: var(--text-secondary); transform: translateY(-1px); }
.lut-swatch:hover { color: var(--text-primary); }
.lut-swatch.active { color: var(--accent); font-weight: 700; }
.lut-swatch.active .lut-swatch-preview { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft), 0 0 0 3px var(--accent); }
.lut-swatch:focus-visible { outline: none; }
.lut-swatch:focus-visible .lut-swatch-preview { box-shadow: 0 0 0 2px var(--bg-primary), 0 0 0 4px var(--accent); }
.lut-swatch-source { font-size: 0.66rem; color: var(--text-secondary); margin: -4px 0 6px; }

.lut-import-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.lut-import-row .lut-import-btn { font-size: 11px; padding: 5px 10px; flex: 0 0 auto; }
.lut-import-status { font-size: 0.7rem; color: var(--text-secondary); flex: 1 1 160px; min-width: 0; }
.lut-import-status.is-error { color: var(--danger); }
.lut-import-status.is-ok { color: var(--accent); font-weight: 600; }

/* Color Adjustments */
.grade-adjust { border: 1px solid var(--border); border-radius: var(--radius-md); padding: 12px; background: var(--bg-secondary); }
.grade-adjust > .label-row { margin-bottom: 4px; }
.grade-adjust .grade-title { display: inline-flex; align-items: center; gap: 6px; font-weight: 700; }
.grade-adjust .grade-modified-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); display: none; }
.grade-adjust.is-modified .grade-modified-dot { display: inline-block; }
.grade-reset-btn { font-size: 11px !important; padding: 3px 8px !important; }
.grade-wheels { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px 12px; margin-top: 10px; }
.grade-wheel { min-width: 0; background: var(--bg-primary); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 8px 9px 6px; }
.grade-wheel .label-row { gap: 6px; }
.grade-wheel label { display: inline-flex; align-items: center; gap: 6px; font-size: 0.78rem; font-weight: 700; }
.grade-sub { font-weight: 500; color: var(--text-secondary); font-size: 0.66rem; }
.grade-chip {
  width: 12px; height: 12px; border-radius: 50%;
  border: 1px solid var(--border);
  background: #808080;
  flex-shrink: 0;
}
.grade-channels { margin-top: 4px; }
.grade-channels summary {
  font-size: 0.66rem; font-weight: 600; color: var(--text-secondary); cursor: pointer; list-style: none;
  padding: 2px 0; user-select: none;
}
.grade-channels summary::-webkit-details-marker { display: none; }
.grade-channels summary::before { content: '▸ '; }
.grade-channels[open] summary::before { content: '▾ '; }
.grade-channel { display: grid; grid-template-columns: 12px minmax(0, 1fr) 30px; align-items: center; gap: 6px; margin-top: 3px; }
.grade-channel > span:first-child { font: 700 0.62rem/1 'JetBrains Mono', monospace; }
/* The HSL panel's rows reuse .grade-channel (Hue/Sat/Lum sliders per color band) but their
   first-column label is "Hue"/"Sat"/"Lum" — 3 characters, not the single R/G/B letter the
   12px column above was sized for. */
#hslGroup .grade-channel { grid-template-columns: 28px minmax(0, 1fr) 30px; }
.grade-channel .value-display { font-size: 0.62rem; text-align: right; min-width: 0; }
.grade-channel-r > span:first-child { color: #e5484d; }
.grade-channel-g > span:first-child { color: #30a46c; }
.grade-channel-b > span:first-child { color: #3e7bfa; }
.grade-channel-r .slider { accent-color: #e5484d; }
.grade-channel-g .slider { accent-color: #30a46c; }
.grade-channel-b .slider { accent-color: #3e7bfa; }
.grade-extra { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px 12px; margin-top: 10px; }
.grade-extra .control-group { margin: 0; }
.grade-extra label { font-size: 0.76rem; }
.grade-temperature-track { background: linear-gradient(90deg, #5aa9ff, #d9d9d9 50%, #ffb347); }
.grade-tint-track { background: linear-gradient(90deg, #3fcf6a, #d9d9d9 50%, #e05ad8); }
.grade-extra .grade-temperature-track, .grade-extra .grade-tint-track { height: 3px; border-radius: 2px; margin-top: 2px; opacity: 0.8; }
@media (max-width: 420px) {
  .grade-wheels, .grade-extra { grid-template-columns: minmax(0, 1fr); }
}

/* Keyframe markers under the Playback progress bar — visible even with the editor closed. */
.progress-group { position: relative; }
.kf-progress-markers { position: relative; height: 7px; margin: 1px 8px 0; pointer-events: none; }
.kf-progress-marker {
  position: absolute; top: 0; width: 6px; height: 6px; margin-left: -3px;
  background: var(--accent-warm, #e0562e); transform: rotate(45deg); border-radius: 1px;
}

/* Keyframes pane */
.kf-toolbar { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; margin-bottom: 8px; }
.kf-toolbar .kf-add-select { flex: 1 1 150px; min-width: 0; font-size: 0.78rem; padding-top: 6px; padding-bottom: 6px; }
.kf-btn { font-size: 11px !important; padding: 5px 8px !important; flex: 0 0 auto; }
.kf-btn:disabled { opacity: 0.45; }
.kf-note { font-size: 0.72rem; color: var(--text-secondary); background: var(--bg-secondary); border: 1px dashed var(--border); border-radius: var(--radius-sm); padding: 8px 10px; margin-bottom: 8px; }

.kf-timeline {
  --kf-head: 104px;
  --kf-tail: 22px;
  /* Zoom multiplies the *time axis* only (the width left over after the fixed head/tail
     columns), so 1x is exactly the old layout and every percentage-based position inside a
     lane keeps its meaning at any zoom. See kfSetZoom() in main.js. */
  --kf-zoom: 1;
  --kf-axis: calc(100% - var(--kf-head) - var(--kf-tail));
  position: relative;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg-primary);
  overflow-x: auto;
  overflow-y: hidden;
  user-select: none;
  -webkit-user-select: none;
}
.kf-timeline.is-disabled { opacity: 0.55; pointer-events: none; }
.kf-ruler-row { display: grid; grid-template-columns: var(--kf-head) minmax(0, 1fr) var(--kf-tail); border-bottom: 1px solid var(--border); background: var(--bg-secondary); min-width: calc(var(--kf-head) + var(--kf-tail) + var(--kf-axis) * var(--kf-zoom)); }
.kf-ruler-label { font: 700 0.6rem/1 'JetBrains Mono', monospace; color: var(--text-secondary); padding: 7px 8px; letter-spacing: 0.06em; text-transform: uppercase; }
.kf-ruler { position: relative; height: 22px; cursor: ew-resize; touch-action: none; }
.kf-tick {
  position: absolute; top: 0; bottom: 0; padding-left: 3px;
  border-left: 1px solid var(--border);
  font: 500 0.58rem/22px 'JetBrains Mono', monospace; color: var(--text-secondary);
  white-space: nowrap;
}
.kf-tracks { max-height: 260px; overflow-y: auto; }
.kf-track { display: grid; grid-template-columns: var(--kf-head) minmax(0, 1fr) var(--kf-tail); border-bottom: 1px solid var(--border); min-height: 34px; min-width: calc(var(--kf-head) + var(--kf-tail) + var(--kf-axis) * var(--kf-zoom)); }
.kf-track:last-child { border-bottom: 0; }
.kf-track-head {
  display: flex; flex-direction: column; justify-content: center; align-items: flex-start; gap: 1px;
  min-width: 0; padding: 4px 8px; border: 0; border-right: 1px solid var(--border);
  background: var(--bg-primary); color: var(--text-primary); text-align: left; cursor: pointer;
}
.kf-track-name { font-size: 0.7rem; font-weight: 700; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kf-track-value { font: 500 0.6rem/1.2 'JetBrains Mono', monospace; color: var(--text-secondary); max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kf-track.is-selected .kf-track-head { background: var(--accent-soft); box-shadow: inset 3px 0 0 var(--accent); }
.kf-track.is-selected .kf-lane { background: color-mix(in srgb, var(--accent-soft) 45%, var(--bg-primary)); }
.kf-lane { position: relative; min-width: 0; cursor: crosshair; touch-action: none; }
.kf-curve { position: absolute; inset: 3px 0; width: 100%; height: calc(100% - 6px); pointer-events: none; }
.kf-curve-area { fill: var(--accent-soft); }
.kf-curve-line { fill: none; stroke: var(--accent); stroke-width: 1.5; }
.kf-seg {
  position: absolute; top: 6px; bottom: 6px;
  border-radius: 3px; background: var(--accent-soft); border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  color: var(--accent-hover); font-size: 0.6rem; font-weight: 700; line-height: 1; padding: 0 4px 0 11px;
  display: flex; align-items: center; overflow: hidden; white-space: nowrap; pointer-events: none;
}
.kf-seg.is-off { background: repeating-linear-gradient(135deg, var(--bg-secondary) 0 4px, transparent 4px 8px); border-color: var(--border); color: var(--text-secondary); font-weight: 500; }
.kf-key {
  position: absolute; top: 50%; width: 12px; height: 12px; margin: -6px 0 0 -6px; padding: 0;
  background: var(--bg-primary); border: 2px solid var(--accent); border-radius: 2px;
  transform: rotate(45deg); cursor: grab; z-index: 2; touch-action: none;
  transition: transform 0.12s ease, background-color var(--transition);
}
.kf-key::after { content: ''; position: absolute; inset: -8px; } /* bigger hit area for fingers */
.kf-key.is-hold { border-radius: 0; transform: none; }
.kf-key:hover { background: var(--accent-soft); }
.kf-key.is-selected { background: var(--accent); }
.kf-key.is-dragging { cursor: grabbing; transform: rotate(45deg) scale(1.25); }
.kf-key.is-hold.is-dragging { transform: scale(1.25); }
.kf-key:focus-visible { outline: 2px solid var(--text-primary); outline-offset: 2px; }
.kf-track-remove {
  border: 0; border-left: 1px solid var(--border); background: var(--bg-primary);
  color: var(--text-secondary); font-size: 0.9rem; cursor: pointer; padding: 0;
}
.kf-track-remove:hover { color: var(--danger); background: var(--bg-secondary); }
.kf-playhead-layer {
  position: absolute; top: 0; bottom: 0; left: var(--kf-head);
  width: calc(var(--kf-axis) * var(--kf-zoom));
  pointer-events: none; z-index: 3;
}
.kf-playhead { position: absolute; top: 0; bottom: 0; width: 0; border-left: 2px solid var(--danger); margin-left: -1px; }
.kf-playhead::before {
  content: ''; position: absolute; top: 0; left: -6px;
  border-left: 5px solid transparent; border-right: 5px solid transparent; border-top: 7px solid var(--danger);
}
.kf-empty { padding: 16px 12px; font-size: 0.74rem; color: var(--text-secondary); text-align: center; line-height: 1.5; }
.kf-summary { font-size: 0.68rem; color: var(--text-secondary); margin-top: 6px; min-height: 1em; }

.kf-inspector { margin-top: 10px; border: 1px solid var(--border); border-radius: var(--radius-md); padding: 10px; background: var(--bg-secondary); }
.kf-inspector-head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; margin-bottom: 8px; }
.kf-inspector-head strong { font-size: 0.8rem; }
.kf-muted { font-size: 0.66rem; color: var(--text-secondary); }
.kf-inspector-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
.kf-field { display: flex; flex-direction: column; gap: 4px; min-width: 0; margin-top: 8px; }
.kf-inspector-grid .kf-field { margin-top: 0; }
.kf-field > span { font-size: 0.64rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-secondary); }
.kf-field .text-input, .kf-field .dropdown { font-size: 0.78rem; padding-top: 6px; padding-bottom: 6px; }
.kf-value-slot .toggle-control { margin: 0; }
.kf-number-editor { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; align-items: center; }
.kf-inspector-actions { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 10px; }
.kf-inspector-actions .btn { font-size: 11px; padding: 5px 8px; flex: 1 1 auto; }
.kf-time-combo { display: flex; gap: 4px; }
.kf-time-combo .text-input { flex: 1; min-width: 0; text-align: center; }
.kf-mini-btn { font-size: 12px !important; padding: 2px 8px !important; flex: 0 0 auto; }

.kf-segment { margin-top: 10px; border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--bg-primary); }
.kf-segment > summary { cursor: pointer; padding: 9px 10px; font-size: 0.76rem; font-weight: 700; list-style: none; display: flex; align-items: center; gap: 8px; }
.kf-segment > summary::-webkit-details-marker { display: none; }
.kf-segment > summary::before { content: '+'; display: inline-flex; width: 16px; height: 16px; align-items: center; justify-content: center; border-radius: 3px; background: var(--accent-soft); color: var(--accent); font-weight: 800; }
.kf-segment[open] > summary::before { content: '–'; }
.kf-segment-body { padding: 0 10px 10px; }
.kf-wide-btn { width: 100%; margin-top: 10px; font-size: 0.78rem; }
.kf-seg-status { display: block; font-size: 0.7rem; color: var(--accent); margin-top: 6px; min-height: 1em; }

@media (max-width: 768px) {
  .kf-timeline { --kf-head: 88px; --kf-tail: 28px; }
  .kf-track { min-height: 42px; }
  .kf-key { width: 16px; height: 16px; margin: -8px 0 0 -8px; }
  .lut-swatch-row { grid-auto-columns: 84px; }
}

/* ─────────────────────────────────────────────────────────────────────────────
   Guide pages & homepage upgrade (2026-09-12): preset spec card, steps, "take it
   further", related guides, closing CTA band, LUT gallery + before/after, keyframe
   timeline illustration. Every rule is scoped to lp-* classes that the app's own
   index.html never uses — this stylesheet is shared with the app.
   ───────────────────────────────────────────────────────────────────────────── */
.lp-page-header .header-nav a.nav-cta {
  background: var(--accent); color: var(--on-accent); padding: 8px 14px; border-radius: var(--radius-md);
  font-weight: 700; text-decoration: none; opacity: 1;
}
.lp-page-header .header-nav a.nav-cta:hover { background: var(--accent-hover); color: var(--on-accent); }

.lp-hero p.lp-preset-note {
  display: inline-block; max-width: calc(100% - 8px);
  margin: 18px auto 0; padding: 6px 13px 6px 10px;
  font-size: 0.8rem; line-height: 1.3; color: var(--text-secondary);
  border: 1px dashed var(--border); border-radius: var(--radius-full); background: var(--bg-primary);
}
.lp-preset-note strong { color: var(--text-primary); }
.lp-preset-dot { display: inline-block; vertical-align: 1px; margin-right: 8px; width: 8px; height: 8px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }

/* Steps + preset spec */
.lp-setup { padding-top: 20px; }
.lp-setup-grid { display: grid; grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr); gap: 32px; align-items: start; max-width: 1100px; margin: 0 auto; }
.lp-setup .lp-kicker { margin-bottom: 12px; }
.lp-setup h2, .lp-block-title {
  font-family: var(--font-display); font-size: clamp(1.4rem, 2.6vw, 1.95rem); font-weight: 800;
  letter-spacing: -0.02em; line-height: 1.15; margin: 0 0 20px;
}
.lp-steps { list-style: none; margin: 0; padding: 0; }
.lp-steps li { display: grid; grid-template-columns: 40px minmax(0, 1fr); gap: 14px; position: relative; padding-bottom: 22px; }
.lp-steps li:last-child { padding-bottom: 0; }
.lp-steps li:not(:last-child)::before { content: ''; position: absolute; left: 19px; top: 44px; bottom: 4px; width: 1px; background: var(--border); }
.lp-step-num {
  width: 40px; height: 40px; display: flex; align-items: center; justify-content: center;
  border-radius: var(--radius-md); border: 1px solid var(--border); background: var(--bg-primary);
  font: 700 0.95rem/1 'JetBrains Mono', monospace; color: var(--accent);
}
.lp-steps h3 { font-size: 1.02rem; font-weight: 700; margin: 8px 0 5px; letter-spacing: -0.01em; }
.lp-steps p { font-size: 0.88rem; color: var(--text-secondary); line-height: 1.6; margin: 0; }
.lp-steps a { color: var(--accent); font-weight: 600; }

.lp-spec-card {
  position: sticky; top: 24px;
  background: var(--bg-primary); border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: inset 0 3px 0 var(--accent), var(--shadow-md);
  overflow: hidden;
}
.lp-spec-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; padding: 18px 18px 12px; border-bottom: 1px solid var(--border); }
.lp-spec-kicker { font: 700 0.62rem/1 'JetBrains Mono', monospace; text-transform: uppercase; letter-spacing: 0.1em; color: var(--text-secondary); }
.lp-spec-head strong { font-family: var(--font-display); font-size: 1.08rem; font-weight: 800; letter-spacing: -0.01em; text-align: right; }
.lp-spec-list { margin: 0; padding: 4px 18px; }
.lp-spec-list > div { display: grid; grid-template-columns: 116px minmax(0, 1fr); gap: 12px; padding: 9px 0; border-bottom: 1px dashed var(--border); font-size: 0.84rem; }
.lp-spec-list > div:last-child { border-bottom: 0; }
.lp-spec-list dt { color: var(--text-secondary); }
.lp-spec-list dd { margin: 0; font-weight: 600; color: var(--text-primary); display: flex; align-items: center; gap: 7px; flex-wrap: wrap; min-width: 0; }
.lp-spec-swatch { width: 14px; height: 14px; border-radius: 3px; border: 1px solid var(--border); display: inline-block; flex-shrink: 0; }
.lp-spec-hex { font: 500 0.72rem/1 'JetBrains Mono', monospace; color: var(--text-secondary); }
.lp-spec-chips { display: flex; flex-wrap: wrap; gap: 6px; padding: 6px 18px 16px; }
.lp-spec-chip { font-size: 0.72rem; font-weight: 600; padding: 4px 9px; border-radius: var(--radius-full); background: var(--accent-soft); color: var(--accent-hover); }
.lp-spec-cta { display: flex !important; justify-content: center; margin: 0 18px; }
.lp-spec-cta .btn-arrow { margin-left: 7px; }
.lp-spec-foot { font-size: 0.72rem; color: var(--text-secondary); text-align: center; padding: 10px 18px 16px; margin: 0; }

/* Take it further */
.lp-further-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; max-width: 1100px; margin: 0 auto; }
.lp-further-card {
  display: flex; flex-direction: column; gap: 8px; padding: 14px 14px 16px;
  border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--bg-primary);
  color: inherit; text-decoration: none;
  transition: border-color var(--transition), transform var(--transition);
}
.lp-further-card:hover { border-color: var(--accent); transform: translateY(-2px); }
.lp-further-card strong { font-size: 1rem; margin-top: 4px; }
.lp-further-card .lp-further-desc { font-size: 0.82rem; color: var(--text-secondary); line-height: 1.55; }
.lp-further-card em { font-style: normal; font-size: 0.8rem; font-weight: 700; color: var(--accent); margin-top: auto; padding-top: 4px; }
.lp-further-visual { height: 68px; border-radius: var(--radius-sm); overflow: hidden; display: grid; border: 1px solid var(--border); background: var(--bg-secondary); }
.lp-further-visual.lp-lut-strip { grid-template-columns: repeat(4, 1fr); }
.lp-lut-strip img { width: 100%; height: 100%; object-fit: cover; display: block; }
.lp-further-visual.lp-mini-timeline { padding: 10px 12px; gap: 7px; align-content: center; }
.lp-mini-timeline i { display: block; height: 9px; border-radius: 3px; background: var(--bg-tertiary); position: relative; }
.lp-mini-timeline i::after { content: ''; position: absolute; top: 0; bottom: 0; left: var(--s); width: var(--w); background: var(--accent); border-radius: 3px; opacity: 0.8; }
.lp-further-visual.lp-mini-icon { place-items: center; color: var(--accent); }
.lp-mini-icon svg { width: 30px; height: 30px; }

/* Related guides */
.lp-related-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 12px; max-width: 1100px; margin: 0 auto; }
.lp-related-card {
  display: flex; flex-direction: column; gap: 6px; padding: 14px 16px;
  border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--bg-primary);
  color: inherit; text-decoration: none; transition: border-color var(--transition), background-color var(--transition);
}
.lp-related-card:hover { border-color: var(--text-primary); background: var(--bg-secondary); }
.lp-related-cat { font: 700 0.6rem/1 'JetBrains Mono', monospace; letter-spacing: 0.08em; text-transform: uppercase; color: var(--accent-warm); }
.lp-related-card strong { font-size: 0.92rem; }
.lp-related-card .lp-related-desc { font-size: 0.78rem; color: var(--text-secondary); line-height: 1.5; }

/* Closing CTA band — inverted panel, so it reads as the page's end rather than one
   more card. Tokens flip with the theme, so it's dark-on-light in light mode and
   light-on-dark in dark mode. */
.lp-cta-band { padding: 8px 24px 56px; }
.lp-cta-band-inner {
  max-width: 1100px; margin: 0 auto; display: flex; align-items: center; justify-content: space-between; gap: 24px;
  padding: 30px 34px; border-radius: var(--radius-lg);
  background: var(--text-primary); color: var(--bg-primary);
  position: relative; overflow: hidden; isolation: isolate;
}
.lp-cta-band-inner::before {
  content: ''; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background-image: radial-gradient(color-mix(in srgb, var(--bg-primary) 18%, transparent) 1px, transparent 1px);
  background-size: 18px 18px;
  -webkit-mask-image: linear-gradient(90deg, transparent 30%, black);
  mask-image: linear-gradient(90deg, transparent 30%, black);
}
.lp-cta-band h2 { font-family: var(--font-display); font-size: clamp(1.3rem, 2.6vw, 1.85rem); font-weight: 800; letter-spacing: -0.02em; line-height: 1.15; margin: 0 0 6px; }
.lp-cta-band p { margin: 0; opacity: 0.72; font-size: 0.9rem; line-height: 1.5; }
.lp-cta-band-actions { display: flex; gap: 10px; flex-wrap: wrap; flex-shrink: 0; }
.lp-cta-band .btn { padding: 13px 22px; }
.lp-cta-band .btn-arrow { margin-left: 7px; }
.lp-cta-band .secondary-btn { background: transparent; color: var(--bg-primary); border: 1px solid color-mix(in srgb, var(--bg-primary) 35%, transparent); }
.lp-cta-band .secondary-btn:hover { background: color-mix(in srgb, var(--bg-primary) 10%, transparent); color: var(--bg-primary); }

/* Guide feature cards: hover lift only — they already carry their own 01-06 numbering.
   Guide pages only; home's #about-app cards have their own treatment. */
.lp-body .seo-content-section:not(#about-app) .seo-card { transition: border-color var(--transition), transform var(--transition); }
.lp-body .seo-content-section:not(#about-app) .seo-card:hover { border-color: var(--text-secondary); transform: translateY(-1px); }

/* LUT gallery + before/after compare (color grading guide) */
.lp-compare {
  --pos: 50%;
  position: relative; max-width: 820px; margin: 0 auto; aspect-ratio: 16 / 10;
  border-radius: var(--radius-lg); overflow: hidden; border: 1px solid var(--border); background: var(--bg-tertiary);
}
.lp-compare img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
.lp-compare .lp-compare-after { clip-path: inset(0 0 0 var(--pos)); }
.lp-compare::after {
  content: ''; position: absolute; top: 0; bottom: 0; left: var(--pos); width: 2px; margin-left: -1px;
  background: #fff; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.25); pointer-events: none;
}
.lp-compare-knob {
  position: absolute; top: 50%; left: var(--pos); width: 38px; height: 38px; margin: -19px 0 0 -19px; z-index: 2;
  border-radius: 50%; background: #fff; color: #111; display: flex; align-items: center; justify-content: center;
  font-size: 0.9rem; font-weight: 800; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); pointer-events: none;
}
.lp-compare-label {
  position: absolute; top: 12px; z-index: 2; padding: 4px 9px; border-radius: var(--radius-full);
  background: rgba(0, 0, 0, 0.6); color: #fff; font: 700 0.66rem/1 'JetBrains Mono', monospace; letter-spacing: 0.06em; text-transform: uppercase;
  pointer-events: none;
}
.lp-compare-label-before { left: 12px; }
.lp-compare-label-after { right: 12px; }
.lp-compare input[type="range"] { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: ew-resize; z-index: 3; }
.lp-compare-picks { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px; max-width: 820px; margin: 14px auto 0; }
.lp-compare-pick {
  font-size: 0.76rem; font-weight: 600; padding: 6px 11px; border-radius: var(--radius-full);
  border: 1px solid var(--border); background: var(--bg-primary); color: var(--text-secondary); cursor: pointer;
}
.lp-compare-pick:hover { color: var(--text-primary); border-color: var(--text-secondary); }
.lp-compare-pick[aria-pressed="true"] { background: var(--text-primary); color: var(--bg-primary); border-color: var(--text-primary); }
.lp-compare-note { text-align: center; font-size: 0.76rem; color: var(--text-secondary); margin: 10px auto 0; max-width: 620px; }

.lp-lut-group-title {
  font: 700 0.7rem/1 'JetBrains Mono', monospace; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-secondary);
  max-width: 1100px; margin: 30px auto 12px; display: flex; align-items: center; gap: 12px;
}
.lp-lut-group-title::after { content: ''; flex: 1; height: 1px; background: var(--border); }
.lp-lut-gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(168px, 1fr)); gap: 14px; max-width: 1100px; margin: 0 auto; }
.lp-lut-gallery figure { margin: 0; }
.lp-lut-gallery img { width: 100%; height: auto; aspect-ratio: 16 / 10; object-fit: cover; display: block; border-radius: var(--radius-sm); border: 1px solid var(--border); }
.lp-lut-gallery figcaption { display: flex; justify-content: space-between; align-items: baseline; gap: 6px; font-size: 0.8rem; font-weight: 600; margin-top: 7px; }
.lp-lut-gallery figcaption span { font: 500 0.68rem/1 'JetBrains Mono', monospace; color: var(--text-secondary); white-space: nowrap; }

/* Keyframe timeline illustration (keyframes guide + homepage) */
.lp-kf-demo {
  --name: 116px;
  position: relative; max-width: 820px; margin: 0 auto;
  border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--bg-primary);
  overflow: hidden; font-size: 0.72rem; text-align: left;
}
.lp-kf-ruler, .lp-kf-row { display: grid; grid-template-columns: var(--name) minmax(0, 1fr); }
.lp-kf-ruler { background: var(--bg-secondary); border-bottom: 1px solid var(--border); }
.lp-kf-ruler > span { border-right: 1px solid var(--border); padding: 7px 10px; font: 700 0.58rem/1 'JetBrains Mono', monospace; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-secondary); }
.lp-kf-ruler > div { display: flex; justify-content: space-between; padding: 7px 8px; font: 500 0.6rem/1 'JetBrains Mono', monospace; color: var(--text-secondary); }
.lp-kf-row { border-bottom: 1px solid var(--border); min-height: 40px; }
.lp-kf-row:last-of-type { border-bottom: 0; }
.lp-kf-name { display: flex; align-items: center; padding: 0 10px; font-weight: 700; border-right: 1px solid var(--border); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lp-kf-lane { position: relative; }
.lp-kf-seg {
  position: absolute; top: 9px; bottom: 9px; display: flex; align-items: center; padding: 0 8px;
  border-radius: 3px; background: var(--accent-soft); border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  color: var(--accent-hover); font-style: normal; font-weight: 700; font-size: 0.64rem; white-space: nowrap; overflow: hidden;
}
.lp-kf-seg.is-off { background: repeating-linear-gradient(135deg, var(--bg-secondary) 0 4px, transparent 4px 8px); border-color: var(--border); color: var(--text-secondary); font-weight: 500; }
.lp-kf-lane svg { position: absolute; left: 0; top: 5px; width: 100%; height: calc(100% - 10px); overflow: visible; }
.lp-kf-lane svg .lp-kf-area { fill: var(--accent-soft); stroke: none; }
.lp-kf-lane svg .lp-kf-line { fill: none; stroke: var(--accent); stroke-width: 2; vector-effect: non-scaling-stroke; }
.lp-kf-lane b { position: absolute; top: 50%; width: 10px; height: 10px; margin: -5px 0 0 -5px; background: var(--bg-primary); border: 2px solid var(--accent); transform: rotate(45deg); }
.lp-kf-overlay { position: absolute; top: 0; bottom: 0; left: var(--name); right: 0; pointer-events: none; }
.lp-kf-playhead { position: absolute; top: 0; bottom: 0; left: 62%; border-left: 2px solid var(--danger); }
.lp-kf-playhead::before { content: ''; position: absolute; top: 0; left: -6px; border-left: 5px solid transparent; border-right: 5px solid transparent; border-top: 7px solid var(--danger); }
@media (prefers-reduced-motion: no-preference) {
  .lp-kf-playhead { animation: lpKfSweep 9s linear infinite; }
}
@keyframes lpKfSweep { from { left: 0%; } to { left: 100%; } }

/* Homepage "Color & Motion" split */
.lp-feature-split { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; max-width: 1100px; margin: 0 auto; }
.lp-feature-panel { border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 18px; background: var(--bg-primary); display: flex; flex-direction: column; gap: 10px; }
.lp-feature-panel h3 { font-family: var(--font-display); font-size: 1.28rem; font-weight: 800; letter-spacing: -0.015em; line-height: 1.2; margin: 8px 0 0; }
.lp-feature-panel p { font-size: 0.88rem; color: var(--text-secondary); line-height: 1.6; margin: 0; }
.lp-lut-mosaic { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 6px; }
.lp-lut-mosaic figure { margin: 0; position: relative; }
.lp-lut-mosaic img { width: 100%; height: auto; aspect-ratio: 16 / 10; object-fit: cover; border-radius: var(--radius-sm); display: block; }
.lp-lut-mosaic figcaption { position: absolute; left: 4px; bottom: 4px; font-size: 0.58rem; font-weight: 700; color: #fff; background: rgba(0, 0, 0, 0.58); padding: 2px 5px; border-radius: 3px; max-width: calc(100% - 8px); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lp-feature-panel .lp-kf-demo { --name: 96px; max-width: none; margin: 0; }
.lp-checklist { list-style: none; padding: 0; margin: 2px 0 0; display: grid; gap: 7px; }
.lp-checklist li { font-size: 0.84rem; line-height: 1.45; padding-left: 22px; position: relative; }
.lp-checklist li::before { content: ''; position: absolute; left: 2px; top: 4px; width: 10px; height: 5px; border-left: 2px solid var(--accent); border-bottom: 2px solid var(--accent); transform: rotate(-45deg); }
.lp-text-link { font-weight: 700; font-size: 0.86rem; color: var(--accent); text-decoration: none; margin-top: auto; padding-top: 4px; }
.lp-text-link:hover { text-decoration: underline; }

@media (max-width: 900px) {
  .lp-setup-grid, .lp-feature-split { grid-template-columns: minmax(0, 1fr); }
  .lp-spec-card { position: static; }
  .lp-further-grid { grid-template-columns: minmax(0, 1fr); }
  .lp-cta-band-inner { flex-direction: column; align-items: flex-start; }
}
@media (max-width: 600px) {
  .lp-spec-list > div { grid-template-columns: 96px minmax(0, 1fr); }
  .lp-lut-mosaic { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .lp-cta-band { padding: 4px 16px 40px; }
  .lp-cta-band-inner { padding: 24px 20px; }
  .lp-cta-band-actions { width: 100%; flex-direction: column; }
  .lp-cta-band .btn { width: 100%; justify-content: center; text-align: center; }
  .lp-kf-demo { --name: 84px; }
  .lp-lut-gallery { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
}

/* ═════════════════════════════════════════════════════════════════════════════
   Landing polish v2 (2026-09-13) — homepage + guide pages. Appended last on purpose
   so it overrides the earlier landing rules at equal specificity; every selector is
   lp-/seo-/hero-/style-card-scoped (see the .lp-section .mode-card comment above on why
   unscoped selectors here collide with the app's own empty-state components).
   ═════════════════════════════════════════════════════════════════════════════ */

/* Section rhythm — more air between sections, bigger section headlines. */
.lp-section { padding-top: 28px; padding-bottom: 76px; }
.lp-section-head { max-width: 720px; margin-bottom: 38px; }
.lp-section-head h2 { font-size: clamp(1.85rem, 3.6vw, 2.6rem); letter-spacing: -0.03em; line-height: 1.08; margin-bottom: 12px; }
.lp-section-head p { font-size: 1rem; line-height: 1.65; }
/* Was 68/80 against the shared 28/76 rhythm every other section uses — the styles grid
   is the longest one on the page, so the extra gap stacked with its own first row's
   spacing and read as a gap in the page rather than a section break. */
#styles { padding-top: 28px; padding-bottom: 76px; }
#grade { scroll-margin-top: calc(var(--header-h, 76px) + 12px); }

/* Hero (homepage) */
.home-header + main .lp-hero { max-width: 1040px; padding-bottom: 64px; }
.home-header + main .lp-hero > p { font-size: 1.08rem; max-width: 660px; }
.lp-cta .btn { padding: 15px 30px; font-size: 0.95rem; }
.lp-hero .lp-cta .primary-btn { box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18), 0 10px 24px -12px color-mix(in srgb, var(--accent) 65%, transparent); }
.lp-body .hero-stats {
  display: inline-grid; grid-template-columns: repeat(5, auto);
  margin: 30px auto 0; padding: 14px 4px;
  border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--bg-primary);
}
.lp-body .hero-stat { padding: 2px 26px; }
.hero-stat-num { font-size: 1.9rem; font-weight: 800; letter-spacing: -0.03em; }
.hero-stat-label { font-size: 0.62rem; margin-top: 5px; }

/* The demo is a window into the product, and the product is a dark canvas app — so the
   window is dark in both themes. On the old light panel the pose-mode joints (drawn
   white) all but disappeared. */
.hero-demo-wrap { max-width: 920px; margin-top: 56px; position: relative; }
.hero-demo-wrap::before {
  content: ''; position: absolute; left: 10%; right: 10%; top: 18%; bottom: 2%; z-index: -1; pointer-events: none;
  background: radial-gradient(60% 60% at 50% 55%, color-mix(in srgb, var(--accent) 30%, transparent), transparent 72%);
  filter: blur(34px);
}
.hero-demo-panel {
  background: #0b0c10; border-color: rgba(255, 255, 255, 0.09);
  box-shadow: 0 0 0 1px rgba(14, 14, 18, 0.08), 0 44px 90px -44px rgba(14, 14, 18, 0.6);
}
.hero-demo-panel canvas { background: radial-gradient(rgba(255, 255, 255, 0.07) 1px, transparent 1px) 0 0 / 22px 22px, #0b0c10; }
.hero-demo-chrome { background: #15161c; border-bottom-color: rgba(255, 255, 255, 0.07); }
.hero-demo-chrome-label { color: rgba(255, 255, 255, 0.5); }
.hero-stack-line { margin-top: 20px; font-size: 0.76rem; }

/* How it works — three real cards instead of floating text with arrow glyphs. */
.how-steps { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; max-width: 1100px; }
.how-step-arrow { display: none; }
.how-step {
  max-width: none; text-align: left; padding: 24px 24px 26px;
  border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--bg-primary);
}
.how-step-num {
  width: auto; height: auto; padding: 5px 10px; margin-bottom: 20px; border-radius: var(--radius-full);
  font: 700 0.68rem/1 'JetBrains Mono', monospace; letter-spacing: 0.08em;
}
.how-step-num::before { content: 'STEP 0'; }
.how-step h3 { font-family: var(--font-display); font-size: 1.15rem; font-weight: 800; letter-spacing: -0.015em; margin-bottom: 8px; }
.how-step p { font-size: 0.9rem; line-height: 1.6; }

/* Tracking modes — 3×3, icon beside text: nine cards divide evenly instead of leaving
   a ragged second row in a 5-column grid. */
.lp-section .mode-card-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; max-width: 1100px; margin: 0 auto; }
.lp-section .mode-card {
  display: grid; grid-template-columns: auto minmax(0, 1fr); grid-template-rows: auto 1fr;
  column-gap: 14px; row-gap: 4px; align-items: start; text-align: left; padding: 18px 20px;
}
.lp-section .mode-card .icon { grid-row: 1 / span 2; margin: 0; }
.lp-section .mode-card strong { margin: 3px 0 0; font-size: 1rem; }
.lp-section .mode-card span { font-size: 0.85rem; }

/* Visual styles — each card leads with the look's own render. It used to lead with a
   synthetic tile (a dot, a glowing rectangle, the card's accent) that was identical on all
   27 cards bar the colour, so the one section whose whole job is showing 27 different looks
   showed the same look 27 times. The image is the accent's job now; the accent survives as
   the hover ring and the image's own tinted base. */
.lp-section .style-card-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; max-width: 1100px; margin: 0 auto; }
.lp-section .style-card {
  position: relative; overflow: hidden; padding: 0 0 16px;
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--bg-primary);
  transition: border-color var(--transition), box-shadow var(--transition), transform var(--transition);
}
.lp-section .style-card:hover {
  border-color: color-mix(in srgb, var(--card-accent, var(--accent)) 55%, var(--border));
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--card-accent, var(--accent)) 28%, transparent),
              0 14px 30px -22px color-mix(in srgb, var(--card-accent, var(--accent)) 75%, transparent);
  transform: translateY(-2px);
}
.lp-section .style-card img {
  display: block; width: 100%; height: auto; aspect-ratio: 16 / 10;
  object-fit: cover; background: #0c0d12;
  border-bottom: 2px solid var(--card-accent, var(--accent));
  transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
.lp-section .style-card:hover img { transform: scale(1.03); }
/* The colour key the old tile carried, kept as a hairline of the card's own accent along
   the image's bottom edge — a border rather than a positioned pseudo-element, so it lands
   on the image edge at every card width instead of at one guessed offset. */
.lp-section .style-card img { border-bottom: 2px solid var(--card-accent, var(--accent)); }

.lp-section .style-card strong, .lp-section .style-card > span { display: block; padding: 0 14px; }
.lp-section .style-card strong { margin: 14px 0 4px; }
.lp-section .style-card > span { font-size: 0.78rem; color: var(--text-secondary); line-height: 1.5; }

/* Full renderer list — centered like every other section. */
.style-chip-groups { align-items: center; text-align: center; max-width: 980px; margin-inline: auto; }
.style-chip-row { justify-content: center; }
.lp-section > .lp-cta { display: flex; justify-content: center; margin-bottom: 0; }

/* "Why" features + FAQ — no more boxed panel with left-aligned headings sitting between
   centered sections; the FAQ gets a readable measure instead of the full 1200px. */
.lp-body .seo-content-section, .lp-body .seo-content-section.glass-panel { border: 0; box-shadow: none; background: transparent; margin-top: 0; padding: 20px 24px 72px; }
.lp-body .seo-container article > .seo-kicker { display: table; margin: 0 auto; }
.lp-body .seo-container article > h3 { text-align: center; font-size: clamp(1.7rem, 3.2vw, 2.3rem); letter-spacing: -0.03em; line-height: 1.1; margin: 12px auto 32px; max-width: 760px; }
.lp-body .seo-container article > h3#faq { margin-top: 8px; }
.lp-body .seo-grid { grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 14px; max-width: 1100px; margin: 0 auto 72px; }
.lp-body .seo-card { padding: 20px 20px 22px; border-radius: var(--radius-lg); }
.lp-body .seo-card h4 { font-size: 1rem; margin-bottom: 8px; }
.lp-body .seo-container article .seo-card p { font-size: 0.87rem; line-height: 1.6; margin-bottom: 0; }
.lp-body .seo-faq-accordion { max-width: 820px; margin: 0 auto; }

/* Guide pages: related guides divide 6 → 3×2; header loses its boxed side borders. */
.lp-related-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.lp-page-header:not(.home-header) { border: 0 !important; border-bottom: 1px solid var(--border) !important; }
.lp-further .lp-section-head, .lp-related .lp-section-head { margin-bottom: 30px; }

@media (max-width: 900px) {
  .lp-section .mode-card-grid, .lp-related-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .lp-section .style-card-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .how-steps { grid-template-columns: minmax(0, 1fr); max-width: 560px; margin: 0 auto; }
}
@media (max-width: 640px) {
  .lp-section { padding-bottom: 56px; }
  .lp-body .hero-stats { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); width: 100%; padding: 12px 0; row-gap: 12px; }
  .lp-body .hero-stat { grid-column: span 2; padding: 0 4px; }
  .lp-body .hero-stat:nth-child(n+4) { grid-column: span 3; border-top: 1px solid var(--border); padding-top: 12px; }
  .lp-body .hero-stat:nth-child(4) { border-left: none; }
  .hero-stat-num { font-size: 1.45rem; }
  .hero-demo-wrap { margin-top: 36px; }
  .lp-section .mode-card-grid, .lp-related-grid { grid-template-columns: minmax(0, 1fr); }
  .lp-section .style-card-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  .lp-section .style-card::before { height: 78px; }
  .lp-section .style-card::after { top: 20px; width: 52px; height: 38px; }
  .lp-body .seo-grid { grid-template-columns: minmax(0, 1fr); margin-bottom: 52px; }
  .lp-body .seo-content-section, .lp-body .seo-content-section.glass-panel { padding: 8px 16px 56px; }
}
.lp-body .seo-container article > p { max-width: 760px; margin-left: auto; margin-right: auto; text-align: center; }

/* Headlines wrap as balanced lines, never a one-word orphan ("…Like an / Edit."). */
.lp-hero h1, .lp-section-head h2, .lp-block-title, .lp-setup h2, .lp-cta-band h2, .lp-body .seo-container article > h3 { text-wrap: balance; }
.lp-cta-band .btn { white-space: nowrap; flex: 0 0 auto; }
@media (max-width: 640px) {
  .home-header + main .lp-hero { padding-bottom: 36px; }
  .hero-demo-badge { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: calc(100% - 24px); font-size: 0.58rem; }
  .lp-cta-band .btn { white-space: normal; }
}

/* ─── Trim export + mobile UX (2026-09-13) ──────────────────────────────────── */
.trim-export-row { display: grid; grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr); gap: 8px; }
.trim-export-row .btn { font-size: 11px; padding: 7px 8px; display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
.trim-export-row .btn .icon { width: 14px; height: 14px; margin: 0; }
.trim-export-row .btn.is-busy { background: var(--bg-tertiary); border-color: var(--border); color: var(--text-primary); }
.trim-export-status { display: block; font-size: 0.7rem; color: var(--text-secondary); margin-top: 6px; min-height: 1em; }
.trim-export-status.is-error { color: var(--danger); }
.trim-export-status.is-ok { color: var(--accent); font-weight: 600; }
.progress-trim-track { position: relative; height: 4px; margin: 2px 8px 0; border-radius: 2px; background: var(--bg-tertiary); pointer-events: none; }
.progress-trim-track span { position: absolute; top: 0; bottom: 0; border-radius: 2px; background: var(--accent); }
/* Mobile scrub: the trim range painted under the progress fill as a second layer
   (background-image only, so the base rule's 4px strip sizing still applies). */
.mp-scrub.has-trim {
  background-image:
    linear-gradient(to right, #fff var(--progress), transparent var(--progress)),
    linear-gradient(to right, rgba(255, 255, 255, 0.22) var(--trim-a), color-mix(in srgb, var(--accent) 90%, transparent) var(--trim-a), color-mix(in srgb, var(--accent) 90%, transparent) var(--trim-b), rgba(255, 255, 255, 0.22) var(--trim-b));
}
.mp-trim-btn .icon { width: 17px; height: 17px; }
.mobile-player-bar.is-webcam .mp-trim-btn { display: none; }
.sheet-only-btn { display: none; }
.sheet-font-btn { font-weight: 800; font-size: 0.8rem; }

/* Color Adjustments header: the Reset button was rendering as bare floating text. */
.grade-adjust > .label-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.grade-adjust .grade-reset-btn { flex: 0 0 auto; background: var(--bg-tertiary); border: 1px solid var(--border); color: var(--text-primary); border-radius: var(--radius-sm); }

@media (max-width: 768px) {
  /* Floating theme/font toggle sat on top of the video's scrub bar once a clip loaded —
     it moves into the settings sheet instead (see #sheetThemeBtn). */
  body.has-source .theme-font-toggle { display: none; }
  .sheet-only-btn { display: inline-flex; align-items: center; justify-content: center; }
  /* Toasts sat on top of the transport bar and the sheet's tab row for up to 8 seconds;
     at the top, under the header, they cover nothing you need to tap. */
  /* At the top they then covered the quick-style bar instead; just above the player bar
     sits over the video's own letterbox space and covers neither row of controls. */
  body.has-source #undoToast { top: auto; bottom: calc(var(--bottom-nav-h, 60px) + 64px); }
  body.sidebar-open #undoToast { top: calc(var(--header-h, 60px) + 8px); bottom: auto; }
  /* The Settings nudge is the third bottom-anchored bubble, and at its own `bottom: nav +
     10px` it sat directly on the transport bar — measured on a 390x844 phone: bubble
     y722-774 against the bar's own y731-775 — so all four of the bar's right-hand buttons
     (Clips/Text/Trim/Fullscreen) took a tap that landed on the bubble's <span> instead. The
     right half of the transport bar was dead for the nudge's 6 seconds. Lifted into the same
     band as the toast above, for the same reason. */
  body.has-source .settings-nudge { bottom: calc(var(--bottom-nav-h, 60px) + 64px); }
  /* ...and the bubble's downward arrow goes with it: it only reads as "the Settings button
     down there" while the bubble sits flush above the nav. Once lifted, the arrow tip would
     point into the transport bar at a row of unrelated icons. */
  body.has-source .settings-nudge::after { display: none; }

  /* Compact quick-style bar (2026-09-14): 32px controls and tighter gaps take the bar from
     ~89px to ~75px of chrome over the preview. Same selectors as the originals above, so
     source order wins; selects keep 16px text (iOS zoom-on-focus threshold). */
  .mobile-quick-style-bar { gap: 4px; padding-top: 4px; }
  .mq-mode-row { gap: 6px; padding: 2px 8px 1px; }
  .mq-tool-row { gap: 6px; padding: 0 8px; }
  .mq-mode-row .mode-tab.mq-mode-pill { min-height: 32px; height: 32px; padding: 0 11px; gap: 5px; font-size: 0.66rem; }
  .mq-mode-row .mode-tab.mq-mode-pill .icon { width: 15px; height: 15px; }
  .mq-select.mq-style-select,
  .mq-select.mq-fill-style-select,
  .mq-select.mq-aspect-select { min-height: 32px; height: 32px; padding: 0 12px; }
  .mq-effect-pill { min-height: 32px; height: 32px; padding: 0 11px; }
  .mq-linewidth-wrap { min-height: 32px; height: 32px; }
  .mq-pill, .mq-color-wrap { width: 32px; height: 32px; }
  /* Taller sheet for the Clip Editor (trim, keyframes) — set by openClipEditorPane(). */
  .sidebar.content-expanded.sheet-tall { max-height: min(62dvh, calc(100dvh - var(--header-h) - var(--bottom-nav-h) - 16px)); }
  .trim-export-row { grid-template-columns: minmax(0, 1fr); }
  .trim-export-row .btn { min-height: 44px; font-size: 13px; }
  .kf-toolbar .kf-btn { min-height: 40px; min-width: 40px; }
  /* Same iOS zoom-on-focus issue as the .dropdown/.text-input fix elsewhere in this file
     — this select's own 0.78rem rule (above, outside any media query) ties this override
     on specificity, so it has to come after it in source order to actually win. */
  .kf-toolbar .kf-add-select { font-size: 16px; min-height: 44px; }
  .kf-segment > summary, .kf-inspector-actions .btn { min-height: 44px; }
  .grade-adjust .grade-reset-btn { min-height: 36px; padding: 6px 12px !important; }
  .grade-channels summary { padding: 8px 0; }
  .lut-filter-chip { padding: 8px 12px; font-size: 0.74rem; }
}

/* ─── Guide previews, effects hub, TouchDesigner explainer (2026-09-13) ──────── */
.lp-hero-preview { max-width: 780px; margin: 34px auto 0; }
.lp-hero-preview img {
  display: block; width: 100%; height: auto; aspect-ratio: 16 / 10; object-fit: cover;
  border-radius: var(--radius-lg); border: 1px solid var(--border); background: #0b0c10;
  box-shadow: 0 34px 80px -44px rgba(14, 14, 18, 0.6);
}
.lp-hero-preview figcaption { margin-top: 10px; font-size: 0.74rem; color: var(--text-secondary); }

.lp-hub-count { font: 700 0.62rem/1 'JetBrains Mono', monospace; padding: 3px 7px; border-radius: var(--radius-full); background: var(--bg-tertiary); color: var(--text-secondary); letter-spacing: 0.04em; }
.lp-hub-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 14px; max-width: 1100px; margin: 0 auto; }
.lp-hub-card {
  display: flex; flex-direction: column; gap: 6px; padding: 0 0 16px; overflow: hidden;
  border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--bg-primary);
  color: inherit; text-decoration: none; transition: border-color var(--transition), transform var(--transition);
}
.lp-hub-card:hover { border-color: var(--accent); transform: translateY(-2px); }
.lp-hub-thumb { display: block; aspect-ratio: 16 / 10; background: #0c0d12; border-bottom: 1px solid var(--border); margin-bottom: 8px; overflow: hidden; }
.lp-hub-thumb img { display: block; width: 100%; height: 100%; object-fit: cover; transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1); }
.lp-hub-card:hover .lp-hub-thumb img { transform: scale(1.04); }
.lp-hub-thumb.is-empty { background: radial-gradient(rgba(255, 255, 255, 0.08) 1px, transparent 1px) 0 0 / 14px 14px, radial-gradient(55% 70% at 50% 50%, color-mix(in srgb, var(--accent) 22%, transparent), transparent 70%), #0c0d12; }
.lp-hub-card strong { font-size: 0.95rem; padding: 0 14px; }
.lp-hub-card .lp-hub-desc { font-size: 0.8rem; color: var(--text-secondary); line-height: 1.5; padding: 0 14px; }

.lp-node-chain { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 8px; max-width: 900px; margin: 0 auto 28px; }
.lp-node { font: 600 0.8rem/1 'JetBrains Mono', monospace; padding: 10px 13px; border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--bg-primary); white-space: nowrap; }
.lp-node b { color: var(--accent); font-weight: 800; }
.lp-node-arrow { color: var(--text-secondary); font-size: 1.1rem; }
.lp-td-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; max-width: 1100px; margin: 0 auto; }
.lp-td-card { border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--bg-primary); padding: 20px 20px 22px; }
.lp-td-card h3 { font-family: var(--font-display); font-size: 1.08rem; font-weight: 800; letter-spacing: -0.01em; margin: 0 0 8px; }
.lp-td-card p { font-size: 0.87rem; line-height: 1.6; color: var(--text-secondary); margin: 0; }
@media (max-width: 900px) { .lp-td-grid { grid-template-columns: minmax(0, 1fr); } }
@media (max-width: 640px) {
  .lp-hub-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  .lp-hub-card .lp-hub-desc { display: none; }
  .lp-hub-card strong { font-size: 0.82rem; padding: 0 10px; }
  .lp-node-arrow { transform: rotate(90deg); }
  .lp-node-chain { flex-direction: column; }
}

/* ─── Home: "What is blob tracking?" explainer (2026-09-13) ─────────────────── */
.lp-explainer-grid {
  list-style: none; margin: 0 auto; padding: 0; max-width: 1100px;
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; counter-reset: none;
}
.lp-explainer-step {
  position: relative; padding: 22px 20px 24px;
  border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--bg-primary);
}
.lp-explainer-step:not(:last-child)::after {
  content: ''; position: absolute; top: 34px; right: -12px; width: 10px; height: 10px;
  border-top: 2px solid var(--border); border-right: 2px solid var(--border); transform: rotate(45deg);
}
.lp-explainer-num { display: inline-block; font: 700 0.72rem/1 'JetBrains Mono', monospace; color: var(--accent); letter-spacing: 0.08em; margin-bottom: 14px; }
.lp-explainer-step h3 { font-family: var(--font-display); font-size: 1.08rem; font-weight: 800; letter-spacing: -0.015em; margin-bottom: 8px; }
.lp-explainer-step p { font-size: 0.87rem; line-height: 1.6; color: var(--text-secondary); margin: 0; }
#what-is-blob-tracking { scroll-margin-top: calc(var(--header-h, 76px) + 12px); }
.lp-explainer-more { max-width: 760px; margin: 24px auto 0; text-align: center; font-size: 0.95rem; line-height: 1.7; color: var(--text-secondary); }
.lp-explainer-more a { color: var(--text-primary); font-weight: 600; text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }
.lp-explainer-more a:hover { color: var(--accent); }
@media (max-width: 900px) {
  .lp-explainer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .lp-explainer-step::after { display: none; }
}
@media (max-width: 560px) {
  .lp-explainer-grid { grid-template-columns: minmax(0, 1fr); }
}

/* ═══ Round 3 (2026-09-14): before/after heroes, sticky mobile CTA, footer, hub filters ═══ */

/* Before/after hero slider. Registering --pos lets the one-time hint animation interpolate it;
   the animation has no fill, so once it ends (or the visitor drags) the inline value rules. */
@property --pos { syntax: '<percentage>'; inherits: true; initial-value: 50%; }
.lp-hero-preview .lp-hero-compare {
  max-width: none; aspect-ratio: 16 / 10; border-radius: var(--radius-lg);
  box-shadow: 0 34px 80px -44px rgba(14, 14, 18, 0.6); background: #0b0c10;
}
.lp-hero-preview .lp-hero-compare img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  border: 0; border-radius: 0; box-shadow: none; aspect-ratio: auto;
}
.lp-hero-compare .lp-compare-label { top: 14px; }
@media (prefers-reduced-motion: no-preference) {
  .lp-hero-compare:not(.is-touched) { animation: lpCompareHint 2.2s cubic-bezier(0.45, 0, 0.2, 1) 1.1s 1; }
}
@keyframes lpCompareHint { 0% { --pos: 50%; } 35% { --pos: 32%; } 70% { --pos: 66%; } 100% { --pos: 50%; } }

/* Sticky mobile CTA */
.lp-sticky-cta {
  display: none; position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
  padding: 10px 14px calc(10px + env(safe-area-inset-bottom));
  background: color-mix(in srgb, var(--bg-primary) 92%, transparent);
  -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
  border-top: 1px solid var(--border);
  transform: translateY(110%); transition: transform 0.28s cubic-bezier(0.16, 1, 0.3, 1);
}
.lp-sticky-cta-inner { display: flex; align-items: center; justify-content: space-between; gap: 12px; max-width: 600px; margin: 0 auto; }
.lp-sticky-cta-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; }
.lp-sticky-cta-text strong { font-size: 0.92rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lp-sticky-cta-text span { font-size: 0.7rem; color: var(--text-secondary); }
.lp-sticky-cta .btn { flex: 0 0 auto; padding: 11px 18px; }
.lp-sticky-cta .btn-arrow { margin-left: 6px; }
@media (max-width: 768px) {
  .lp-sticky-cta { display: block; }
  .lp-sticky-cta.is-visible { transform: translateY(0); }
}

/* Footer: toggle buttons read as plain headings on desktop, collapse into rows on phones. */
.footer-group-toggle {
  all: unset; display: flex; align-items: center; gap: 8px; width: 100%;
  font: inherit; letter-spacing: inherit; text-transform: inherit; color: inherit; cursor: default;
}
.footer-group-count { display: none; }
@media (max-width: 768px) {
  .lp-body .footer-guides.js-collapsible .footer-guides-groups { gap: 0; }
  .lp-body .footer-guides.js-collapsible .footer-guides-group { border-top: 1px solid rgba(255, 255, 255, 0.12); }
  .lp-body .footer-guides.js-collapsible .footer-guides-group:last-child { border-bottom: 1px solid rgba(255, 255, 255, 0.12); }
  .lp-body .footer-guides.js-collapsible .footer-guides-group h4 { margin: 0; opacity: 1; }
  .lp-body .footer-guides.js-collapsible .footer-group-toggle { cursor: pointer; padding: 15px 2px; opacity: 0.85; font-size: 0.78rem; }
  .lp-body .footer-guides.js-collapsible .footer-group-count {
    display: inline-flex; margin-left: auto; padding: 2px 8px; border-radius: 999px;
    background: rgba(255, 255, 255, 0.1); font-size: 0.66rem; letter-spacing: 0;
  }
  .lp-body .footer-guides.js-collapsible .footer-group-toggle::after {
    content: ''; width: 7px; height: 7px; margin: 0 4px 3px 6px;
    border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
    transform: rotate(45deg); transition: transform 0.2s ease;
  }
  .lp-body .footer-guides.js-collapsible .is-open .footer-group-toggle::after { transform: rotate(-135deg); margin-bottom: -3px; }
  .lp-body .footer-guides.js-collapsible .footer-guides-group:not(.is-open) ul { display: none; }
  .lp-body .footer-guides.js-collapsible .footer-guides-group ul { padding: 0 2px 16px; gap: 11px; }
  .lp-body .footer-guides.js-collapsible .footer-guides-group li { font-size: 0.86rem; }
}

/* Effects hub toolbar */
.lp-hub-toolbar { max-width: 1100px; margin: 0 auto 6px; display: flex; flex-wrap: wrap; align-items: center; gap: 10px 12px; }
.lp-hub-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.lp-hub-chip {
  font: 600 0.78rem/1 inherit; padding: 9px 14px; border-radius: var(--radius-full);
  border: 1px solid var(--border); background: var(--bg-primary); color: var(--text-secondary); cursor: pointer;
  transition: background-color var(--transition), color var(--transition), border-color var(--transition);
}
.lp-hub-chip:hover { color: var(--text-primary); border-color: var(--text-secondary); }
.lp-hub-chip[aria-pressed="true"] { background: var(--text-primary); color: var(--bg-primary); border-color: var(--text-primary); }
.lp-hub-search {
  margin-left: auto; display: flex; align-items: center; gap: 8px; min-width: 240px;
  border: 1px solid var(--border); border-radius: var(--radius-md); padding: 0 12px; background: var(--bg-primary);
  transition: border-color var(--transition), box-shadow var(--transition);
}
.lp-hub-search svg { width: 15px; height: 15px; color: var(--text-secondary); flex-shrink: 0; }
.lp-hub-search input { border: 0; outline: 0; background: transparent; padding: 10px 0; font: inherit; font-size: 0.88rem; color: var(--text-primary); width: 100%; }
.lp-hub-search:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.lp-hub-result { order: 3; width: 100%; font: 600 0.7rem/1 'JetBrains Mono', monospace; color: var(--text-secondary); letter-spacing: 0.04em; }
.lp-hub-empty { text-align: center; color: var(--text-secondary); padding: 48px 0; }
.lp-hub-card[hidden], .lp-hub-group[hidden] { display: none !important; }
@media (max-width: 640px) {
  .lp-hub-search { margin-left: 0; width: 100%; min-width: 0; }
  .lp-hub-chips { flex-wrap: nowrap; overflow-x: auto; width: 100%; padding-bottom: 4px; scrollbar-width: none; }
  .lp-hub-chip { white-space: nowrap; }
}

/* App (desktop): the theme/font toggle sat on top of the sidebar's own controls (Auto Color,
   Cinematic LUT dropdowns) — it now floats over the canvas area's corner instead. */
@media (min-width: 769px) {
  .theme-font-toggle { right: calc(var(--sidebar-w, 380px) + 16px); bottom: 16px; }
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   Vis Exo hero — split-stage redesign (homepage only)
   ──────────────────────────────────────────────────────────────────────────────────────
   Scoped to .lp-hero-home so the other 75 pages keep the centered hero they share. The
   layout is copy-left / live-stage-right on desktop, single-column below 1080px. The
   backdrop is still the monochrome dot grid (marketing pages in this genre do not need a
   color-gradient mesh), but the aura layers add the "something is running live here" cue
   that a tracking product should lead with, and the whole thing leans toward the pointer.
   Animations are limited to transform/opacity on their own layers, so they never trigger
   layout, and every one of them is switched off for prefers-reduced-motion.
   ══════════════════════════════════════════════════════════════════════════════════════ */
.lp-hero-home {
  display: grid;
  grid-template-columns: minmax(0, 1.04fr) minmax(0, 0.96fr);
  align-items: center;
  gap: 48px;
  max-width: 1240px;
  padding: 54px 32px 50px;
  text-align: left;
  overflow: hidden; /* the aura blobs bleed past the section edges by design */
}
/* Same dot grid, aimed at the copy column rather than the middle of a centered stack. */
.lp-hero-home::before {
  height: calc(100% + 52px);
  background-size: 26px 26px;
  -webkit-mask-image: radial-gradient(ellipse 58% 80% at 26% 42%, black 0%, transparent 72%);
  mask-image: radial-gradient(ellipse 58% 80% at 26% 42%, black 0%, transparent 72%);
}
/* Warm counter-glow, bottom-right, so the stage reads as lit from the product itself. */
.lp-hero-home::after {
  content: '';
  position: absolute;
  z-index: -1;
  right: -170px;
  bottom: -190px;
  width: 560px;
  height: 560px;
  border-radius: 50%;
  background: radial-gradient(closest-side, color-mix(in srgb, var(--accent-warm) 15%, transparent), transparent 76%);
  filter: blur(30px);
  pointer-events: none;
}
.lp-hero-aura { position: absolute; inset: 0; z-index: -1; pointer-events: none; }
/* Pointer spotlight: one radial gradient whose center is two custom properties the hero's
   own pointermove handler updates (one write per frame, compositor-side). */
.lp-hero-aura::before {
  content: '';
  position: absolute;
  inset: -12%;
  background: radial-gradient(520px 520px at var(--hero-mx, 30%) var(--hero-my, 34%),
    color-mix(in srgb, var(--accent) 17%, transparent), transparent 68%);
  opacity: 0.75;
}
/* The accent glow the hero already had, now drifting instead of static. */
.lp-hero-aura::after {
  content: '';
  position: absolute;
  left: -180px;
  top: -150px;
  width: 640px;
  height: 640px;
  border-radius: 50%;
  background: radial-gradient(closest-side, color-mix(in srgb, var(--accent) 24%, transparent), transparent 78%);
  filter: blur(34px);
  opacity: 0.55;
}
@media (prefers-reduced-motion: no-preference) {
  .lp-hero-aura::after { animation: heroAuraDrift 19s ease-in-out infinite alternate; }
  .lp-hero-home::after { animation: heroAuraDriftWarm 26s ease-in-out infinite alternate; }
}
@keyframes heroAuraDrift {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to { transform: translate3d(140px, 84px, 0) scale(1.14); }
}
@keyframes heroAuraDriftWarm {
  from { transform: translate3d(0, 0, 0) scale(1.06); }
  to { transform: translate3d(-120px, -70px, 0) scale(0.94); }
}

.lp-hero-copy { position: relative; z-index: 1; min-width: 0; }
.lp-hero-home .lp-kicker { margin-bottom: 24px; }
.lp-hero-home h1 {
  font-size: clamp(2.25rem, 4.6vw, 3.85rem);
  line-height: 1.04;
  letter-spacing: -0.035em;
  margin-bottom: 18px;
  text-wrap: balance;
}
.lp-hero-home .hero-title-accent { color: var(--accent); }
.lp-hero-copy > p {
  max-width: 58ch;
  margin: 0 0 26px;
  font-size: 1.02rem;
  line-height: 1.62;
  color: var(--text-secondary);
}
.lp-hero-home .lp-cta { justify-content: flex-start; gap: 14px; margin-bottom: 0; }
.lp-hero-home .lp-cta .btn { padding: 15px 30px; }
/* The four trust points, as a quiet mono line instead of a second row of pills — the
   stats strip below already carries the numbers, so these stay text-weight. */
.lp-hero-trust {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 20px;
  margin: 24px 0 0;
  padding: 0;
  list-style: none;
}
.lp-hero-trust li {
  display: flex;
  align-items: center;
  gap: 8px;
  font: 600 0.7rem/1 'JetBrains Mono', monospace;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-secondary);
}
.lp-hero-trust li::before {
  content: '';
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent);
  flex-shrink: 0;
}
/* Stats: a rule-topped strip that reads as data, not a card. */
/* The proof band runs the full width under both columns — inside the copy column it wrapped
   onto a second ragged row at 1440, which is the opposite of the quick-scan strip it is. */
.lp-body .lp-hero-home .hero-stats {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  width: 100%;
  margin: 34px 0 0;
  padding: 18px 0 0;
  border: 0;
  border-top: 1px solid var(--border);
  border-radius: 0;
  background: none;
}
.lp-body .lp-hero-home .hero-stat {
  align-items: flex-start;
  gap: 4px;
  padding: 0 30px 0 0;
  margin: 0;
  border-left: 0;
  border-right: 1px solid var(--border);
}
.lp-body .lp-hero-home .hero-stat:last-child { padding-right: 0; border-right: 0; }
.lp-body .lp-hero-home .hero-stat-num { font-size: 1.75rem; font-weight: 800; letter-spacing: -0.035em; }
.lp-body .lp-hero-home .hero-stat-label { font-size: 0.58rem; letter-spacing: 0.08em; }

/* ── The live stage ─────────────────────────────────────────────────────────────────── */
.lp-hero-home .hero-demo-wrap { max-width: 620px; width: 100%; margin: 0; justify-self: end; }
.lp-hero-home .hero-demo-panel {
  border-radius: 16px;
  background: #0b0c10;
  border-color: rgba(255, 255, 255, 0.1);
  box-shadow:
    0 0 0 1px rgba(255, 255, 255, 0.03),
    0 40px 80px -42px rgba(0, 0, 0, 0.72),
    0 0 110px -50px color-mix(in srgb, var(--accent) 60%, transparent);
  transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}
.lp-hero-home .hero-demo-panel:hover { transform: translateY(-4px); }
.lp-hero-home .hero-demo-chrome { padding: 10px 13px; }
/* Right-aligned LIVE chip replaces the old single centered label, so the chrome bar now
   reads left-to-right as window → source → state. */
.lp-hero-home .hero-demo-chrome-label { text-align: left; margin-right: 0; color: rgba(255, 255, 255, 0.55); }
.hero-demo-live {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font: 700 0.58rem/1 'JetBrains Mono', monospace;
  letter-spacing: 0.14em;
  color: #ff8b8b;
  flex-shrink: 0;
}
.hero-demo-live i { width: 6px; height: 6px; border-radius: 50%; background: #ff4444; }
@media (prefers-reduced-motion: no-preference) {
  .hero-demo-live i { animation: heroDemoPulse 1.4s ease-in-out infinite; }
}
/* Bottom overlay: what is being tracked on the left, which mode is drawing it on the right. */
.hero-demo-overlay {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 8px;
  padding: 12px;
  pointer-events: none;
}
.lp-hero-home .hero-demo-badge { position: static; }
.hero-demo-modes { display: flex; gap: 6px; flex-shrink: 0; }
.hero-demo-modes span {
  font: 600 0.62rem/1 'JetBrains Mono', monospace;
  letter-spacing: 0.04em;
  padding: 6px 10px;
  border-radius: var(--radius-full);
  background: rgba(14, 14, 18, 0.74);
  border: 1px solid rgba(255, 255, 255, 0.08);
  color: rgba(255, 255, 255, 0.5);
  transition: color 0.35s ease, border-color 0.35s ease, background-color 0.35s ease;
}
.hero-demo-modes span.is-active {
  color: var(--on-accent);
  border-color: color-mix(in srgb, var(--accent) 70%, transparent);
  background: color-mix(in srgb, var(--accent) 32%, rgba(14, 14, 18, 0.74));
}
.lp-hero-home .hero-stack-line { text-align: right; margin-top: 18px; font-size: 0.74rem; }

/* ── Entrance: a rise-and-focus instead of a plain fade ─────────────────────────────── */
@media (prefers-reduced-motion: no-preference) {
  .lp-hero-home .hero-in { animation-name: heroRiseIn; animation-duration: 0.85s; }
  .lp-hero-home .hero-in-1 { animation-delay: 0.04s; }
  .lp-hero-home .hero-in-2 { animation-delay: 0.12s; }
  .lp-hero-home .hero-in-3 { animation-delay: 0.2s; }
  .lp-hero-home .hero-in-4 { animation-delay: 0.28s; }
  .lp-hero-home .hero-in-5 { animation-delay: 0.36s; }
  .lp-hero-home .hero-in-6 { animation-delay: 0.44s; }
  .lp-hero-home .hero-in-7 { animation-delay: 0.5s; }
  @keyframes heroRiseIn {
    from { opacity: 0; transform: translateY(22px) scale(0.988); filter: blur(7px); }
    to { opacity: 1; transform: none; filter: blur(0); }
  }
}

/* ── Responsive ─────────────────────────────────────────────────────────────────────── */
@media (max-width: 1080px) {
  .lp-hero-home {
    grid-template-columns: minmax(0, 1fr);
    gap: 42px;
    max-width: 880px;
    padding: 56px 24px 52px;
    text-align: center;
  }
  .lp-hero-home::before {
    -webkit-mask-image: radial-gradient(ellipse 74% 58% at 50% 24%, black 0%, transparent 74%);
    mask-image: radial-gradient(ellipse 74% 58% at 50% 24%, black 0%, transparent 74%);
  }
  .lp-hero-copy > p { margin-left: auto; margin-right: auto; }
  .lp-hero-home .lp-cta { justify-content: center; }
  .lp-hero-trust { justify-content: center; }
  .lp-hero-home .hero-demo-wrap { justify-self: center; }
  .lp-hero-home .hero-stack-line { text-align: center; }
}
@media (max-width: 640px) {
  .lp-hero-home { padding: 42px 18px 44px; gap: 34px; }
  .lp-hero-home h1 { font-size: clamp(1.95rem, 8.2vw, 2.5rem); }
  .lp-hero-copy > p { font-size: 0.98rem; }
  .lp-hero-home .lp-cta { gap: 10px; }
  /* Two columns of stats beat a ragged wrap; dividers come off with the columns. The blanket
     resets also undo the older mobile rule that had items 4-5 span the whole row. */
  .lp-body .lp-hero-home .hero-stats {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 16px 14px;
    margin-top: 34px;
    padding-top: 16px;
  }
  .lp-body .lp-hero-home .hero-stats .hero-stat {
    grid-column: auto;
    border-top: 0;
    border-right: 0;
    padding: 0;
    margin: 0;
    align-items: flex-start;
  }
  .lp-hero-home .hero-demo-badge { font-size: 0.58rem; padding: 5px 8px; }
  .lp-hero-home .hero-demo-modes span { padding: 5px 8px; font-size: 0.58rem; }
}
@media (prefers-reduced-motion: reduce) {
  .lp-hero-aura::before { display: none; }
  .lp-hero-aura::after,
  .lp-hero-home::after { animation: none; }
  .lp-hero-home .hero-demo-panel { transition: none; }
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   Mobile pass (≤768px) — touch targets, micro-type, iOS input zoom
   ──────────────────────────────────────────────────────────────────────────────────────
   Measured at 390px, the rows below were 16–34px tall. A 44px minimum is the platform
   floor for a touch target (Apple HIG) and the WCAG 2.2 target size: at 24px the FAQ
   rows and footer links are a tap-and-miss between two neighbours, which on a phone
   reads as the page being broken rather than small. Everything here is mobile-only —
   the desktop density is deliberate and unchanged.
   ══════════════════════════════════════════════════════════════════════════════════════ */
@media (max-width: 768px) {
  /* ── touch targets ── */
  .header-nav a,
  .lp-nav-jump {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    padding-top: 0;
    padding-bottom: 0;
  }
  /* The FAQ accordion is the densest interactive surface on every page (6-8 rows of
     24px). Padding rather than min-height alone, so the divider gap grows with it. */
  .seo-faq-accordion summary {
    min-height: 44px;
    padding-top: 12px;
    padding-bottom: 12px;
  }
  .lp-text-link { display: inline-flex; align-items: center; min-height: 44px; padding-top: 0; }
  .footer-links li a,
  .footer-links a,
  .footer-guides a { min-height: 44px; display: inline-flex; align-items: center; padding: 4px 0; }
  /* Breadcrumbs are navigation too: two 15px links stacked over the page title. */
  .lp-breadcrumb a { min-height: 44px; display: inline-flex; align-items: center; }
  /* Trim in/out fields were 38px tall; 44 covers the field plus its focus ring. */
  .vc-trim input { min-height: 44px; }
  /* Card headings that are links (related converters, related guides, FAQ follow-ups). */
  .seo-card h4 a,
  .lp-related-card a,
  .lp-related-grid a { display: inline-flex; align-items: center; min-height: 44px; }
  /* Inline prose links cannot carry a 44px row without breaking the sentence, but a few
     pixels of vertical padding still moves them off the exact-text hitbox. */
  .seo-content-section p a,
  .lp-explainer p a,
  .lp-step p a,
  .lp-spec-list a,
  .hero-copy p a { padding: 4px 0; }

  /* ── micro-type ── */
  /* Nothing that carries meaning should need a squint: these were 9.3-10.9px, which is
     below every readable-label floor, and they are all short strings that fit at 11px. */
  .header-badges span,
  .lp-badges span { font-size: 0.7rem; padding: 4px 9px; }
  .lp-spec-kicker { font-size: 0.7rem; }
  .lp-related-cat { font-size: 0.7rem; }
  .seo-kicker { font-size: 0.72rem; }
  .footer-group-count { font-size: 0.7rem; }
  /* Needs the .lp-body prefix to beat the desktop rule that set it to 0.58rem — without it
     the media query loses on specificity and the label stays 9.3px on a phone. */
  .lp-body .lp-hero-home .hero-stat-label { font-size: 0.66rem; letter-spacing: 0.06em; }
  .lp-hero-home .hero-demo-chrome-label,
  .lp-hero-home .hero-demo-badge,
  .lp-hero-home .hero-demo-modes span,
  .hero-demo-live { font-size: 0.66rem; }

  /* ── iOS focus zoom ── */
  /* Safari on iOS zooms the whole viewport when a control smaller than 16px takes focus,
     and does not zoom back out — the page is left horizontally scrolled. Text-entry
     controls only; buttons, ranges and checkboxes are unaffected. */
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not([type="file"]),
  select,
  textarea { font-size: 16px; }
}

/* ══════════════════════════════════════════════════════════════════════════════════════
   Settings density + sticky navigation (desktop)
   ──────────────────────────────────────────────────────────────────────────────────────
   The panel holds 25 sections, 183 control groups and 91 sliders; at the old spacing the
   visible tab alone measured 1411px inside a 924px panel — 1.5 screens of scrolling, most
   of it gap rather than content. This tightens the rhythm (padding, group gaps, control
   heights, section headers) and pins the two things you navigate by: the settings search
   at the top, and each section's own header while you are inside that section.

   Scoped to ≥769px on purpose: the mobile sheet has its own 44px-tall controls and
   taller tabs, and compacting those would trade a real thumb target for a few pixels.
   ══════════════════════════════════════════════════════════════════════════════════════ */
@media (min-width: 769px) {
  .sidebar { padding: 12px 12px 16px; gap: 10px; }
  .control-section { gap: 6px; padding-bottom: 9px; }
  .control-group { gap: 3px; }
  .toggle-control { min-height: 26px; gap: 7px; font-size: 0.76rem; }
  /* 20px is still ~2.5x the visible track height: a comfortable drag target on a mouse,
     which is the pointer this panel is designed around. */
  .slider { height: 20px; }
  .hint-text { font-size: 0.7rem; line-height: 1.4; }
  .sidebar .dropdown,
  .sidebar .text-input { padding: 6px 9px; min-height: 32px; font-size: 0.8rem; }
  .sidebar .mt-3 { margin-top: 8px; }
  .control-section h3 { font-size: 0.72rem; letter-spacing: 0.06em; padding: 2px 0; }

  /* The two things that make a long list navigable stay put: search at the top, and the
     current section's header just below it. Both bleed to the panel's edges so content
     can never show around them while they are pinned. */
  .sidebar-search-wrap {
    position: sticky;
    top: 0;
    z-index: 5;
    margin: 0 -12px;
    padding: 0 12px 8px;
    background: var(--bg-primary);
    border-bottom: 1px solid var(--border);
  }
  /* Height of the pinned search bar: 12px + 8px padding, a 32px input, 1px border, and the
     panel's own 1px border — kept in one variable so the section headers can never end up
     tucked underneath it. */
  .sidebar { --settings-pin-h: 56px; }
  /* Section headers deliberately do NOT pin. They used to (position:sticky, top:var(--settings-pin-h)),
     but a pinned title floats over the controls for the whole length of its section — and the
     band above and below a 16px-tall sticky box is transparent, so row content scrolled
     through it. On the long sections (Captions is ~1700px, Sound ~900px) that reads as a
     broken, overlapping panel rather than a helpful heading. The settings search stays pinned
     at the top, which is what actually navigates this list; the headers now scroll with their
     own content like every other row. */
  .control-section h3 { position: static; }
  /* A sticky element can never leave its containing block, so the panel keeps its top
     padding on the search bar instead of on itself — otherwise the header pins 12px down
     and row content shows in the band above it. */
  .sidebar { padding-top: 0; }
  .sidebar-search-wrap { padding-top: 12px; }

  /* Hints are supplementary: one line while scanning, full text on demand. Two lines plus
     a separate 15px "Show more" row cost 46px per control, and 110 of the 145 hints in
     this panel are clamped — that is the single biggest block of vertical space here. */
  .hint-text.hint-clamped { -webkit-line-clamp: 1; }
  .hint-toggle { padding: 0; margin-top: 1px; font-size: 0.66rem; line-height: 1.2; }
}

/* Timeline zoom. Buttons rather than a slider: there are five useful stops on a clip of any
   length, and a slider at this width cannot resolve them. */
.kf-zoom { display: inline-flex; align-items: center; gap: 2px; margin-left: auto; }
.kf-zoom-btn {
  border: 1px solid var(--border); background: var(--bg-primary); color: var(--text-secondary);
  border-radius: var(--radius-sm); font: 700 0.68rem/1 'JetBrains Mono', monospace;
  min-width: 24px; min-height: 24px; padding: 0 5px; cursor: pointer;
}
.kf-zoom-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.kf-zoom-btn:disabled { opacity: 0.4; cursor: default; }
.kf-zoom-label { font: 600 0.62rem/1 'JetBrains Mono', monospace; color: var(--text-secondary); min-width: 26px; text-align: center; }

/* Hold-to-compare. Reads as a modifier, not a second Reset: neutral while resting, filled
   while the bypass is actually engaged so the canvas state is never ambiguous. */
#lutCompareBtn { margin-left: 6px; }
#lutCompareBtn.is-active { background: var(--accent-warm-soft); border-color: var(--accent-warm); color: var(--accent-warm); }

/* ── Converter pages: the "which target" decision table ──────────────────────────────
   A real <table> (caption + scope attributes), because this is tabular data and screen
   readers should get it as such — and because a three-column comparison is the shape
   answer engines quote back for "mp4 vs webm" style questions. It scrolls rather than
   stacking: cutting rows up on a phone would lose the row/column relationship that is the
   entire point of the table. */
.conv-table-wrap { max-width: 1000px; margin: 0 auto; overflow-x: auto; }
.conv-table { width: 100%; min-width: 560px; border-collapse: collapse; font-size: 0.86rem; }
.conv-table caption { text-align: left; font: 600 0.72rem/1 'JetBrains Mono', monospace; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-secondary); padding: 0 0 10px; }
.conv-table th, .conv-table td { text-align: left; vertical-align: top; padding: 12px 14px; border-bottom: 1px solid var(--border); line-height: 1.5; }
.conv-table thead th { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-secondary); border-bottom: 1px solid var(--text-secondary); }
.conv-table tbody th { font-weight: 700; color: var(--text-primary); white-space: nowrap; }
.conv-table td { color: var(--text-secondary); }
.conv-table tbody tr:last-child th, .conv-table tbody tr:last-child td { border-bottom: 0; }
.conv-table tbody tr:hover th, .conv-table tbody tr:hover td { background: var(--bg-secondary); }
@media (max-width: 768px) { .conv-table th, .conv-table td { padding: 10px 12px; } }

/* Brief highlight on the Subtitles controls when the quick-bar pill or the greyed-out
   toggle sends someone there (revealSubtitlesSetup() in main.js), so the eye lands on
   Generate/Import rather than hunting through the Export tab. */
@keyframes subtitles-attention {
  0%, 100% { box-shadow: 0 0 0 0 transparent; }
  30% { box-shadow: 0 0 0 3px var(--accent); }
}
.subtitles-attention { animation: subtitles-attention 1.4s ease 2; border-radius: 8px; }
@media (prefers-reduced-motion: reduce) {
  .subtitles-attention { animation: none; outline: 2px solid var(--accent); outline-offset: 4px; }
}

/* ─── 2026-09-21 mobile pass: horizontal, compact navigation + readability ───
   Appended last on purpose: several of the rules above are same-specificity overrides
   spread across three different max-width:768px blocks, and a later rule is the only
   reliable way to win without raising specificity everywhere. */
@media (max-width: 768px) {
  /* Category tabs: one segmented row, icon BESIDE the label (was stacked above it, a
     44px-tall two-line box per tab). Sentence case instead of all-caps: the same words
     read faster and take less width, which is what lets icon + label share a row. If
     a very narrow phone still can't fit all five, the row scrolls sideways rather than
     truncating. */
  .sidebar-tabs {
    gap: 3px;
    padding: 3px;
    margin-bottom: 12px;
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    border-radius: 10px;
    overflow-x: auto;
    scrollbar-width: none;
    /* The sheet is a flex column, and a scroll container's automatic min-height is 0 —
       without this the row collapsed to its 8px of padding. */
    flex-shrink: 0;
  }
  .sidebar-tabs::-webkit-scrollbar { display: none; }
  .sidebar-tab {
    flex: 1 0 auto;
    flex-direction: row;
    gap: 5px;
    min-height: 36px;
    padding: 0 8px;
    border-radius: 7px;
    background: transparent;
    font-size: 0.74rem;
    font-weight: 650;
    text-transform: none;
    letter-spacing: 0;
    overflow: visible;
  }
  .sidebar-tab span { overflow: visible; }
  .sidebar-tab .tab-icon { width: 15px; height: 15px; }
  .sidebar-tab.active { box-shadow: 0 1px 6px color-mix(in srgb, var(--accent) 30%, transparent); }
}
/* Phones: all five labels only fit on one row without icons, so only the active tab
   keeps its icon (it doubles as the "you are here" mark). */
@media (max-width: 430px) {
  .sidebar-tab { padding: 0 7px; gap: 4px; font-size: 0.72rem; }
  .sidebar-tab:not(.active) .tab-icon { display: none; }

  /* Modes in the sheet: pills that flow left-to-right and wrap, instead of a 3-column
     grid of 44px bordered cells. Every mode stays visible without a swipe (the reason
     the old horizontal-scroll strip was replaced by that grid), just in about two short
     rows instead of three tall ones. The 1-9 shortcut numbers are keyboard hints with
     no meaning on a touch screen. */
  .mobile-mode-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin: -8px 0 12px;
    padding: 0 0 12px;
    border-bottom: 1px solid var(--border);
  }
  .mobile-mode-tabs .mode-tab,
  .mobile-mode-tabs .mode-tab:nth-child(3n),
  .mobile-mode-tabs .mode-tab:nth-last-child(-n+3) {
    flex: 1 0 auto;
    min-height: 36px;
    padding: 0 12px;
    gap: 6px;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--bg-secondary);
    color: var(--text-primary);
    font-size: 0.78rem;
    font-weight: 600;
    text-transform: none;
    letter-spacing: 0;
  }
  .mobile-mode-tabs .mode-tab.active {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--on-accent);
  }
  .mobile-mode-tabs .mode-tab .icon { width: 15px; height: 15px; }
  /* Grow to fill a row, but a lone pill on the last row (Squash) shouldn't stretch
     edge to edge — it read as a different kind of control. */
  .mobile-mode-tabs .mode-tab { max-width: 50%; }
  .mobile-mode-tabs .mode-tab-key { display: none; }

  /* Empty-state stats: a clean 2x2 grid. The four stats were a single wrapping flex
     row, so on a phone they broke 2+2 with the divider borders landing mid-row. */
  .hero-stats { display: grid; grid-template-columns: 1fr 1fr; gap: 14px 0; }
  .hero-stat:nth-child(odd) { border-left: none; }

  /* Readability: the settings sheet had dropped body copy to 0.7rem hints and 0.72rem
     all-caps section headers. A touch larger, sentence-weighted, and full-contrast
     secondary text reads far better at arm's length. */
  .hint-text { font-size: 0.74rem; line-height: 1.45; }
  .control-section h3 { font-size: 0.76rem; letter-spacing: 0.05em; color: var(--text-primary); }
}

/* ─── Site header: brand link + sectioned navigation (scripts/guides/site_nav.py) ─── */
.brand-link {
  display: inline-flex;
  align-items: center;
  gap: 11px;
  color: inherit;
  text-decoration: none;
  border-radius: 12px;
}
.brand-link:hover .header-logo { transform: rotate(-4deg) scale(1.04); }
.brand-link .header-logo { transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1); }
.brand-link h1, .brand-link .site-wordmark { flex-basis: auto; margin: 0; }
.brand-link .site-wordmark {
  font-family: var(--font-display);
  font-size: 1.45rem;
  font-weight: 800;
  letter-spacing: -0.035em;
  line-height: 1;
  color: var(--text-primary);
}
.header-byline { display: flex; flex-direction: column; gap: 4px; }
@media (max-width: 1180px) { .header-byline { display: none; } }

.site-nav { display: flex; align-items: center; gap: 8px; }
.site-nav-list { display: flex; align-items: center; gap: 2px; list-style: none; margin: 0; padding: 0; }
.site-nav-group { position: relative; }
.site-nav-trigger {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 8px 12px;
  border: none;
  border-radius: 999px;
  background: none;
  font: inherit;
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--text-secondary);
  cursor: pointer;
  transition: background-color var(--transition), color var(--transition);
}
.site-nav-chevron { transition: transform 0.18s ease; opacity: 0.7; }
.site-nav-group:hover .site-nav-trigger,
.site-nav-group:focus-within .site-nav-trigger { background: var(--accent-soft); color: var(--text-primary); }
/* Hover bridge: a transparent strip under the hovered trigger keeps the group hovered while
   the pointer crosses the header's padding on the way down to the flyout. Trigger-wide, so
   it never overlaps a neighbouring trigger. */
.site-nav-group:hover .site-nav-trigger::after,
.site-nav-group:focus-within .site-nav-trigger::after {
  content: '';
  position: absolute;
  left: 0; right: 0; top: 100%;
  /* 130px: the app's flyout sits ~30px below the bar, and its own top padding means the
     first hit-testable content is further down still — the bridge has to reach it, not just
     the panel's box. Trigger-wide, so it never overlaps a neighbouring trigger. */
  height: 130px;
  background: transparent;
}
.site-nav-group:hover .site-nav-chevron,
.site-nav-group:focus-within .site-nav-chevron { transform: rotate(180deg); }
/* Hook class only: home.html's script rewrites these hrefs to the last-used mode. Declared
   so the converter-page CSS subset check can see it is deliberately unstyled. */
.js-launch-cta { text-decoration: none; }
.site-nav-heading {
  margin: 0 0 4px;
  padding: 0 10px;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--accent);
}
.site-nav-col { display: flex; flex-direction: column; gap: 1px; }
.site-nav-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px 10px;
  border-radius: 9px;
  text-decoration: none;
  color: var(--text-primary);
  transition: background-color var(--transition);
}
.site-nav-item strong { font-size: 0.88rem; font-weight: 650; }
.site-nav-item span { font-size: 0.76rem; color: var(--text-secondary); line-height: 1.35; }
.site-nav-item:hover, .site-nav-item:focus-visible { background: var(--bg-tertiary); }
.site-nav-item[aria-current="page"] { background: var(--accent-soft); }
.site-nav-item[aria-current="page"] strong { color: var(--accent); }
.site-nav-cta {
  margin-left: 6px;
  padding: 9px 16px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 0.86rem;
  font-weight: 700;
  text-decoration: none;
  white-space: nowrap;
}
.site-nav-cta:hover { background: var(--accent-hover); color: var(--on-accent); }

/* Mobile menu (guide pages): hidden on desktop. */
.site-nav-mobile { display: none; }
@media (max-width: 900px) {
  .site-nav:not(.header-nav) .site-nav-list, .site-nav:not(.header-nav) .site-nav-cta { display: none; }
  .site-nav-mobile { display: block; }
  .site-nav-mobile > summary {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 9px 14px;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--bg-secondary);
    color: var(--text-primary);
    font-size: 0.86rem;
    font-weight: 650;
    list-style: none;
    cursor: pointer;
  }
  .site-nav-mobile > summary::-webkit-details-marker { display: none; }
  .site-nav-mobile[open] > summary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
  .site-nav-mobile-panel {
    position: fixed;
    left: 0; right: 0;
    top: var(--header-h, 64px);
    max-height: calc(100dvh - var(--header-h, 64px));
    overflow-y: auto;
    z-index: 400;
    padding: 14px 16px 28px;
    background: var(--bg-primary);
    border-top: 1px solid var(--border);
    box-shadow: 0 24px 40px -20px rgba(0, 0, 0, 0.6);
  }
  .site-nav-mobile-cta {
    display: block;
    text-align: center;
    padding: 13px;
    margin-bottom: 10px;
    border-radius: 12px;
    background: var(--accent);
    color: var(--on-accent);
    font-weight: 700;
    text-decoration: none;
  }
  .site-nav-mobile-group { border-bottom: 1px solid var(--border); }
  .site-nav-mobile-group > summary {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 15px 4px;
    font-size: 1rem;
    font-weight: 700;
    color: var(--text-primary);
    list-style: none;
    cursor: pointer;
  }
  .site-nav-mobile-group > summary::-webkit-details-marker { display: none; }
  .site-nav-mobile-group[open] .site-nav-chevron { transform: rotate(180deg); }
  .site-nav-mobile-group .site-nav-heading { margin-top: 6px; padding: 0 4px; }
  .site-nav-mobile-group .site-nav-item { padding: 10px 4px; }
  .site-nav-mobile-group[open] { padding-bottom: 10px; }
}
/* The app's own header already has a phone layout (settings sheet); its desktop nav
   just drops out below the width where it fits. */
@media (max-width: 1024px) { .app-container > header .site-nav { display: none; } }

/* ─── Guide-page content blocks added 2026-09-21 (scripts/guides/squash_page.py) ─── */
.seo-container .seo-h {
  font-family: var(--font-display);
  font-size: clamp(1.35rem, 2.6vw, 1.75rem);
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.15;
  margin: 6px 0 18px;
  color: var(--text-primary);
}
.seo-container .seo-kicker + .seo-h { margin-top: 6px; }
.seo-container .seo-grid + .seo-kicker, .seo-container .seo-table-wrap + .seo-h { margin-top: 48px; }
.seo-container .seo-grid + .seo-kicker { display: inline-block; }
.seo-lead { color: var(--text-secondary); margin: -8px 0 16px; max-width: 62ch; line-height: 1.55; }
.seo-table-wrap { overflow-x: auto; border: 1px solid var(--border); border-radius: 12px; }
.seo-size-table { width: 100%; border-collapse: collapse; font-size: 0.92rem; }
.seo-size-table th, .seo-size-table td { padding: 12px 14px; text-align: left; border-bottom: 1px solid var(--border); white-space: nowrap; }
.seo-size-table thead th { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-secondary); background: var(--bg-secondary); }
.seo-size-table tbody tr:last-child th, .seo-size-table tbody tr:last-child td { border-bottom: none; }
.seo-size-table tbody th { font-weight: 650; color: var(--text-primary); }
.seo-size-table td:nth-child(2) { color: var(--accent); font-weight: 700; }
/* Breadcrumb items drifted out of line on phones (the current-page item sat higher than
   "Vis Exo /") — pin them to one baseline with no inherited list spacing. */
.lp-breadcrumb ol { align-items: baseline; margin: 0; padding: 0; }
.lp-breadcrumb li { margin: 0; padding: 0; line-height: 1.4; }
@media (max-width: 600px) {
  /* Four columns don't fit a phone: drop the settings-name column (the Aspect column says
     the same thing) so Size stays visible without a sideways scroll. */
  .seo-size-table th:nth-child(4), .seo-size-table td:nth-child(4) { display: none; }
  .seo-size-table th, .seo-size-table td { padding: 11px 10px; font-size: 0.86rem; white-space: normal; }
  .seo-size-table td:nth-child(3) { white-space: nowrap; }
}

/* ─── Grading: histogram + split-toning hue tracks (2026-09-21) ─── */
.grade-scope-group { margin-top: 10px; }
.grade-histogram {
  display: block;
  width: 100%;
  height: 64px;
  background: #05070d;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.grade-hue-track {
  height: 5px;
  margin-top: 3px;
  border-radius: 999px;
  background: linear-gradient(to right, #ff4040 0%, #ffe040 17%, #40ff60 33%, #40ffe0 50%, #4080ff 67%, #e040ff 83%, #ff4040 100%);
  opacity: 0.75;
}

/* ─── Homepage: real-footage demo, free-tools hub, motion (scripts/guides/home_page.py) ─── */
.real-demo-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: clamp(24px, 5vw, 64px);
  align-items: center;
}
.real-demo-media { margin: 0; position: relative; }
.real-demo-media video {
  /* style.css hides every <video> globally (the app's off-screen decoder element — see the
     bare `video` rule), so a visible one has to undo all of it explicitly. */
  position: static;
  opacity: 1;
  overflow: visible;
  pointer-events: auto;
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  background: #000;
  border: 1px solid var(--border);
  border-radius: 18px;
  box-shadow: 0 30px 80px -30px color-mix(in srgb, var(--accent) 45%, transparent), 0 0 0 1px rgba(255, 255, 255, 0.03) inset;
}
.real-demo-media figcaption {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 12px;
  font-size: 0.78rem;
  color: var(--text-secondary);
}
.real-demo-rec { width: 8px; height: 8px; border-radius: 50%; background: #ef4444; box-shadow: 0 0 0 4px rgba(239, 68, 68, 0.18); animation: realDemoPulse 1.6s ease-in-out infinite; }
@keyframes realDemoPulse { 50% { opacity: 0.35; } }
.real-demo-copy h2 {
  font-family: var(--font-display);
  font-size: clamp(1.6rem, 3.2vw, 2.4rem);
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.08;
  margin: 10px 0 14px;
}
.real-demo-copy > p { color: var(--text-secondary); line-height: 1.6; max-width: 56ch; }
.real-demo-points { list-style: none; margin: 18px 0 24px; padding: 0; display: grid; gap: 10px; }
.real-demo-points li {
  position: relative;
  padding-left: 22px;
  color: var(--text-secondary);
  line-height: 1.5;
}
.real-demo-points li::before {
  content: '';
  position: absolute;
  left: 0; top: 0.5em;
  width: 10px; height: 10px;
  border-radius: 3px;
  background: var(--accent);
  box-shadow: 0 0 12px color-mix(in srgb, var(--accent) 60%, transparent);
}
.real-demo-points strong { color: var(--text-primary); }
@media (max-width: 860px) { .real-demo-grid { grid-template-columns: 1fr; } }

.tool-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 12px;
}
.tool-card {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: 16px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--bg-secondary);
  color: var(--text-primary);
  text-decoration: none;
  transition: border-color var(--transition), transform 0.2s cubic-bezier(0.22, 1, 0.36, 1), background-color var(--transition);
}
.tool-card:hover { border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); transform: translateY(-2px); background: var(--bg-tertiary); }
.tool-card-icon {
  flex: 0 0 38px;
  height: 38px;
  display: grid;
  place-items: center;
  border-radius: 10px;
  background: var(--accent-soft);
  color: var(--accent);
}
.tool-card-icon svg { width: 19px; height: 19px; }
.tool-card-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.tool-card-body strong { font-size: 0.98rem; font-weight: 700; }
.tool-card-body span { font-size: 0.84rem; line-height: 1.45; color: var(--text-secondary); }
.tool-card-arrow { margin-left: auto; color: var(--text-secondary); transition: transform 0.2s ease, color var(--transition); }
.tool-card:hover .tool-card-arrow { transform: translateX(3px); color: var(--accent); }

/* Scroll progress bar (home only — created by its motion script). */
.scroll-progress {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: 3px;
  z-index: 500;
  transform-origin: 0 50%;
  transform: scaleX(0);
  background: linear-gradient(90deg, var(--accent), color-mix(in srgb, var(--accent) 50%, #a78bfa));
  box-shadow: 0 0 12px color-mix(in srgb, var(--accent) 60%, transparent);
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  /* Headline word cascade: each word rises out of a slight blur in sequence. The H1 itself
     stays visible (its words carry the animation), so there is no flash of empty heading. */
  .hero-words-ready.hero-in { animation: none; opacity: 1; }
  .hero-word {
    display: inline-block;
    opacity: 0;
    transform: translateY(0.45em) rotate(1.5deg);
    filter: blur(6px);
    animation: heroWordIn 0.75s cubic-bezier(0.16, 1, 0.3, 1) forwards;
  }
  @keyframes heroWordIn { to { opacity: 1; transform: none; filter: blur(0); } }
  /* A slow sheen across the accent phrase once it has landed. */
  .hero-title-accent {
    background: linear-gradient(100deg, var(--accent) 0%, var(--accent) 40%, #c7dcff 50%, var(--accent) 60%, var(--accent) 100%);
    background-size: 250% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    animation: heroSheen 5s ease-in-out 1.6s infinite;
  }
  @keyframes heroSheen { 0% { background-position: 100% 0; } 55%, 100% { background-position: -50% 0; } }

  /* Scroll reveal, refined: longer travel plus a blur-to-sharp settle, so sections arrive
     rather than just fade. Same arm/in-view mechanism as before. */
  .reveal.reveal-armed {
    opacity: 0;
    transform: translateY(28px) scale(0.985);
    filter: blur(6px);
    transition: opacity 0.7s ease, transform 0.8s cubic-bezier(0.16, 1, 0.3, 1), filter 0.7s ease;
  }
  .reveal.reveal-armed.in-view { opacity: 1; transform: none; filter: blur(0); }
  /* Tool cards and the how-it-works steps cascade like the other card grids. */
  .tool-grid.reveal.reveal-armed, .how-steps.reveal.reveal-armed { opacity: 1; transform: none; filter: none; }
  .tool-grid.reveal .tool-card, .how-steps.reveal .how-step {
    opacity: 0;
    transform: translateY(18px);
    transition: opacity 0.55s ease, transform 0.65s cubic-bezier(0.16, 1, 0.3, 1), border-color var(--transition), background-color var(--transition);
  }
  .tool-grid.reveal.in-view .tool-card, .how-steps.reveal.in-view .how-step { opacity: 1; transform: none; }
  .tool-grid .tool-card:nth-child(2), .how-steps .how-step:nth-of-type(2) { transition-delay: 0.06s; }
  .tool-grid .tool-card:nth-child(3), .how-steps .how-step:nth-of-type(3) { transition-delay: 0.12s; }
  .tool-grid .tool-card:nth-child(4) { transition-delay: 0.18s; }
  .tool-grid .tool-card:nth-child(5) { transition-delay: 0.24s; }
  .tool-grid .tool-card:nth-child(6) { transition-delay: 0.3s; }
  .tool-grid .tool-card:nth-child(n+7) { transition-delay: 0.36s; }
  .tool-grid.reveal.in-view .tool-card:hover { transform: translateY(-2px); transition-delay: 0s; }
  .hero-demo-panel { will-change: transform; }
}

/* Closing CTA band: was an inverted panel (text-primary background), which on the black
   default theme became a glaring white slab at the end of every page. Now an accent-tinted
   panel that sits in the page in either theme. */
.lp-cta-band-inner {
  background:
    radial-gradient(120% 140% at 100% 0%, color-mix(in srgb, var(--accent) 26%, transparent), transparent 60%),
    var(--bg-secondary);
  color: var(--text-primary);
  border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--border));
  box-shadow: 0 30px 60px -40px color-mix(in srgb, var(--accent) 60%, transparent);
}
.lp-cta-band-inner::before {
  background-image: radial-gradient(color-mix(in srgb, var(--text-primary) 14%, transparent) 1px, transparent 1px);
}
.lp-cta-band p { color: var(--text-secondary); opacity: 1; }
.lp-cta-band .secondary-btn {
  background: transparent;
  color: var(--text-primary);
  border: 1px solid var(--border);
}
.lp-cta-band .secondary-btn:hover { border-color: var(--accent); color: var(--accent); }

/* ─── Brand: minimal black & white (2026-09-21) ───
   The mark is a flat black tile with a white reticle; the wordmark is a single colour.
   The blue stays the product's interface accent, not part of the logo. Overrides the
   earlier two-tone wordmark and the accent glow under the tile. */
.header-logo { filter: none; }
.brand-link:hover .header-logo { transform: none; }
.brand-wordmark span, .brand-link .site-wordmark span { color: inherit; }
.brand-link .site-wordmark, .header-content h1.brand-wordmark {
  font-weight: 700;
  letter-spacing: -0.04em;
  color: var(--text-primary);
}


/* ─── Grading colour wheels (desktop; built by buildGradeWheel in main.js) ─── */
.grade-trackball { display: none; }
@media (min-width: 769px) {
  .grade-trackball {
    display: block;
    width: min(100%, 150px);
    aspect-ratio: 1 / 1;
    margin: 6px auto 8px;
  }
  .grade-trackball canvas {
    display: block;
    width: 100%;
    height: 100%;
    cursor: grab;
    touch-action: none;
    border-radius: 50%;
  }
  .grade-trackball canvas:active { cursor: grabbing; }
  .grade-trackball canvas:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
}

/* ─── Clip strip (multi-clip editor, CapCut-style) ─── */
.clip-bin { display: flex; flex-direction: column; gap: 8px; }
.clip-bin-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.clip-bin-title { font-size: 0.8rem; font-weight: 700; color: var(--text-primary); }
.clip-bin-count { color: var(--text-secondary); font-weight: 600; }
.clip-bin-add { width: auto !important; flex: 0 0 auto !important; padding: 6px 12px; font-size: 0.78rem; gap: 6px; }
.clip-bin-add .icon { width: 14px; height: 14px; }
.clip-strip {
  display: flex;
  gap: 8px;
  list-style: none;
  margin: 0;
  padding: 2px 2px 8px;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  scrollbar-width: thin;
}
.clip-strip:empty { display: none; }
.clip-card {
  flex: 0 0 118px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  scroll-snap-align: start;
  user-select: none;
  -webkit-user-select: none;
  transition: transform 0.18s ease, opacity 0.18s ease;
}
.clip-card.dragging { transform: scale(1.06) rotate(-1.5deg); opacity: 0.92; z-index: 2; }
.clip-card.dragging .clip-card-thumb { box-shadow: 0 16px 30px -10px rgba(0, 0, 0, 0.7), 0 0 0 2px var(--accent); }
.clip-card-thumb {
  position: relative;
  display: block;
  width: 118px;
  height: 66px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
  background: #000;
  cursor: grab;
  touch-action: pan-x;
}
.clip-card-thumb:active { cursor: grabbing; }
.clip-card-thumb:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.clip-card-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; pointer-events: none; }
.clip-card-idx, .clip-card-dur {
  position: absolute;
  bottom: 4px;
  padding: 1px 5px;
  border-radius: 4px;
  background: rgba(0, 0, 0, 0.72);
  color: #fff;
  font-size: 0.62rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.clip-card-idx { left: 4px; }
.clip-card-dur { right: 4px; }
/* Per-clip speed (2026-10-07): only rendered when a clip has been sped (the phone editor's
   Speed tool). Top-right so it never collides with the trim badge bottom-right. */
.clip-card-speed {
  position: absolute;
  top: 4px;
  right: 4px;
  padding: 1px 5px;
  border-radius: 4px;
  background: color-mix(in srgb, var(--accent) 88%, #000);
  color: #fff;
  font-size: 0.62rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.clip-card-name {
  font-size: 0.68rem;
  color: var(--text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.clip-card-actions { display: flex; gap: 3px; }
.clip-card-btn {
  flex: 1;
  min-height: 24px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg-secondary);
  color: var(--text-primary);
  font-size: 0.85rem;
  line-height: 1;
  cursor: pointer;
}
.clip-card-btn:disabled { opacity: 0.3; cursor: default; }
.clip-card-btn:not(:disabled):hover { border-color: var(--accent); color: var(--accent); }
.clip-card-remove:not(:disabled):hover { border-color: var(--danger); color: var(--danger); }
.clip-bin-foot { display: flex; align-items: center; gap: 8px; }
.clip-bin-foot[hidden] { display: none; }
.clip-bin-total { margin-right: auto; font-size: 0.74rem; color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.clip-bin-foot .btn { padding: 7px 12px; font-size: 0.78rem; }

/* Clip pool on a phone (2026-10-04): the card's five actions were ~20×24px flex buttons inside
   a 118px card — well under a comfortable touch target and easy to mis-tap when adding and
   ordering several clips. Wider cards with the actions on a 3-up grid give each control a real
   target, and the Add / Add-loaded / Join buttons grow to thumb size. */
@media (max-width: 768px) {
  .clip-card { flex: 0 0 150px; }
  .clip-card-thumb { width: 150px; height: 84px; }
  .clip-card-actions { display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; }
  .clip-card-btn { min-height: 36px; font-size: 1rem; }
  .clip-bin-add { min-height: 42px; padding: 8px 16px; font-size: 0.82rem; }
  .clip-add-loaded { min-height: 42px; font-size: 0.8rem; }
  .clip-bin-tools .tx-chip { min-height: 34px; }
  .clip-bin-foot .btn { min-height: 44px; padding: 11px 18px; font-size: 0.84rem; }
}

/* ─── Landing polish (2026-09-21, second pass) ─── */
/* Footer: was an inverted panel (text-primary background), which on the black default theme
   became a bright white block under every page. Now a quiet dark band like the rest. */
.site-footer {
  background: var(--bg-secondary);
  color: var(--text-primary);
  border-top: 1px solid var(--border);
}
.footer-links a, .footer-guides a { color: var(--text-secondary); }
.footer-links a:hover, .footer-guides a:hover { color: var(--text-primary); opacity: 1; }
.site-footer h3, .site-footer h4, .site-footer strong { color: var(--text-primary); }
.site-footer p, .site-footer li { color: var(--text-secondary); }

/* Styles grid: show the first twelve looks, then a button for the rest (home_page.py). A
   28-card wall pushed everything below it ~2,000px down the page. */
.style-card-grid.is-collapsed .style-card:nth-child(n+13) { display: none; }
.styles-more { display: flex; justify-content: center; margin-top: 18px; }
.styles-more[hidden] { display: none; }

@media (prefers-reduced-motion: no-preference) {
  /* Hero aurora: the two soft glows behind the hero drift slowly instead of sitting still. */
  .lp-hero-aura::before { animation: auroraDriftA 18s ease-in-out infinite alternate; }
  .lp-hero-aura::after { animation: auroraDriftB 22s ease-in-out infinite alternate; }
  @keyframes auroraDriftA { from { transform: translate(-4%, -2%) scale(1); } to { transform: translate(6%, 4%) scale(1.12); } }
  @keyframes auroraDriftB { from { transform: translate(3%, 3%) scale(1.05); } to { transform: translate(-5%, -3%) scale(0.95); } }

  /* Section headings wipe up from behind a mask as they arrive, instead of only fading. */
  .lp-section-head.reveal.reveal-armed h2 {
    clip-path: inset(0 0 100% 0);
    transform: translateY(0.35em);
    transition: clip-path 0.9s cubic-bezier(0.16, 1, 0.3, 1) 0.08s, transform 0.9s cubic-bezier(0.16, 1, 0.3, 1) 0.08s;
  }
  .lp-section-head.reveal.reveal-armed.in-view h2 { clip-path: inset(0 0 -0.2em 0); transform: none; }
  .lp-section-head.reveal.reveal-armed .lp-kicker { opacity: 0; transform: translateY(8px); transition: opacity 0.5s ease, transform 0.6s cubic-bezier(0.16, 1, 0.3, 1); }
  .lp-section-head.reveal.reveal-armed.in-view .lp-kicker { opacity: 1; transform: none; }

  /* Real-footage video tilts up into place as it scrolls in (driven by --demo-p, 0..1). */
  .real-demo-media { perspective: 1200px; }
  .real-demo-media video {
    transform: rotateX(calc((1 - var(--demo-p, 1)) * 14deg)) scale(calc(0.9 + var(--demo-p, 1) * 0.1));
    transform-origin: 50% 100%;
    will-change: transform;
  }

  /* Cards lean toward the pointer on hover (desktop pointers only). */
  @media (hover: hover) and (pointer: fine) {
    .mode-card, .tool-card, .seo-card { transform-style: preserve-3d; }
    .tilt-active { transition: transform 0.12s ease-out !important; }
  }
}
.styles-more .btn { width: auto; flex: 0 0 auto; padding: 11px 22px; border: 1px solid var(--border); border-radius: 999px; background: var(--bg-secondary); }
.styles-more .btn:hover { border-color: var(--accent); color: var(--accent); }

/* ─── Loading mark (replaces the ring spinner) ─── */
.spinner.spinner-mark { width: 56px; height: 56px; margin-bottom: 18px; }
.spinner.spinner-mark::before, .spinner.spinner-mark::after { content: none; }
.spinner-mark svg { display: block; overflow: visible; filter: drop-shadow(0 8px 24px rgba(91, 157, 255, 0.25)); }
/* Each mask cell carries --d (its x+y diagonal index), so the dim-and-relight pulse sweeps
   across the blob corner to corner; the centroid cell has no class and never dims. */
.spinner-mark .spinner-px { animation: markPx 1.6s ease-in-out infinite; animation-delay: calc(var(--d) * 110ms); }
@keyframes markPx {
  0%, 100% { opacity: 1; }
  40% { opacity: 0.22; }
}
@media (prefers-reduced-motion: reduce) {
  .spinner-mark .spinner-px { animation: none; }
}

/* ─── App start screen: upload zone ─── */
.upload-zone {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  width: min(560px, 100%);
  margin: 22px auto 14px;
  padding: 30px 20px;
  border: 1.5px dashed color-mix(in srgb, var(--accent) 45%, var(--border));
  border-radius: 18px;
  background:
    radial-gradient(120% 120% at 50% 0%, color-mix(in srgb, var(--accent) 12%, transparent), transparent 70%),
    var(--bg-secondary);
  color: var(--text-primary);
  cursor: pointer;
  text-align: center;
  transition: border-color 0.2s ease, transform 0.25s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.25s ease, background-color 0.2s ease;
}
.upload-zone:hover, .empty-state.drag-active .upload-zone {
  border-color: var(--accent);
  border-style: solid;
  transform: translateY(-2px);
  box-shadow: 0 20px 50px -24px color-mix(in srgb, var(--accent) 70%, transparent);
}
.empty-state.drag-active .upload-zone { transform: scale(1.02); }
.upload-zone-icon {
  width: 52px; height: 52px;
  display: grid; place-items: center;
  border-radius: 14px;
  background: var(--accent);
  color: var(--on-accent);
  box-shadow: 0 10px 30px -10px color-mix(in srgb, var(--accent) 80%, transparent);
}
.upload-zone-icon svg { width: 24px; height: 24px; }
.upload-zone-title { font-family: var(--font-display); font-size: 1.15rem; font-weight: 700; letter-spacing: -0.01em; }
.upload-zone-title u { text-decoration-color: var(--accent); text-underline-offset: 3px; }
.upload-zone-sub { font-size: 0.82rem; color: var(--text-secondary); }
.upload-zone-mobile { display: none; }
@media (max-width: 768px) {
  .upload-zone { padding: 22px 16px; margin: 16px auto 12px; }
  .upload-zone-desktop { display: none; }
  .upload-zone-mobile { display: inline; }
}
@media (prefers-reduced-motion: no-preference) {
  .upload-zone-icon { animation: uploadNudge 2.8s ease-in-out infinite; }
  @keyframes uploadNudge { 0%, 70%, 100% { transform: translateY(0); } 80% { transform: translateY(-4px); } 90% { transform: translateY(0); } }
}
.empty-state-actions .btn { flex: 0 1 auto; }

/* ─── Desktop quick-settings bar above the preview (placeQuickSettingsBar in main.js) ───
   The phone's over-video bar, reused as a toolbar row above the canvas. Only the tool row
   shows (the sidebar already has the modes right beside it), and only once there is a video
   or webcam to adjust. Sizes are restated here because the bar's own rules live in the
   phone media query. */
@media (min-width: 769px) {
  .mobile-quick-style-bar.is-desktop-bar {
    display: flex;
    position: static;
    width: min(1280px, 100%);
    margin: 0 auto 10px;
    padding: 6px;
    gap: 6px;
    border: 1px solid var(--border);
    border-radius: 14px;
    background: var(--bg-secondary);
    box-shadow: none;
    pointer-events: auto;
    transform: none;
    opacity: 1;
  }
  body:not(.has-source) .mobile-quick-style-bar.is-desktop-bar { display: none; }
  .is-desktop-bar .mq-mode-row,
  .is-desktop-bar .mq-photobooth-row { display: none !important; }
  .is-desktop-bar .mq-tool-row {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    width: 100%;
    padding: 0;
  }
  .is-desktop-bar .mq-tool-row::-webkit-scrollbar { display: none; }
  .is-desktop-bar .mq-tool-row > * { flex: 0 0 auto; }
  .is-desktop-bar .mq-select {
    height: 32px;
    min-height: 32px;
    padding: 0 28px 0 12px;
    border: 1px solid var(--border);
    border-radius: 999px;
    background-color: var(--bg-tertiary);
    color: var(--text-primary);
    font: inherit;
    font-size: 0.78rem;
    font-weight: 600;
    max-width: 190px;
  }
  .is-desktop-bar .mq-effect-pill,
  .is-desktop-bar .mq-pill {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 32px;
    min-height: 32px;
    padding: 0 12px;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--bg-tertiary);
    color: var(--text-primary);
    font: inherit;
    font-size: 0.78rem;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
  }
  .is-desktop-bar .mq-pill { width: 32px; padding: 0; justify-content: center; }
  .is-desktop-bar .mq-effect-pill[aria-pressed="true"],
  .is-desktop-bar .mq-pill[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
  .is-desktop-bar .mq-effect-pill:hover, .is-desktop-bar .mq-pill:hover, .is-desktop-bar .mq-select:hover { border-color: color-mix(in srgb, var(--accent) 60%, var(--border)); }
  .is-desktop-bar .mq-effect-pill[hidden], .is-desktop-bar .mq-mini-pill[hidden], .is-desktop-bar [hidden] { display: none !important; }
  .is-desktop-bar .mq-tool-icon { width: 15px; height: 15px; }
  .is-desktop-bar .mq-level { font-size: 0.66rem; opacity: 0.8; }
  .is-desktop-bar .mq-divider { width: 1px; height: 20px; align-self: center; background: var(--border); }
  .is-desktop-bar .mq-color-wrap { width: 32px; height: 32px; border-radius: 50%; overflow: hidden; border: 1px solid var(--border); display: inline-flex; }
  .is-desktop-bar .mq-color { width: 44px; height: 44px; margin: -6px; border: none; padding: 0; cursor: pointer; background: none; }
  .is-desktop-bar .mq-linewidth-wrap { display: inline-flex; align-items: center; gap: 6px; height: 32px; padding: 0 10px; border: 1px solid var(--border); border-radius: 999px; background: var(--bg-tertiary); color: var(--text-secondary); }
  .is-desktop-bar .mq-linewidth { width: 90px; }
  .is-desktop-bar .mq-mini-pill { height: 32px; min-height: 32px; background: var(--bg-tertiary); border-color: var(--border); color: var(--text-primary); }
  /* Squash mode swaps in its own controls, same as on phones. */
  .is-desktop-bar.mq-squash-active .mq-track-only { display: none !important; }
}

/* ─── Homepage demo gallery (three real exports) ─── */
.demo-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 16px;
  align-items: start;
  perspective: 1200px;
}
.demo-item { margin: 0; display: flex; flex-direction: column; gap: 10px; }
.demo-item video {
  /* Undo the global video-hiding rule (the app's off-screen decoder element): it also
     pins height to 1px, which beats aspect-ratio and collapsed these to a 2px sliver. */
  position: static; opacity: 1; overflow: visible; pointer-events: auto;
  display: block;
  width: 100%;
  /* One height for all three so the row reads evenly — the 9:16 clip sits inside its
     column with its own bars rather than making the row twice as tall. */
  height: clamp(240px, 26vw, 340px);
  object-fit: cover;
  background: #000;
  border: 1px solid var(--border);
  border-radius: 16px;
  transform: rotateX(calc((1 - var(--demo-p, 1)) * 10deg)) scale(calc(0.94 + var(--demo-p, 1) * 0.06));
  transform-origin: 50% 100%;
  box-shadow: 0 26px 60px -34px color-mix(in srgb, var(--accent) 60%, transparent);
}
.demo-item-portrait video { object-fit: contain; }
.demo-item figcaption { display: flex; flex-direction: column; gap: 3px; font-size: 0.82rem; }
.demo-item figcaption strong { color: var(--text-primary); font-weight: 650; }
.demo-item figcaption span { color: var(--text-secondary); line-height: 1.45; }
.real-demo-cta { justify-content: center; margin-top: 24px; display: flex; gap: 10px; flex-wrap: wrap; }
@media (prefers-reduced-motion: reduce) { .demo-item video { transform: none; } }

/* ─── Guide-page demo clip (scripts/guides/demo_videos.py) ─── */
.demo-strip { display: flex; flex-direction: column; align-items: center; gap: 12px; }
.demo-strip-media { margin: 0; width: min(760px, 100%); }
.demo-strip-media video {
  /* Undoes the global video-hiding rule (the app's off-screen decoder element). */
  position: static; opacity: 1; overflow: visible; pointer-events: auto;
  display: block;
  width: 100%;
  height: clamp(260px, 42vw, 440px);
  object-fit: cover;
  background: #000;
  border: 1px solid var(--border);
  border-radius: 18px;
  box-shadow: 0 30px 70px -36px color-mix(in srgb, var(--accent) 60%, transparent);
}
/* A 9:16 clip in a 760px-wide frame is mostly empty black — give the portrait demos a
   portrait-shaped frame instead. */
.demo-strip-portrait { width: min(300px, 78vw); }
.demo-strip-portrait video { height: clamp(380px, 62vw, 530px); object-fit: cover; }
.demo-strip-media figcaption {
  display: flex; align-items: center; gap: 8px;
  margin-top: 10px; font-size: 0.78rem; color: var(--text-secondary);
}
.demo-strip-dot { width: 8px; height: 8px; border-radius: 50%; background: #ef4444; box-shadow: 0 0 0 4px rgba(239, 68, 68, 0.18); }
.demo-strip-copy { max-width: 62ch; text-align: center; color: var(--text-secondary); line-height: 1.6; margin: 0; }

/* ─── Cycle-pill jump menu (press and hold / right-click a Squash pill) ─── */
.mq-pill-menu {
  position: fixed;
  z-index: 400;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 148px;
  max-height: 60vh;
  overflow-y: auto;
  padding: 5px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: color-mix(in srgb, var(--bg-secondary) 94%, transparent);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  box-shadow: 0 18px 44px -18px rgba(0, 0, 0, 0.8);
  opacity: 0;
  transform: translateY(6px) scale(0.97);
  transition: opacity 0.14s ease, transform 0.14s cubic-bezier(0.16, 1, 0.3, 1);
}
.mq-pill-menu.is-open { opacity: 1; transform: none; }
.mq-pill-menu-item {
  appearance: none;
  border: none;
  background: none;
  color: var(--text-primary);
  font: inherit;
  font-size: 0.82rem;
  font-weight: 600;
  text-align: left;
  padding: 9px 11px;
  border-radius: 8px;
  cursor: pointer;
  white-space: nowrap;
}
.mq-pill-menu-item:hover { background: var(--bg-tertiary); }
.mq-pill-menu-item.is-current { background: var(--accent); color: var(--on-accent); }

/* ─── Tech marquee under the hero (home_page.py) ───
   A quiet, continuously scrolling strip of what the app actually runs on. Monochrome and
   low-contrast by default so it reads as a credit line rather than a logo wall; a chip
   lifts on hover. The track holds two copies of the list and translates exactly one
   copy's width, so the loop has no visible seam. */
.stack-marquee {
  position: relative;
  overflow: hidden;
  padding: 18px 0 6px;
  border-block: 1px solid var(--border);
  background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 5%, transparent), transparent);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
}
.stack-marquee-track { display: flex; width: max-content; gap: 0; }
.stack-marquee-list {
  display: flex;
  align-items: center;
  gap: 38px;
  margin: 0;
  padding: 0 19px;
  list-style: none;
}
.stack-chip {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  color: var(--text-secondary);
  font-size: 0.88rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  white-space: nowrap;
  opacity: 0.62;
  transition: opacity 0.2s ease, color 0.2s ease, transform 0.2s ease;
}
.stack-chip svg { width: 18px; height: 18px; opacity: 0.85; }
.stack-chip:hover { opacity: 1; color: var(--text-primary); transform: translateY(-1px); }
@media (prefers-reduced-motion: no-preference) {
  .stack-marquee-track { animation: stackScroll 38s linear infinite; }
  .stack-marquee:hover .stack-marquee-track { animation-play-state: paused; }
  @keyframes stackScroll { to { transform: translateX(-50%); } }
}
@media (prefers-reduced-motion: reduce) {
  /* No motion: show one set, centred, and let it scroll by hand if it overflows. */
  .stack-marquee { overflow-x: auto; }
  .stack-marquee-list:last-child { display: none; }
  .stack-marquee-track { width: 100%; justify-content: center; }
}
@media (max-width: 768px) { .stack-marquee-list { gap: 26px; } .stack-chip { font-size: 0.8rem; } }

/* ─── Landing: depth on the dark theme ───
   The page was one flat black from top to bottom, so sections ran together and cards had
   almost nothing to sit on. This gives alternating sections a barely-there raised surface,
   a hairline rule between them, and a soft accent wash behind the two sections that carry
   the product's own colour. All of it is a few percent — the page still reads black. */
.lp-body .lp-section { position: relative; }
.lp-body .lp-section + .lp-section::before {
  content: '';
  position: absolute;
  top: 0; left: 50%;
  transform: translateX(-50%);
  width: min(1100px, 82%);
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--border), transparent);
}
.lp-body #real-demo, .lp-body #styles, .lp-body #free-tools {
  background:
    radial-gradient(90% 70% at 50% 0%, color-mix(in srgb, var(--accent) 7%, transparent), transparent 70%),
    color-mix(in srgb, var(--bg-secondary) 55%, transparent);
}
.lp-body #grade {
  background: radial-gradient(80% 60% at 20% 0%, color-mix(in srgb, var(--accent-warm) 7%, transparent), transparent 70%);
}
/* Cards read as objects on the surface rather than outlines on a void. */
.lp-body .seo-card, .lp-body .mode-card, .lp-body .how-step, .lp-body .style-card {
  background: color-mix(in srgb, var(--bg-secondary) 88%, transparent);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03);
}
.lp-body .seo-faq-accordion details { background: color-mix(in srgb, var(--bg-secondary) 80%, transparent); }

@media (prefers-reduced-motion: no-preference) {
  /* The hero recedes as the page scrolls past it (driven by --hero-p, 0..1). */
  .lp-hero-home .lp-hero-copy, .lp-hero-home .hero-stats {
    opacity: calc(1 - var(--hero-p, 0) * 0.85);
    transform: translateY(calc(var(--hero-p, 0) * -26px));
    will-change: opacity, transform;
  }
}

@media (prefers-reduced-motion: no-preference) {
  /* The accent phrase carries two animations: its own rise-in (as one unit — see the word
     splitter's note about background-clip) and the slow sheen. Declared together because a
     second `animation` shorthand would replace the first, leaving it stuck at opacity 0. */
  .hero-title-accent.hero-word {
    animation:
      heroWordIn 0.75s cubic-bezier(0.16, 1, 0.3, 1) forwards,
      heroSheen 5s ease-in-out 1.6s infinite;
  }
}

/* ─── /guides hub (scripts/guides/guides_hub.py) ─── */
.guides-hero { padding-bottom: 8px; }
.guides-search {
  display: flex;
  align-items: center;
  gap: 10px;
  width: min(560px, 100%);
  margin: 22px auto 0;
  padding: 0 16px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--bg-secondary);
}
.guides-search:focus-within { border-color: var(--accent); box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 18%, transparent); }
.guides-search svg { width: 18px; height: 18px; color: var(--text-secondary); flex-shrink: 0; }
.guides-search input {
  flex: 1;
  min-width: 0;
  height: 48px;
  border: none;
  background: none;
  color: var(--text-primary);
  font: inherit;
  font-size: 0.95rem;
  outline: none;
}
.guides-empty { margin-top: 16px; color: var(--text-secondary); }
.guides-empty button { background: none; border: none; color: var(--accent); font: inherit; cursor: pointer; text-decoration: underline; }
.guides-body { max-width: 1200px; margin: 0 auto; padding: 8px 24px 40px; display: flex; flex-direction: column; gap: 40px; }
.guide-group[hidden] { display: none; }
.guide-group-head { margin-bottom: 14px; }
.guide-group-head h2 {
  display: flex; align-items: center; gap: 10px;
  font-family: var(--font-display); font-size: 1.35rem; font-weight: 800; letter-spacing: -0.02em;
  margin: 0 0 4px;
}
.guide-count {
  font-family: var(--font-body);
  font-size: 0.72rem; font-weight: 700;
  padding: 2px 8px; border-radius: 999px;
  background: var(--accent-soft); color: var(--accent);
}
.guide-group-head p { margin: 0; color: var(--text-secondary); font-size: 0.88rem; }
.guide-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 10px; }
.guide-card {
  display: flex; flex-direction: column; gap: 4px;
  padding: 14px 16px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: color-mix(in srgb, var(--bg-secondary) 88%, transparent);
  color: var(--text-primary);
  text-decoration: none;
  transition: border-color var(--transition), transform 0.18s cubic-bezier(0.16, 1, 0.3, 1), background-color var(--transition);
}
.guide-card[hidden] { display: none; }
.guide-card:hover { border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); transform: translateY(-2px); background: var(--bg-tertiary); }
.guide-card strong { font-size: 0.95rem; font-weight: 650; line-height: 1.25; }
.guide-card span { font-size: 0.82rem; color: var(--text-secondary); line-height: 1.45; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }

/* Desktop quick bar: the same five levels, legible on the dark toolbar. */
@media (min-width: 769px) {
  .is-desktop-bar .mq-effect-pill[data-level="faint"] { background: color-mix(in srgb, var(--accent) 6%, var(--bg-tertiary)); border-color: color-mix(in srgb, var(--accent) 35%, var(--border)); color: var(--text-primary); }
  .is-desktop-bar .mq-effect-pill[data-level="subtle"] { background: color-mix(in srgb, var(--accent) 16%, var(--bg-tertiary)); border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); color: var(--text-primary); }
  .is-desktop-bar .mq-effect-pill[data-level="light"] { background: color-mix(in srgb, var(--accent) 30%, var(--bg-tertiary)); border-color: var(--accent); color: var(--text-primary); }
  .is-desktop-bar .mq-effect-pill[data-level="medium"] { background: color-mix(in srgb, var(--accent) 62%, var(--bg-tertiary)); border-color: var(--accent); color: #fff; }
  .is-desktop-bar .mq-effect-pill[data-level="heavy"] { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
}

/* ─── Readability pass (2026-09-22) ───
   Small text was doing too much of the work: 0.7rem hints and value badges, 0.58rem level
   badges, and several labels dimmed further with opacity on top of an already-secondary
   colour. Sizes go up a step, line-height opens up, and text is never dimmed by opacity
   below the secondary colour itself. Secondary text on the dark theme is now #bcc4d3
   (11.6:1 on black, up from 9.4:1). */
body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-rendering: optimizeLegibility; }
.hint-text { font-size: 0.78rem; line-height: 1.5; }
.value-display { font-size: 0.74rem; color: var(--text-primary); }
label { font-size: 0.86rem; }
.control-section h3 { font-size: 0.8rem; color: var(--text-primary); }
.sidebar-tab { color: color-mix(in srgb, var(--text-primary) 78%, var(--text-secondary)); }
.hero-stat-label, .lp-kicker, .site-nav-heading { letter-spacing: 0.06em; }
.hero-stat-label { color: var(--text-secondary); opacity: 1; font-size: 0.7rem; }
.lp-section-head p, .lp-hero p, .seo-card p, .how-step p, .mode-card p, .style-card p { line-height: 1.6; }
.seo-card p, .how-step p, .tool-card-body span, .guide-card span, .site-nav-item span, .demo-item figcaption span { color: var(--text-secondary); opacity: 1; }
.lp-breadcrumb ol { font-size: 0.82rem; }
.lp-badges span, .header-badges span { color: var(--text-primary); border-color: color-mix(in srgb, var(--text-primary) 22%, transparent); }
@media (max-width: 768px) {
  .hint-text { font-size: 0.76rem; }
  .sidebar .dropdown, .sidebar .text-input { font-size: 0.86rem; }
  .control-section h3 { font-size: 0.78rem; }
}

/* ─── Desktop quick-settings bar, grouped (2026-09-22) ───
   The phone strip laid out for a desk: controls ordered into labelled groups (Look,
   Grade, Overlay, Effects) and allowed to wrap onto a second line instead of scrolling
   out of sight. Order is set here rather than in the markup because the same elements
   keep their phone order inside the overlay bar. */
.mq-desktop-only { display: none !important; }
@media (min-width: 769px) {
  .is-desktop-bar .mq-desktop-only { display: inline-flex !important; }
  .is-desktop-bar .mq-tool-row { flex-wrap: wrap; overflow: visible; row-gap: 8px; column-gap: 6px; }
  .is-desktop-bar .mq-divider { display: none; }
  .is-desktop-bar .mq-group-label {
    align-items: center;
    height: 32px;
    padding: 0 2px 0 10px;
    margin-left: 6px;
    border-left: 1px solid var(--border);
    font-size: 0.66rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-secondary);
  }
  .is-desktop-bar .mq-group-label[data-group="look"] { border-left: none; margin-left: 0; padding-left: 4px; }
  .is-desktop-bar .mq-group-label[data-group="look"] { order: 0; }
  .is-desktop-bar #mqStyleSelect { order: 1; }
  .is-desktop-bar .mq-color-wrap.mq-track-only { order: 2; }
  .is-desktop-bar .mq-linewidth-wrap { order: 3; }
  .is-desktop-bar #mqFillToggle { order: 4; }
  .is-desktop-bar #mqFillStyleSelect { order: 5; }
  .is-desktop-bar .mq-group-label[data-group="grade"] { order: 10; }
  .is-desktop-bar #mqLutSelect { order: 11; max-width: 170px; }
  .is-desktop-bar #mqPostFxSelect { order: 12; max-width: 160px; }
  .is-desktop-bar .mq-group-label[data-group="overlay"] { order: 20; }
  .is-desktop-bar #mqConnectToggle { order: 21; }
  .is-desktop-bar #mqConnectColorWrap { order: 22; }
  .is-desktop-bar #mqSubtitlesToggle { order: 23; }
  .is-desktop-bar .mq-group-label[data-group="effects"] { order: 30; }
  .is-desktop-bar #mqGhostTrailsToggle { order: 31; }
  .is-desktop-bar #mqStepPrintToggle { order: 32; }
  .is-desktop-bar #mqHalationToggle { order: 33; }
  .is-desktop-bar .mq-squash-item { order: 40; }
  .is-desktop-bar #mqMoreSettingsBtn { order: 99; margin-left: auto; }
  /* Squash mode swaps the Look group for its own pills, so drop the Look caption too. */
  .is-desktop-bar.mq-squash-active .mq-group-label[data-group="look"] { display: none !important; }
  .is-desktop-bar .mq-more-btn {
    align-items: center;
    height: 32px;
    padding: 0 12px;
    border: 1px solid transparent;
    border-radius: 999px;
    background: none;
    color: var(--accent);
    font: inherit;
    font-size: 0.78rem;
    font-weight: 650;
    cursor: pointer;
    white-space: nowrap;
  }
  .is-desktop-bar .mq-more-btn:hover { border-color: color-mix(in srgb, var(--accent) 50%, transparent); background: var(--accent-soft); }
  /* Selects read as controls, with a visible chevron on the dark toolbar. */
  .is-desktop-bar .mq-select {
    appearance: none;
    -webkit-appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23bcc4d3' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 10px center;
    background-size: 12px;
    padding-right: 28px;
  }
}

/* ─── Squash controls on the desktop bar ───
   They were sized only by the phone styles, so on desktop the icons rendered huge and the
   value labels spilled out under the pills — with no caption saying what each one was.
   Now: a captioned Squash group at the front, each pill reading "Aspect: 9:16" etc., and a
   click opening the list of options (main.js) rather than cycling blind. */
@media (min-width: 769px) {
  .is-desktop-bar .mq-group-label[data-group="squash"] { order: -10; border-left: none; margin-left: 0; padding-left: 4px; }
  .is-desktop-bar #mqSquashToggle { order: -9; }
  .is-desktop-bar .mq-mini-pill { order: -8; }
  .is-desktop-bar .mq-mini-pill {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 32px;
    padding: 0 26px 0 11px;
    border: 1px solid var(--border);
    border-radius: 999px;
    background-color: var(--bg-tertiary);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23bcc4d3' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 9px center;
    background-size: 11px;
    color: var(--text-primary);
    font: inherit;
    font-size: 0.78rem;
    font-weight: 600;
    white-space: nowrap;
    cursor: pointer;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  .is-desktop-bar .mq-mini-pill .mq-mini-icon { width: 14px; height: 14px; flex: 0 0 14px; opacity: 0.8; }
  .is-desktop-bar .mq-mini-pill[data-name]::before {
    content: attr(data-name) ':';
    color: var(--text-secondary);
    font-weight: 600;
    order: 1;
  }
  .is-desktop-bar .mq-mini-pill .mq-mini-label { order: 2; }
  .is-desktop-bar .mq-mini-pill:hover { border-color: color-mix(in srgb, var(--accent) 60%, var(--border)); }
  /* Set away from its default: accent edge, so a changed setting is visible at a glance. */
  .is-desktop-bar .mq-mini-pill[data-value]:not([data-value="original"]):not([data-value="center"]):not([data-value="stretch"]):not([data-value="solid"]):not([data-value="horizontal"]) {
    border-color: var(--accent);
    background-color: color-mix(in srgb, var(--accent) 16%, var(--bg-tertiary));
  }
  /* In Squash mode the Overlay caption would label only Subtitles; keep the bar tidy. */
  .is-desktop-bar.mq-squash-active .mq-group-label[data-group="overlay"] { display: none !important; }
}
@media (min-width: 769px) { .is-desktop-bar .mq-desktop-only[hidden] { display: none !important; } }

/* ─── App start screen, compact (2026-09-22) ───
   It needed scrolling everywhere: ~1,000px of content in a 518px (laptop) to 700px
   (desktop, phone) box. The big mode cards alone were 420-470px, and a marketing stats row
   (the homepage already has it) sat in the middle. Now: tighter title, one-row upload zone,
   the actions and photo link on one line, stats dropped here, and the modes as a single
   row of compact chips (icon + name; the description stays as the tooltip). */
.canvas-wrapper.is-empty .empty-state { justify-content: center; gap: 0; padding-block: 18px; }
.empty-state .empty-state-title { font-size: clamp(1.3rem, 2.2vw, 1.85rem); line-height: 1.12; margin-bottom: 8px; }
.empty-state > .empty-state-sub:first-of-type {
  font-size: 0.9rem;
  max-width: 58ch;
  margin-inline: auto;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.empty-state .upload-zone {
  flex-direction: row;
  justify-content: center;
  text-align: left;
  gap: 14px;
  width: min(540px, 100%);
  margin: 16px auto 12px;
  padding: 14px 20px;
}
.empty-state .upload-zone-icon { width: 44px; height: 44px; flex-shrink: 0; }
.empty-state .upload-zone-title, .empty-state .upload-zone-sub { display: block; }
.empty-state .upload-zone > .upload-zone-title { font-size: 1.02rem; }
.empty-state .upload-zone-text { display: flex; flex-direction: column; gap: 2px; }
.empty-state .empty-state-actions { gap: 8px; margin: 0; flex-wrap: wrap; justify-content: center; }
.empty-state .empty-state-actions .btn { padding: 8px 14px; font-size: 0.84rem; min-height: 36px; }
.empty-state > .empty-state-sub:not(:first-of-type) { margin-top: 8px; font-size: 0.8rem; }
.empty-state .hero-stats { display: none; }
.empty-state .empty-state-features { margin-top: 16px; padding-top: 14px; }
.empty-state .empty-state-features-title { margin: 0 0 10px; font-size: 0.78rem; }
.empty-state .mode-card-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px;
  max-width: 760px;
  margin: 0 auto;
}
.empty-state .mode-card {
  flex-direction: row;
  align-items: center;
  gap: 7px;
  width: auto;
  min-height: 0;
  height: 36px;
  padding: 0 13px;
  border-radius: 999px;
}
.empty-state .mode-card .icon { width: 16px; height: 16px; margin: 0; }
.empty-state .mode-card-desc, .empty-state .mode-card-key { display: none; }
.empty-state .mode-card-name { font-size: 0.84rem; margin: 0; }
.empty-state .mode-card-divider { display: none; }
@media (max-width: 768px) {
  .empty-state .upload-zone { padding: 12px 14px; }
  .empty-state .empty-state-actions .btn { flex: 1 1 40%; }
  .empty-state .mode-card { height: 34px; padding: 0 11px; }
}

/* ─── Landing motion, reworked (2026-09-22) ───
   Text: words slide up out of clipped slots (.tw > .tw-i, built by home_page.py's motion
   script) — the headline on load, section headings as they arrive, with the kicker leading
   and the paragraph following the last word (--n = word count). Replaces the blur-and-tilt
   word cascade and the whole-heading clip wipe.
   Scroll: blocks rise and fade in without the blur filter. Blurring whole sections was
   expensive to paint and read as out-of-focus, and on section heads it fought the heading's
   own wipe. */
.tw { display: inline-block; vertical-align: top; overflow: hidden; padding: 0.06em 0.02em 0.14em; margin: -0.06em -0.02em -0.14em; }
.tw-i { display: inline-block; }
@media (prefers-reduced-motion: no-preference) {
  /* Headline (on load). */
  .lp-hero-home h1.tw-ready.hero-in { animation: none; opacity: 1; }
  .lp-hero-home h1.tw-ready .tw-i {
    transform: translateY(105%);
    animation: twRise 1s cubic-bezier(0.19, 1, 0.22, 1) both;
    animation-delay: calc(0.12s + var(--i) * 0.07s);
  }
  .lp-hero-home h1.tw-ready .hero-title-accent.tw-i {
    animation:
      twRise 1s cubic-bezier(0.19, 1, 0.22, 1) calc(0.12s + var(--i) * 0.07s) both,
      heroSheen 5s ease-in-out 2.2s infinite;
  }
  @keyframes twRise { from { transform: translateY(105%); } to { transform: none; } }

  /* Section headings (on scroll): the head block itself no longer moves or blurs. */
  .lp-section-head.tw-head.reveal.reveal-armed { opacity: 1; transform: none; filter: none; transition: none; }
  .lp-section-head.tw-head.reveal.reveal-armed h2 { clip-path: none; transform: none; transition: none; }
  .tw-head.reveal-armed .tw-i,
  h3.tw-ready.reveal-armed .tw-i,
  .lp-cta-band h2.tw-ready .tw-i {
    transform: translateY(105%);
    transition: transform 0.95s cubic-bezier(0.19, 1, 0.22, 1);
    transition-delay: calc(0.06s + var(--i) * 0.045s);
  }
  .tw-head.in-view .tw-i, h3.tw-ready.in-view .tw-i, .lp-cta-band h2.tw-in .tw-i { transform: none; }
  h3.tw-ready.reveal.reveal-armed { opacity: 1; transform: none; filter: none; transition: none; }
  .tw-head.reveal-armed > p {
    opacity: 0;
    transform: translateY(12px);
    transition: opacity 0.7s ease, transform 0.9s cubic-bezier(0.19, 1, 0.22, 1);
    transition-delay: calc(0.25s + var(--n, 4) * 0.045s);
  }
  .tw-head.in-view > p { opacity: 1; transform: none; }

  /* Blocks: clean rise, no blur. */
  .reveal.reveal-armed {
    filter: none;
    transform: translateY(36px);
    transition: opacity 0.8s cubic-bezier(0.19, 1, 0.22, 1), transform 1.1s cubic-bezier(0.19, 1, 0.22, 1);
  }
  .reveal.reveal-armed.in-view { transform: none; }
}

/* Subtitles: letter-case segmented control (2026-09-22). */
.subs-seg { display: inline-flex; gap: 2px; padding: 3px; border-radius: 10px; background: var(--bg-tertiary); border: 1px solid var(--border); }
.subs-seg button {
  min-width: 44px;
  padding: 5px 10px;
  border: 0;
  border-radius: 7px;
  background: none;
  color: var(--text-secondary);
  font: inherit;
  font-size: 0.8rem;
  font-weight: 700;
  cursor: pointer;
  transition: background-color var(--transition), color var(--transition);
}
.subs-seg button:hover { color: var(--text-primary); }
.subs-seg button.is-active { background: var(--accent); color: var(--on-accent); }

/* ─── Header + flyout, Apple-style (2026-09-23) ───────────────────────────────────────
   Replaces three layered passes (the boxed bar that became a floating pill on scroll, the
   floating dropdown cards with icon tiles, and the featured-card column). Now:
   - a thin full-width bar: translucent, blurred, one hairline under it, small quiet links;
   - a full-width flyout directly under it: the section's main links large on the left,
     secondary lists small beside them, columns aligned to the bar's own content width;
   - no dim or blur over the page while it's open (tried 2026-09-22, read as the screen
     locking), just a soft shadow under the sheet.
   The bar's blur sits on ::before rather than the header itself: a backdrop-filter on the
   header would make it the backdrop root for the flyout inside it, and the flyout's own blur
   would then only see the header (the page read straight through — see 2026-09-22). */
:root { --bar-h: 52px; --bar-w: 1024px; --bar-pad: max(22px, calc((100vw - var(--bar-w)) / 2)); }

@media (min-width: 901px) {
  /* Guide pages wrap header + breadcrumb in a max-width div that a sticky header can't
     escape; make it layout-transparent and move the centring onto the breadcrumb. */
  .lp-body > div[style*="max-width:1200px"] { display: contents; }
  .lp-breadcrumb { max-width: 1200px; margin: 0 auto; }

  .lp-page-header {
    position: sticky;
    top: 0;
    z-index: 300;
    display: flex;
    align-items: stretch;
    justify-content: space-between;
    gap: 24px;
    width: 100%;
    max-width: none;
    height: var(--bar-h);
    margin: 0;
    padding: 0 var(--bar-pad);
    border: 0;
    border-radius: 0;
    background: transparent;
    box-shadow: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    animation: none;
    opacity: 1;
  }
  .lp-page-header::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    background: color-mix(in srgb, var(--bg-primary) 72%, transparent);
    -webkit-backdrop-filter: saturate(180%) blur(20px);
    backdrop-filter: saturate(180%) blur(20px);
    border-bottom: 1px solid color-mix(in srgb, var(--text-primary) 9%, transparent);
    transition: background-color 0.24s ease;
  }
  .lp-page-header::after { display: none; }
  /* While a flyout is open the bar and the sheet read as one surface. */
  .lp-page-header:has(.site-nav-group:hover:not(.is-shut))::before,
  .lp-page-header:has(.site-nav-group:focus-within:not(.is-shut))::before {
    background: var(--bg-primary);
    border-bottom-color: transparent;
  }
  .lp-page-header .header-content { display: flex; align-items: center; padding: 0; }
  .lp-page-header .header-logo { width: 26px; height: 26px; }
  .lp-page-header .brand-link { gap: 9px; }
  .lp-page-header .brand-link .site-wordmark { font-size: 1.12rem; font-weight: 650; letter-spacing: -0.02em; }
  .lp-page-header .site-nav { align-self: stretch; }
}

/* Links in the bar (landing pages and the app header). */
.site-nav .site-nav-list, .site-nav .site-nav-group { align-self: stretch; }
.site-nav .site-nav-list { display: flex; align-items: stretch; gap: 0; }
.site-nav .site-nav-group { position: static; display: flex; }
/* Hover bridge (2026-09-28): the app header leaves ~30px of padding between the bar and
   the flyout, and crossing that dead band dropped :hover — the sheet closed mid-travel and
   stayed closed. This transparent strip lives inside the group (so hovering it keeps the
   group hovered) and spans only the trigger's own width, so it can never block a neighbour. */
.site-nav .site-nav-trigger {
  position: relative;
  display: flex;
  align-items: center;
  height: 100%;
  padding: 0 13px;
  border: 0;
  border-radius: 0;
  background: none;
  font: inherit;
  font-size: 0.8rem;
  font-weight: 450;
  letter-spacing: -0.005em;
  color: color-mix(in srgb, var(--text-primary) 78%, transparent);
  cursor: default;
  transition: color 0.2s ease;
}
.site-nav .site-nav-chevron { display: none; }
.site-nav .site-nav-group:hover .site-nav-trigger,
.site-nav .site-nav-group:focus-within .site-nav-trigger,
.site-nav .site-nav-group.is-current .site-nav-trigger { background: none; color: var(--text-primary); }
.site-nav .site-nav-group.is-current .site-nav-trigger::after { display: none; }
.site-nav .site-nav-cta {
  align-self: center;
  margin-left: 10px;
  padding: 0 14px;
  height: 30px;
  display: inline-flex;
  align-items: center;
  border-radius: 980px;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: -0.005em;
}

/* The flyout. */
.site-nav .site-nav-panel {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 300;
  display: block;
  width: auto;
  min-width: 0;
  max-width: none;
  margin: 0;
  padding: 34px var(--bar-pad) 50px;
  border: 0;
  border-radius: 0;
  background: var(--bg-primary);
  box-shadow: 0 36px 50px -36px rgba(0, 0, 0, 0.55);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  /* Slide + fade, NOT a clip-path reveal (2026-09-28). The old inset() clip animated the
     sheet open from the top, and clip-path clips HIT TESTING as well as painting: a pointer
     that crossed from the trigger into the sheet before the 0.3s reveal finished landed on a
     clipped-away area, hover dropped, the sheet closed, and it flapped for anyone who moved
     the mouse at normal speed. Nothing about the reveal is worth that. */
  transform: translateY(-6px);
  transition: opacity 0.2s ease, transform 0.28s cubic-bezier(0.4, 0, 0.2, 1), visibility 0s linear 0.42s;
}
.site-nav .site-nav-panel::before { display: none; }
.site-nav .site-nav-group:hover > .site-nav-panel,
.site-nav .site-nav-group:focus-within > .site-nav-panel {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: none;
  transition: opacity 0.2s ease, transform 0.28s cubic-bezier(0.4, 0, 0.2, 1), visibility 0s;
}
/* Sliding across the bar from one section to the next: the old sheet goes at once and the new
   one is already open, so the two never stack or flash the page between them — only the
   links cross-fade. */
.site-nav .site-nav-list:hover .site-nav-group:not(:hover) > .site-nav-panel { transition: none; }
.site-nav .site-nav-list:hover .site-nav-group:hover > .site-nav-panel { transition: none; }
.site-nav .site-nav-group.is-shut > .site-nav-panel { opacity: 0; visibility: hidden; pointer-events: none; }

.site-nav .site-nav-panel-inner { display: flex; align-items: flex-start; gap: clamp(40px, 5vw, 76px); }
.site-nav .site-nav-col { display: flex; flex-direction: column; gap: 0; min-width: 150px; }
.site-nav .site-nav-heading {
  margin: 0 0 12px;
  padding: 0;
  font-size: 0.72rem;
  font-weight: 450;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-secondary);
}
.site-nav .site-nav-item {
  display: block;
  padding: 5px 0;
  border-radius: 0;
  background: none;
  text-decoration: none;
  color: color-mix(in srgb, var(--text-primary) 86%, transparent);
  font-size: 0.8rem;
  font-weight: 550;
  letter-spacing: -0.005em;
  line-height: 1.3;
  transition: color 0.15s ease;
}
.site-nav .site-nav-col.is-primary .site-nav-item {
  padding: 4px 0;
  font-size: 1.5rem;
  font-weight: 650;
  letter-spacing: -0.025em;
  line-height: 1.22;
  color: var(--text-primary);
}
.site-nav .site-nav-item:hover, .site-nav .site-nav-item:focus-visible { background: none; color: var(--accent); }
.site-nav .site-nav-item[aria-current="page"] { background: none; color: var(--accent); }
/* Links arrive a beat after the sheet, top to bottom (--i is set per link by site_nav.py). */
.site-nav .site-nav-panel .site-nav-item,
.site-nav .site-nav-panel .site-nav-heading { opacity: 0; transform: translateY(-5px); }
.site-nav .site-nav-group:hover > .site-nav-panel .site-nav-item,
.site-nav .site-nav-group:hover > .site-nav-panel .site-nav-heading,
.site-nav .site-nav-group:focus-within > .site-nav-panel .site-nav-item,
.site-nav .site-nav-group:focus-within > .site-nav-panel .site-nav-heading {
  opacity: 1;
  transform: none;
  transition: opacity 0.3s ease, transform 0.36s cubic-bezier(0.2, 0.8, 0.2, 1), color 0.15s ease;
  transition-delay: calc(60ms + var(--i, 0) * 16ms), calc(60ms + var(--i, 0) * 16ms), 0s;
}
/* The app header's generic `.header-nav a` pill rule also matches the flyout's links. */
.header-nav a.site-nav-item { padding: 5px 0; border-radius: 0; background: none; font-size: 0.8rem; }
.header-nav .site-nav-col.is-primary a.site-nav-item { padding: 4px 0; font-size: 1.5rem; }
.header-nav a.site-nav-item:hover { background: none; color: var(--accent); }
@media (prefers-reduced-motion: reduce) {
  .site-nav .site-nav-panel, .site-nav .site-nav-panel .site-nav-item, .site-nav .site-nav-panel .site-nav-heading {
    transition: none !important;
    transform: none;
  }
}

/* Mobile: the same thin bar, a plain two-line menu icon, and a full-height sheet of large
   links (sections expand in place). */
@media (max-width: 900px) {
  /* No backdrop-filter on the header itself: that would make it the containing block for the
     position:fixed menu sheet, trapping the sheet inside the 48px bar. The blur sits on ::before. */
  .lp-page-header {
    position: sticky;
    top: 0;
    z-index: 300;
    height: 48px;
    padding: 0 16px;
    border: 0;
    border-radius: 0;
    background: transparent;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    box-shadow: none;
  }
  .lp-page-header::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    background: color-mix(in srgb, var(--bg-primary) 80%, transparent);
    -webkit-backdrop-filter: saturate(180%) blur(20px);
    backdrop-filter: saturate(180%) blur(20px);
    border-bottom: 1px solid color-mix(in srgb, var(--text-primary) 9%, transparent);
  }
  .lp-page-header:has(.site-nav-mobile[open])::before { background: var(--bg-primary); }
  .lp-page-header::after { display: none; }
  .lp-page-header .header-logo { width: 24px; height: 24px; }
  .lp-page-header .brand-link .site-wordmark { font-size: 1.05rem; font-weight: 650; letter-spacing: -0.02em; }
  .site-nav-mobile > summary {
    width: 40px;
    height: 40px;
    padding: 0;
    justify-content: center;
    border: 0;
    border-radius: 50%;
    background: none;
    color: var(--text-primary);
  }
  .site-nav-mobile > summary span { display: none; }
  .site-nav-mobile[open] > summary { background: none; color: var(--text-primary); }
  /* The two lines fold into a close mark. */
  .site-nav-glyph path { transform-box: fill-box; transform-origin: center; transition: transform 0.32s cubic-bezier(0.4, 0, 0.2, 1); }
  .site-nav-mobile[open] .site-nav-glyph-a { transform: translateY(3px) rotate(45deg); }
  .site-nav-mobile[open] .site-nav-glyph-b { transform: translateY(-3px) rotate(-45deg); }
  .site-nav-mobile-panel {
    position: fixed;
    top: 48px;
    left: 0;
    right: 0;
    bottom: 0;
    width: auto;
    max-height: none;
    margin: 0;
    padding: 18px 26px calc(40px + env(safe-area-inset-bottom));
    overflow-y: auto;
    overscroll-behavior: contain;
    border: 0;
    border-radius: 0;
    background: var(--bg-primary);
    box-shadow: none;
    animation: mobileSheetIn 0.32s cubic-bezier(0.2, 0.8, 0.2, 1) both;
  }
  @keyframes mobileSheetIn { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: none; } }
  .site-nav-mobile-group { border-bottom: 1px solid color-mix(in srgb, var(--text-primary) 9%, transparent); }
  .site-nav-mobile-group > summary {
    padding: 16px 0;
    font-size: 1.6rem;
    font-weight: 650;
    letter-spacing: -0.025em;
  }
  .site-nav-mobile-group .site-nav-heading {
    margin: 14px 0 6px;
    padding: 0;
    font-size: 0.78rem;
    font-weight: 450;
    letter-spacing: 0;
    text-transform: none;
    color: var(--text-secondary);
  }
  .site-nav-mobile-group .site-nav-item {
    display: block;
    padding: 9px 0;
    border-radius: 0;
    background: none;
    font-size: 1.02rem;
    font-weight: 550;
    color: var(--text-primary);
    text-decoration: none;
  }
  .site-nav-mobile-group .site-nav-item[aria-current="page"] { color: var(--accent); background: none; }
  .site-nav-mobile-cta { margin: 0 0 8px; border-radius: 980px; }
  .site-nav-mobile-group[open] { padding-bottom: 14px; }
}

/* ─── Homepage, Apple-style (2026-09-23) ──────────────────────────────────────────────
   Scoped to body.lp-home (added by home_page.py) so the guide pages keep their layout.
   Centred hero with a real screenshot of the app, big sentence-case headings over plain
   eyebrow text, borderless rounded tiles on a flat background, lots of air, and an examples
   gallery that scrolls sideways. Colours stay on the site's tokens, so light theme follows. */
.lp-home {
  --ah-tile: color-mix(in srgb, var(--text-primary) 5.5%, var(--bg-primary));
  --ah-tile-hover: color-mix(in srgb, var(--text-primary) 8%, var(--bg-primary));
  --ah-band: color-mix(in srgb, var(--text-primary) 3%, var(--bg-primary));
  --ah-hair: color-mix(in srgb, var(--text-primary) 10%, transparent);
  background: var(--bg-primary);
}
.lp-home main, .lp-home .lp-section, .lp-home .seo-content-section { background: none; box-shadow: none; }
.lp-home .lp-section::before, .lp-home .lp-section::after { display: none; }

/* Hero */
.lp-home .lp-hero-home.ah-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0;
  max-width: none;
  padding: clamp(56px, 9vw, 110px) 0 clamp(40px, 6vw, 72px);
  text-align: center;
  overflow: visible;
}
.lp-home .lp-hero-home.ah-hero::before, .lp-home .lp-hero-home.ah-hero::after { display: none; }
.lp-home .ah-hero-copy { max-width: 900px; padding: 0 22px; }
.lp-home .ah-eyebrow { margin: 0 0 14px; font-size: clamp(1.05rem, 1.6vw, 1.35rem); font-weight: 600; letter-spacing: -0.01em; color: var(--accent); }
.lp-home .ah-hero h1 {
  margin: 0 auto;
  font-family: var(--font-body);
  font-size: clamp(2.6rem, 6.6vw, 5.2rem);
  font-weight: 700;
  letter-spacing: -0.035em;
  line-height: 1.04;
  color: var(--text-primary);
}
.lp-home .ah-hero h1 .hero-title-accent { display: inline; }
.lp-home .ah-sub {
  max-width: 680px;
  margin: 22px auto 0;
  font-size: clamp(1.08rem, 1.7vw, 1.4rem);
  font-weight: 450;
  line-height: 1.42;
  letter-spacing: -0.01em;
  color: var(--text-secondary);
}
.lp-home .ah-ctas { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 14px 28px; margin-top: 30px; }
.lp-home .ah-ctas .primary-btn { padding: 12px 24px; border-radius: 980px; font-size: 1rem; font-weight: 600; letter-spacing: -0.01em; box-shadow: none; }
.lp-home .ah-link { font-size: 1.05rem; font-weight: 500; color: var(--accent); text-decoration: none; }
.lp-home .ah-link::after { content: ' \203A'; font-size: 1.2em; line-height: 0; }
.lp-home .ah-link:hover { text-decoration: underline; }
.lp-home .ah-hero-shot {
  width: min(1100px, calc(100% - 44px));
  margin: clamp(40px, 6vw, 64px) auto 0;
  border-radius: clamp(12px, 1.6vw, 20px);
  overflow: hidden;
  background: #05070b;
  box-shadow: 0 0 0 1px var(--ah-hair), 0 50px 120px -50px rgba(0, 0, 0, 0.85);
}
.lp-home .ah-hero-shot img { display: block; width: 100%; height: auto; }
.lp-home .ah-hero .hero-stats {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 18px clamp(28px, 5vw, 72px);
  max-width: 1100px;
  margin: clamp(40px, 5vw, 60px) auto 0;
  padding: 0 22px;
  border: 0;
  background: none;
}
.lp-home .ah-hero .hero-stat { border: 0; padding: 0; align-items: center; text-align: center; }
.lp-home .ah-hero .hero-stat::before, .lp-home .ah-hero .hero-stat::after { display: none; }
.lp-home .ah-hero .hero-stat-num { font-family: var(--font-body); font-size: clamp(1.9rem, 3.2vw, 2.6rem); font-weight: 700; letter-spacing: -0.03em; }
.lp-home .ah-hero .hero-stat-label { margin-top: 2px; font-size: 0.85rem; font-weight: 450; letter-spacing: 0; text-transform: none; color: var(--text-secondary); }

/* Sections */
.lp-home .lp-section { max-width: 1140px; padding: clamp(72px, 10vw, 136px) 22px 0; }
.lp-home #about-app { padding-top: clamp(72px, 10vw, 136px); }
.lp-home .lp-section-head { max-width: 820px; margin: 0 auto clamp(36px, 5vw, 60px); text-align: center; }
.lp-home .lp-kicker, .lp-home .lp-section-head .lp-kicker {
  display: block;
  margin: 0 0 12px;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  font-family: var(--font-body);
  font-size: clamp(1rem, 1.4vw, 1.2rem);
  font-weight: 600;
  letter-spacing: -0.01em;
  text-transform: none;
  color: var(--accent);
}
.lp-home .lp-kicker::before, .lp-home .lp-kicker::after { display: none; }
.lp-home .lp-section-head h2, .lp-home .seo-content-section h2, .lp-home .lp-cta-band h2 {
  font-family: var(--font-body);
  font-size: clamp(2.1rem, 5vw, 3.5rem);
  font-weight: 700;
  letter-spacing: -0.033em;
  line-height: 1.07;
  margin: 0 0 16px;
  color: var(--text-primary);
}
.lp-home .lp-section-head p, .lp-home .seo-content-section > .seo-container > p {
  font-size: clamp(1.02rem, 1.5vw, 1.25rem);
  line-height: 1.5;
  letter-spacing: -0.005em;
  color: var(--text-secondary);
}

/* Tiles: one quiet surface, no borders, generous radius. */
.lp-home .how-step, .lp-home .mode-card, .lp-home .tool-card, .lp-home .seo-card, .lp-home .lp-feature-panel {
  border: 0;
  border-radius: 22px;
  background: var(--ah-tile);
  box-shadow: none;
  transform: none;
  transition: background-color 0.25s ease;
}
.lp-home a.mode-card:hover, .lp-home a.tool-card:hover { background: var(--ah-tile-hover); transform: none; box-shadow: none; border: 0; }
.lp-home .how-step, .lp-home .seo-card { padding: 30px 28px; }
.lp-home .how-step-arrow { display: none; }
.lp-home .how-steps { gap: 18px; }
.lp-home .how-step h3, .lp-home .seo-card h4, .lp-home .mode-card strong, .lp-home .tool-card-body strong {
  font-family: var(--font-body);
  font-weight: 650;
  letter-spacing: -0.02em;
}
.lp-home .how-step h3 { font-size: 1.3rem; }
.lp-home .mode-card-grid { gap: 14px; }
.lp-home .mode-card { padding: 26px 24px; }
.lp-home .mode-card strong { font-size: 1.12rem; }
.lp-home .mode-card .icon, .lp-home .tool-card-icon { background: none; border: 0; color: var(--accent); }
.lp-home .tool-grid { gap: 14px; }
.lp-home .tool-card { padding: 22px 22px; }
.lp-home .lp-feature-panel { padding: clamp(26px, 3vw, 40px); }
.lp-home .lp-feature-split { gap: 18px; }

/* Style cards: the image is the card. */
.lp-home .style-card-grid { gap: 26px 18px; }
.lp-home .style-card { padding: 0; border: 0; border-radius: 0; background: none; box-shadow: none; transform: none; }
.lp-home .style-card::before, .lp-home .style-card::after { display: none; }
.lp-home .style-card img { border-radius: 16px; transition: transform 0.5s cubic-bezier(0.2, 0.8, 0.2, 1); }
.lp-home .style-card:hover img { transform: scale(1.02); }
.lp-home .style-card strong { margin-top: 12px; font-size: 1.02rem; font-weight: 650; letter-spacing: -0.015em; }
.lp-home .style-card span { font-size: 0.86rem; }

/* FAQ: plain rows. */
.lp-home .seo-faq-accordion { max-width: 820px; margin-inline: auto; }
.lp-home .seo-faq-accordion details { border: 0; border-bottom: 1px solid var(--ah-hair); border-radius: 0; background: none; box-shadow: none; }
.lp-home .seo-faq-accordion summary { padding: 22px 0; font-size: 1.12rem; font-weight: 600; letter-spacing: -0.015em; }
.lp-home .seo-content-section.glass-panel { border: 0; background: none; box-shadow: none; -webkit-backdrop-filter: none; backdrop-filter: none; }

/* Closing band. */
.lp-home .lp-cta-band { margin-top: clamp(80px, 11vw, 140px); }
.lp-home .lp-cta-band-inner {
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 22px;
  padding: clamp(48px, 7vw, 90px) 24px;
  border: 0;
  border-radius: 28px;
  background: var(--ah-tile);
  box-shadow: none;
}
.lp-home .lp-cta-band-inner::before, .lp-home .lp-cta-band-inner::after { display: none; }
.lp-home .lp-cta-band .primary-btn { border-radius: 980px; }

/* Examples gallery. The track runs edge to edge; its padding lines the first tile up with
   the header's content edge (--bar-pad), and snap points use the same inset. */
.lp-home .ah-examples { max-width: none; padding-left: 0; padding-right: 0; }
.lp-home .ah-examples .lp-section-head { padding: 0 22px; }
.ah-gallery { position: relative; }
.ah-gallery-track {
  display: flex;
  gap: 18px;
  margin: 0;
  padding: 4px var(--bar-pad) 10px;
  list-style: none;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: var(--bar-pad);
  scrollbar-width: none;
  outline: none;
}
.ah-gallery-track::-webkit-scrollbar { display: none; }
.ah-gallery-track:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.ah-tile { flex: 0 0 clamp(250px, 25vw, 330px); scroll-snap-align: start; }
.ah-tile-link {
  position: relative;
  display: block;
  aspect-ratio: 3 / 4;
  border-radius: 24px;
  overflow: hidden;
  isolation: isolate;
  background: #0b0d12;
  color: #fff;
  text-decoration: none;
}
.ah-tile-link img, .ah-tile-link video {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.7s cubic-bezier(0.2, 0.8, 0.2, 1);
}
/* A scrim behind the caption only: the tops of bright frames (snow, sky) otherwise swallow it. */
.ah-tile-link::before {
  content: '';
  position: absolute;
  inset: 0 0 45% 0;
  z-index: -1;
  background: linear-gradient(to bottom, rgba(0, 0, 0, 0.62), rgba(0, 0, 0, 0));
}
.ah-tile-cap { position: absolute; top: 22px; left: 22px; right: 22px; display: flex; flex-direction: column; gap: 6px; }
.ah-tile-cap em { font-style: normal; font-size: 0.8rem; font-weight: 600; letter-spacing: -0.005em; color: rgba(255, 255, 255, 0.82); }
.ah-tile-cap strong { font-size: clamp(1.15rem, 1.6vw, 1.4rem); font-weight: 700; letter-spacing: -0.022em; line-height: 1.18; text-wrap: balance; }
.ah-tile-go {
  position: absolute;
  right: 16px;
  bottom: 16px;
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: rgba(40, 40, 44, 0.72);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  font-size: 1.35rem;
  font-weight: 400;
  line-height: 1;
  transition: transform 0.3s cubic-bezier(0.2, 0.8, 0.2, 1), background-color 0.2s ease;
}
.ah-tile-badge {
  position: absolute;
  left: 16px;
  bottom: 18px;
  padding: 5px 10px;
  border-radius: 980px;
  background: rgba(40, 40, 44, 0.72);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  font-size: 0.72rem;
  font-weight: 600;
}
.ah-tile-link:hover img, .ah-tile-link:hover video { transform: scale(1.035); }
.ah-tile-link:hover .ah-tile-go { transform: rotate(90deg); background: rgba(255, 255, 255, 0.28); }
.ah-tile-link:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }
.ah-gallery-nav { display: flex; justify-content: flex-end; gap: 12px; padding: 18px var(--bar-pad) 0; }
.ah-gallery-btn {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border: 0;
  border-radius: 50%;
  background: color-mix(in srgb, var(--text-primary) 12%, transparent);
  color: var(--text-primary);
  cursor: pointer;
  transition: background-color 0.2s ease, opacity 0.2s ease;
}
.ah-gallery-btn:hover:not(:disabled) { background: color-mix(in srgb, var(--text-primary) 18%, transparent); }
.ah-gallery-btn:disabled { opacity: 0.35; cursor: default; }
.ah-credit { max-width: 1100px; margin: 26px auto 0; padding: 0 22px; font-size: 0.75rem; color: var(--text-secondary); text-align: center; }
.ah-credit a { color: inherit; }
@media (max-width: 700px) {
  .ah-tile { flex-basis: 76vw; }
  .ah-gallery-nav { display: none; } /* swipe on touch */
  .lp-home .ah-ctas .primary-btn { width: auto; }
  .lp-home .ah-hero .hero-stats { gap: 16px 26px; }
}
@media (prefers-reduced-motion: reduce) {
  .ah-tile-link img, .ah-tile-link video, .ah-tile-go, .lp-home .style-card img { transition: none; }
}

/* Homepage fixes after the first screenshots (2026-09-23). */
.lp-home #styles, .lp-home #free-tools, .lp-home #grade { background: none; }
.lp-home #styles::before { display: none; }
.lp-home .ah-ctas .primary-btn { flex: none; width: auto; }
.lp-home .how-step-num {
  width: auto;
  height: auto;
  margin: 0 0 18px;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  font: 700 2rem/1 var(--font-body);
  letter-spacing: -0.03em;
  color: var(--accent);
}
.lp-home .how-step-num::before { content: none; }
.lp-home .lp-explainer-grid { gap: 18px; }
.lp-home .lp-explainer-step { border: 0; border-radius: 22px; background: var(--ah-tile); box-shadow: none; padding: 30px 26px; }
.lp-home .lp-explainer-step::before, .lp-home .lp-explainer-step::after { display: none; }
.lp-home .lp-explainer-num { font: 700 1.6rem/1 var(--font-body); letter-spacing: -0.03em; color: var(--accent); background: none; border: 0; padding: 0; }
.lp-home .lp-section .style-card, .lp-home .lp-section .style-card:hover { border: 0; box-shadow: none; background: none; }
.lp-home .lp-section .style-card img { border-bottom: 0; }
@media (min-width: 1000px) { .lp-home .mode-card-grid { grid-template-columns: repeat(5, minmax(0, 1fr)); } }
.lp-home #about-app .seo-container { max-width: 1100px; margin: 0 auto; }
.lp-home #about-app .seo-kicker {
  display: block;
  margin: 0 0 12px;
  padding: 0;
  border: 0;
  background: none;
  text-align: center;
  font: 600 clamp(1rem, 1.4vw, 1.2rem)/1.2 var(--font-body);
  letter-spacing: -0.01em;
  text-transform: none;
  color: var(--accent);
}
.lp-home #about-app h3.reveal {
  margin: 0 auto clamp(36px, 5vw, 56px);
  text-align: center;
  font: 700 clamp(2.1rem, 5vw, 3.5rem)/1.07 var(--font-body);
  letter-spacing: -0.033em;
  color: var(--text-primary);
}
.lp-home #about-app h3#faq { margin-top: clamp(80px, 11vw, 140px); }
.lp-home .seo-card h4 { font-size: 1.1rem; }
/* Five across leaves too little width for an icon beside the text: stack them. */
.lp-home .lp-section .mode-card { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
.lp-home .lp-section .mode-card .icon { width: 26px; height: 26px; margin: 0 0 10px; padding: 0; }

/* Mobile: the Trim button's editor-only sheet (setEditorFocus in main.js). */
@media (max-width: 768px) {
  .sidebar.editor-focus .control-section:not(.has-editor-focus) { display: none; }
  .sidebar.editor-focus .has-editor-focus > :not(.is-editor-focus-target) { display: none; }
  .sidebar.editor-focus .is-editor-focus-target { margin-top: 0; }
}

/* ─── Community presets as an Explore page (2026-09-23, after artkit.cc) ─────────────────
   Sidebar of filters beside a grid of cards: a real render of the look on top with a status
   badge, title with mode chips, two lines of description, and a full-width "Try it now" row.
   Staff picks and community presets share the card, so they read as one gallery. */
.cx-page {
  --cx-tile: color-mix(in srgb, var(--text-primary) 4.5%, var(--bg-primary));
  --cx-hair: color-mix(in srgb, var(--text-primary) 11%, transparent);
  background: var(--bg-primary);
}
.cx-page .lp-breadcrumb { display: none; }
.cx-main {
  display: grid;
  grid-template-columns: 210px minmax(0, 1fr);
  gap: 40px;
  max-width: 1320px;
  margin: 0 auto;
  padding: 28px 28px 80px;
}
.cx-side { position: sticky; top: calc(var(--bar-h, 52px) + 20px); align-self: start; display: flex; flex-direction: column; gap: 22px; }
.cx-side-group { display: flex; flex-direction: column; gap: 2px; }
.cx-side-h { margin: 0 0 6px; padding: 0 12px; font-size: 0.72rem; font-weight: 500; color: var(--text-secondary); }
.cx-side-item {
  display: block;
  width: 100%;
  padding: 8px 12px;
  border: 0;
  border-radius: 10px;
  background: none;
  color: color-mix(in srgb, var(--text-primary) 80%, transparent);
  font: inherit;
  font-size: 0.9rem;
  font-weight: 500;
  text-align: left;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.cx-side-item:hover { background: var(--cx-tile); color: var(--text-primary); }
.cx-side-item.is-active { background: var(--cx-tile); color: var(--text-primary); font-weight: 650; }
.cx-side-item:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.cx-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; flex-wrap: wrap; margin-bottom: 34px; }
.cx-head-copy { max-width: 620px; }
.cx-eyebrow { margin: 0 0 6px; font-size: 1rem; font-weight: 600; color: var(--accent); }
.cx-head h1 { margin: 0 0 10px; font-family: var(--font-body); font-size: clamp(2rem, 4vw, 3rem); font-weight: 700; letter-spacing: -0.03em; line-height: 1.05; }
.cx-head-copy > p:last-child { margin: 0; font-size: 1.02rem; line-height: 1.5; color: var(--text-secondary); }
.cx-head-actions { display: flex; gap: 10px; align-items: center; }
.cx-search { width: 240px; height: 40px; border-radius: 980px; padding: 0 16px; }
.cx-share-btn { height: 40px; padding: 0 18px; border-radius: 980px; white-space: nowrap; }

.cx-section { margin-top: 12px; margin-bottom: 44px; }
.cx-section-head { display: flex; align-items: baseline; gap: 12px; margin-bottom: 16px; }
.cx-section-head h2 { margin: 0; font-family: var(--font-body); font-size: 1.35rem; font-weight: 700; letter-spacing: -0.02em; }
.cx-count { font-size: 0.85rem; color: var(--text-secondary); }
.cx-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(290px, 1fr)); gap: 20px; }

.cx-card {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--cx-hair);
  border-radius: 16px;
  overflow: hidden;
  background: var(--cx-tile);
  transition: border-color 0.2s ease, transform 0.25s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.cx-card:hover { border-color: color-mix(in srgb, var(--text-primary) 22%, transparent); }
.cx-card-media { position: relative; display: block; aspect-ratio: 16 / 10; overflow: hidden; background: #0b0d12; }
.cx-card-media img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform 0.6s cubic-bezier(0.2, 0.8, 0.2, 1); }
.cx-card:hover .cx-card-media img { transform: scale(1.04); }
.cx-badge {
  position: absolute;
  top: 12px;
  right: 12px;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 10px;
  border-radius: 980px;
  font-size: 0.72rem;
  font-weight: 700;
  line-height: 1.3;
  color: #0b0d12;
  box-shadow: 0 4px 14px -6px rgba(0, 0, 0, 0.6);
}
.cx-badge-pick { background: #7ef0a6; }
.cx-badge-pop { background: #7ef0a6; }
.cx-badge-new { background: #ffffff; }
.cx-badge-exp { background: #ffb547; }
.cx-card-body { flex: 1; padding: 18px 20px 16px; }
.cx-card-title { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; margin-bottom: 8px; }
.cx-card-title h3 { margin: 0; font-family: var(--font-body); font-size: 1.12rem; font-weight: 700; letter-spacing: -0.018em; line-height: 1.25; }
.cx-card-title h3 a { color: inherit; text-decoration: none; }
.cx-card-title h3 a:hover { text-decoration: underline; }
.cx-chips { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 5px; flex: none; }
.cx-chip { padding: 3px 9px; border-radius: 980px; background: color-mix(in srgb, var(--text-primary) 10%, transparent); font-size: 0.68rem; font-weight: 650; color: var(--text-primary); }
.cx-card-by { margin: -2px 0 8px; font-size: 0.78rem; color: var(--text-secondary); }
.cx-card-desc {
  margin: 0;
  font-size: 0.88rem;
  line-height: 1.5;
  color: color-mix(in srgb, var(--text-primary) 76%, transparent);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.cx-card-foot { display: flex; align-items: stretch; border-top: 1px solid var(--cx-hair); }
.cx-card-go {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 20px;
  font-size: 0.9rem;
  font-weight: 650;
  color: var(--text-primary);
  text-decoration: none;
  transition: background-color 0.15s ease;
}
.cx-card-go svg { transition: transform 0.2s ease; }
.cx-card-go:hover { background: color-mix(in srgb, var(--text-primary) 5%, transparent); }
.cx-card-go:hover svg { transform: translateX(3px); }
.cx-icon-btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 0 14px;
  border: 0;
  border-left: 1px solid var(--cx-hair);
  background: none;
  color: var(--text-secondary);
  font: inherit;
  font-size: 0.82rem;
  font-weight: 650;
  cursor: pointer;
  transition: color 0.15s ease, background-color 0.15s ease;
}
.cx-icon-btn svg { width: 17px; height: 17px; }
.cx-icon-btn:hover { color: var(--text-primary); background: color-mix(in srgb, var(--text-primary) 5%, transparent); }
.cx-icon-btn.voted { color: var(--accent); }
.cx-icon-btn.is-copied { color: #7ef0a6; }
.cx-empty { padding: 28px 0; font-size: 0.95rem; color: var(--text-secondary); }
.cx-inline-link { border: 0; padding: 0; background: none; color: var(--accent); font: inherit; cursor: pointer; text-decoration: underline; }
.cx-page .cp-error { margin-top: 8px; border-radius: 14px; }
.cx-page .cp-submit-section { margin-bottom: 8px; }

@media (max-width: 900px) {
  .cx-main { display: block; padding: 18px 16px 60px; }
  /* Sidebar becomes three sideways-scrolling chip rows above the grid. */
  .cx-side { position: static; gap: 10px; margin-bottom: 22px; }
  .cx-side-group { flex-direction: row; gap: 6px; overflow-x: auto; scrollbar-width: none; margin: 0 -16px; padding: 0 16px; }
  .cx-side-group::-webkit-scrollbar { display: none; }
  .cx-side-h { display: none; }
  .cx-side-item { flex: none; width: auto; padding: 7px 14px; border-radius: 980px; background: var(--cx-tile); font-size: 0.85rem; white-space: nowrap; }
  .cx-side-item.is-active { background: var(--text-primary); color: var(--bg-primary); }
  .cx-head { order: -1; margin-bottom: 20px; }
  .cx-head-actions { width: 100%; }
  .cx-search { flex: 1; width: auto; }
  .cx-grid { grid-template-columns: minmax(0, 1fr); gap: 16px; }
}
@media (max-width: 900px) {
  /* The header block reads first on a phone, then the filters. */
  .cx-main { display: flex; flex-direction: column; }
  .cx-content { display: contents; }
  .cx-head { order: 0; }
  .cx-side { order: 1; }
  .cx-content > :not(.cx-head) { order: 2; }
}
@media (prefers-reduced-motion: reduce) {
  .cx-card-media img, .cx-card-go svg { transition: none; }
}
.cx-page .cp-error { margin: 0; padding: 18px 20px; border: 1px solid var(--cx-hair); background: var(--cx-tile); color: var(--text-secondary); text-align: left; font-size: 0.9rem; }
.cx-page .cp-loading { padding: 18px 0; text-align: left; color: var(--text-secondary); }
.cx-page .cp-submit-section { max-width: 680px; margin: 0 0 32px; padding: 0; }
.cx-page .cp-vote-error { max-width: none; margin: 0 0 14px; }

/* Desktop hover descriptions (hoverHints in main.js). */
html.hints-hover .sidebar .hint-text.hint-static,
html.hints-hover .sidebar .hint-text.hint-static + .hint-toggle { display: none; }
.hint-dot { display: none; }
html.hints-hover .hint-dot {
  display: inline-grid;
  place-items: center;
  width: 14px;
  height: 14px;
  margin-left: 6px;
  border: 1px solid var(--border);
  border-radius: 50%;
  font: 700 9px/1 var(--font-body);
  font-style: normal;
  color: var(--text-secondary);
  vertical-align: 1px;
  text-transform: none;
  letter-spacing: 0;
  transition: color 0.15s ease, border-color 0.15s ease;
}
html.hints-hover [data-hint]:hover > label .hint-dot,
html.hints-hover [data-hint]:hover > .label-row .hint-dot,
html.hints-hover [data-hint]:hover .hint-dot { color: var(--accent); border-color: var(--accent); }
.hint-pop {
  position: fixed;
  z-index: 2000;
  max-width: 300px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg-secondary);
  box-shadow: 0 16px 40px -16px rgba(0, 0, 0, 0.6);
  color: var(--text-primary);
  font-size: 0.8rem;
  line-height: 1.5;
  white-space: pre-line;
  pointer-events: none;
  opacity: 0;
  transform: translateX(4px);
  transition: opacity 0.14s ease, transform 0.14s ease;
}
.hint-pop.is-on { opacity: 1; transform: none; }
.hint-pop.is-measuring { visibility: hidden; opacity: 1; transition: none; }
@media (prefers-reduced-motion: reduce) { .hint-pop { transition: none; } }

/* Mobile: while the settings sheet is up, the quick pill bar over the preview goes — its
   controls are in the sheet anyway, and it was taking ~95px from the picture exactly when
   you're watching a setting change (syncMobileChromeInsets then reserves nothing for it). */
@media (max-width: 768px) {
  body.sidebar-open .mobile-quick-style-bar { display: none; }
}

/* ─── Grade picker (desktop quick bar; gradePicker in main.js) ─────────────────────── */
/* The grade picker button replaces this select on the desktop bar (the select stays in the
   DOM: it's what mirrors #cinematicLutSelect). !important to beat .mq-desktop-only's. */
.is-desktop-bar #mqLutSelect.mq-desktop-only { display: none !important; }
/* Takes the hidden select's slot in the bar (items are placed by `order`). */
.is-desktop-bar #mqGradeBtn { order: 11; }
.mq-grade-btn { display: none; }
@media (min-width: 769px) {
  .is-desktop-bar .mq-grade-btn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    height: 34px;
    max-width: 210px;
    padding: 0 10px 0 4px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--bg-tertiary);
    color: var(--text-primary);
    font: inherit;
    font-size: 0.82rem;
    font-weight: 600;
    cursor: pointer;
    transition: border-color 0.15s ease, background-color 0.15s ease;
  }
  .is-desktop-bar .mq-grade-btn:hover, .is-desktop-bar .mq-grade-btn[aria-expanded="true"] { border-color: var(--accent); }
}
.mq-grade-thumb {
  flex: none;
  width: 40px;
  height: 26px;
  border-radius: 6px;
  background: linear-gradient(135deg, #3b4252, #1b1f27) center / cover no-repeat;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.12);
}
.mq-grade-thumb.is-none { background: repeating-linear-gradient(135deg, var(--bg-secondary) 0 4px, var(--bg-tertiary) 4px 8px); }
.mq-grade-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mq-grade-chev { flex: none; opacity: 0.6; transition: transform 0.2s ease; }
.mq-grade-btn[aria-expanded="true"] .mq-grade-chev { transform: rotate(180deg); }

.grade-pop {
  position: fixed;
  z-index: 1500;
  display: flex;
  flex-direction: column;
  padding: 14px;
  border: 1px solid var(--border);
  border-radius: 16px;
  background: var(--bg-secondary);
  box-shadow: 0 30px 70px -30px rgba(0, 0, 0, 0.8), 0 8px 20px -10px rgba(0, 0, 0, 0.4);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px);
  transition: opacity 0.16s ease, transform 0.2s cubic-bezier(0.2, 0.8, 0.2, 1), visibility 0s linear 0.2s;
}
.grade-pop.is-open { opacity: 1; visibility: visible; transform: none; transition: opacity 0.16s ease, transform 0.2s cubic-bezier(0.2, 0.8, 0.2, 1), visibility 0s; }
.grade-pop-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 12px; }
.grade-pop-filters { display: flex; flex-wrap: wrap; gap: 6px; }
.grade-pop-compare {
  flex: none;
  padding: 6px 12px;
  border: 1px solid var(--border);
  border-radius: 980px;
  background: var(--bg-tertiary);
  color: var(--text-primary);
  font: inherit;
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
  user-select: none;
  touch-action: none;
}
.grade-pop-compare.is-held { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.grade-pop-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 10px;
  min-height: 0;
  overflow-y: auto;
  padding: 2px;
  margin: -2px;
  overscroll-behavior: contain;
}
.grade-pop-grid .lut-swatch {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: auto;
  padding: 0;
  border: 0;
  background: none;
  color: var(--text-secondary);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.grade-pop-grid .lut-swatch-preview {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 10;
  border-radius: 10px;
  background: linear-gradient(135deg, #2a303b, #151920) center / cover no-repeat;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
  transition: box-shadow 0.15s ease, transform 0.2s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.grade-pop-grid .lut-swatch-name { font-size: 0.74rem; font-weight: 600; line-height: 1.2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.grade-pop-grid .lut-swatch:hover .lut-swatch-preview,
.grade-pop-grid .lut-swatch:focus-visible .lut-swatch-preview,
.grade-pop-grid .lut-swatch.is-previewing .lut-swatch-preview { box-shadow: inset 0 0 0 2px var(--text-primary); transform: translateY(-1px); }
.grade-pop-grid .lut-swatch:focus-visible { outline: none; }
.grade-pop-grid .lut-swatch.active .lut-swatch-preview { box-shadow: inset 0 0 0 2px var(--accent); }
.grade-pop-grid .lut-swatch.active .lut-swatch-name,
.grade-pop-grid .lut-swatch:hover .lut-swatch-name { color: var(--text-primary); }
.grade-pop-foot { display: flex; align-items: center; gap: 14px; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border); }
.grade-pop-intensity-wrap { flex: 1; display: flex; align-items: center; gap: 10px; font-size: 0.78rem; font-weight: 600; color: var(--text-secondary); }
.grade-pop-intensity-wrap.is-disabled { opacity: 0.4; pointer-events: none; }
.grade-pop-intensity { flex: 1; min-width: 80px; }
.grade-pop-intensity-val { min-width: 36px; text-align: right; font-variant-numeric: tabular-nums; color: var(--text-primary); }
.grade-pop-link { border: 0; padding: 4px 0; background: none; color: var(--accent); font: inherit; font-size: 0.8rem; font-weight: 600; cursor: pointer; white-space: nowrap; }
.grade-pop-link:hover { text-decoration: underline; }
.grade-pop-src { margin: 8px 0 0; font-size: 0.72rem; color: var(--text-secondary); }
@media (prefers-reduced-motion: reduce) { .grade-pop, .grade-pop-grid .lut-swatch-preview { transition: none; } }
.grade-pop-cmp { display: inline-flex; gap: 6px; flex: none; }
.grade-pop-compare.is-active, #gradeSplitBtn.is-active { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }

/* Flyout visual column (site_nav.py VISUALS): real renders, pushed to the right edge. */
.site-nav .site-nav-visuals { display: flex; gap: 14px; margin-left: auto; }
.site-nav .site-nav-visual {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 200px;
  color: var(--text-primary);
  font-size: 0.8rem;
  font-weight: 600;
  text-decoration: none;
  opacity: 0;
  transform: translateY(-5px);
}
.site-nav .site-nav-visual img { width: 100%; height: auto; aspect-ratio: 16 / 10; object-fit: cover; border-radius: 12px; transition: transform 0.5s cubic-bezier(0.2, 0.8, 0.2, 1); }
.site-nav .site-nav-visual:hover img { transform: scale(1.03); }
.site-nav .site-nav-visual:hover span { color: var(--accent); }
.site-nav .site-nav-group:hover > .site-nav-panel .site-nav-visual,
.site-nav .site-nav-group:focus-within > .site-nav-panel .site-nav-visual {
  opacity: 1;
  transform: none;
  transition: opacity 0.35s ease 0.12s, transform 0.4s cubic-bezier(0.2, 0.8, 0.2, 1) 0.12s;
}
.header-nav a.site-nav-visual { padding: 0; border-radius: 0; background: none; font-size: 0.8rem; }
@media (max-width: 1100px) { .site-nav .site-nav-visual { width: 150px; } }
@media (max-width: 960px) { .site-nav .site-nav-visuals { display: none; } }
.site-nav-mobile .site-nav-visuals { display: none; }

/* Landing header slides away on scroll down (HIDE_SCRIPT in site_nav.py). */
.lp-page-header { transition: transform 0.32s cubic-bezier(0.4, 0, 0.2, 1); }
.lp-page-header.is-hidden { transform: translateY(-100%); }
@media (prefers-reduced-motion: reduce) { .lp-page-header { transition: none; } }

/* Long-tail SEO sections on effect guides (scripts/guides/seo_extras.py). */
.seo-extras { margin: 8px 0 36px; }
.seo-extras h3 { margin: 28px 0 12px; font-size: 1.25rem; font-weight: 700; letter-spacing: -0.015em; }
.seo-extras p { color: var(--text-secondary); line-height: 1.65; }
.seo-extras-steps { margin: 0; padding: 0; list-style: none; counter-reset: step; display: grid; gap: 12px; }
.seo-extras-steps li { counter-increment: step; position: relative; padding: 14px 16px 14px 56px; border-radius: 14px; background: color-mix(in srgb, var(--text-primary) 4%, var(--bg-primary)); color: var(--text-secondary); line-height: 1.55; }
.seo-extras-steps li::before { content: counter(step); position: absolute; left: 16px; top: 14px; width: 26px; height: 26px; display: grid; place-items: center; border-radius: 50%; background: var(--accent); color: var(--on-accent); font-weight: 700; font-size: 0.85rem; }
.seo-extras-steps strong { display: block; margin-bottom: 2px; color: var(--text-primary); }
.seo-extras-related a { color: var(--accent); }
.seo-extras-steps a { color: var(--accent); }

/* Converter landing pages: the tool itself, embedded under the hero (convert_pages.py). */
.vc-embed-section { max-width: 880px; margin: 0 auto; padding-top: 8px; scroll-margin-top: 80px; }
.vc-embed-frame { display: block; width: 100%; height: 330px; border: 0; background: transparent; color-scheme: normal; transition: height 0.25s ease; }
@media (prefers-reduced-motion: reduce) { .vc-embed-frame { transition: none; } }

/* Flyout polish (2026-09-23). The sheet used to be the page's own black with a shadow you
   can't see on black, so it had no edge: it read as the hero being chopped off mid-word.
   Now it's a slightly lifted surface with a hairline, a real drop shadow, and the bar
   takes the same surface while it's open so the two read as one piece. */
.site-nav { --nav-sheet: color-mix(in srgb, var(--text-primary) 4.5%, var(--bg-primary)); }
.site-nav .site-nav-panel {
  padding-bottom: 18px;
  background: var(--nav-sheet);
  border-bottom: 1px solid color-mix(in srgb, var(--text-primary) 10%, transparent);
  box-shadow: 0 28px 60px -12px rgba(0, 0, 0, 0.6), 0 2px 0 rgba(0, 0, 0, 0.2);
}
:root[data-theme="light"] .site-nav .site-nav-panel { box-shadow: 0 28px 60px -18px rgba(15, 23, 42, 0.22); }
@media (min-width: 901px) {
  .lp-page-header:has(.site-nav-group:hover:not(.is-shut))::before,
  .lp-page-header:has(.site-nav-group:focus-within:not(.is-shut))::before {
    background: color-mix(in srgb, var(--text-primary) 4.5%, var(--bg-primary));
  }
}
/* Primary links: an arrow slides in on hover so the big type still reads as a link. */
.site-nav .site-nav-col.is-primary .site-nav-item::after {
  content: '→';
  display: inline-block;
  margin-left: 10px;
  font-weight: 500;
  opacity: 0;
  transform: translateX(-8px);
  transition: opacity 0.18s ease, transform 0.25s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.site-nav .site-nav-col.is-primary .site-nav-item:hover::after,
.site-nav .site-nav-col.is-primary .site-nav-item:focus-visible::after { opacity: 1; transform: none; }
/* Secondary links carry their blurb underneath. */
.site-nav .site-nav-item.has-detail { padding: 6px 0; }
.site-nav .site-nav-item.has-detail small {
  display: block;
  margin-top: 2px;
  font-size: 0.72rem;
  font-weight: 400;
  letter-spacing: 0;
  color: var(--text-secondary);
  transition: color 0.15s ease;
}
.site-nav .site-nav-item.has-detail:hover small { color: color-mix(in srgb, var(--accent) 70%, var(--text-secondary)); }
.site-nav-mobile .site-nav-item.has-detail small { display: none; }
/* Tools: the converter's drop zone in miniature. */
.site-nav .site-nav-drop { width: 240px; }
.site-nav .site-nav-drop-box {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  aspect-ratio: 16 / 10;
  border: 1.5px dashed color-mix(in srgb, var(--accent) 55%, transparent);
  border-radius: 12px;
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  color: var(--accent);
  transition: background-color 0.2s ease, border-color 0.2s ease, transform 0.5s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.site-nav .site-nav-drop-box b { font-size: 0.92rem; font-weight: 650; color: var(--text-primary); }
.site-nav .site-nav-drop-box i { font-style: normal; font-size: 0.7rem; letter-spacing: 0.02em; color: var(--text-secondary); }
.site-nav .site-nav-drop:hover .site-nav-drop-box { background: color-mix(in srgb, var(--accent) 14%, transparent); border-color: var(--accent); transform: scale(1.02); }
@media (max-width: 1100px) { .site-nav .site-nav-drop { width: 190px; } }
/* The strip along the bottom of each flyout. */
.site-nav .site-nav-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  margin-top: 30px;
  padding-top: 14px;
  border-top: 1px solid color-mix(in srgb, var(--text-primary) 8%, transparent);
  font-size: 0.74rem;
  color: var(--text-secondary);
  opacity: 0;
}
.site-nav .site-nav-group:hover > .site-nav-panel .site-nav-foot,
.site-nav .site-nav-group:focus-within > .site-nav-panel .site-nav-foot { opacity: 1; transition: opacity 0.35s ease 0.18s; }
.site-nav .site-nav-foot-cta { color: var(--accent); font-weight: 600; text-decoration: none; }
.site-nav .site-nav-foot-cta:hover { text-decoration: underline; }
.header-nav a.site-nav-foot-cta { padding: 0; background: none; border-radius: 0; }
@media (prefers-reduced-motion: reduce) {
  .site-nav .site-nav-col.is-primary .site-nav-item::after, .site-nav .site-nav-foot { transition: none !important; }
}

/* Example tiles play on hover (home_page.py). The clip sits over the still and only fades in
   once it is actually playing; the still is the clip's first frame, so nothing jumps. */
.ah-tile-link video.ah-tile-clip { opacity: 0; transition: opacity 0.35s ease, transform 0.7s cubic-bezier(0.2, 0.8, 0.2, 1); }
.ah-tile-link video.ah-tile-clip.is-main, .ah-tile-link.is-playing video.ah-tile-clip { opacity: 1; }
.ah-tile-play {
  position: absolute;
  right: 60px;
  bottom: 16px;
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: rgba(40, 40, 44, 0.72);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  color: #fff;
  transition: opacity 0.25s ease, transform 0.3s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.ah-tile-link.is-playing .ah-tile-play { opacity: 0; transform: scale(0.7); }
@media (prefers-reduced-motion: reduce) { .ah-tile-link video.ah-tile-clip, .ah-tile-play { transition: none; } }

/* Flyout visuals, round 2 (2026-09-24): portrait tiles whose clips play while the panel is
   open, and a short accent rule under the open section's name. */
.site-nav .site-nav-visual { width: 150px; }
.site-nav .site-nav-visual-media {
  position: relative;
  display: block;
  aspect-ratio: 3 / 4;
  border-radius: 14px;
  overflow: hidden;
  background: #0b0d12;
}
.site-nav .site-nav-visual-media img, .site-nav .site-nav-visual-media video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  aspect-ratio: auto;
  object-fit: cover;
  border-radius: 0;
  transition: opacity 0.35s ease, transform 0.5s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.site-nav .site-nav-visual-media video { opacity: 0; }
.site-nav .site-nav-visual-media video.is-on { opacity: 1; }
.site-nav .site-nav-visual:hover .site-nav-visual-media img,
.site-nav .site-nav-visual:hover .site-nav-visual-media video { transform: scale(1.04); }
.site-nav .site-nav-visual:hover img { transform: scale(1.04); }
.site-nav .site-nav-group:hover > .site-nav-panel .site-nav-visual:nth-child(2),
.site-nav .site-nav-group:focus-within > .site-nav-panel .site-nav-visual:nth-child(2) { transition-delay: 0.17s; }
.site-nav .site-nav-group:hover > .site-nav-panel .site-nav-visual:nth-child(3),
.site-nav .site-nav-group:focus-within > .site-nav-panel .site-nav-visual:nth-child(3) { transition-delay: 0.22s; }
@media (max-width: 1240px) { .site-nav .site-nav-visual:nth-child(3) { display: none; } }
@media (max-width: 1100px) { .site-nav .site-nav-visual { width: 120px; } }
.site-nav .site-nav-trigger { position: relative; }
.site-nav .site-nav-trigger::before {
  content: '';
  position: absolute;
  left: 13px;
  right: 13px;
  bottom: 0;
  height: 2px;
  border-radius: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transition: transform 0.25s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.site-nav .site-nav-group:hover:not(.is-shut) .site-nav-trigger::before,
.site-nav .site-nav-group:focus-within:not(.is-shut) .site-nav-trigger::before { transform: scaleX(1); }
@media (prefers-reduced-motion: reduce) {
  .site-nav .site-nav-trigger::before, .site-nav .site-nav-visual-media img, .site-nav .site-nav-visual-media video { transition: none; }
}

/* Hero reel: sits exactly over the video area of the app screenshot (measured on the
   2400x1500 image: x 28-1740, y 420-1380). */
.lp-home .ah-hero-shot { position: relative; }
.ah-hero-reel {
  position: absolute;
  left: 1.1667%;
  top: 28%;
  width: 71.3333%;
  height: 64%;
  border-radius: 0.6% / 1%;
  overflow: hidden;
  pointer-events: none;
}
.ah-hero-reel video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 0.6s ease;
}
.ah-hero-reel video.is-on { opacity: 1; }
.ah-hero-reel-label {
  position: absolute;
  left: 14px;
  top: 14px;
  padding: 5px 11px;
  border-radius: 980px;
  background: rgba(20, 20, 24, 0.66);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  color: #fff;
  font-size: 0.74rem;
  font-weight: 600;
  opacity: 0;
  transition: opacity 0.3s ease;
}
.ah-hero-reel.is-live .ah-hero-reel-label:not(:empty) { opacity: 1; }
@media (max-width: 700px) { .ah-hero-reel-label { left: 8px; top: 8px; font-size: 0.66rem; padding: 3px 8px; } }
.site-nav .site-nav-clip { pointer-events: none; }
/* The hover arrow must never wrap: in a squeezed column it dropped onto its own (invisible)
   line and spaced the big links out unevenly. */
.site-nav .site-nav-col.is-primary .site-nav-item { position: relative; white-space: nowrap; }
.site-nav .site-nav-col.is-primary .site-nav-item::after { position: absolute; top: 50%; margin: -0.62em 0 0 10px; }
.site-nav .site-nav-col:not(.is-primary) { min-width: 190px; }
.site-nav .site-nav-panel-inner { gap: clamp(32px, 4vw, 64px); }
@media (max-width: 1480px) { .site-nav .site-nav-visual:nth-child(3) { display: none; } }
/* A playing (composited) video escapes overflow:hidden + border-radius in Chrome; a clip-path
   holds it to the tile's rounded shape. */
.ah-tile-link { clip-path: inset(0 round 24px); }
.site-nav .site-nav-visual-media { clip-path: inset(0 round 14px); }
.ah-hero-reel { clip-path: inset(0 round 6px); }

/* ─── Readability pass (2026-09-24) ─────────────────────────────────────────────
   Measured with a contrast audit over every visible text node (home, converter, guide
   pages, both themes). What failed or read poorly:
   - the footer faded its already-grey text with opacity 0.5-0.8 (3.2-3.9:1 in light);
   - inline links in page copy had no style at all, so they drew in the browser's default
     #0000EE, 2.2:1 on black;
   - the warm orange kicker text was 3.35:1 on white;
   - card copy sat at 12.5-13.6px grey in narrow columns, 4-6 lines per sentence. */
:root { --text-secondary: #465064; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { --text-secondary: #c9d0dc; } }
:root[data-theme="dark"] { --text-secondary: #c9d0dc; }
:root[data-theme="light"] { --text-secondary: #465064; }

/* Footer: token colours, no opacity fades, nothing under 13px. */
.footer-brand p, .footer-links h3, .footer-guides h3, .footer-links li, .footer-guides-group h4,
.footer-guides li, .lp-body .footer-guides.js-collapsible .footer-group-toggle, .site-footer small { opacity: 1; }
.footer-links h3, .footer-guides h3, .footer-guides-group h4, .footer-group-toggle { color: var(--text-primary); }
.footer-brand p, .footer-links li, .footer-guides li { font-size: 0.875rem; color: var(--text-secondary); }
.site-footer a { color: var(--text-secondary); }
.site-footer a:hover { color: var(--text-primary); }
.site-footer small { font-size: 0.8125rem; color: var(--text-secondary); }

/* Links inside running copy: accent, underlined, never the UA default blue. */
.lp-body main p a:not([class]), .lp-body main li a:not([class]), .lp-body main td a:not([class]),
.lp-body main dd a:not([class]), .ah-credit a {
  color: var(--accent);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}
.lp-body main p a:not([class]):hover, .lp-body main li a:not([class]):hover { text-decoration-thickness: 2px; }

/* Warm orange as text needs to be darker on white. */
:root[data-theme="light"] .lp-kicker-warm, :root[data-theme="light"] .lp-related-cat,
:root[data-theme="light"] .lp-icon-warm { color: #b83c14; }

/* Card copy: bigger, looser, full width. */
.lp-section .mode-card span, .lp-home .mode-card span { font-size: 0.92rem; line-height: 1.55; }
.lp-section .style-card span, .seo-card p, .lp-body .seo-container article .seo-card p,
.lp-explainer-step p, .how-step p, .lp-steps p { font-size: 0.95rem; line-height: 1.6; }
.lp-home .ah-credit { font-size: 0.8125rem; }
@media (min-width: 1000px) and (max-width: 1279px) { .lp-home .mode-card-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
/* Tool cards: icon and title on one line, description across the whole card beneath, arrow
   in the corner — the old row layout squeezed the text into a ~130px column. */
.lp-home .tool-card { display: grid; grid-template-columns: auto 1fr; align-items: center; align-content: start; column-gap: 12px; row-gap: 8px; position: relative; padding: 22px 44px 22px 22px; }
.lp-home .tool-card-body { display: contents; }
.lp-home .tool-card-body strong { grid-column: 2; font-size: 1.02rem; line-height: 1.3; }
.lp-home .tool-card-body span { grid-column: 1 / -1; font-size: 0.92rem; line-height: 1.55; }
.lp-home .tool-card-icon { width: auto; height: auto; }
.lp-home .tool-card-arrow { position: absolute; top: 22px; right: 20px; margin: 0; }
.footer-bottom { opacity: 1; border-top-color: color-mix(in srgb, var(--text-primary) 14%, transparent); }

/* Scroll-linked motion on the homepage (MOTION_SCRIPT sets --p / --q; see home_page.py).
   Individual transform properties (rotate/scale/translate) are used rather than `transform`
   so they compose with the hero's entrance animation instead of fighting it. Nothing here
   applies until the script has run (.scrub-ready), and reduced motion never gets it. */
@media (prefers-reduced-motion: no-preference) {
  .lp-home .ah-hero { perspective: 1800px; }
  .scrub-ready .ah-hero-shot[data-scrub] {
    transform-origin: 50% 0;
    rotate: x calc((1 - var(--p, 1)) * 18deg);
    scale: calc(0.9 + var(--p, 1) * 0.1);
    will-change: rotate, scale;
  }
  .scrub-ready .ah-hero-copy[data-scrub-out] {
    translate: 0 calc(var(--q, 0) * -60px);
    opacity: calc(1 - var(--q, 0) * 0.9);
    scale: calc(1 - var(--q, 0) * 0.04);
  }
  .scrub-ready .ah-gallery-track[data-scrub] { translate: calc((1 - var(--p, 1)) * 14vw) 0; }
  /* Tiles rise one after another when the gallery arrives (it carries .reveal). */
  .ah-gallery.reveal.reveal-armed .ah-tile { opacity: 0; translate: 0 56px; transition: opacity 0.7s ease, translate 0.9s cubic-bezier(0.16, 1, 0.3, 1); transition-delay: calc(var(--k, 0) * 80ms); }
  .ah-gallery.reveal.reveal-armed.in-view .ah-tile { opacity: 1; translate: 0 0; }
  /* Cards in the tool, mode and style grids travel further and settle slower: the old 12-20px
     rise read as a flicker rather than movement. */
  .tool-grid.reveal.reveal-armed .tool-card, .mode-card-grid.reveal.reveal-armed .mode-card,
  .style-card-grid.reveal.reveal-armed .style-card { translate: 0 36px; }
  .tool-grid.reveal.in-view .tool-card, .mode-card-grid.reveal.in-view .mode-card,
  .style-card-grid.reveal.in-view .style-card { translate: 0 0; transition-property: opacity, transform, translate; transition-duration: 0.7s, 0.8s, 0.9s; transition-timing-function: ease, cubic-bezier(0.16, 1, 0.3, 1), cubic-bezier(0.16, 1, 0.3, 1); }
}
/* The gallery slides in from the right; clip it so the page never gains a horizontal scroll. */
.lp-home .ah-examples { overflow-x: clip; }

/* ─── App start screen, round 3 (2026-09-27) ───
   Balanced headline (no orphaned "Browser"), the intro no longer clamped mid-sentence, icon
   buttons in one row on phones too (they used to stack into 60px-tall slabs with the third
   pushed off the right edge: an old column rule plus flex-basis 40%), a "Start with a
   look" strip of real preset renders, and the guide links as chips. */
/* `safe center`: the 2026-09-22 compact rule above set plain `center`, which pushes an
   overflowing start screen ABOVE its scroll origin (title unreachable on phones). */
.canvas-wrapper.is-empty .empty-state { justify-content: safe center; }
.empty-state .empty-state-title { text-wrap: balance; max-width: 30ch; margin-inline: auto; font-size: clamp(1.4rem, 2.6vw, 2.1rem); letter-spacing: -0.025em; }
.empty-state > .empty-state-sub:first-of-type { display: block; -webkit-line-clamp: unset; overflow: visible; max-width: 52ch; text-wrap: pretty; }
.empty-state .empty-state-actions { flex-direction: row; flex-wrap: nowrap; width: auto; max-width: 100%; }
.empty-state .empty-state-actions .btn { display: inline-flex; align-items: center; justify-content: center; gap: 7px; flex: 0 1 auto; height: 38px; min-height: 0; white-space: nowrap; }
.empty-state .empty-state-actions .btn .icon { width: 16px; height: 16px; flex-shrink: 0; margin: 0; }
.empty-state-looks { margin-top: 18px; width: 100%; max-width: 760px; }
.look-strip {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 132px;
  gap: 10px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  padding: 2px 2px 10px;
  scrollbar-width: thin;
  -webkit-mask-image: linear-gradient(90deg, #000 88%, transparent);
  mask-image: linear-gradient(90deg, #000 88%, transparent);
}
.look-card {
  scroll-snap-align: start;
  display: flex; flex-direction: column; gap: 6px;
  padding: 0; border: 0; background: none; cursor: pointer;
  color: var(--text-primary); font: inherit; text-align: left;
}
.look-card img {
  display: block; width: 100%; height: auto; aspect-ratio: 16 / 10; object-fit: cover;
  border-radius: 10px; background: var(--bg-secondary);
  outline: 1px solid var(--border); outline-offset: -1px;
  transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1), outline-color 0.15s ease;
}
.look-card span { font-size: 0.78rem; font-weight: 600; padding-left: 2px; }
.look-card:hover img { transform: translateY(-2px); outline-color: var(--text-secondary); }
.look-card:focus-visible { outline: none; }
.look-card:focus-visible img, .look-card.is-active img { outline: 2px solid var(--accent); outline-offset: 1px; }
.look-card.is-active span { color: var(--accent); }
.empty-state .empty-state-guides { gap: 8px 18px; margin-top: 14px; }
.empty-state .empty-state-guides-group { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.empty-state .empty-state-guides a {
  text-decoration: none; font-size: 0.74rem; font-weight: 500;
  padding: 3px 10px; border-radius: 999px; border: 1px solid var(--border);
  color: var(--text-secondary); transition: color 0.15s ease, border-color 0.15s ease;
}
.empty-state .empty-state-guides a:hover { color: var(--text-primary); border-color: var(--text-secondary); }
@media (max-width: 768px) {
  .empty-state .empty-state-title { font-size: 1.35rem; }
  .empty-state .empty-state-actions { gap: 6px; width: 100%; }
  .empty-state .empty-state-actions .btn { flex: 1 1 0; min-width: 0; padding: 0 8px; font-size: 0.78rem; gap: 5px; }
  .empty-state .empty-state-actions .btn .icon { width: 14px; height: 14px; }
  .look-strip { grid-auto-columns: 112px; gap: 8px; }
  .empty-state .empty-state-guides { justify-content: center; text-align: center; }
  .empty-state .empty-state-guides-group { justify-content: center; }
}
@media (prefers-reduced-motion: reduce) { .look-card img { transition: none; } .look-card:hover img { transform: none; } }

/* ─── Text layers (2026-09-27) — Clip Editor › Text ─── */
.tx-head { display: flex; align-items: center; gap: 10px; }
.tx-add { display: inline-flex; align-items: center; gap: 6px; height: 34px; padding: 0 14px; font-size: 0.8rem; }
.tx-add .icon { width: 15px; height: 15px; margin: 0; }
.tx-muted { font-size: 0.7rem; color: var(--text-secondary); }
.tx-templates { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 12px; }
.tx-tpl {
  font: 600 0.72rem/1 var(--font-body, inherit); color: var(--text-primary);
  padding: 7px 11px; border-radius: 999px; border: 1px solid var(--border);
  background: var(--bg-secondary); cursor: pointer;
  transition: border-color 0.15s ease, background-color 0.15s ease, transform 0.1s ease;
}
.tx-tpl:hover { border-color: var(--accent); }
.tx-tpl:active { transform: scale(0.97); }
.tx-empty { font-size: 0.74rem; color: var(--text-secondary); line-height: 1.5; margin: 0 0 8px; }
.tx-timeline {
  --tx-eye: 30px;
  position: relative; border: 1px solid var(--border); border-radius: var(--radius-md, 10px);
  background: var(--bg-primary); overflow: hidden; user-select: none; -webkit-user-select: none;
  margin-bottom: 12px;
}
.tx-ruler {
  position: relative; height: 22px; margin-left: var(--tx-eye); cursor: ew-resize; touch-action: none;
  background: var(--bg-secondary); border-bottom: 1px solid var(--border);
}
.tx-tick { position: absolute; top: 0; bottom: 0; padding-left: 3px; border-left: 1px solid var(--border); font: 500 0.58rem/22px 'JetBrains Mono', monospace; color: var(--text-secondary); white-space: nowrap; }
.tx-lanes { max-height: 196px; overflow-y: auto; }
.tx-lane { position: relative; display: flex; align-items: stretch; height: 30px; border-bottom: 1px solid var(--border); }
.tx-lane:last-child { border-bottom: 0; }
.tx-eye {
  flex: 0 0 var(--tx-eye); display: grid; place-items: center; border: 0; padding: 0;
  border-right: 1px solid var(--border); background: var(--bg-secondary); color: var(--text-secondary); cursor: pointer;
}
.tx-eye svg { width: 14px; height: 14px; }
.tx-eye:hover { color: var(--text-primary); }
.tx-caps-tag { font: 700 0.58rem/1 'JetBrains Mono', monospace; cursor: default; }
.tx-bar {
  position: absolute; top: 4px; bottom: 4px;
  display: flex; align-items: center; min-width: 14px;
  border-radius: 6px; background: color-mix(in srgb, var(--accent) 22%, var(--bg-secondary));
  border: 1px solid color-mix(in srgb, var(--accent) 55%, transparent);
  cursor: grab; touch-action: none; overflow: hidden;
}
.tx-bar.is-selected { background: var(--accent); border-color: var(--accent); color: var(--on-accent, #fff); }
.tx-bar.is-hidden { opacity: 0.4; }
.tx-bar:active { cursor: grabbing; }
.tx-bar-label { flex: 1; min-width: 0; padding: 0 8px; font-size: 0.66rem; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; pointer-events: none; }
.tx-bar-h { flex: 0 0 8px; align-self: stretch; cursor: ew-resize; background: color-mix(in srgb, currentColor 18%, transparent); }
.tx-caps-track { position: relative; flex: 1; }
.tx-cap { position: absolute; top: 8px; bottom: 8px; padding: 0; border: 0; border-radius: 3px; background: color-mix(in srgb, var(--text-secondary) 45%, transparent); cursor: pointer; }
.tx-cap:hover { background: var(--text-primary); }
.tx-playhead { position: absolute; top: 0; bottom: 0; width: 0; border-left: 2px solid var(--danger, #ef4444); margin-left: -1px; pointer-events: none; z-index: 2; }
.tx-inspector { display: flex; flex-direction: column; gap: 10px; padding-top: 2px; }
.tx-text { width: 100%; resize: none; min-height: 44px; font-size: 0.92rem; line-height: 1.35; }
.tx-row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 8px; }
/* Background strip (2026-10-02) */
.tx-bgrow { flex-direction: column; flex-wrap: nowrap; align-items: stretch; gap: 6px; width: 100%; min-width: 0; max-width: 100%; }
.tx-bgrow > * { min-width: 0; max-width: 100%; }
.tx-bghead { display: flex; align-items: center; gap: 8px; font-size: 0.68rem; font-weight: 600; color: var(--text-secondary); }
.tx-bghead b { color: var(--text-primary); font-weight: 600; flex: 1 1 0; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tx-bggroup { font-size: 0.68rem; margin: 0; }
.tx-bgstep { flex: none; width: 28px; height: 28px; border-radius: 8px; border: 1px solid var(--border); background: var(--bg-secondary); color: var(--text-primary); font-size: 1.05rem; line-height: 1; cursor: pointer; }
.tx-bgstep:hover { border-color: var(--accent); }
.tx-bgstrip { display: flex; gap: 6px; overflow-x: auto; padding: 2px 2px 6px; scroll-snap-type: x proximity; scrollbar-width: thin; }
.tx-bgchip { flex: none; width: 84px; padding: 0; border: 2px solid transparent; border-radius: 8px; background: none; cursor: pointer; scroll-snap-align: start; color: var(--text-secondary); text-align: left; }
.tx-bgchip .sw { display: block; width: 100%; aspect-ratio: 16 / 9; border-radius: 6px; background: #222 center / cover no-repeat; box-shadow: inset 0 0 0 1px rgba(255,255,255,0.12); }
.tx-bgchip span:last-child { display: block; padding: 3px 2px 0; font-size: 0.62rem; line-height: 1.2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tx-bgchip:hover .sw { box-shadow: inset 0 0 0 1px rgba(255,255,255,0.4); }
.tx-bgchip.is-on { border-color: var(--accent); color: var(--text-primary); }
.tx-bgchip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (max-width: 768px) { .tx-bgchip { width: 96px; } .tx-bgstep { width: 36px; height: 36px; } }
.tx-field { display: flex; flex-direction: column; gap: 4px; flex: 1 1 90px; min-width: 0; font-size: 0.68rem; color: var(--text-secondary); font-weight: 600; }
.tx-field b { color: var(--text-primary); font-weight: 600; font-variant-numeric: tabular-nums; margin-left: 4px; }
.tx-field .dropdown, .tx-field .text-input { width: 100%; min-width: 0; }
.tx-grow { flex: 1 1 140px; }
.tx-mini { font-size: 0.7rem; padding: 6px 10px; height: 34px; flex: 0 0 auto; }
.tx-colors { align-items: center; }
.tx-color { flex: 0 0 auto; }
.tx-seg { display: inline-flex; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.tx-seg button { width: 34px; height: 32px; border: 0; background: var(--bg-secondary); color: var(--text-secondary); cursor: pointer; font-size: 0.9rem; }
.tx-seg button + button { border-left: 1px solid var(--border); }
.tx-seg button.is-active, .tx-chip.is-active { background: var(--accent); color: var(--on-accent, #fff); }
.tx-chip { height: 32px; min-width: 38px; padding: 0 10px; border-radius: 8px; border: 1px solid var(--border); background: var(--bg-secondary); color: var(--text-primary); font-weight: 700; font-size: 0.72rem; cursor: pointer; }
.tx-place { align-items: center; }
.tx-grid9 { display: grid; grid-template-columns: repeat(3, 16px); gap: 3px; padding: 4px; border: 1px solid var(--border); border-radius: 8px; }
.tx-grid9 button { width: 16px; height: 12px; padding: 0; border: 0; border-radius: 3px; background: var(--bg-secondary); outline: 1px solid var(--border); cursor: pointer; }
.tx-grid9 button:hover { background: var(--accent); outline-color: var(--accent); }
.tx-actions { justify-content: flex-end; }
.tx-classic { margin-top: 16px; border-top: 1px solid var(--border); padding-top: 10px; }
.tx-classic > summary { cursor: pointer; font-size: 0.74rem; font-weight: 600; color: var(--text-secondary); margin-bottom: 8px; }
.tx-select {
  position: absolute; z-index: 6; pointer-events: none; box-sizing: border-box;
  border: 1.5px dashed var(--accent); border-radius: 6px; transform-origin: center;
  box-shadow: 0 0 0 1px rgba(0,0,0,0.35);
}
.tx-select[hidden] { display: none; }
.tx-select-handle {
  position: absolute; right: -7px; bottom: -7px; width: 14px; height: 14px; border-radius: 50%;
  background: var(--accent); border: 2px solid #fff; pointer-events: auto; cursor: nwse-resize; touch-action: none;
}
@media (max-width: 768px) {
  .tx-select-handle { width: 20px; height: 20px; right: -10px; bottom: -10px; }
  .tx-text { font-size: 16px; }
  .tx-field .text-input { font-size: 16px; }
}

.tx-tpl-label { flex-basis: 100%; font: 700 0.6rem/1 'JetBrains Mono', monospace; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-secondary); margin-top: 4px; }
.tx-tpl-label:first-child { margin-top: 0; }
.tx-bars { flex: 0 0 auto; margin-bottom: 8px; font-size: 0.74rem; }
/* Sound shaping row (2026-10-02): the exit effect beside the two sliders it pairs with. */
.tx-sfxrow .tx-field { flex: 1 1 120px; }
/* Sample before adding (2026-10-02): the toggle, and the bar that names what is on the preview
   and the sound it plays — change that sound, hear it again, add it, or discard it. */
.tx-sample-toggle { margin: 8px 0 6px; font-size: 0.72rem; }
.tx-sample-bar { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 8px; padding: 8px 10px; margin-bottom: 8px; border: 1px solid var(--border); border-left: 3px solid var(--accent); border-radius: 10px; background: var(--bg-secondary); }
.tx-sample-bar[hidden] { display: none; }
.tx-sample-name { flex: 1 1 100%; min-width: 0; font-size: 0.78rem; font-weight: 600; color: var(--text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tx-sample-field { display: flex; flex-direction: column; gap: 4px; flex: 1 1 150px; min-width: 0; font-size: 0.68rem; font-weight: 600; color: var(--text-secondary); }
.tx-sample-field .dropdown { width: 100%; min-width: 0; }

/* Subtitles tools (2026-09-27) */
.subs-inline { display: flex; gap: 6px; align-items: stretch; }
.subs-inline .dropdown { flex: 1; min-width: 0; }
.subs-inline-btn { font-size: 0.7rem; padding: 4px 10px; white-space: nowrap; }
.subs-tools { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0; }
.subs-tools .text-input { flex: 1 1 110px; min-width: 0; }
.subs-tools .hint-text { flex-basis: 100%; margin: 0; }
.subs-safe-zone { position: absolute; z-index: 5; pointer-events: none; }
.subs-safe-zone[hidden] { display: none; }
.subs-safe-zone > span { position: absolute; background: repeating-linear-gradient(135deg, rgba(255,60,60,0.22) 0 8px, rgba(255,60,60,0.1) 8px 16px); outline: 1px dashed rgba(255,90,90,0.8); outline-offset: -1px; }
.ssz-top { left: 0; right: 0; top: 0; height: 12%; }
.ssz-bottom { left: 0; right: 0; bottom: 0; height: 22%; display: flex; align-items: flex-end; justify-content: center; }
.ssz-bottom em { font: 600 10px/1 var(--font-body, sans-serif); font-style: normal; color: #fff; background: rgba(0,0,0,0.5); padding: 3px 6px; border-radius: 4px; margin-bottom: 6px; }
.ssz-right { right: 0; top: 40%; bottom: 22%; width: 14%; }
@media (max-width: 768px) { .subs-tools .text-input { font-size: 16px; } }

/* Split toning quick controls (2026-09-28) */
.st-quick { margin: 10px 0 6px; display: flex; flex-direction: column; gap: 8px; }
.st-clear { font-size: 0.68rem; padding: 3px 9px; border-radius: 999px; border: 1px solid var(--border); background: var(--bg-secondary); color: var(--text-secondary); cursor: pointer; }
.st-looks { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 6px; }
.st-look { display: flex; align-items: center; gap: 6px; padding: 6px 8px; border-radius: 8px; border: 1px solid var(--border); background: var(--bg-secondary); color: var(--text-primary); font-size: 0.7rem; font-weight: 600; cursor: pointer; text-align: left; }
.st-look span { flex: 0 0 18px; height: 18px; border-radius: 50%; background: linear-gradient(135deg, var(--a) 0 50%, var(--b) 50% 100%); box-shadow: inset 0 0 0 1px rgba(255,255,255,0.25); }
.st-look:hover { border-color: var(--text-secondary); }
.st-look.is-active { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.st-swatch-row { display: flex; align-items: center; gap: 8px; }
.st-swatch-label { flex: 0 0 68px; font-size: 0.68rem; font-weight: 600; color: var(--text-secondary); }
.st-swatches { display: flex; flex-wrap: wrap; gap: 5px; }
.st-swatches button { width: 20px; height: 20px; padding: 0; border-radius: 50%; border: 0; cursor: pointer; background: hsl(var(--h) 70% 55%); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.25); }
.st-swatches button.is-active { box-shadow: 0 0 0 2px var(--bg-primary), 0 0 0 4px var(--accent); }
@media (max-width: 768px) { .st-swatches button { width: 26px; height: 26px; } }

/* Thumbnail creator (2026-09-28) */
.thumb-modal { position: fixed; inset: 0; z-index: 1000; display: grid; place-items: center; padding: 16px; background: rgba(0,0,0,0.6); }
.thumb-modal[hidden] { display: none; }
.thumb-dialog { width: min(1100px, 100%); max-height: calc(100dvh - 32px); overflow: auto; background: var(--bg-primary); color: var(--text-primary); border: 1px solid var(--border); border-radius: 14px; box-shadow: 0 30px 80px rgba(0,0,0,0.5); }
.thumb-head { display: flex; align-items: center; justify-content: space-between; padding: 14px 18px; border-bottom: 1px solid var(--border); }
.thumb-head h2 { font-size: 1rem; margin: 0; }
.thumb-close { width: 34px; height: 34px; border-radius: 50%; border: 0; background: var(--bg-secondary); color: var(--text-primary); font-size: 1.3rem; cursor: pointer; }
.thumb-body { display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(260px, 1fr); gap: 18px; padding: 18px; }
.thumb-stage-wrap { min-width: 0; display: flex; flex-direction: column; gap: 10px; }
.thumb-stage { aspect-ratio: 1280 / 720; max-height: 56dvh; margin: 0 auto; max-width: 100%; background: #000; border-radius: 10px; overflow: hidden; cursor: grab; touch-action: none; display: grid; place-items: center; }
.thumb-stage canvas { width: 100%; height: 100%; object-fit: contain; display: block; }
.thumb-frames { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 4px; }
.thumb-frame { flex: 0 0 auto; display: flex; flex-direction: column; gap: 3px; padding: 0; border: 0; background: none; color: var(--text-secondary); font-size: 0.66rem; cursor: pointer; }
.thumb-frame canvas { width: 104px; height: auto; border-radius: 6px; outline: 1px solid var(--border); }
.thumb-frame.is-active canvas { outline: 2px solid var(--accent); }
.thumb-frame.is-active { color: var(--accent); }
.thumb-frame-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.thumb-frame-actions .btn { font-size: 0.74rem; padding: 6px 12px; }
.thumb-controls { display: flex; flex-direction: column; gap: 12px; }
.thumb-seg { display: flex; flex-wrap: wrap; gap: 6px; }
.thumb-seg button { padding: 7px 10px; border-radius: 8px; border: 1px solid var(--border); background: var(--bg-secondary); color: var(--text-primary); font-size: 0.72rem; font-weight: 600; cursor: pointer; }
.thumb-seg button.is-active { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
.thumb-downloads { display: flex; gap: 8px; }
.thumb-downloads .btn { flex: 1; }
@media (max-width: 768px) {
  .thumb-body { grid-template-columns: 1fr; padding: 12px; }
  .thumb-modal { padding: 0; }
  .thumb-dialog { max-height: 100dvh; border-radius: 0; }
  .thumb-controls .text-input { font-size: 16px; }
}

/* Clip editing (2026-09-28) */
.clip-add-loaded { width: 100%; font-size: 0.74rem; margin: 6px 0; }
.clip-timeline { display: flex; gap: 2px; height: 26px; margin: 6px 0; border-radius: 6px; overflow: hidden; }
.clip-timeline[hidden] { display: none; }
.clip-tl-seg { flex-basis: 0; min-width: 14px; border: 0; padding: 0; cursor: pointer; font: 700 0.62rem/26px 'JetBrains Mono', monospace; color: #fff; background: hsl(var(--hue) 55% 42%); }
.clip-tl-seg.is-selected { box-shadow: inset 0 0 0 2px #fff; }
.clip-card.is-selected .clip-card-thumb { outline: 2px solid var(--accent); outline-offset: 1px; }
.clip-inspector { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; padding: 10px; border: 1px solid var(--border); border-radius: 10px; background: var(--bg-secondary); }
.clip-inspector[hidden] { display: none; }
.clip-inspector-head { font-size: 0.74rem; }
video.clip-inspector-video { position: static; width: 100%; height: auto; aspect-ratio: 16 / 9; opacity: 1; background: #000; border-radius: 6px; object-fit: contain; }

/* Horror Flicker (2026-09-28). Its own controls run taller than .control-reveal's
   default 600px open-state cap once the depth section is expanded, which would clip the
   depth rows off the bottom of an open block — this raises just that ceiling, keeping the
   same collapse animation.
   The :not(.collapsed) is load-bearing (2026-09-30): this rule is a single ID, and
   .control-reveal.collapsed is a single class, so ID specificity won — the collapsed block
   kept a 2600px ceiling instead of 0, i.e. opacity:0 but still 577px of empty layout, and
   that dead space sat directly under Post FX Amount (the next visible row was measured 609px
   below it). Qualifying the rule with the open state means it only ever applies while the
   block really is open, and the collapsed rule goes back to governing the collapsed case. */
#horrorControlsBlock:not(.collapsed) { max-height: 2600px; }
#horrorControlsBlock .progress-track { margin-top: 6px; }

/* ─── Start screen: upload card v2 (2026-09-28) ───────────────────────────────────────
   A media card rather than a dashed drop box: the app's own renders loop behind frosted
   glass (startUploadReel in main.js), one white pill is the call to action, and the other
   ways in sit underneath as a quiet row of tiles. The card is a dark media surface in both
   themes on purpose — it is a window onto video, like a product tile, so its text is white. */
.uz-card {
  position: relative;
  display: grid;
  place-items: center;
  width: min(640px, 100%);
  min-height: 300px;
  /* The start screen is a height-capped flex column; without this the card (overflow:
     hidden for its rounded media) shrank to nothing on phones and clipped on desktop. */
  flex-shrink: 0;
  margin: 24px auto 14px;
  border-radius: 28px;
  font-weight: 400;
  overflow: hidden;
  isolation: isolate;
  cursor: pointer;
  color: #fff;
  text-align: center;
  background: #0b0d12;
  box-shadow: 0 1px 0 rgba(255,255,255,0.06) inset, 0 30px 70px -34px rgba(0,0,0,0.65), 0 0 0 1px color-mix(in srgb, var(--border) 70%, transparent);
  transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.5s cubic-bezier(0.16, 1, 0.3, 1);
  -webkit-tap-highlight-color: transparent;
}
.uz-media { position: absolute; inset: 0; z-index: -1; }
.uz-media img, .uz-media video {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  transform: scale(1.06);
  transition: opacity 1.2s ease, transform 6s cubic-bezier(0.16, 1, 0.3, 1);
}
.uz-media video { opacity: 0; }
.uz-card.has-reel .uz-media video { opacity: 1; }
.uz-card.has-reel .uz-media img { opacity: 0; }
/* Legibility: a soft vignette plus a frosted middle band the text sits on. */
.uz-media::after {
  content: ''; position: absolute; inset: 0;
  background:
    radial-gradient(70% 80% at 50% 55%, rgba(8,10,16,0.55), rgba(8,10,16,0.15) 70%, rgba(8,10,16,0.45)),
    linear-gradient(to bottom, rgba(8,10,16,0.25), rgba(8,10,16,0.6));
}
.uz-body { display: flex; flex-direction: column; align-items: center; gap: 10px; padding: 40px 28px 34px; }
.uz-glyph {
  width: 56px; height: 56px; display: grid; place-items: center; border-radius: 17px;
  background: rgba(255,255,255,0.14);
  box-shadow: 0 0 0 1px rgba(255,255,255,0.18) inset, 0 10px 30px -12px rgba(0,0,0,0.6);
  -webkit-backdrop-filter: blur(18px) saturate(1.6);
  backdrop-filter: blur(18px) saturate(1.6);
}
.uz-glyph svg { width: 28px; height: 28px; }
.uz-title {
  font-family: var(--font-display);
  font-size: clamp(1.7rem, 1.2rem + 1.6vw, 2.3rem);
  font-weight: 700; letter-spacing: -0.025em; line-height: 1.08;
  margin-top: 4px;
  text-shadow: 0 2px 24px rgba(0,0,0,0.35);
}
.uz-sub { font-size: 1rem; line-height: 1.45; color: rgba(255,255,255,0.82); max-width: 36ch; text-wrap: pretty; }
.uz-cta {
  display: inline-flex; align-items: center; gap: 6px;
  margin-top: 10px; padding: 0 22px 0 26px; height: 48px; border-radius: 999px;
  background: #fff; color: #0b0d12;
  font-weight: 600; font-size: 1.02rem; letter-spacing: -0.01em;
  box-shadow: 0 12px 30px -12px rgba(0,0,0,0.55);
  transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.35s ease, background-color 0.2s ease;
}
.uz-cta svg { width: 17px; height: 17px; transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1); }
.uz-formats { margin-top: 4px; font-size: 0.78rem; letter-spacing: 0.02em; color: rgba(255,255,255,0.62); }
.uz-mobile { display: none; }

@media (hover: hover) {
  .uz-card:hover { transform: translateY(-3px); box-shadow: 0 1px 0 rgba(255,255,255,0.08) inset, 0 40px 80px -34px rgba(0,0,0,0.7), 0 0 0 1px color-mix(in srgb, var(--accent) 35%, transparent); }
  .uz-card:hover .uz-media img, .uz-card:hover .uz-media video { transform: scale(1.1); }
  .uz-card:hover .uz-cta { transform: scale(1.03); }
  .uz-card:hover .uz-cta svg { transform: translateX(3px); }
}
.uz-card:active { transform: scale(0.985); transition-duration: 0.15s; }
.uz-card:focus-within, #fileInput:focus-visible + .uz-card { outline: none; }
.uz-card:has(+ .uz-alts) { margin-bottom: 14px; }
/* Keyboard focus lands on the hidden file input's label: ring the pill, not the photo. */
.uz-card:focus-visible .uz-cta, #fileInput:focus-visible ~ * .uz-cta { box-shadow: 0 0 0 3px var(--accent), 0 12px 30px -12px rgba(0,0,0,0.55); }

/* Holding a file over the start screen. */
.empty-state.drag-active .uz-card {
  transform: scale(1.02);
  box-shadow: 0 0 0 2px var(--accent), 0 0 0 8px color-mix(in srgb, var(--accent) 22%, transparent), 0 40px 90px -30px rgba(0,0,0,0.7);
}
.uz-card::after {
  content: 'Release to open';
  position: absolute; inset: 0; display: grid; place-items: center;
  font-family: var(--font-display); font-size: 1.6rem; font-weight: 700; letter-spacing: -0.02em;
  background: color-mix(in srgb, var(--accent) 55%, rgba(8,10,16,0.55));
  -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
  opacity: 0; pointer-events: none; transition: opacity 0.25s ease;
}
.empty-state.drag-active .uz-card::after { opacity: 1; }

/* The other ways in. */
.uz-alts {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px;
  width: min(640px, 100%); margin: 0 auto 18px; flex-shrink: 0;
}
.uz-alt {
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  padding: 14px 14px 13px; min-height: 0; border-radius: 18px;
  background: var(--bg-secondary);
  border: 1px solid color-mix(in srgb, var(--border) 80%, transparent);
  color: var(--text-primary); text-align: left; cursor: pointer; font: inherit;
  transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1), border-color 0.2s ease, background-color 0.2s ease, box-shadow 0.35s ease;
  -webkit-tap-highlight-color: transparent;
}
.uz-alt-icon {
  width: 34px; height: 34px; display: grid; place-items: center; margin-bottom: 8px;
  border-radius: 11px; color: var(--accent);
  background: color-mix(in srgb, var(--accent) 13%, transparent);
}
.uz-alt-icon svg { width: 19px; height: 19px; }
.uz-alt-name { font-weight: 600; font-size: 0.95rem; letter-spacing: -0.01em; }
.uz-alt-desc { font-size: 0.8rem; color: var(--text-secondary); }
@media (hover: hover) {
  .uz-alt:hover { transform: translateY(-2px); border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); box-shadow: 0 14px 30px -20px rgba(0,0,0,0.45); }
}
.uz-alt:active { transform: scale(0.97); transition-duration: 0.12s; }
.uz-alt:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.uz-alt.is-loading { opacity: 0.7; cursor: progress; }
.uz-alt.is-loading .uz-alt-icon { animation: uzPulse 1s ease-in-out infinite; }
@keyframes uzPulse { 50% { opacity: 0.45; } }

/* Sample tile split control (2026-10-04): the tile loads the chosen clip; the caret above it
   opens a menu of every clip in public/samples. The wrapper is the grid item so the caret and
   the menu can sit over/under the tile without becoming grid items themselves. */
.uz-sample-cell { position: relative; display: flex; }
.uz-sample-cell > .empty-state-sample-btn { flex: 1 1 auto; min-width: 0; }
.uz-sample-caret {
  position: absolute; top: 7px; right: 7px; width: 26px; height: 26px; padding: 0;
  display: grid; place-items: center; border-radius: 8px; cursor: pointer;
  border: 1px solid transparent; background: color-mix(in srgb, var(--bg-primary) 55%, transparent);
  color: var(--text-secondary); -webkit-tap-highlight-color: transparent;
}
.uz-sample-caret:hover { color: var(--text-primary); border-color: var(--border); }
.uz-sample-caret:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.uz-sample-caret[aria-expanded="true"] { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); }
.uz-sample-caret svg { width: 14px; height: 14px; }
.uz-sample-menu {
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 40;
  min-width: 190px; max-width: min(260px, 80vw); padding: 6px;
  display: grid; gap: 2px;
  background: var(--bg-secondary); border: 1px solid var(--border); border-radius: 12px;
  box-shadow: 0 20px 50px -22px rgba(0,0,0,0.65);
}
.uz-sample-menu[hidden] { display: none; }
.uz-sample-item {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 8px 10px; border: 0; border-radius: 8px; background: transparent;
  color: var(--text-primary); font: inherit; font-size: 0.84rem; text-align: left; cursor: pointer;
}
.uz-sample-item:hover { background: var(--bg-tertiary); }
.uz-sample-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.uz-sample-item[aria-checked="true"] { color: var(--accent); font-weight: 600; }
.uz-sample-tick { margin-left: auto; opacity: 0; flex: 0 0 auto; width: 14px; height: 14px; }
.uz-sample-item[aria-checked="true"] .uz-sample-tick { opacity: 1; }

/* Arrives with the page: card, then tiles, a beat apart. */
@media (prefers-reduced-motion: no-preference) {
  .uz-card { animation: uzIn 0.8s cubic-bezier(0.16, 1, 0.3, 1) both; }
  .uz-alt { animation: uzIn 0.7s cubic-bezier(0.16, 1, 0.3, 1) both; }
  .uz-alt:nth-child(1) { animation-delay: 0.12s; } .uz-alt:nth-child(2) { animation-delay: 0.17s; }
  .uz-alt:nth-child(3) { animation-delay: 0.22s; } .uz-alt:nth-child(4) { animation-delay: 0.27s; }
  @keyframes uzIn { from { opacity: 0; transform: translateY(14px) scale(0.985); } to { opacity: 1; transform: none; } }
}

@media (max-width: 768px) {
  /* On a phone the card IS the pitch: the intro paragraph above it only pushed the one
     thing to tap below the fold. */
  .empty-state:has(.uz-card) > .empty-state-title + .empty-state-sub { display: none; }
  .empty-state:has(.uz-card) > .empty-state-title { font-size: 1.45rem; }
  .uz-desktop { display: none; }
  .uz-mobile { display: inline; }
  .uz-card { min-height: 0; border-radius: 26px; margin: 18px auto 12px; }
  .uz-body { padding: 34px 22px 28px; gap: 8px; }
  .uz-cta { height: 50px; width: 100%; max-width: 280px; justify-content: center; }
  .uz-alts { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
  .uz-alt { flex-direction: row; align-items: center; gap: 10px; padding: 12px; border-radius: 16px; }
  .uz-alt-icon { margin: 0; flex-shrink: 0; }
  .uz-alt-desc { display: none; }
}

/* VHS mode's tape-type pills (2026-09-28): one tap between tape stocks, in the mode's own
   Tracking section and in the mobile quick bar. Sized like the existing mini pills. */
.vhs-type-row { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 4px; }
.vhs-type-pill {
  font: inherit; font-size: 11px; font-weight: 600;
  padding: 5px 10px; border-radius: 999px; cursor: pointer;
  color: var(--text-secondary); background: var(--surface-2, rgba(127,127,127,0.08));
  border: 1px solid var(--border);
}
.vhs-type-pill:hover { color: var(--text-primary); }
.vhs-type-pill.active { color: #fff; background: var(--accent); border-color: var(--accent); }
.mp-speed-btn {
  font: inherit; font-size: 11px; font-weight: 700; letter-spacing: -0.01em;
  min-width: 34px; height: 26px; padding: 0 6px; border-radius: 7px; cursor: pointer;
  color: var(--text-secondary); background: transparent; border: 1px solid var(--border);
}
.mp-speed-btn.is-off { color: var(--accent); border-color: var(--accent); }
.mq-mini-pill.is-on .mq-mini-label { color: var(--accent); }

/* Automation tools row (2026-09-28): scale/nudge for the selected track and for every track.
   Wraps rather than scrolls so nothing is hidden on a phone. */
.kf-toolbar-tools { flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.kf-tool-label {
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--text-secondary); align-self: center;
}

/* Timeline markers (2026-09-28): ticks over the progress bar, the text ruler and the
   keyframe ruler, plus keyframe ticks on the text ruler for lining a card up to animation. */
.timeline-markers { position: absolute; inset: 0; pointer-events: none; }
.timeline-marker {
  position: absolute; top: 50%; width: 9px; height: 9px; margin: -4.5px 0 0 -4.5px;
  padding: 0; border: 0; border-radius: 2px; background: var(--accent, #2563eb);
  box-shadow: 0 0 0 1.5px rgba(0,0,0,0.55); cursor: pointer; pointer-events: auto;
}
.timeline-marker:hover { transform: scale(1.35); }
.progress-group { position: relative; }
.tx-marker, .kf-marker {
  position: absolute; top: 0; bottom: 0; width: 2px; margin-left: -1px;
  background: var(--accent, #2563eb); opacity: 0.85; pointer-events: none;
}
.tx-kf-tick {
  position: absolute; bottom: 2px; width: 5px; height: 5px; margin-left: -2.5px;
  background: var(--warning, #f0a30a); transform: rotate(45deg); opacity: 0.9; pointer-events: none;
}

/* Mobile title editing (2026-09-28): the template strip becomes a snap carousel with real
   touch targets instead of a wrap of 20px chips, and the editor tabs stay thumb-sized. */
.tx-add-title { background: linear-gradient(120deg, var(--accent), color-mix(in srgb, var(--accent) 70%, #ff7a4d)); }
@media (max-width: 768px) {
  .tx-templates {
    display: flex; flex-wrap: nowrap; overflow-x: auto; gap: 8px;
    scroll-snap-type: x proximity; padding-bottom: 8px; -webkit-overflow-scrolling: touch;
  }
  .tx-tpl, .tx-tpl-label { flex: 0 0 auto; scroll-snap-align: start; }
  .tx-tpl { min-height: 38px; padding: 9px 14px; font-size: 12px; }
  .tx-tpl-label { align-self: center; }
  .clip-editor-tab { min-height: 38px; padding: 8px 10px; }
  /* Opaque, or the templates and counts scroll visibly underneath it. */
  .tx-head { position: sticky; top: 0; z-index: 2; background: var(--bg-primary); padding-block: 6px; box-shadow: 0 6px 10px -8px rgba(0,0,0,0.6); }
  .tx-add-title { order: -1; }
}

/* App header flyout hover bridge (2026-09-28). The trigger's own bridge (above) covers the
   header's padding, but the app header's box ends before the flyout starts, so nothing inside
   the header can be hit across that gap. The panel itself paints outside the header, so its
   ::before can: a transparent strip reaching up from the sheet's top edge to the bar keeps the
   group hovered all the way down. Scoped to .app-container so the landing pages' full-width
   sheet (which has no gap, and whose bridge would overlap the triggers) is untouched. */
.app-container .site-nav .site-nav-panel::before {
  content: '';
  display: block;
  position: absolute;
  left: 0; right: 0; top: -46px;
  height: 46px;
  background: transparent;
}

/* ─── 2026-09-28 compact mobile menus ───────────────────────────────────────────────
   App settings sheet: open on a category it is capped at 36dvh (~240px on a phone), and
   the search row, tab bar and the gaps around them used 155px of that — about 85px of
   actual settings were left. Every gap here is trimmed to the minimum that still reads as
   a separate row; nothing is hidden. Touch targets stay >= 32px (tabs, search icons) —
   the section headers keep a 38px row. */
@media (max-width: 768px) {
  .sidebar { gap: 6px; padding-top: 14px; }
  .sidebar::before { top: 5px; }
  .sidebar-search-wrap { margin-bottom: 0; }
  .sidebar-search-input { padding-top: 7px; padding-bottom: 7px; min-height: 34px; }
  .sidebar-reset-btn { width: 34px; height: 34px; }
  .sidebar-tabs { margin-bottom: 0; padding: 2px; }
  .sidebar-tab { min-height: 32px; }
  .control-section { padding-bottom: 6px; }
  .control-section h3 { min-height: 38px; }
  /* One line of hint while scanning, as on desktop; "Show more" still opens the rest. */
  .hint-text.hint-clamped { -webkit-line-clamp: 1; }
  .hint-toggle { margin-top: 1px; font-size: 0.7rem; line-height: 1.3; }
}
@media (max-width: 900px) {
  /* Landing-page menu (shown up to 900px, like the rest of its rules): section rows were 1.6rem headlines 58px apart, and every link sat
     on its own full-width line, so a single open section was two screens of scrolling.
     Rows are now list-sized and links flow two to a line. */
  .site-nav-mobile-panel { padding: 12px 20px calc(28px + env(safe-area-inset-bottom)); }
  .site-nav-mobile-cta { min-height: 42px; padding-top: 10px; padding-bottom: 10px; margin-bottom: 4px; }
  .site-nav-mobile-group > summary { padding: 12px 0; font-size: 1.12rem; letter-spacing: -0.015em; }
  .site-nav-mobile-group .site-nav-heading { margin: 8px 0 2px; font-size: 0.7rem; }
  .site-nav-mobile-group .site-nav-item {
    display: inline-block;
    width: 50%;
    vertical-align: top;
    padding: 7px 10px 7px 0;
    font-size: 0.9rem;
    font-weight: 500;
    line-height: 1.3;
  }
  .site-nav-mobile-group[open] { padding-bottom: 10px; }
}

/* ─── Add Text: quick add, template tabs, timeline tools + zoom (2026-09-28) ─── */
.tx-quick { display: flex; gap: 6px; margin-top: 10px; }
.tx-quick .text-input { flex: 1; min-width: 0; }
.tx-tip { margin: 6px 0 0; font-size: 0.7rem; color: var(--text-secondary); }
.tx-tpl-tabs { display: flex; gap: 4px; margin-top: 10px; overflow-x: auto; scrollbar-width: none; padding-bottom: 2px; }
.tx-tpl-tabs::-webkit-scrollbar { display: none; }
.tx-tpl-tab {
  flex: 0 0 auto; height: 28px; padding: 0 10px; border-radius: 999px;
  border: 1px solid var(--border); background: transparent; color: var(--text-secondary);
  font-size: 0.72rem; font-weight: 650; cursor: pointer; white-space: nowrap;
}
.tx-tpl-tab:hover { color: var(--text-primary); }
.tx-tpl-tab.is-active { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
/* The tab names the group, so its in-strip label is redundant once tabbed. */
.tx-templates.is-tabbed .tx-tpl-label { display: none !important; }
.tx-templates.is-tabbed { margin-top: 8px; }
.tx-tl-tools { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin: 4px 0 6px; }
.tx-tl-tools .tx-chip { height: 28px; font-size: 0.7rem; }
.tx-tl-hint { font-size: 0.66rem; color: var(--text-secondary); }
@media (hover: none) { .tx-tl-hint { display: none; } }
/* Zoom: the timeline scrolls sideways and its content is --tx-zoom times as wide. The eye
   column stays pinned so the show/hide toggles never scroll away. */
.tx-timeline { overflow-x: auto; overflow-y: hidden; }
.tx-timeline:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.tx-timeline.is-zoomed .tx-ruler { width: calc((100% - var(--tx-eye)) * var(--tx-zoom, 1)); }
.tx-timeline.is-zoomed .tx-lanes { width: calc(var(--tx-eye) + (100% - var(--tx-eye)) * var(--tx-zoom, 1)); overflow-y: visible; max-height: none; }
.tx-timeline.is-zoomed .tx-eye { position: sticky; left: 0; z-index: 3; background: var(--bg-primary); }

/* ─── Homepage hero, round three (2026-09-28): brand lockup, lock-on brackets, aurora,
   look chips, and wider scroll-linked motion. Scoped to .ah-hero / .lp-home. ─── */
.ah-hero { position: relative; isolation: isolate; }
.ah-aurora { position: absolute; inset: -10% -20% auto; height: 820px; z-index: -1; pointer-events: none; overflow: hidden; opacity: 0.9; }
.ah-aurora i {
  position: absolute; border-radius: 50%; filter: blur(80px); opacity: 0.5;
  animation: ahAurora 18s ease-in-out infinite alternate;
}
.ah-aurora i:nth-child(1) { width: 560px; height: 420px; left: 12%; top: 4%; background: color-mix(in srgb, var(--accent) 70%, transparent); }
.ah-aurora i:nth-child(2) { width: 460px; height: 380px; right: 10%; top: 12%; background: rgba(168, 85, 247, 0.45); animation-duration: 22s; animation-delay: -6s; }
.ah-aurora i:nth-child(3) { width: 520px; height: 300px; left: 38%; top: 34%; background: rgba(34, 211, 238, 0.28); animation-duration: 26s; animation-delay: -12s; }
@keyframes ahAurora {
  0% { transform: translate(0, 0) scale(1); }
  50% { transform: translate(6%, 8%) scale(1.12); }
  100% { transform: translate(-5%, -4%) scale(0.94); }
}
:root[data-theme="light"] .ah-aurora { opacity: 0.45; }
@media (prefers-color-scheme: light) { :root:not([data-theme="dark"]) .ah-aurora { opacity: 0.45; } }

.ah-brand { display: inline-flex; align-items: center; gap: 12px; margin: 0 auto 22px; }
.ah-brand-mark { width: 44px; height: 44px; border-radius: 12px; box-shadow: 0 0 0 1px color-mix(in srgb, var(--text-primary) 12%, transparent), 0 8px 30px color-mix(in srgb, var(--accent) 35%, transparent); }
.ah-brand-name { font-size: 1.7rem; font-weight: 750; letter-spacing: -0.035em; color: var(--text-primary); }
.ah-brand-name span { color: var(--accent); }
.ah-brand-live {
  display: inline-flex; align-items: center; gap: 7px; height: 28px; padding: 0 12px; border-radius: 999px;
  font-size: 0.74rem; font-weight: 600; color: var(--text-secondary);
  border: 1px solid color-mix(in srgb, var(--text-primary) 14%, transparent);
  background: color-mix(in srgb, var(--bg-primary) 60%, transparent);
}
.ah-brand-live i { width: 7px; height: 7px; border-radius: 50%; background: #34d399; box-shadow: 0 0 0 0 rgba(52, 211, 153, 0.6); animation: ahLive 2s ease-out infinite; }
@keyframes ahLive { 0% { box-shadow: 0 0 0 0 rgba(52, 211, 153, 0.55); } 100% { box-shadow: 0 0 0 9px rgba(52, 211, 153, 0); } }

/* Lock-on brackets: four corners snap in around the word after the headline lands, a tiny
   ID label rides the top-left corner, and the box keeps a faint tracker's jitter. */
.ah-lock { position: relative; display: inline-block; }
.ah-lock::before, .ah-lock::after { content: ''; position: absolute; pointer-events: none; }
.ah-lock::before {
  inset: -0.02em -0.14em -0.06em;
  --c: color-mix(in srgb, var(--accent) 90%, white);
  background:
    linear-gradient(var(--c), var(--c)) top left / 0.32em 2px no-repeat,
    linear-gradient(var(--c), var(--c)) top left / 2px 0.32em no-repeat,
    linear-gradient(var(--c), var(--c)) top right / 0.32em 2px no-repeat,
    linear-gradient(var(--c), var(--c)) top right / 2px 0.32em no-repeat,
    linear-gradient(var(--c), var(--c)) bottom left / 0.32em 2px no-repeat,
    linear-gradient(var(--c), var(--c)) bottom left / 2px 0.32em no-repeat,
    linear-gradient(var(--c), var(--c)) bottom right / 0.32em 2px no-repeat,
    linear-gradient(var(--c), var(--c)) bottom right / 2px 0.32em no-repeat;
  opacity: 0; transform: scale(1.35);
  animation: ahLockIn 0.55s cubic-bezier(0.2, 0.9, 0.2, 1.2) 1.35s forwards, ahLockJitter 3.2s steps(2) 2.2s infinite;
}
.ah-lock::after {
  content: attr(data-label);
  left: -0.14em; top: -0.02em; transform: translateY(-100%);
  font: 700 0.62rem/1 'JetBrains Mono', monospace; letter-spacing: 0.06em;
  padding: 3px 5px; border-radius: 3px 3px 3px 0;
  background: var(--accent); color: var(--on-accent);
  opacity: 0; animation: ahFadeIn 0.3s ease 1.8s forwards;
}
.ah-lock-2::before { animation-delay: 1.65s, 2.6s; }
.ah-lock-2::after { animation-delay: 2.1s; }
@keyframes ahLockIn { to { opacity: 1; transform: scale(1); } }
@keyframes ahFadeIn { to { opacity: 1; } }
@keyframes ahLockJitter { 0%, 100% { translate: 0 0; } 50% { translate: 1px -1px; } }

.ah-reel-chips { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 6px; max-width: 980px; margin: 18px auto 0; }
.ah-reel-chips-label { font-size: 0.74rem; font-weight: 600; color: var(--text-secondary); margin-right: 4px; }
.ah-reel-chip {
  height: 32px; padding: 0 13px; border-radius: 999px; cursor: pointer;
  border: 1px solid color-mix(in srgb, var(--text-primary) 14%, transparent);
  background: color-mix(in srgb, var(--bg-primary) 70%, transparent); color: var(--text-primary);
  font-size: 0.8rem; font-weight: 600;
  transition: background-color 0.2s ease, border-color 0.2s ease, transform 0.15s ease;
}
.ah-reel-chip:hover { border-color: var(--accent); }
.ah-reel-chip:active { transform: scale(0.96); }
.ah-reel-chip.is-active { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
@media (max-width: 640px) {
  .ah-brand { gap: 9px; margin-bottom: 16px; flex-wrap: wrap; justify-content: center; }
  .ah-brand-mark { width: 36px; height: 36px; }
  .ah-brand-name { font-size: 1.35rem; }
  .ah-brand-live { height: 24px; font-size: 0.68rem; }
  .ah-aurora { height: 560px; }
  .ah-aurora i { filter: blur(56px); }
  /* One swipeable row of chips on a phone instead of four wrapped rows. */
  .ah-reel-chips { flex-wrap: nowrap; justify-content: flex-start; overflow-x: auto; scrollbar-width: none; padding: 0 16px; margin-inline: -16px; }
  .ah-reel-chips::-webkit-scrollbar { display: none; }
  .ah-reel-chip { flex: 0 0 auto; }
}
@media (prefers-reduced-motion: no-preference) {
  .scrub-ready .ah-aurora[data-scrub-out] { translate: 0 calc(var(--q, 0) * 120px); opacity: calc(0.9 - var(--q, 0) * 0.6); }
  .scrub-ready .lp-home .lp-section-head[data-scrub],
  .scrub-ready .lp-section-head[data-scrub] { translate: 0 calc((1 - var(--p, 1)) * 48px); }
  .scrub-ready .hero-stats[data-scrub] { scale: calc(0.92 + var(--p, 1) * 0.08); }
  .scrub-ready .lp-cta-band[data-scrub] { scale: calc(0.9 + var(--p, 1) * 0.1); }
  .scrub-ready .ah-reel-chips[data-scrub] { translate: 0 calc((1 - var(--p, 1)) * 24px); }
}
@media (prefers-reduced-motion: reduce) {
  .ah-aurora i, .ah-brand-live i { animation: none; }
  .ah-lock::before, .ah-lock::after { animation: none; opacity: 1; transform: none; }
}
@media (max-width: 640px) { .ah-lock::after { display: none; } }

/* Vis Exo brand chip on landing heroes (scripts/guides/brand_pass.py, 2026-09-28). */
.lp-brand-chip {
  display: inline-flex; align-items: center; gap: 7px; align-self: center;
  height: 30px; padding: 0 12px 0 6px; margin: 0 auto 12px; border-radius: 999px;
  font-size: 0.82rem; font-weight: 700; letter-spacing: -0.01em; text-decoration: none;
  color: var(--text-primary);
  border: 1px solid color-mix(in srgb, var(--text-primary) 14%, transparent);
  background: color-mix(in srgb, var(--accent) 10%, var(--bg-primary));
  transition: border-color 0.2s ease;
}
.lp-brand-chip img { width: 20px; height: 20px; border-radius: 6px; }
.lp-brand-chip span { color: var(--accent); }
.lp-brand-chip:hover { border-color: var(--accent); }
.lp-hero > .lp-brand-chip { display: flex; width: max-content; }
.lp-brand-chip b { font-weight: inherit; }

/* Keyframe track tools row (2026-09-28). */
.kf-track-tools { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 8px; font-size: 0.72rem; color: var(--text-secondary); }
.kf-track-tools .kf-mini-btn { width: auto; padding: 0 10px; font-size: 0.72rem; }
.vhs-text-quick-row { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 4px; }

/* Text drag guides (2026-09-28): title-safe frame while dragging + the snapped line. */
.tx-guides { position: absolute; pointer-events: none; z-index: 6; }
.tx-guides[hidden], .tx-guides [hidden] { display: none; }
.tx-guide-safe { position: absolute; inset: 5%; border: 1px dashed rgba(255, 255, 255, 0.35); border-radius: 2px; }
.tx-guide-v, .tx-guide-h { position: absolute; background: #ff3ea5; box-shadow: 0 0 0 0.5px rgba(0, 0, 0, 0.4); }
.tx-guide-v { top: 0; bottom: 0; width: 1px; margin-left: -0.5px; }
.tx-guide-h { left: 0; right: 0; height: 1px; margin-top: -0.5px; }

/* Timeline bars (2026-09-28): colour stripe, card marker, multi-select, drag readout. */
.tx-bar { box-shadow: inset 3px 0 0 var(--tx-c, transparent); }
.tx-bar.is-card { background-image: linear-gradient(90deg, rgba(0,0,0,0.35), rgba(0,0,0,0.35)); }
.tx-bar.is-extra { outline: 1px dashed var(--accent); outline-offset: 1px; }
.tx-drag-tip {
  position: absolute; z-index: 5; pointer-events: none; white-space: nowrap;
  font: 600 0.62rem/1 'JetBrains Mono', monospace; padding: 4px 6px; border-radius: 4px;
  background: var(--text-primary); color: var(--bg-primary);
}
.tx-drag-tip[hidden] { display: none; }

/* ─── Mobile UX + timeline pass (2026-09-28) ─── */
/* Timeline: waveform strip behind the ruler, snap line, touch hints, pinch owned by us. */
.tx-timeline { touch-action: pan-x pan-y; }
.tx-ruler.has-wave { height: 34px; }
.tx-wave { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.tx-tick { z-index: 1; }
.tx-snap-line { position: absolute; top: 0; bottom: 0; width: 0; border-left: 1px dashed #ff3ea5; pointer-events: none; z-index: 3; }
.tx-snap-line[hidden] { display: none; }
.tx-lane { cursor: pointer; }
.tip-touch, .tx-tl-touch-hint { display: none; }
@media (hover: none) {
  .tip-mouse { display: none; }
  .tip-touch { display: inline; }
  .tx-tl-touch-hint { display: block; margin: -6px 0 10px; font-size: 0.68rem; color: var(--text-secondary); }
  .tx-timeline[hidden] + .tx-tl-touch-hint { display: none; }
  /* Bars are the thing you drag with a finger — give them a real touch height. */
  .tx-lane { height: 38px; }
  .tx-bar-h { flex-basis: 14px; }
}

@media (max-width: 768px) {
  /* Timeline tools: one swipeable row instead of two wrapped rows of chips. */
  .tx-tl-tools { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; padding-bottom: 2px; }
  .tx-tl-tools::-webkit-scrollbar { display: none; }
  .tx-tl-tools > * { flex-shrink: 0; }
  .tx-tl-tools .kf-zoom { margin-left: auto; }

  /* Player bar: the quick bar already has a speed pill, so the duplicate goes, and the
     scrubber may shrink — before this the trim and fullscreen buttons ran off the edge. */
  .mobile-player-bar { gap: 6px; }
  .mobile-player-bar .mp-speed-btn { display: none; }
  .mp-scrub { min-width: 0; }
  .mp-time { font-size: 0.62rem; }

  /* With the settings sheet open the preview is a small strip at the top; a toast there
     hid it completely. Just above the bottom bar it only covers scrollable sheet content. */
  body.sidebar-open #undoToast { top: auto; bottom: calc(var(--bottom-nav-h, 60px) + 10px); }

  /* The cut-off cards already say "scroll for more"; the chevron only covered them. */
  .empty-state-scroll-hint.has-overflow { display: none; }
}
@media (max-width: 360px) {
  .mp-dur { display: none; }   /* iPhone SE / small Androids: elapsed time only */
}
/* Landscape phone: the floating theme/font buttons and the toast both landed on the
   transport bar. The buttons live in the sheet once a clip is loaded (as in portrait), and
   the toast goes to the empty middle of the header. */
@media (max-width: 900px) and (max-height: 500px) and (orientation: landscape) {
  body.has-source .theme-font-toggle { display: none; }
  body.has-source #undoToast { top: 8px; bottom: auto; max-width: min(420px, 50vw); }
  .mobile-player-bar .mp-speed-btn { display: none; }
}

/* ─── Text editor round three (2026-09-28): history, filmstrip, anim zones, script,
   template search / recent. ─── */
.tx-history { display: inline-flex; gap: 4px; margin-left: auto; }
.tx-hist-btn {
  display: grid; place-items: center; width: 32px; height: 32px; padding: 0; border-radius: 8px; cursor: pointer;
  color: var(--text-primary); background: transparent; border: 1px solid var(--border);
}
.tx-hist-btn svg { width: 16px; height: 16px; }
.tx-hist-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.tx-hist-btn:disabled { opacity: 0.35; cursor: default; }
.tx-chip:disabled { opacity: 0.4; cursor: default; }
.tx-chip[hidden] { display: none; }

.tx-film {
  position: relative; height: 30px; margin-left: var(--tx-eye); overflow: hidden; cursor: ew-resize; touch-action: none;
  background: #000; border-bottom: 1px solid var(--border);
}
.tx-film[hidden] { display: none; }
.tx-film-canvas { display: block; width: 100%; height: 100%; opacity: 0.85; }
.tx-timeline.is-zoomed .tx-film { width: calc((100% - var(--tx-eye)) * var(--tx-zoom, 1)); }
@media (hover: none) { .tx-film { height: 36px; } }

/* In / out animation zones: a darker wash over the part of the bar the animation takes. */
.tx-bar::before, .tx-bar::after {
  content: ''; position: absolute; top: 0; bottom: 0; pointer-events: none;
}
.tx-bar::before { left: 0; width: var(--tx-in, 0%); background: linear-gradient(90deg, rgba(0,0,0,0.38), rgba(0,0,0,0.08)); }
.tx-bar::after { right: 0; width: var(--tx-out, 0%); background: linear-gradient(270deg, rgba(0,0,0,0.38), rgba(0,0,0,0.08)); }

.tx-script { margin: 2px 0 10px; border: 1px solid var(--border); border-radius: var(--radius-md, 10px); padding: 0 10px; }
.tx-script > summary { cursor: pointer; padding: 9px 0; font-size: 0.74rem; font-weight: 600; color: var(--text-secondary); }
.tx-script[open] > summary { color: var(--text-primary); }
.tx-script[open] { padding-bottom: 10px; }
.tx-script .tx-row { margin-top: 8px; }
.tx-script .tx-text { min-height: 96px; }

.tx-tpl-search { width: 100%; margin: 2px 0 8px; }
.tx-tpl-recent { display: flex; align-items: center; gap: 6px; flex-wrap: nowrap; overflow-x: auto; margin-bottom: 8px; scrollbar-width: none; }
.tx-tpl-recent[hidden] { display: none; }
.tx-tpl-recent::-webkit-scrollbar { display: none; }
.tx-tpl-recent > * { flex-shrink: 0; }
.tx-tpl-recent-label { font-size: 0.64rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-secondary); }
.tx-tpl-tabs.is-searching { opacity: 0.4; }
/* The app's global `canvas` rule (object-fit: contain, max-height) letterboxed the strip
   and the waveform; these canvases are stretched to their box on purpose. */
.tx-film-canvas, .tx-wave { object-fit: fill; max-width: none; max-height: none; }
@media (max-width: 768px) {
  /* Cover the sheet's top padding too, so nothing scrolling underneath peeks above it. */
  .tx-head { box-shadow: 0 -16px 0 0 var(--bg-primary), 0 6px 10px -8px rgba(0,0,0,0.6); }
}
@media (max-width: 480px) {
  /* Undo / redo need the room more than the count does; keep both add buttons on one line. */
  .tx-head #textCount { display: none; }
  .tx-head .tx-add { white-space: nowrap; }
}

/* ─── Mobile settings compactness (2026-09-28) ───
   Measured on a 390px phone: the Style tab alone had 37 "Show more" lines, colour swatches
   wrapping to two rows of big dots, split-tone presets as a three-row card grid, and the
   Tracking tab's mode switcher as a three-row grid repeating the quick bar's own row. */
@media (max-width: 768px) {
  /* Long hints: one line, tap the hint itself to expand; no separate toggle line. */
  .sidebar .hint-text.hint-clamped + .hint-toggle { display: none; }
  .sidebar .hint-text.hint-clamped { cursor: pointer; -webkit-line-clamp: 1; }
  .sidebar .hint-text.hint-clamped.expanded { -webkit-line-clamp: unset; }
  .sidebar .hint-text { margin-top: 2px; font-size: 0.72rem; line-height: 1.35; }

  /* Mode switcher: one swipeable row of compact pills instead of a grid. */
  .sidebar .mobile-mode-tabs { display: flex !important; flex-wrap: nowrap !important; overflow-x: auto !important; gap: 6px; margin-bottom: 10px; scrollbar-width: none; }
  .sidebar .mobile-mode-tabs .mode-tab { flex: 0 0 auto; min-height: 34px; padding: 6px 12px; }

  /* Split tone: presets and swatches as single swipeable rows with smaller dots. */
  .sidebar .st-looks { display: flex !important; flex-wrap: nowrap; overflow-x: auto; gap: 6px; scrollbar-width: none; padding-bottom: 2px; }
  .sidebar .st-looks > * { flex: 0 0 auto; min-height: 36px; padding-block: 6px; }
  .sidebar .st-swatch-row { display: flex; align-items: center; gap: 8px; }
  .sidebar .st-swatches { display: flex !important; flex-wrap: nowrap; overflow-x: auto; gap: 6px; scrollbar-width: none; padding: 2px 0; }
  .sidebar .st-swatches > button { flex: 0 0 auto; width: 24px; height: 24px; min-width: 24px; }
  .sidebar .st-looks::-webkit-scrollbar, .sidebar .st-swatches::-webkit-scrollbar, .sidebar .mobile-mode-tabs::-webkit-scrollbar { display: none; }

  /* Tighter rhythm between controls; tap targets (selects, toggles, slider thumbs) keep
     their size, only the air between them shrinks. */
  .sidebar .control-group { gap: 3px; }
  .sidebar .mt-2 { margin-top: 6px; }
  .sidebar .label-row { min-height: 18px; line-height: 1.2; }
  .sidebar .control-section > h3 { padding-block: 2px; }
}

/* ─── Start-screen reel, round two (2026-09-28) ───
   Two video layers crossfade; the picture is no longer buried under a full-card dark wash —
   the shade sits behind the words only, so the footage reads as footage. */
.uz-media video { opacity: 0; transition: opacity 0.9s ease, transform 6s cubic-bezier(0.16, 1, 0.3, 1); filter: saturate(1.12) contrast(1.04); }
.uz-card.has-reel .uz-media video { opacity: 0; }
.uz-card.has-reel .uz-media video.is-on { opacity: 1; }
.uz-media::after {
  background:
    radial-gradient(46% 52% at 50% 52%, rgba(6,8,14,0.62), rgba(6,8,14,0.28) 62%, rgba(6,8,14,0) 100%),
    radial-gradient(120% 100% at 50% 50%, rgba(0,0,0,0) 55%, rgba(0,0,0,0.45)),
    linear-gradient(to bottom, rgba(6,8,14,0) 55%, rgba(6,8,14,0.45));
}
.uz-title { text-shadow: 0 2px 30px rgba(0,0,0,0.55), 0 1px 2px rgba(0,0,0,0.35); }
.uz-sub, .uz-formats { text-shadow: 0 1px 12px rgba(0,0,0,0.6); }
.uz-now {
  position: absolute; left: 14px; top: 14px; z-index: 1;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 5px 11px 5px 9px; border-radius: 999px;
  font-size: 0.72rem; font-weight: 600; letter-spacing: 0.01em; color: #fff;
  background: rgba(10,12,18,0.45); box-shadow: 0 0 0 1px rgba(255,255,255,0.16) inset;
  -webkit-backdrop-filter: blur(12px) saturate(1.4); backdrop-filter: blur(12px) saturate(1.4);
  opacity: 0; transform: translateY(-4px); transition: opacity 0.5s ease, transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
  pointer-events: none;
}
.uz-now i { width: 6px; height: 6px; border-radius: 50%; background: #ff453a; box-shadow: 0 0 8px #ff453a; }
.uz-now.is-shown { opacity: 1; transform: none; }

/* ─── Text inspector: tabs and chips (2026-09-28) ─── */
.tx-chiprow { flex-wrap: nowrap; align-items: center; gap: 8px; }
.tx-chiprow-label { flex: 0 0 auto; font-size: 0.68rem; font-weight: 600; color: var(--text-secondary); min-width: 62px; }
.tx-chipstrip { display: flex; gap: 6px; overflow-x: auto; flex: 1; min-width: 0; padding: 2px 0; scrollbar-width: none; -webkit-mask-image: linear-gradient(to right, #000 88%, transparent); mask-image: linear-gradient(to right, #000 88%, transparent); }
.tx-chipstrip::-webkit-scrollbar { display: none; }
.tx-pchip {
  flex: 0 0 auto; min-height: 34px; padding: 6px 12px; border-radius: 999px; cursor: pointer; white-space: nowrap;
  font-size: 0.8rem; color: var(--text-primary); background: var(--bg-secondary); border: 1px solid var(--border);
}
.tx-pchip:hover { border-color: var(--accent); }
.tx-pchip.is-on { background: var(--accent); border-color: var(--accent); color: var(--on-accent, #fff); }
/* Surface chips (2026-10-02): each carries a live "Aa" rendered through its own texture in the
   layer's font and colour, on a mid-grey card so white and black letters both read. */
.tx-texstrip .tx-pchip { display: inline-flex; flex-direction: column; align-items: center; gap: 3px; min-width: 76px; padding: 5px 6px 6px; border-radius: 10px; font-size: 0.7rem; }
.tx-texstrip .tx-pchip::before { content: ''; display: block; width: 66px; height: 32px; border-radius: 6px; background: var(--tex-thumb, none) center / contain no-repeat, linear-gradient(135deg, #3a3d46, #23252c); }
.tx-texstrip .tx-pchip.is-on::before { box-shadow: 0 0 0 1px rgba(255,255,255,0.6); }
/* Inspector tabs on every screen (2026-10-04): one group at a time, desktop included. */
.tx-itabs {
  display: grid; grid-template-columns: repeat(6, 1fr); gap: 2px; padding: 3px; margin: 8px 0 10px; border-radius: 12px;
  background: var(--bg-secondary); border: 1px solid var(--border); position: sticky; top: 0; z-index: 2;
}
.tx-itabs button { min-height: 34px; padding: 0 4px; border: 0; border-radius: 9px; background: transparent; color: var(--text-secondary); font: inherit; font-size: 0.8rem; font-weight: 600; white-space: nowrap; cursor: pointer; }
.tx-itabs button:hover { color: var(--text-primary); }
.tx-itabs button.is-on { background: var(--accent); color: var(--on-accent, #fff); }
/* Hide the rows of every other tab (a hide, never a show, so each row keeps its own display
   and anything already `hidden` stays hidden). */
.tx-inspector[data-tab="style"] > [data-txg]:not([data-txg="style"]),
.tx-inspector[data-tab="animate"] > [data-txg]:not([data-txg="animate"]),
.tx-inspector[data-tab="sound"] > [data-txg]:not([data-txg="sound"]),
.tx-inspector[data-tab="fx"] > [data-txg]:not([data-txg="fx"]),
.tx-inspector[data-tab="texture"] > [data-txg]:not([data-txg="texture"]),
.tx-inspector[data-tab="layout"] > [data-txg]:not([data-txg="layout"]) { display: none; }
.tx-inspector > details.tx-more { border: 1px solid var(--border); border-radius: 10px; padding: 0 10px; }
.tx-inspector[data-tab]:not([data-tab="animate"]) > details.tx-more { display: none; }
.tx-more > summary { cursor: pointer; padding: 9px 0; font-size: 0.82rem; font-weight: 600; color: var(--text-secondary); list-style: revert; }
.tx-more[open] > summary { color: var(--text-primary); }
.tx-more > .tx-row { margin-bottom: 8px; }
@media (max-width: 768px) {
  .tx-itabs { top: 60px; }
  .tx-itabs button { min-height: 38px; padding: 0 2px; font-size: 0.74rem; }
  /* Three dropdowns per row truncated their labels on a phone; two per row reads. */
  .tx-inspector .tx-row > .tx-field { flex: 1 1 42%; }
  .tx-chiprow-label { min-width: 0; }
  .tx-chiprow { flex-direction: column; align-items: stretch; gap: 4px; }
  .tx-chiprow .tx-chipstrip { width: 100%; }
  /* Mobile text editor (2026-10-01): the inspector is a phone-first surface, so its controls
     get real thumb targets — 44px dropdowns/inputs and a taller slider — rather than the
     dense desktop sizing, plus a little more air between rows so a mis-tap is less likely. */
  .tx-inspector .dropdown,
  .tx-inspector .text-input { min-height: 44px; font-size: 0.85rem; }
  .tx-inspector .slider { height: 30px; }
  /* Add Text scrolls the text box to the top of the sheet; keep it clear of the sticky Add bar. */
  #textLayerInput { scroll-margin-top: 72px; }
  .tx-inspector .tx-field { gap: 6px; }
  .tx-inspector .tx-row { gap: 10px; }
}

/* ─── Text timeline, round four (2026-09-29) ─── */
.tx-lane.is-compact .tx-row-num { font: 600 0.58rem/1 'JetBrains Mono', monospace; color: var(--text-secondary); cursor: default; }
.tx-bar .tx-bar-eye {
  position: absolute; right: 10px; top: 50%; transform: translateY(-50%); z-index: 1;
  width: 18px; height: 18px; flex: none; border: 0; border-radius: 4px; background: rgba(0,0,0,0.35); color: #fff;
  opacity: 0; transition: opacity 0.15s;
}
.tx-bar .tx-bar-eye svg { width: 12px; height: 12px; }
.tx-bar:hover .tx-bar-eye, .tx-bar.is-selected .tx-bar-eye, .tx-bar.is-hidden .tx-bar-eye { opacity: 1; }
@media (hover: none) { .tx-bar.is-selected .tx-bar-eye { opacity: 1; } }
.tx-chip.is-on, .tx-chip[aria-pressed="true"] { border-color: var(--accent); color: var(--accent); }
.tx-ph-time {
  position: absolute; top: 1px; left: 3px; z-index: 3; pointer-events: none; white-space: nowrap;
  font: 600 0.56rem/1 'JetBrains Mono', monospace; padding: 2px 4px; border-radius: 3px;
  background: var(--danger, #ef4444); color: #fff;
}
.tx-hover {
  position: absolute; top: 2px; z-index: 6; pointer-events: none; display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 3px; border-radius: 6px; background: var(--bg-primary); box-shadow: 0 4px 14px rgba(0,0,0,0.45); border: 1px solid var(--border);
}
.tx-hover[hidden] { display: none; }
.tx-hover-thumb { display: block; width: 120px; height: auto; border-radius: 3px; object-fit: fill; max-height: none; }
.tx-hover-thumb[hidden] { display: none; }
.tx-hover-time { font: 600 0.6rem/1 'JetBrains Mono', monospace; color: var(--text-primary); }

/* ─── Visual template picker (2026-09-29): each template is a card showing a real frame of
   itself, in a grid, on every screen size. ─── */
.tx-templates.is-tabbed {
  display: grid !important; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 8px;
  overflow: visible !important; flex-wrap: wrap; scroll-snap-type: none; padding-bottom: 4px;
}
.tx-templates.is-tabbed .tx-tpl-label { display: none; }
.tx-templates.is-tabbed .tx-tpl {
  position: relative; display: flex; align-items: flex-end; min-height: 0; aspect-ratio: 16 / 9; padding: 0; overflow: hidden;
  border-radius: 10px; border: 1px solid var(--border);
  background: #0b0d12 var(--thumb, none) center / cover no-repeat;
  text-align: left; transition: transform 0.15s ease, border-color 0.15s ease;
}
.tx-templates.is-tabbed .tx-tpl[hidden] { display: none; }
.tx-templates.is-tabbed .tx-tpl:hover { border-color: var(--accent); }
.tx-templates.is-tabbed .tx-tpl:active { transform: scale(0.97); }
.tx-templates.is-tabbed .tx-tpl-name {
  width: 100%; padding: 14px 8px 6px; font-size: 0.72rem; font-weight: 600; line-height: 1.2; color: #fff;
  background: linear-gradient(to top, rgba(0,0,0,0.82), rgba(0,0,0,0));
}
.tx-tpl-recent .tx-tpl .tx-tpl-name { background: none; padding: 0; color: inherit; font: inherit; }
.tx-only[hidden] { display: none; }
@media (max-width: 768px) {
  .tx-templates.is-tabbed { grid-template-columns: repeat(2, 1fr); }
  /* Category tabs: short, sticky under the add row so switching never needs a scroll back up. */
  .tx-tpl-tabs { position: sticky; top: 58px; z-index: 1; background: var(--bg-primary); padding: 6px 0; }
}

/* Keyframes: active-effect chips + value dragging (2026-09-29) */
.kf-active { display: flex; flex-direction: column; gap: 6px; margin: 6px 0 8px; }
.kf-active[hidden] { display: none; }
.kf-active-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.kf-active-chip { min-height: 30px; padding: 4px 10px; font-size: 0.74rem; }
.kf-auto { font-size: 0.74rem; }
.kf-lane.is-value { min-height: 46px; }
.kf-track:has(.kf-lane.is-value) { min-height: 48px; }
.kf-drag-val {
  position: absolute; z-index: 5; pointer-events: none; white-space: nowrap; transform: translate(-50%, -150%);
  font: 700 0.64rem/1 'JetBrains Mono', monospace; padding: 3px 6px; border-radius: 4px;
  background: var(--text-primary); color: var(--bg-primary);
}
@media (max-width: 768px) {
  .kf-active-chips { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
  .kf-active-chip { flex: 0 0 auto; min-height: 36px; }
  .kf-lane.is-value { min-height: 56px; }
}
/* Intro row + on-preview delete (2026-09-29) */
.tx-intro-row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; padding: 6px 8px; border-radius: 10px; background: var(--bg-secondary); border: 1px solid var(--border); }
.tx-intro-row[hidden] { display: none; }
.tx-intro-name { flex: 1 1 auto; min-width: 0; font-size: 0.78rem; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tx-chip-danger { color: var(--danger, #ef4444); border-color: color-mix(in srgb, var(--danger, #ef4444) 45%, transparent); }
.tx-select-del {
  position: absolute; top: -14px; right: -14px; width: 26px; height: 26px; padding: 0; border-radius: 50%;
  border: 2px solid #fff; background: var(--danger, #ef4444); color: #fff; font: 700 15px/1 Inter, sans-serif;
  cursor: pointer; pointer-events: auto; box-shadow: 0 2px 8px rgba(0,0,0,0.4);
}
@media (hover: none) { .tx-select-del { width: 32px; height: 32px; top: -17px; right: -17px; } }

/* ─── Visible option pickers (2026-09-29) ───────────────────────────────────────────
   select[data-picker] → a row of chips or a grid of cards (enhanceOptionPicker in main.js).
   The select itself stays in the DOM for presets/autosave but is not shown. */
select.opt-picker-source { display: none !important; }
.opt-picker { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.opt-picker[hidden] { display: none; }
.opt-item {
  appearance: none; cursor: pointer; text-align: left; font: inherit; color: var(--text-primary);
  background: var(--bg-secondary); border: 1px solid var(--border); border-radius: 10px;
  transition: border-color var(--transition), background var(--transition), transform 0.12s ease;
}
.opt-item:hover:not(:disabled) { border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); }
.opt-item:active:not(:disabled) { transform: scale(0.98); }
.opt-item:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.opt-item:disabled { opacity: 0.45; cursor: not-allowed; }
.opt-item.is-on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, var(--bg-secondary)); box-shadow: inset 0 0 0 1px var(--accent); }
.opt-title { display: block; font-size: 0.8rem; font-weight: 650; line-height: 1.2; }
.opt-sub { display: block; margin-top: 3px; font-size: 0.7rem; line-height: 1.3; color: var(--text-secondary); }
.opt-chips .opt-item { flex: 1 1 auto; min-height: 34px; padding: 6px 10px; text-align: center; }
.opt-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
.opt-cards .opt-item { padding: 9px 11px; min-height: 58px; }
@media (max-width: 768px) {
  .opt-chips { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; padding-bottom: 2px; }
  .opt-chips .opt-item { flex: 0 0 auto; min-height: 40px; }
  .opt-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ─── Export tab (2026-09-29) ─────────────────────────────────────────────────────── */
.sidebar[data-active-tab="export"] .mobile-mode-tabs { display: none; }
.ex-card {
  position: relative; padding: 14px; margin-bottom: 14px; border-radius: 14px; overflow: hidden;
  background:
    radial-gradient(120% 90% at 100% 0%, color-mix(in srgb, var(--accent) 22%, transparent), transparent 60%),
    radial-gradient(90% 80% at 0% 100%, color-mix(in srgb, #e0562e 16%, transparent), transparent 65%),
    var(--bg-secondary);
  border: 1px solid var(--border);
}
.ex-summary { display: flex; flex-wrap: wrap; gap: 4px 0; margin-bottom: 12px; font-size: 0.74rem; color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.ex-summary span:not(:last-child)::after { content: '·'; margin: 0 7px; opacity: 0.5; }
.ex-summary .ex-sum-main { color: var(--text-primary); font-weight: 700; letter-spacing: 0.02em; }
.ex-card .record-cta-row { display: flex; gap: 8px; margin: 0; }
.ex-card #startRecordBtn { flex: 1 1 auto; min-height: 48px; font-size: 0.95rem; font-weight: 700; letter-spacing: 0.01em; gap: 8px; border-radius: 12px; display: inline-flex; align-items: center; justify-content: center; }
.ex-card #stopRecordBtn { flex: 0 0 auto; min-height: 48px; padding: 0 16px; border-radius: 12px; display: inline-flex; align-items: center; gap: 6px; }
.ex-card #startRecordBtn .icon, .ex-card #stopRecordBtn .icon { width: 16px; height: 16px; }
.ex-card #startRecordBtn:not(:disabled) .icon { animation: exRecPulse 2.4s ease-in-out infinite; }
@keyframes exRecPulse { 50% { opacity: 0.55; } }
.ex-card .control-group { margin-bottom: 0; }
.ex-extras { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.ex-extras .ex-mini { width: 100%; margin: 0 !important; min-height: 40px; }
#formatSupportNote.is-warn { color: #f59e0b; }

/* ─── Progress & loading states, second pass (2026-09-29) ───────────────────────────
   Every progress bar in the app (.progress-track/.progress-fill: export, calibrate, join)
   becomes a thin luminous strip with a moving light across the filled part and a faint
   film-grain texture, and the numbers sit in tabular figures. Loaders get an orbiting
   blob instead of a plain spinner. */
.progress-track {
  position: relative; height: 8px !important; border-radius: 999px !important; overflow: hidden;
  background: color-mix(in srgb, var(--text-primary) 10%, transparent) !important;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--text-primary) 8%, transparent);
}
.progress-track::after {
  content: ''; position: absolute; inset: 0; pointer-events: none; opacity: 0.35; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='8'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.4' numOctaves='2'/%3E%3C/filter%3E%3Crect width='80' height='8' filter='url(%23n)'/%3E%3C/svg%3E");
}
.progress-fill {
  position: relative; height: 100% !important; border-radius: inherit !important;
  background: linear-gradient(90deg, #7c3aed, var(--accent) 45%, #22d3ee 80%, #e0562e) !important;
  background-size: 200% 100% !important;
  animation: progFlow 3.2s linear infinite;
  box-shadow: 0 0 14px color-mix(in srgb, var(--accent) 55%, transparent);
  transition: width 0.35s cubic-bezier(0.22, 1, 0.36, 1) !important;
}
.progress-fill::after {
  content: ''; position: absolute; top: 0; bottom: 0; width: 40%; right: 0;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,0.55), transparent);
  animation: progSheen 1.6s ease-in-out infinite;
}
@keyframes progFlow { to { background-position: -200% 0; } }
@keyframes progSheen { from { transform: translateX(-250%); } to { transform: translateX(120%); } }
.progress-meta { font-variant-numeric: tabular-nums; font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: 0.7rem !important; letter-spacing: 0.02em; }
#exportProgressPct { font-weight: 700; color: var(--text-primary); }
.buffering-indicator {
  border: none !important; border-radius: 50% !important;
  background: conic-gradient(from 0deg, transparent 0 55%, var(--accent) 80%, #22d3ee 92%, transparent) !important;
  -webkit-mask: radial-gradient(circle, transparent 54%, #000 56%); mask: radial-gradient(circle, transparent 54%, #000 56%);
  filter: drop-shadow(0 0 8px color-mix(in srgb, var(--accent) 60%, transparent));
}
@media (prefers-reduced-motion: reduce) {
  .progress-fill, .progress-fill::after, .ex-card #startRecordBtn .icon { animation: none !important; }
}

/* Sound section (2026-09-29) */
.sound-wave { display: block; width: 100%; height: 56px; max-height: none !important; object-fit: fill !important; border-radius: 10px; cursor: pointer;
  background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 10%, var(--bg-secondary)), var(--bg-secondary)); border: 1px solid var(--border); }
.sound-fades { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 8px; }
/* Dynamics + stretch windows (2026-09-30) */
.sound-dyn { margin-top: 4px; }
.sound-windows { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border); }
.sound-window-list { display: flex; flex-direction: column; gap: 8px; margin-top: 8px; }
.sound-window-list[hidden] { display: none; }
.sound-window-row { display: flex; align-items: center; gap: 8px; padding: 8px 10px; border-radius: 12px; background: var(--bg-secondary); border: 1px solid var(--border); }
/* Same reading order as a Music row (description · amount · remove) and the same treatment:
   the description is the part that can get long on a narrow sidebar, so it is the one that
   shrinks, and the amount keeps the monospace figure style. */
.sound-window-text { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.78rem; }
.sound-window-mix { flex: 0 0 auto; font: 500 0.7rem/1 'JetBrains Mono', monospace; color: var(--text-secondary); }
.sound-window-del { flex: 0 0 auto; width: 26px; height: 26px; border-radius: 50%; border: 0; background: var(--bg-tertiary); color: var(--text-primary); cursor: pointer; font-size: 16px; }

/* ─── Full-screen editor (2026-09-29) ───────────────────────────────────────────────
   Four panes over the page: settings (left), preview (centre), inspector (right), and the
   timeline across the bottom. The panes hold the app's own nodes (moved, not cloned). */
body.editor-full { overflow: hidden; }
.ed-shell {
  position: fixed; inset: 0; z-index: 900; display: grid;
  grid-template-columns: minmax(300px, 22vw) 1fr minmax(340px, 25vw);
  grid-template-rows: 48px 1fr var(--ed-dock-h, 360px);
  grid-template-areas: "top top top" "left center right" "dock dock dock";
  background: var(--bg-primary); color: var(--text-primary);
}
.ed-shell[hidden] { display: none; }
.ed-top { grid-area: top; display: flex; align-items: center; gap: 12px; padding: 0 12px; border-bottom: 1px solid var(--border); background: var(--bg-primary); }
.ed-btn { display: inline-flex; align-items: center; gap: 6px; height: 32px; padding: 0 12px 0 8px; border-radius: 8px; border: 1px solid var(--border); background: var(--bg-secondary); color: var(--text-primary); font: 600 0.8rem/1 var(--font-body); cursor: pointer; }
.ed-btn svg { width: 16px; height: 16px; }
.ed-btn:hover { border-color: var(--accent); }
.ed-title { font: 700 0.9rem/1 var(--font-display); letter-spacing: -0.01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 40vw; }
.ed-status { flex: 1 1 auto; font-size: 0.75rem; color: var(--text-secondary); font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ed-export { display: inline-flex; align-items: center; gap: 6px; height: 34px; padding: 0 16px; border-radius: 9px; }
.ed-export svg { width: 16px; height: 16px; }
.ed-left { grid-area: left; min-height: 0; overflow: hidden; border-right: 1px solid var(--border); display: flex; }
.ed-center { grid-area: center; min-width: 0; min-height: 0; display: flex; }
.ed-right { grid-area: right; min-height: 0; overflow-y: auto; border-left: 1px solid var(--border); padding: 10px 12px; background: var(--bg-primary); }
.ed-left > aside.sidebar { position: static !important; transform: none !important; width: 100% !important; max-width: none !important; height: 100% !important; max-height: none !important; border-left: 0 !important; grid-column: auto; grid-row: auto; }
.ed-center > main.video-area { flex: 1 1 auto; height: 100% !important; min-height: 0; animation: none; }
.ed-right #trimPanel { display: block !important; }
.ed-right .clip-editor-tabs { position: sticky; top: -10px; z-index: 3; background: var(--bg-primary); padding: 4px 0 8px; margin-top: -4px; }
body.editor-full .desktop-clip-editor-dock, body.editor-full #edOpenBtn { display: none !important; }
.ed-dock { grid-area: dock; position: relative; display: flex; flex-direction: column; min-height: 0; border-top: 1px solid var(--border); background: var(--bg-secondary); }
.ed-resize { position: absolute; top: -4px; left: 0; right: 0; height: 8px; cursor: ns-resize; z-index: 5; }
.ed-resize:hover { background: linear-gradient(180deg, transparent 3px, var(--accent) 3px, var(--accent) 5px, transparent 5px); }
.ed-toolbar { display: flex; align-items: center; gap: 4px; padding: 6px 10px; border-bottom: 1px solid var(--border); }
.ed-tool { display: inline-flex; align-items: center; gap: 5px; height: 30px; min-width: 30px; padding: 0 9px; border: 0; border-radius: 7px; background: transparent; color: var(--text-primary); font: 600 0.76rem/1 var(--font-body); cursor: pointer; }
.ed-tool svg { width: 16px; height: 16px; flex: none; }
.ed-tool:hover:not(:disabled) { background: color-mix(in srgb, var(--text-primary) 8%, transparent); }
.ed-tool:disabled { opacity: 0.35; cursor: default; }
.ed-add { color: var(--accent); }
.ed-time { font: 600 0.78rem/1 'JetBrains Mono', ui-monospace, monospace; font-variant-numeric: tabular-nums; padding: 0 8px; min-width: 128px; }
.ed-sep { width: 1px; height: 20px; background: var(--border); margin: 0 6px; }
.ed-grow { flex: 1 1 auto; }
.ed-progress { display: inline-flex; align-items: center; gap: 8px; font-size: 0.72rem; color: var(--text-secondary); }
.ed-progress .progress-track { width: 120px; display: block; }
.ed-progress .progress-fill { display: block; }
.ed-progress[hidden] { display: none; }
.ed-zoom { display: inline-flex; align-items: center; gap: 6px; color: var(--text-secondary); margin-left: 8px; }
.ed-zoom svg { width: 15px; height: 15px; }
.ed-zoom input { width: 110px; }
/* Zoom readout + −/+ pair (2026-10-01): square flat buttons at the other tools' height, and a
   monospace label so "Fit" / "250%" does not wobble the toolbar as the number changes. */
.ed-zoom-label { font: 600 0.7rem/1 'JetBrains Mono', monospace; min-width: 38px; text-align: right; color: var(--text-primary); }
/* Zoom −/+ pair (2026-10-01): square, flat, same height as the other toolbar tools. */
.ed-zoom-btn {
  flex: none; width: 28px; height: 28px; margin-left: 2px; padding: 0; line-height: 1;
  border: 1px solid var(--border); border-radius: 7px; background: transparent;
  color: var(--text-primary); font: 700 0.95rem/1 var(--font-body); cursor: pointer;
}
.ed-zoom-btn:hover { border-color: var(--accent); color: var(--accent); }
.ed-scroll { flex: 0 0 auto; overflow-x: auto; overflow-y: hidden; padding: 0 12px; }
.ed-content { position: relative; min-width: 100%; padding-bottom: 6px; }
.ed-ruler { position: relative; height: 22px; cursor: ew-resize; user-select: none; border-bottom: 1px solid var(--border); }
.ed-tick { position: absolute; bottom: 0; width: 1px; height: 5px; background: color-mix(in srgb, var(--text-primary) 25%, transparent); }
.ed-tick.is-major { height: 9px; background: color-mix(in srgb, var(--text-primary) 45%, transparent); font: 500 0.62rem/1 'JetBrains Mono', monospace; color: var(--text-secondary); text-indent: 4px; white-space: nowrap; }
.ed-tick.is-major::before { content: attr(data-x); }
.ed-track { position: relative; margin-top: 10px; }
.ed-video { height: 80px; }
.ed-audio { height: 48px; border-radius: 8px; background: color-mix(in srgb, var(--accent) 8%, var(--bg-primary)); cursor: pointer; overflow: hidden; }
.ed-audio canvas { display: block; max-height: none !important; object-fit: fill !important; }
.ed-track-tag { position: absolute; left: 6px; top: 4px; font: 700 0.58rem/1 var(--font-body); letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-secondary); pointer-events: none; }
.ed-empty { display: flex; align-items: center; justify-content: center; height: 100%; border: 1.5px dashed var(--border); border-radius: 10px; font-size: 0.8rem; color: var(--text-secondary); }
.ed-clip {
  position: absolute; top: 0; height: 80px; border-radius: 9px; overflow: hidden; cursor: grab; user-select: none; touch-action: none;
  background: hsl(var(--hue) 45% 28%); box-shadow: inset 0 0 0 1.5px hsl(var(--hue) 60% 55% / 0.55);
  transition: box-shadow 0.12s, transform 0.12s;
}
.ed-clip.is-selected { box-shadow: inset 0 0 0 2px #fff, 0 0 0 2px var(--accent); z-index: 2; }
.ed-clip.is-dragging { cursor: grabbing; opacity: 0.85; z-index: 4; box-shadow: 0 10px 24px rgba(0,0,0,0.45), inset 0 0 0 2px #fff; transition: none; }
/* Title cards + gaps (2026-10-02). A card is a slate, not footage: dark block, the title in
   the middle in the card's own font weight. A gap is the black a nudge leaves — hatched so it
   reads as "nothing here" rather than a missing thumbnail. */
.ed-clip.is-card { background: linear-gradient(180deg, #17171c, #0b0b0e); border: 1px solid rgba(255,255,255,0.14); }
.ed-card-title { position: absolute; inset: 0 8px 18px; display: flex; align-items: center; justify-content: center; font: 700 0.72rem/1.15 var(--font-body); letter-spacing: 0.12em; text-transform: uppercase; color: rgba(255,255,255,0.9); text-align: center; overflow: hidden; pointer-events: none; }
.ed-card-title::before { content: 'T'; flex: none; display: inline-grid; place-items: center; width: 16px; height: 16px; margin-right: 6px; border-radius: 4px; background: var(--accent); color: #fff; font-size: 0.62rem; letter-spacing: 0; }
.ed-gap { position: absolute; top: 4px; bottom: 4px; border-radius: 4px; pointer-events: none; background: repeating-linear-gradient(135deg, rgba(255,255,255,0.07) 0 4px, transparent 4px 8px); box-shadow: inset 0 0 0 1px rgba(255,255,255,0.1); }
.ed-gap span { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); font: 600 0.6rem/1 'JetBrains Mono', monospace; color: rgba(255,255,255,0.55); white-space: nowrap; }
.ed-tool.ed-icon-only { padding-left: 7px; padding-right: 7px; }
.ed-clip-strip { position: absolute; inset: 0; display: flex; }
.ed-clip-strip img { flex: 1 1 0; min-width: 0; height: 100%; object-fit: cover; opacity: 0.92; }
.ed-clip-label { position: absolute; left: 8px; bottom: 5px; right: 8px; font: 600 0.68rem/1.2 var(--font-body); color: #fff; text-shadow: 0 1px 3px rgba(0,0,0,0.8); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; pointer-events: none; }
.ed-clip-edge { position: absolute; top: 0; bottom: 0; width: 10px; cursor: ew-resize; touch-action: none; background: rgba(255,255,255,0); transition: background 0.12s; }
.ed-clip-edge::after { content: ''; position: absolute; top: 50%; left: 50%; width: 3px; height: 22px; margin: -11px 0 0 -1.5px; border-radius: 2px; background: rgba(255,255,255,0.85); opacity: 0; transition: opacity 0.12s; }
.ed-edge-l { left: 0; } .ed-edge-r { right: 0; }
.ed-clip:hover .ed-clip-edge::after, .ed-clip.is-selected .ed-clip-edge::after { opacity: 1; }
.ed-clip.is-selected .ed-clip-edge { background: rgba(255,255,255,0.18); }
.ed-join { position: absolute; top: 20px; width: 24px; height: 24px; margin-left: -12px; z-index: 3; border-radius: 50%; border: 2px solid var(--bg-secondary); background: var(--bg-primary); color: var(--text-secondary); display: grid; place-items: center; padding: 0; cursor: pointer; opacity: 0; transition: opacity 0.12s, transform 0.12s; }
.ed-join svg { width: 11px; height: 11px; }
.ed-video:hover .ed-join, .ed-join.has-trans { opacity: 1; }
.ed-join.has-trans { background: var(--accent); color: var(--on-accent); }
/* J-cut / L-cut (2026-10-02): the join button carries the letter for the cut it has — J when the
   sound leads, L when it trails — so a boundary whose sound no longer matches its picture is
   visible at a glance without hovering for the tooltip. */
.ed-join.has-acut { opacity: 1; box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 45%, transparent); }
.ed-join.has-acut::after { content: attr(data-acut); position: absolute; top: -7px; right: -7px; min-width: 13px; height: 13px; padding: 0 2px; border-radius: 7px; background: var(--accent); color: var(--on-accent); font: 700 0.5rem/1 var(--font-body); display: grid; place-items: center; }
.ed-join:hover { transform: scale(1.12); color: var(--text-primary); }
.ed-playhead { position: absolute; top: 0; bottom: 0; left: 0; width: 2px; margin-left: -1px; background: #ff3b5c; pointer-events: none; z-index: 6; box-shadow: 0 0 8px rgba(255,59,92,0.6); }
.ed-playhead span { position: absolute; top: 0; left: -5px; width: 12px; height: 12px; border-radius: 0 0 6px 6px; background: #ff3b5c; }
.ed-insert { position: absolute; top: 26px; height: 72px; width: 3px; margin-left: -1.5px; border-radius: 2px; background: var(--accent); box-shadow: 0 0 10px var(--accent); z-index: 7; pointer-events: none; }
.ed-insert[hidden] { display: none; }
/* Snap indicator (2026-10-01): the amber line that appears on the boundary a dragged edge has
   grabbed, so it is obvious WHY the clip stopped moving. Amber, not the accent, so it is not
   confused with the accent-coloured insert marker. */
.ed-snapline { position: absolute; top: 0; bottom: 0; width: 1px; margin-left: -0.5px; background: #ffd400; box-shadow: 0 0 7px rgba(255, 212, 0, 0.85); z-index: 8; pointer-events: none; }
.ed-snapline[hidden] { display: none; }
/* A toolbar tool that toggles stays lit while it is on. */
.ed-tool-toggle.is-on { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
/* Overlay track (2026-10-02): the second picture over the first. Its own lane between the
   video clips and the music, tall enough to read the placement chip and keep a 10px handle on
   each edge, and marked with a dashed outline while it is empty so it reads as a place things
   can go rather than a stray empty box. */
.ed-overlay { height: 52px; border-radius: 8px; border: 1.5px dashed var(--border); }
.ed-overlay.is-empty { background: color-mix(in srgb, var(--accent) 4%, transparent); }
.ed-overlay.is-empty .ed-track-tag { opacity: 0.75; }
.ed-overlay:not(.is-empty) { border-color: transparent; }
.ed-overlay.is-drop { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); }
.ed-oclip {
  position: absolute; top: 0; height: 52px; border-radius: 8px; overflow: hidden; cursor: grab;
  user-select: none; background: hsl(var(--hue) 40% 26%);
  box-shadow: inset 0 0 0 1.5px hsl(var(--hue) 55% 58% / 0.6);
  transition: box-shadow 0.12s;
}
.ed-oclip.is-selected { box-shadow: inset 0 0 0 2px #fff, 0 0 0 2px var(--accent); z-index: 2; }
.ed-oclip.is-dragging { cursor: grabbing; opacity: 0.85; z-index: 4; transition: none; }
/* The strip is one repeated frame of the overlay source, so the block reads as "this picture"
   rather than as an empty placeholder the way a missing thumbnail would. */
.ed-oclip-strip { position: absolute; inset: 0; }
.ed-oclip-strip img { width: 100%; height: 100%; object-fit: cover; opacity: 0.5; }
/* The name and the edge handles are the video clips' own .ed-clip-label / .ed-clip-edge, reused
   deliberately: an overlay block is dragged and trimmed with the same gestures as a clip, so it
   should not look like a different kind of thing. */
/* The placement chip sits top-right, mirroring the track tag top-left on the empty lane. */
.ed-oclip-place { position: absolute; right: 6px; top: 3px; font: 700 0.55rem/1 var(--font-body); letter-spacing: 0.06em; text-transform: uppercase; color: rgba(255,255,255,0.82); background: rgba(0,0,0,0.42); border-radius: 4px; padding: 2px 4px; pointer-events: none; }
.ed-oclip .ed-clip-edge { height: 52px; }
/* Visibility eye (quick overlay on/off) and the dimmed state while hidden. */
.ed-oclip-eye { position: absolute; left: 5px; top: 4px; z-index: 3; width: 20px; height: 20px; padding: 0; border: 0; border-radius: 5px; background: rgba(0,0,0,0.45); color: rgba(255,255,255,0.92); display: grid; place-items: center; cursor: pointer; }
.ed-oclip-eye svg { width: 13px; height: 13px; }
.ed-oclip-eye:hover { background: rgba(0,0,0,0.72); }
.ed-oclip.is-hidden { opacity: 0.55; }
.ed-oclip.is-hidden .ed-oclip-strip { filter: grayscale(0.8) brightness(0.7); }
.ed-oclip.is-hidden .ed-oclip-eye { background: var(--accent); color: var(--on-accent, #fff); }
/* The overlay settings popover (2026-10-02). Built on .ed-trans-menu so it hangs off its block
   and closes on an outside click exactly like the transition menu does; only the rows inside are
   its own. Scrolls rather than growing past the viewport on a laptop. */
.ed-ov-menu { width: 248px; max-height: 72vh; overflow-y: auto; }
.ed-ov-row { padding: 6px 8px 2px; }
.ed-ov-row + .ed-ov-row { border-top: 1px solid var(--border); }
.ed-ov-lab { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; margin-bottom: 5px; font: 600 0.66rem/1.2 var(--font-body); color: var(--text-secondary); }
.ed-ov-val { color: var(--text-primary); font: 600 0.68rem/1 'JetBrains Mono', monospace; font-variant-numeric: tabular-nums; }
.ed-ov-row input.slider { width: 100%; }
.ed-ov-chips { display: flex; flex-wrap: wrap; gap: 4px; }
.ed-ov-chips .ed-trans-dur { flex: 1 1 auto; padding: 0 8px; }
.ed-ov-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; }
.ed-ov-grid .ed-trans-dur { display: grid; place-items: center; padding: 3px; }
/* The position button shows the position: a dot in the cell of a 3×3 frame that it puts the
   overlay in, so the grid reads as the picture at a glance. */
.ed-ov-dot { width: 9px; height: 9px; border-radius: 50%; background: currentColor; opacity: 0.85; }
.ed-ov-menu .ed-trans-dur.is-danger { color: #ff6b6b; border-color: color-mix(in srgb, #ff6b6b 40%, transparent); }
.ed-text-slot { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 4px 12px 8px; border-top: 1px solid var(--border); }
.ed-text-slot .tx-tl-tools { position: sticky; top: 0; z-index: 2; background: var(--bg-secondary); }
.ed-trans-menu { position: fixed; z-index: 1000; width: 210px; padding: 6px; border-radius: 12px; background: var(--bg-primary); border: 1px solid var(--border); box-shadow: 0 16px 40px rgba(0,0,0,0.35); }
.ed-trans-head { font: 700 0.62rem/1 var(--font-body); letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-secondary); padding: 6px 8px 8px; }
.ed-trans-opt { display: flex; align-items: center; gap: 9px; width: 100%; padding: 7px 8px; border: 0; border-radius: 8px; background: transparent; color: var(--text-primary); font: 500 0.8rem/1 var(--font-body); cursor: pointer; text-align: left; }
.ed-trans-opt:hover { background: var(--bg-secondary); }
.ed-trans-opt.is-on { background: color-mix(in srgb, var(--accent) 16%, transparent); }
.ed-trans-swatch { width: 26px; height: 16px; border-radius: 4px; flex: none; }
.ed-sw-cut { background: linear-gradient(90deg, #4a6 50%, #46a 50%); }
.ed-sw-fade { background: linear-gradient(90deg, #4a6, #46a); }
.ed-sw-black { background: linear-gradient(90deg, #4a6, #000 45% 55%, #46a); }
.ed-sw-white { background: linear-gradient(90deg, #4a6, #fff 45% 55%, #46a); }
/* The J / L swatch is the letter itself — the menu is where the difference between the two is
   being explained, so it should not need a legend. */
.ed-sw-acut { display: grid; place-items: center; color: var(--on-accent); background: color-mix(in srgb, var(--accent) 80%, transparent); font: 700 0.66rem/1 var(--font-body); }
.ed-trans-durs { display: flex; gap: 4px; padding: 6px 4px 2px; border-top: 1px solid var(--border); margin-top: 4px; }
.ed-trans-dur { flex: 1; height: 26px; border-radius: 6px; border: 1px solid var(--border); background: transparent; color: var(--text-primary); font: 600 0.72rem/1 var(--font-body); cursor: pointer; }
.ed-trans-dur.is-on { border-color: var(--accent); color: var(--accent); }
/* The sound section's length row has nothing to lengthen until a J- or an L-cut is picked. */
.ed-trans-durs.is-off { opacity: 0.45; }
.ed-trans-dur:disabled { cursor: not-allowed; }
.ed-open-btn { position: absolute; top: 10px; left: 10px; z-index: 20; display: inline-flex; align-items: center; gap: 6px; height: 30px; padding: 0 11px 0 9px; border-radius: 999px; border: 1px solid rgba(255,255,255,0.22); background: rgba(10,12,20,0.55); color: #fff; font: 600 0.74rem/1 var(--font-body); cursor: pointer; backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); opacity: 0.85; transition: opacity 0.15s, background 0.15s; }
.ed-open-btn:hover { opacity: 1; background: rgba(10,12,20,0.8); }
.ed-open-btn svg { width: 15px; height: 15px; }
.ed-open-btn[hidden] { display: none; }
@media (max-width: 999px) { .ed-open-btn { display: none !important; } }

/* Drop anywhere */
.drop-overlay { position: fixed; inset: 0; z-index: 2000; display: grid; place-items: center; pointer-events: none;
  background: color-mix(in srgb, var(--bg-primary) 55%, transparent); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); }
.drop-overlay[hidden] { display: none; }
.drop-overlay.is-subtle { background: transparent; backdrop-filter: none; -webkit-backdrop-filter: none; }
.drop-overlay.is-subtle .drop-card { transform: translateY(-30vh) scale(0.8); opacity: 0.9; }
.drop-card { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 34px 48px; border-radius: 22px; border: 2px dashed var(--accent);
  background: radial-gradient(120% 100% at 50% 0%, color-mix(in srgb, var(--accent) 22%, var(--bg-primary)), var(--bg-primary)); box-shadow: 0 24px 70px rgba(0,0,0,0.35); transition: transform 0.2s, opacity 0.2s; }
.drop-card svg { width: 40px; height: 40px; color: var(--accent); animation: dropBob 1.4s ease-in-out infinite; }
.drop-card strong { font: 700 1.2rem/1.2 var(--font-display); }
.drop-card span { font-size: 0.82rem; color: var(--text-secondary); }
@keyframes dropBob { 50% { transform: translateY(-5px); } }
.ed-top .ed-export { width: auto !important; flex: 0 0 auto; margin: 0; }
/* The join marker sits on the seam ABOVE the clips so it never covers their trim edges. */
.ed-join { top: -12px !important; width: 20px; height: 20px; margin-left: -10px; }
.ed-video { margin-top: 18px !important; }

/* Preview picker (2026-09-29) */
.pv-picker { position: fixed; z-index: 1500; width: min(440px, calc(100vw - 16px)); max-height: min(520px, 70vh); display: flex; flex-direction: column;
  border-radius: 16px; background: var(--bg-primary); border: 1px solid var(--border); box-shadow: 0 20px 60px rgba(0,0,0,0.45); overflow: hidden; }
.pv-picker[hidden] { display: none; }
.pv-picker.is-sheet { left: 0 !important; right: 0; bottom: 0; top: auto !important; width: 100%; max-height: 52vh; border-radius: 18px 18px 0 0; padding-bottom: env(safe-area-inset-bottom); animation: pvUp 0.22s cubic-bezier(0.22,1,0.36,1); }
@keyframes pvUp { from { transform: translateY(40%); opacity: 0; } }
.pv-head { display: flex; align-items: center; justify-content: space-between; padding: 12px 14px 8px; }
.pv-head strong { font: 700 0.9rem/1 var(--font-display); }
.pv-close { width: 30px; height: 30px; border-radius: 50%; border: 0; background: var(--bg-secondary); color: var(--text-primary); font-size: 18px; cursor: pointer; }
.pv-extra { padding: 0 14px 8px; }
.pv-extra[hidden] { display: none; }
.pv-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 8px; padding: 4px 14px 14px; overflow-y: auto; }
.pv-grid.has-thumbs { grid-template-columns: repeat(auto-fill, minmax(124px, 1fr)); }
.pv-opt { display: flex; flex-direction: column; gap: 4px; padding: 6px; border-radius: 12px; border: 1px solid var(--border); background: var(--bg-secondary); color: var(--text-primary); text-align: left; cursor: pointer; font: inherit; min-height: 44px; }
.pv-opt.is-on { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); background: color-mix(in srgb, var(--accent) 12%, var(--bg-secondary)); }
.pv-thumb { width: 100%; aspect-ratio: 16 / 9; height: auto; border-radius: 8px; display: block; max-height: none !important; object-fit: cover !important; }
.pv-name { font: 650 0.76rem/1.2 var(--font-body); padding: 0 2px; }
.pv-sub { font-size: 0.66rem; line-height: 1.3; color: var(--text-secondary); padding: 0 2px; }
.mq-mini-pill.is-on { border-color: var(--accent); color: var(--accent); }
.pv-opt canvas.pv-thumb { width: 100% !important; height: auto !important; max-width: none !important; max-height: none !important; aspect-ratio: 16 / 9; position: static !important; }
.pv-grid { flex: 1 1 auto; min-height: 0; grid-auto-rows: max-content; align-content: start; }
.pv-opt { height: auto !important; }

/* Music track + list (2026-09-29) */
.ed-music { height: 46px; border-radius: 8px; background: color-mix(in srgb, #22c55e 7%, var(--bg-primary)); }
.ed-music.is-empty { border: 1.5px dashed var(--border); background: transparent; }
.ed-mclip { position: absolute; top: 0; height: 46px; border-radius: 8px; overflow: hidden; cursor: grab; touch-action: none; background: linear-gradient(180deg, #169a57, #0f7442); box-shadow: inset 0 0 0 1.5px rgba(255,255,255,0.25); }
.ed-mclip.is-muted { opacity: 0.45; }
.ed-mclip canvas { position: absolute; left: 0; top: 2px; width: 100% !important; height: 42px !important; max-height: none !important; object-fit: fill !important; }
.ed-mclip .ed-clip-label { bottom: 3px; }
.music-list { display: flex; flex-direction: column; gap: 8px; margin-top: 8px; }
.music-list[hidden] { display: none; }
.music-row { padding: 10px; border-radius: 12px; background: var(--bg-secondary); border: 1px solid var(--border); }
.music-head { display: flex; align-items: center; gap: 8px; }
.music-head strong { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.82rem; }
.music-time { font: 500 0.7rem/1 'JetBrains Mono', monospace; color: var(--text-secondary); }
.music-del { width: 26px; height: 26px; border-radius: 50%; border: 0; background: var(--bg-tertiary); color: var(--text-primary); cursor: pointer; font-size: 16px; }
.music-actions { display: flex; gap: 6px; margin-top: 6px; }
.music-add { margin-left: auto; font-size: 0.74rem; padding: 4px 10px; }

/* ─── Sound-effect cues (2026-10-01) ──────────────────────────────────────────────────────
   One row per cue, matching the Music rows so the Sound tab reads as one family: header
   with the effect name, its time and a remove button, then a volume slider. Auto cues
   (derived from an intro) are marked so a hand-placed one is easy to tell apart. */
.sfx-actions { display: flex; gap: 6px; margin-top: 8px; }
.sfx-actions .btn { font-size: 0.74rem; padding: 4px 10px; }
.sfx-list { display: flex; flex-direction: column; gap: 8px; margin-top: 8px; }
.sfx-list[hidden] { display: none; }
.sfx-row { padding: 10px; border-radius: 12px; background: var(--bg-secondary); border: 1px solid var(--border); }
.sfx-row.is-auto { border-left: 3px solid var(--accent); }
.sfx-head { display: flex; align-items: center; gap: 8px; }
.sfx-head strong { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.82rem; }
.sfx-time { font: 500 0.7rem/1 'JetBrains Mono', monospace; color: var(--text-secondary); }
.sfx-del { width: 26px; height: 26px; border-radius: 50%; border: 0; background: var(--bg-tertiary); color: var(--text-primary); cursor: pointer; font-size: 16px; }
/* Sample a cue from the list before touching it (2026-10-02). */
.sfx-sample { width: 26px; height: 26px; flex: 0 0 auto; border-radius: 50%; border: 1px solid var(--border); background: var(--bg-tertiary); color: var(--text-primary); cursor: pointer; font-size: 10px; line-height: 1; }
.sfx-sample:hover { border-color: var(--accent); color: var(--accent); }

/* ─── 2026-09-30 pass: Applied tab, undo history, six-tab row, photo buttons ───────────────
   Appended last, same reason the other passes are: the rules above are spread over several
   same-specificity blocks, and a later rule is the only reliable way to win one without
   raising specificity everywhere. */

/* Six categories instead of five. The row is display:flex with flex:1 per tab, so a sixth
   tab takes its width straight out of the other five — the label was already within a couple
   of px of ellipsis at "Advanced" (see .sidebar-tab's own comment), so the shave has to come
   from padding and the gap, not from dropping a label. Mobile is unaffected: that row
   overrides its own padding and scrolls sideways. */
@media (min-width: 769px) {
  .sidebar-tabs { gap: 4px; }
  .sidebar-tab { padding: 8px 1px; }
  .sidebar-tab span { font-size: 0.54rem; letter-spacing: 0; }
}

/* Photo source row: two side-by-side buttons (Analyse Photo / Take Photo). They were a
   single full-width label before the choice existed (see the group's comment in index.html);
   .row-group is the sidebar's existing two-up helper. */
.row-group .file-upload-label { flex: 1 1 0; justify-content: center; min-width: 0; }

/* ── Applied tab ─────────────────────────────────────────────────────────────────────────
   Rows are read-only references to real controls, so they are styled like a list, not like
   form fields: no borders around the values, no focus ring on the row itself except a subtle
   accent bar, and the whole row is one tap target. */
.applied-summary {
  display: flex; flex-direction: column; gap: 4px;
  padding: 8px 10px; margin-bottom: 10px;
  background: var(--bg-secondary); border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.applied-summary-row { display: flex; align-items: baseline; gap: 8px; font-size: 0.72rem; }
.applied-summary-row span { color: var(--text-secondary); flex: 0 0 auto; }
.applied-summary-row b {
  margin-left: auto; font-weight: 600; color: var(--text-primary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 62%;
}
.applied-list { display: flex; flex-direction: column; gap: 10px; }
.applied-group { display: flex; flex-direction: column; gap: 2px; }
.applied-group-head {
  display: flex; align-items: baseline; gap: 6px;
  font: 700 0.6rem/1.3 'JetBrains Mono', monospace; letter-spacing: 0.07em; text-transform: uppercase;
  color: var(--text-secondary); padding: 0 2px 2px;
}
.applied-group-head em {
  font-style: normal; font-weight: 600; opacity: 0.7;
  padding: 1px 5px; border-radius: 999px; background: var(--bg-tertiary);
}
/* Group count + "reset this group" (2026-10-01). The count sits right after the name so a long
   list says how much is in each block at a glance; the reset is pushed to the far end with
   margin-left:auto and kept small, since it is destructive-ish and should not compete with the
   group's own name. */
.applied-group-count { font-weight: 700; opacity: 0.65; }
.applied-group-reset {
  margin-left: auto; flex: 0 0 auto; width: 20px; height: 20px; line-height: 1;
  border: 1px solid var(--border); border-radius: 999px; background: var(--bg-secondary);
  color: var(--text-secondary); cursor: pointer; font-size: 12px; padding: 0;
}
.applied-group-reset:hover { color: var(--accent); border-color: var(--accent); }
.applied-filter { width: 100%; margin: 2px 0 4px; }
.applied-row {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 8px; border-radius: var(--radius-sm);
  background: var(--bg-secondary); border: 1px solid transparent;
  cursor: pointer; text-align: left;
}
.applied-row:hover { background: var(--bg-tertiary); border-color: var(--border); }
.applied-row:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.applied-row-label {
  min-width: 0; font-size: 0.74rem; color: var(--text-primary);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.applied-row-value {
  margin-left: auto; flex: 0 0 auto; max-width: 40%;
  font: 600 0.68rem/1 'JetBrains Mono', monospace; color: var(--accent);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Rows that were already off their shipped default the moment the app had finished loading (a
   tracking mode's baseline ranges, a restored session value, a format picked for the media you
   opened). They belong in the list — they are applied, and are worth a jump-and-tweak — but they
   rank below the user's own edits, carry a quiet "on load" chip, and drop the accent colour so
   an accent value keeps meaning "you changed this". */
.applied-row-onload .applied-row-value { color: var(--text-secondary); }
/* The app's own post-load writes (a preset rewriting the grade, a mode rewriting its ranges) look
   the same as the session's: not the user's doing, so no accent colour, and a dashed chip to sit
   apart from the solid "on load" one without needing a second colour. */
.applied-row-auto .applied-row-value { color: var(--text-secondary); opacity: 0.85; }
.applied-row-tag-auto { border-style: dashed; }
.applied-row-tag {
  flex: 0 0 auto; padding: 1px 6px; border-radius: 999px;
  background: var(--bg-tertiary); border: 1px solid var(--border); color: var(--text-secondary);
  font: 600 0.55rem/1.5 'JetBrains Mono', monospace; letter-spacing: 0.06em; text-transform: uppercase;
}
/* Narrow phones: the chip is the first thing to go rather than the setting's name — the muted
   value colour still separates the two kinds of row. */
@media (max-width: 420px) { .applied-row-tag { display: none; } }
.applied-row-reset {
  flex: 0 0 auto; width: 26px; height: 26px; border-radius: 50%;
  border: 1px solid var(--border); background: var(--bg-primary); color: var(--text-secondary);
  font-size: 14px; line-height: 1; cursor: pointer;
}
.applied-row-reset:hover { color: var(--accent); border-color: var(--accent); }
.applied-empty { margin: 0; padding: 10px 2px; font-size: 0.72rem; line-height: 1.5; color: var(--text-secondary); }
/* Landing flash for appliedJumpTo(): the control someone picked out of the list is somewhere
   in a long panel by the time the smooth scroll finishes, so it announces itself. */
@keyframes applied-target-flash {
  0%, 100% { box-shadow: 0 0 0 0 transparent; }
  25%, 70% { box-shadow: 0 0 0 3px var(--accent); border-radius: 8px; }
}
.applied-target-flash { animation: applied-target-flash 1.5s ease; }
@media (prefers-reduced-motion: reduce) {
  .applied-target-flash { animation: none; outline: 2px solid var(--accent); outline-offset: 3px; }
}

/* ── Undo toast: the button is now the app-wide undo (⌘Z, see undoStack in main.js), so it
   carries its shortcut, and a bar under the pill drains with the time left on the offer.
   The bar is driven by --undo-ms, set from the exact same deadline as the auto-hide timeout,
   so the two can never disagree about how long is left. */
#undoToast #undoToastBtn { display: inline-flex; align-items: center; gap: 5px; }
#undoToast #undoToastBtn .icon { width: 14px; height: 14px; }
#undoToast #undoToastBtn kbd {
  font: 600 0.6rem/1 'JetBrains Mono', monospace;
  padding: 2px 4px; border-radius: 4px; letter-spacing: 0;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  color: color-mix(in srgb, var(--accent) 85%, var(--text-secondary));
}
#undoToastBar {
  position: absolute; left: 14px; right: 14px; bottom: 3px; height: 2px;
  border-radius: 2px; background: var(--accent); opacity: 0.65;
  transform: scaleX(0); transform-origin: left center;
}
#undoToast.ticking #undoToastBar { animation: undoBarDrain var(--undo-ms, 5000ms) linear forwards; }
@keyframes undoBarDrain { from { transform: scaleX(1); } to { transform: scaleX(0); } }
@media (prefers-reduced-motion: reduce) {
  #undoToast.ticking #undoToastBar { animation: none; transform: scaleX(1); opacity: 0.35; }
}
@media (max-width: 768px) {
  #undoToast #undoToastBtn kbd { display: none; } /* no physical ⌘ key to point at */
}


/* ── Caption at the playhead + preset strip (2026-10-02) ───────────────────────────── */
.subs-now { scroll-margin-top: 14px; margin: 8px 0 10px; padding: 10px; border-radius: 12px; background: var(--bg-secondary); border: 1px solid var(--border); display: flex; flex-direction: column; gap: 8px; }
.subs-now-head { display: flex; justify-content: space-between; gap: 8px; font-size: 0.72rem; color: var(--text-secondary); }
.subs-now-head b { color: var(--text-primary); font-weight: 600; }
.subs-now-text { width: 100%; min-height: 56px; font-size: 16px; line-height: 1.35; resize: vertical; }
.subs-now-row { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.subs-now-timing span { font-size: 0.7rem; color: var(--text-secondary); margin-left: 4px; }
.subs-now-btn { min-height: 36px; min-width: 36px; padding: 0 10px; border-radius: 9px; border: 1px solid var(--border); background: var(--bg-primary, transparent); color: var(--text-primary); font: inherit; font-size: 0.78rem; font-weight: 600; cursor: pointer; }
.subs-now-btn:hover { border-color: var(--accent); }
.subs-now-btn:disabled { opacity: 0.4; cursor: default; }
.subs-now-btn.is-danger { color: #ff6b6b; }
.subs-now-btn.is-add { background: var(--accent); border-color: var(--accent); color: var(--on-accent, #fff); }
.subs-preset-strip { display: flex; gap: 8px; overflow-x: auto; padding: 8px 2px 6px; scroll-snap-type: x proximity; scrollbar-width: thin; }
.subs-preset { flex: none; width: 112px; padding: 0; border: 2px solid transparent; border-radius: 10px; background: none; cursor: pointer; scroll-snap-align: start; text-align: center; color: var(--text-secondary); }
.subs-preset .pv { display: flex; align-items: center; justify-content: center; height: 62px; border-radius: 8px; overflow: hidden; background: linear-gradient(160deg, #4a6378, #2c3a46 55%, #8a6b4a); padding: 0 6px; }
.subs-preset .pv span { line-height: 1.1; white-space: nowrap; max-width: 100%; overflow: hidden; text-overflow: clip; }
.subs-preset .nm { display: block; padding: 3px 2px 0; font-size: 0.64rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.subs-preset.is-on { border-color: var(--accent); color: var(--text-primary); }
@media (max-width: 768px) {
  .subs-now-btn { min-height: 44px; min-width: 44px; font-size: 0.82rem; }
  .subs-preset { width: 124px; }
}

/* Caption font strip (2026-10-02) */
.subs-font-strip { display: flex; gap: 6px; overflow-x: auto; padding: 8px 2px 4px; scrollbar-width: thin; }
.subs-font { flex: none; min-height: 40px; padding: 0 12px; border-radius: 10px; border: 1.5px solid var(--border); background: var(--bg-secondary); color: var(--text-primary); font-size: 15px; line-height: 1; cursor: pointer; white-space: nowrap; }
.subs-font.is-on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 16%, var(--bg-secondary)); }
@media (max-width: 768px) { .subs-font { min-height: 44px; font-size: 16px; } }

/* Keyframe editor interactions (2026-10-02) */
.kf-hint { font-size: 0.66rem; color: var(--text-secondary); flex: 1 1 260px; min-width: 0; }
@media (hover: none) { .kf-hint { display: none; } }
.kf-timeline.is-graph .kf-track:has(.kf-lane.is-value) { min-height: 132px; }
.kf-key.is-multi { box-shadow: 0 0 0 2px var(--text-primary); }
.kf-ghost { position: absolute; width: 0; height: 0; pointer-events: none; z-index: 3; }
.kf-ghost i { position: absolute; left: -5px; top: -5px; width: 10px; height: 10px; transform: rotate(45deg); border: 1.5px dashed var(--accent); background: color-mix(in srgb, var(--accent) 25%, transparent); border-radius: 2px; }
.kf-ghost b { position: absolute; left: 10px; top: -22px; white-space: nowrap; font: 600 10px/1.4 inherit; padding: 1px 6px; border-radius: 6px; background: var(--bg-primary, #111); color: var(--text-primary); border: 1px solid var(--border); }
.kf-menu { position: fixed; z-index: 10000; min-width: 220px; padding: 6px; border-radius: 12px; background: var(--bg-secondary); border: 1px solid var(--border); box-shadow: 0 12px 32px rgba(0,0,0,0.45); }
.kf-menu-head { font-size: 0.66rem; font-weight: 700; color: var(--text-secondary); padding: 2px 6px 6px; text-transform: uppercase; letter-spacing: 0.04em; }
.kf-menu-eases { display: grid; grid-template-columns: repeat(5, 1fr); gap: 4px; padding-bottom: 6px; border-bottom: 1px solid var(--border); margin-bottom: 4px; }
.kf-menu-ease { display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 4px 2px; border-radius: 8px; border: 1px solid transparent; background: none; color: var(--text-primary); cursor: pointer; font: inherit; font-size: 0.56rem; }
.kf-menu-ease:hover { border-color: var(--border); background: var(--bg-primary, transparent); }
.kf-menu-ease.is-on { border-color: var(--accent); color: var(--accent); }
.kf-menu-act { display: block; width: 100%; text-align: left; padding: 7px 8px; border: 0; border-radius: 8px; background: none; color: var(--text-primary); font: inherit; font-size: 0.8rem; cursor: pointer; }
.kf-menu-act:hover { background: var(--bg-primary, rgba(255,255,255,0.06)); }
.kf-menu-act.is-danger { color: #ff6b6b; }
.kf-curve-editor { margin: 8px 0; }
.kf-graph-wrap { display: flex; gap: 12px; align-items: flex-start; flex-wrap: wrap; }
.kf-graph { background: var(--bg-primary, #0b0d12); border-radius: 10px; border: 1px solid var(--border); touch-action: none; flex: none; }
.kf-graph-box { fill: none; stroke: var(--border); stroke-dasharray: 3 3; }
.kf-graph-diag { stroke: var(--border); stroke-width: 1; }
.kf-graph-arm { stroke: var(--text-secondary); stroke-width: 1.2; }
.kf-graph-curve { fill: none; stroke: var(--accent); stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
.kf-graph-handle { fill: var(--bg-secondary); stroke: var(--accent); stroke-width: 2.2; cursor: grab; }
.kf-graph-handle:hover { fill: var(--accent); }
.kf-graph-side { display: flex; flex-direction: column; gap: 6px; min-width: 140px; flex: 1 1 140px; font-size: 0.74rem; }
.kf-graph-side small { color: var(--text-secondary); line-height: 1.35; }
.kf-graph-side code { font-size: 0.7rem; color: var(--text-secondary); }
.kf-ease-chips { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 8px; }
.kf-ease-chip { width: 40px; height: 30px; display: grid; place-items: center; border-radius: 8px; border: 1px solid var(--border); background: var(--bg-secondary); color: var(--text-primary); cursor: pointer; padding: 0; }
.kf-ease-chip:hover { border-color: var(--accent); }
.kf-ease-chip.is-on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 18%, var(--bg-secondary)); color: var(--accent); }

/* Beat grid tag + tempo field (2026-10-02) */
.tx-bpm { position: absolute; right: 6px; bottom: 2px; font-size: 0.6rem; font-weight: 700; color: var(--accent); background: color-mix(in srgb, var(--bg-primary, #000) 70%, transparent); padding: 0 5px; border-radius: 5px; pointer-events: none; z-index: 2; }
.tx-bpm-row { display: flex; gap: 4px; }
.tx-bpm-row .text-input { width: 64px; min-width: 0; }

/* Post FX picker extras (2026-10-02) */
.pv-fx-extra { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 16px; }
.pv-amount { display: flex; flex-direction: column; gap: 4px; flex: 1 1 160px; font-size: 0.74rem; color: var(--text-secondary); }
.pv-amount b { color: var(--text-primary); }

/* Export jump bar (2026-10-03): one tap opens a section of the Export tab and scrolls to it. */
.ex-jump { display: flex; gap: 6px; margin: 0 0 12px; overflow-x: auto; scrollbar-width: none; }
.ex-jump::-webkit-scrollbar { display: none; }
.ex-jump button {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: 6px; min-height: 30px; padding: 4px 11px;
  border-radius: 999px; border: 1px solid var(--border); background: var(--bg-secondary);
  color: var(--text-secondary); font: inherit; font-size: 0.74rem; font-weight: 600; cursor: pointer;
}
.ex-jump button:hover { color: var(--text-primary); border-color: color-mix(in srgb, var(--accent) 50%, var(--border)); }
.ex-jump button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ex-jump .ex-jump-state { font-weight: 500; color: var(--accent); max-width: 9em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ex-jump .ex-jump-state:empty { display: none; }
@media (max-width: 760px) { .ex-jump button { min-height: 38px; } }

/* Your place (2026-10-03): city / area / year for the location templates. */
.tx-place-row { flex: 1 1 100%; display: flex; gap: 6px; margin: 2px 0 6px; }
.tx-place-row .text-input { flex: 1 1 0; min-width: 0; }
.tx-place-row .tx-place-year { flex: 0 0 64px; }
.tx-place-row[hidden] { display: none; }

/* Look presets strip (2026-10-03): each tile is set in its own look. */
.tx-looks { align-items: flex-start; }
.tx-lookstrip { display: flex; gap: 6px; overflow-x: auto; padding: 2px 2px 6px; scrollbar-width: thin; flex: 1 1 auto; min-width: 0; }
.tx-look {
  position: relative; flex: 0 0 auto; width: 92px; height: 66px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  border-radius: 10px; border: 1px solid var(--border); cursor: pointer; padding: 4px; color: var(--text-secondary);
  background: radial-gradient(120% 90% at 30% 20%, #2c3e56, #10151d 70%);
}
.tx-look:hover { border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); }
.tx-look:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.tx-look.is-on { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.tx-look-aa { font-size: 1.05rem; line-height: 1.15; white-space: nowrap; max-width: 100%; overflow: visible; }
.tx-look-name { font: 600 0.62rem/1.1 var(--font-body); color: rgba(255,255,255,0.78); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
.tx-look.is-mine { background: radial-gradient(120% 90% at 30% 20%, #3b2c56, #14101d 70%); }
.tx-look-save { border-style: dashed; background: transparent; }
.tx-look-save .tx-look-aa { font: 300 1.4rem/1 var(--font-body); color: var(--text-secondary); }
.tx-look-del { position: absolute; top: 2px; right: 4px; width: 18px; height: 18px; border-radius: 50%; display: grid; place-items: center; font-size: 13px; line-height: 1; color: #fff; background: rgba(0,0,0,0.55); opacity: 0; transition: opacity .12s; }
.tx-look:hover .tx-look-del, .tx-look:focus-within .tx-look-del { opacity: 1; }
@media (hover: none) { .tx-look-del { opacity: 1; } }
#textLogoRow [data-logo-only][hidden] { display: none !important; }
/* Template sub-sections inside a gallery group (2026-10-04). */
.tx-tpl-sub { flex: 1 1 100%; font: 700 0.66rem/1 var(--font-body); letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-secondary); margin: 8px 0 2px; }
.tx-tpl-sub[hidden] { display: none; }

/* Mobile landing compactness (2026-10-04). The home page was ~17,000px tall on a phone and laid
   out 609px wide (the reel-chip row sized itself to its content). On a phone: the chip row is a
   full-bleed swipe row, the long card grids become swipeable rails with the next card peeking,
   and section padding tightens. Desktop is untouched. */
@media (max-width: 640px) {
  .ah-reel-chips { width: 100vw; max-width: 100vw; margin-inline: calc(50% - 50vw); box-sizing: border-box; }
  /* The aurora glow reaches 20% past each side; clip it (not hidden: no new scroll box). */
  .lp-home .lp-hero-home.ah-hero, .lp-hero-home, .ah-hero { overflow-x: clip; }
  .lp-section { padding-left: 16px; padding-right: 16px; padding-bottom: 30px; }
  .lp-section-head { margin-bottom: 16px; }
  .mode-card-grid, .tool-grid, .style-card-grid, .how-steps, .lp-explainer-grid, .seo-grid {
    display: grid !important; grid-auto-flow: column; grid-template-columns: none !important; grid-template-rows: none !important;
    grid-auto-columns: 78%; gap: 12px; overflow-x: auto; overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory; scroll-padding-inline: 16px; scrollbar-width: none;
    margin-inline: -16px; padding: 2px 16px 10px; max-height: none !important;
  }
  .mode-card-grid::-webkit-scrollbar, .tool-grid::-webkit-scrollbar, .style-card-grid::-webkit-scrollbar,
  .how-steps::-webkit-scrollbar, .lp-explainer-grid::-webkit-scrollbar, .seo-grid::-webkit-scrollbar { display: none; }
  .mode-card-grid > *, .tool-grid > *, .style-card-grid > *, .how-steps > *, .lp-explainer-grid > *, .seo-grid > * { scroll-snap-align: start; min-width: 0; }
  .style-card-grid { grid-auto-columns: 62%; }
  /* Grade: the two feature panels side by side as a rail instead of two screens stacked. */
  #grade .lp-feature-split {
    display: grid !important; grid-auto-flow: column; grid-template-columns: none !important; grid-auto-columns: 86%; gap: 12px;
    overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; margin-inline: -16px; padding: 2px 16px 10px;
  }
  #grade .lp-feature-panel { scroll-snap-align: start; min-width: 0; }
  /* The style grid's "show all" fold is moot once it is a rail. */
  .style-card-grid.is-collapsed > * { display: revert !important; }
  .style-card-grid + .style-grid-toggle, .style-grid-more { display: none !important; }
}
/* Text inspector chip pickers (2026-10-04): a short choice is a row of chips, not a dropdown. */
#textInspector .tx-field .opt-picker { margin-top: 2px; }
#textInspector .tx-field .opt-chips { flex-wrap: wrap; gap: 4px; overflow: visible; }
#textInspector .tx-field .opt-chips .opt-item { flex: 0 0 auto; min-height: 28px; padding: 4px 9px; font-size: 0.72rem; }
@media (max-width: 760px) { #textInspector .tx-field .opt-chips .opt-item { min-height: 34px; } }
#textInspector .tx-row { flex-wrap: wrap; }
#textInspector .tx-field.tx-field-chips { flex: 1 1 100% !important; max-width: none; }
#textInspector .tx-field.tx-field-chips > span:first-child { margin-bottom: 2px; }
/* Text presets as a tabbed grid (2026-10-04). */
.tx-look-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 6px; }
.tx-look-tabs { display: flex; gap: 4px; flex-wrap: wrap; }
.tx-look-tab { border: 1px solid var(--border); background: transparent; color: var(--text-secondary); border-radius: 999px; padding: 3px 10px; font-size: 0.72rem; font-weight: 600; cursor: pointer; min-height: 26px; }
.tx-look-tab:hover { color: var(--text-primary); border-color: var(--text-secondary); }
.tx-look-tab.is-on { background: var(--accent); border-color: var(--accent); color: #fff; }
.tx-lookstrip.tx-lookgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 6px; overflow: visible; max-height: 248px; overflow-y: auto; padding-right: 2px; }
.tx-lookgrid .tx-look { width: auto; min-width: 0; }
@media (max-width: 760px) { .tx-look-tab { min-height: 32px; } .tx-lookstrip.tx-lookgrid { grid-template-columns: repeat(3, 1fr); } }
#textLookStrip { display: block; }
.tx-inspector[data-tab]:not([data-tab="style"]) > #textLookStrip { display: none; }
#textLookStrip .tx-lookgrid { width: 100%; }
/* Applied tab (2026-10-04): show-filter + reset at the top, defaults on each row, tab badge. */
.sidebar[data-active-tab="applied"] .mobile-mode-tabs { display: none !important; }
.applied-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; margin: 10px 0 8px; position: sticky; top: 0; z-index: 3; background: var(--bg-secondary, var(--bg-primary)); padding: 6px 0; }
.applied-show { display: inline-flex; border: 1px solid var(--border); border-radius: 999px; padding: 2px; gap: 2px; }
.applied-show-btn { border: 0; background: transparent; color: var(--text-secondary); font-size: 0.74rem; font-weight: 600; padding: 4px 10px; border-radius: 999px; cursor: pointer; min-height: 28px; }
.applied-show-btn b { font-weight: 700; opacity: 0.7; margin-left: 2px; }
.applied-show-btn.is-on { background: var(--accent); color: #fff; }
.applied-show-btn.is-on b { opacity: 1; }
.applied-resetall-top { padding: 5px 12px; font-size: 0.76rem; min-height: 30px; }
.applied-row-value { display: inline-flex; flex-direction: column; align-items: flex-end; line-height: 1.2; }
.applied-row-was { font-size: 0.66rem; color: var(--text-secondary); opacity: 0.8; font-family: inherit; }
.applied-row-reset { width: 30px; height: 30px; }
.sidebar-tab[data-tab="applied"] { position: relative; }
.applied-tab-badge { position: absolute; top: 3px; right: 6px; min-width: 16px; height: 16px; padding: 0 4px; border-radius: 8px; background: #ff4d6d; color: #fff; font-size: 10px; font-weight: 700; line-height: 16px; text-align: center; pointer-events: none; }
@media (max-width: 760px) { .applied-show-btn { min-height: 34px; } }
.applied-row-value { min-width: 0; max-width: 55%; overflow: hidden; }
.applied-row-now, .applied-row-was { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; text-align: right; }
.tx-anim-browse { margin-left: 6px; border: 1px solid var(--border); background: var(--bg-tertiary); color: var(--text-primary); border-radius: 6px; font-size: 0.68rem; font-weight: 600; padding: 1px 7px; cursor: pointer; line-height: 1.5; }
.tx-anim-browse:hover { border-color: var(--accent); color: var(--accent); }
@media (max-width: 760px) { .tx-anim-browse { padding: 4px 9px; } }
/* Editor sound (2026-10-04): SFX lane, Auto sound panel. */
.ed-sfx { height: 36px; border-radius: 8px; background: color-mix(in srgb, #f59e0b 7%, var(--bg-primary)); }
.ed-sfx.is-empty .ed-track-tag { opacity: 0.7; }
.ed-sfx:not(.is-empty) .ed-track-tag { display: none; }
.ed-sclip { position: absolute; top: 2px; height: 32px; border-radius: 6px; cursor: grab; touch-action: none; background: linear-gradient(180deg, #d98a0b, #a8680a); box-shadow: inset 0 0 0 1.5px rgba(255,255,255,0.25); overflow: hidden; display: flex; align-items: center; padding: 0 6px; min-width: 18px; }
.ed-sclip .ed-clip-label { font-size: 0.66rem; font-weight: 700; color: #fff; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; position: static; }
.ed-sclip.is-auto { background: repeating-linear-gradient(135deg, #b8770c 0 6px, #a06508 6px 12px); }
.ed-sclip.is-sel { box-shadow: inset 0 0 0 2px #fff, 0 0 0 2px var(--accent); z-index: 2; }
.ed-sclip.is-muted { opacity: 0.4; }
.ed-auto-panel { position: fixed; z-index: 10050; width: 340px; max-width: calc(100vw - 16px); background: var(--bg-secondary, #141a24); border: 1px solid var(--border); border-radius: 12px; box-shadow: 0 18px 50px rgba(0,0,0,0.5); padding: 12px 14px; display: grid; gap: 8px; font-size: 0.8rem; color: var(--text-primary); }
.ed-auto-panel[hidden] { display: none; }
.ed-auto-head { display: flex; justify-content: space-between; align-items: center; font-size: 0.9rem; }
.ed-auto-x { background: none; border: 0; color: var(--text-secondary); font-size: 1.2rem; cursor: pointer; }
.ed-auto-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ed-auto-row > span:first-of-type { flex: 1 1 auto; }
.ed-auto-row select, .ed-auto-row input[type=number] { background: var(--bg-tertiary); color: var(--text-primary); border: 1px solid var(--border); border-radius: 6px; padding: 3px 6px; font-size: 0.76rem; }
.ed-auto-row input[type=number] { width: 58px; }
.ed-auto-sub { padding-left: 2px; }
.ed-auto-sub input[type=range] { flex: 1 1 90px; }
.ed-auto-seg { display: inline-flex; border: 1px solid var(--border); border-radius: 999px; padding: 2px; gap: 2px; }
.ed-auto-seg button { border: 0; background: transparent; color: var(--text-secondary); border-radius: 999px; padding: 3px 9px; font-size: 0.72rem; font-weight: 600; cursor: pointer; }
.ed-auto-seg button.is-on { background: var(--accent); color: #fff; }
.ed-auto-note { color: var(--text-secondary); font-size: 0.7rem; margin-top: -4px; }
.ed-auto-scope { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; border-top: 1px solid var(--border); padding-top: 8px; }
.ed-auto-scope > span { color: var(--text-secondary); font-weight: 600; }
.ed-auto-scope label { display: inline-flex; gap: 4px; align-items: center; cursor: pointer; }
.ed-auto-actions { display: flex; justify-content: space-between; gap: 8px; }
.applied-scope { border: 1px solid var(--border); border-radius: 10px; padding: 8px 10px; margin: 0 0 10px; display: grid; gap: 2px; }
.applied-scope .toggle-control { font-size: 0.8rem; }
.applied-scope b { font-weight: 700; word-break: break-all; }
.applied-scope-hint { color: var(--text-secondary); font-size: 0.7rem; }
/* ── Desktop editor, CapCut-style pass (2026-10-04) ──────────────────────────────────────
   Floating dark panels with a gutter, a slim left tab rail (Media / Effects), a media grid,
   compact toolbar, roomy dark timeline with labelled tracks that scroll when there are many. */
.ed-shell {
  --edb: #0c0d10; --edp: #17181d; --edp2: #1f2027; --edl: #2a2b33; --edt: #e9eaee; --edm: #9a9ba6;
  background: var(--edb); gap: 6px; padding: 6px;
  grid-template-columns: minmax(320px, 23vw) 1fr minmax(320px, 24vw);
  grid-template-rows: 44px 1fr var(--ed-dock-h, 320px);
  color: var(--edt);
}
.ed-shell .ed-top { background: var(--edp); border: 1px solid var(--edl); border-radius: 10px; }
.ed-shell .ed-title { font-weight: 700; }
.ed-shell .ed-left, .ed-shell .ed-center, .ed-shell .ed-right { background: var(--edp); border: 1px solid var(--edl); border-radius: 10px; overflow: hidden; }
.ed-shell .ed-right { overflow-y: auto; }
.ed-shell .ed-right .clip-editor-tabs { background: var(--edp); }
.ed-shell .ed-center { background: #000; }
.ed-shell .ed-dock { background: var(--edp); border: 1px solid var(--edl); border-radius: 10px; overflow: hidden; }
.ed-shell .ed-toolbar { background: var(--edp2); border-bottom: 1px solid var(--edl); gap: 2px; flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
.ed-shell .ed-tool { height: 32px; color: var(--edt); border-radius: 6px; flex-direction: column; gap: 2px; padding: 3px 7px; font-size: 0.62rem; font-weight: 600; justify-content: center; }
.ed-shell .ed-tool span { white-space: nowrap; line-height: 1; }
.ed-shell .ed-tool.ed-icon-only, .ed-shell #edPlayBtn { flex-direction: row; }
.ed-shell .ed-tool svg { width: 15px; height: 15px; }
.ed-shell .ed-tool:hover:not(:disabled) { background: #2b2c35; }
.ed-shell .ed-scroll { flex: 1 1 auto; overflow-y: auto; background: #121318; }
.ed-shell .ed-ruler { background: #121318; position: sticky; top: 0; z-index: 4; border-bottom-color: var(--edl); }
.ed-shell .ed-track { margin-top: 6px; border-radius: 6px; }
.ed-shell .ed-video { height: 66px; background: #1a1b21; }
.ed-shell .ed-overlay { height: 54px; border: 1px dashed #34353f; background: #16171c; }
.ed-shell .ed-overlay.is-drop, .ed-shell .ed-track.is-drop { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 16%, #16171c); }
.ed-shell .ed-video.is-file-drop { box-shadow: inset 0 0 0 2px var(--accent); }
.ed-shell .ed-music { background: #14211a; }
.ed-shell .ed-sfx { background: #241c10; }
.ed-shell .ed-audio { background: #151b26; }
.ed-shell .ed-track-tag { position: sticky; left: 4px; top: auto; display: inline-block; margin: 4px 0 0 4px; padding: 2px 6px; border-radius: 4px; background: rgba(12,13,16,0.82); color: var(--edm); z-index: 3; font-size: 0.56rem; }
.ed-shell .ed-video > .ed-v1-tag { position: absolute; left: 4px; top: 4px; margin: 0; color: #fff; }
.ed-shell .ed-playhead { z-index: 6; }
/* Left rail: Media / Effects */
.ed-shell .ed-left { display: grid; grid-template-columns: 56px 1fr; }
.ed-left-tabs { display: flex; flex-direction: column; gap: 4px; padding: 8px 6px; border-right: 1px solid var(--edl); background: var(--edp2); }
.ed-left-tab { display: flex; flex-direction: column; align-items: center; gap: 3px; padding: 8px 2px; border: 0; border-radius: 8px; background: transparent; color: var(--edm); font: 600 0.6rem/1 var(--font-body); cursor: pointer; position: relative; }
.ed-left-tab svg { width: 18px; height: 18px; }
.ed-left-tab:hover { color: var(--edt); background: #2b2c35; }
.ed-left-tab.is-on { color: var(--edt); background: color-mix(in srgb, var(--accent) 22%, transparent); }
.ed-tab-badge { position: absolute; top: 3px; right: 4px; min-width: 14px; height: 14px; padding: 0 3px; border-radius: 999px; background: var(--accent); color: var(--on-accent, #fff); font: 700 0.52rem/14px var(--font-body); text-align: center; box-shadow: 0 0 0 2px var(--edp2); }
.ed-left-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.ed-settings-pane { min-width: 0; min-height: 0; display: flex; overflow: hidden; }
.ed-settings-pane[hidden] { display: none; }
.ed-settings-pane > aside.sidebar { position: static !important; transform: none !important; width: 100% !important; max-width: none !important; height: 100% !important; max-height: none !important; border-left: 0 !important; }
.ed-media { min-width: 0; min-height: 0; overflow-y: auto; padding: 10px; display: flex; flex-direction: column; gap: 10px; }
.ed-media[hidden] { display: none; }
.ed-media-head { display: flex; align-items: center; justify-content: space-between; gap: 6px; flex-wrap: wrap; }
.ed-media-filter { display: inline-flex; gap: 2px; background: var(--edp2); border-radius: 999px; padding: 2px; }
.ed-media-filter button { border: 0; background: transparent; color: var(--edm); font: 600 0.68rem/1 var(--font-body); padding: 5px 9px; border-radius: 999px; cursor: pointer; }
.ed-media-filter button.is-on { background: #3a3b46; color: var(--edt); }
.ed-media-import { display: flex; align-items: center; gap: 10px; width: 100%; padding: 10px 12px; border: 1.5px dashed #3a3b46; border-radius: 10px; background: var(--edp2); color: var(--edt); cursor: pointer; text-align: left; }
.ed-media-import:hover, .is-dropping .ed-media-import { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, var(--edp2)); }
.ed-media-import svg { width: 22px; height: 22px; flex: none; color: var(--accent); }
.ed-media-import span { display: grid; gap: 2px; }
.ed-media-import small { color: var(--edm); font-size: 0.68rem; }
.ed-media-import.is-big { flex-direction: column; justify-content: center; text-align: center; min-height: 160px; }
.ed-media-import.is-big svg { width: 34px; height: 34px; }
.ed-media-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); gap: 8px; }
.ed-media-item { display: grid; gap: 4px; cursor: grab; border-radius: 8px; outline: none; }
.ed-media-item:focus-visible .ed-media-thumb { box-shadow: 0 0 0 2px var(--accent); }
.ed-media-thumb { position: relative; aspect-ratio: 16 / 9; border-radius: 8px; overflow: hidden; background: #262730; display: grid; place-items: center; border: 1px solid var(--edl); }
.ed-media-thumb img { width: 100%; height: 100%; object-fit: cover; }
.ed-media-thumb svg { width: 34px; height: 34px; color: #4ade80; }
.ed-media-item.is-audio .ed-media-thumb { background: linear-gradient(135deg, #12301f, #0f1f17); }
.ed-media-item:hover .ed-media-thumb { border-color: #4a4b57; }
.ed-media-dur { position: absolute; right: 4px; bottom: 4px; padding: 1px 5px; border-radius: 4px; background: rgba(0,0,0,0.7); color: #fff; font: 600 0.6rem/1.4 'JetBrains Mono', monospace; }
.ed-media-add { position: absolute; right: 4px; top: 4px; width: 24px; height: 24px; border-radius: 50%; border: 0; background: var(--accent); color: #fff; font: 700 1rem/1 var(--font-body); cursor: pointer; opacity: 0; transform: scale(0.85); transition: opacity 0.15s, transform 0.15s; }
.ed-media-item:hover .ed-media-add, .ed-media-item:focus-within .ed-media-add { opacity: 1; transform: none; }
.ed-media-item.is-used .ed-media-thumb::after { content: 'Added'; position: absolute; left: 4px; bottom: 4px; padding: 1px 5px; border-radius: 4px; background: rgba(34,197,94,0.85); color: #fff; font: 700 0.56rem/1.4 var(--font-body); }
.ed-media-name { font-size: 0.7rem; color: var(--edm); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ed-media-hint, .ed-media-empty { color: var(--edm); font-size: 0.68rem; }
:root[data-theme="light"] .ed-shell { --edb: #e9eaee; --edp: #ffffff; --edp2: #f3f4f6; --edl: #d9dbe1; --edt: #15161a; --edm: #5d5f6b; }
:root[data-theme="light"] .ed-shell .ed-scroll, :root[data-theme="light"] .ed-shell .ed-ruler { background: #f6f7f9; }
:root[data-theme="light"] .ed-shell .ed-video, :root[data-theme="light"] .ed-shell .ed-overlay { background: #eceef2; }
/* Overlay tracks stack ABOVE the main track (V3 over V2 over V1), audio below — CapCut order. */
.ed-shell .ed-content { display: flex; flex-direction: column; }
.ed-shell .ed-content > .ed-ruler { order: 0; }
.ed-shell .ed-content > .ed-overlay { order: 1; }
.ed-shell .ed-content > .ed-video { order: 2; margin-top: 6px !important; }
.ed-shell .ed-content > .ed-music { order: 3; }
.ed-shell .ed-content > .ed-sfx { order: 4; }
.ed-shell .ed-content > .ed-audio { order: 5; }
.ed-shell .ed-content > .ed-overlay:first-of-type, .ed-shell .ed-content > .ed-ruler + .ed-overlay { margin-top: 8px; }
/* Background picker as a grouped, wrapping grid (2026-10-04). */
.tx-bgrow .tx-bgtabs { margin: 4px 0 6px; }
.tx-bgrow .tx-bgstrip { display: grid; grid-template-columns: repeat(auto-fill, minmax(92px, 1fr)); gap: 6px; overflow-x: visible; overflow-y: auto; max-height: 232px; padding: 2px; }
.tx-bgrow .tx-bgchip { width: auto; min-width: 0; }
.tx-bgrow .tx-bgchip[hidden] { display: none; }
.tx-bgrow .tx-bgchip span:last-child { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Editor context menu, lane mutes, shortcuts (2026-10-04) */
.ed-ctx { position: fixed; z-index: 10060; min-width: 220px; padding: 5px; border-radius: 10px; background: #1d1e25; border: 1px solid #30313b; box-shadow: 0 18px 44px rgba(0,0,0,0.5); display: grid; }
.ed-ctx-item { display: flex; justify-content: space-between; align-items: center; gap: 16px; padding: 7px 10px; border: 0; border-radius: 6px; background: transparent; color: #e9eaee; font: 500 0.8rem/1.2 var(--font-body); cursor: pointer; text-align: left; }
.ed-ctx-item:hover, .ed-ctx-item:focus-visible { background: var(--accent); color: #fff; outline: none; }
.ed-ctx-item kbd { font: 500 0.7rem/1 var(--font-body); color: #8d8f9b; }
.ed-ctx-item:hover kbd { color: rgba(255,255,255,0.8); }
.ed-ctx-item.is-danger { color: #ff7a7a; }
.ed-ctx-item.is-danger:hover { background: #d93a3a; color: #fff; }
.ed-ctx-sep { height: 1px; background: #30313b; margin: 4px 2px; }
.ed-lane-mute { position: sticky; left: calc(100% - 34px); float: right; margin: 4px 4px 0 0; width: 24px; height: 24px; border-radius: 6px; border: 0; background: rgba(12,13,16,0.75); color: #cfd0d8; display: grid; place-items: center; cursor: pointer; z-index: 4; }
.ed-lane-mute svg { width: 14px; height: 14px; }
.ed-lane-mute.is-muted { background: #d93a3a; color: #fff; }
.ed-music .ed-lane-mute, .ed-sfx .ed-lane-mute { position: absolute; right: 6px; left: auto; top: 50%; transform: translateY(-50%); margin: 0; }
.ed-music.is-empty .ed-lane-mute, .ed-sfx.is-empty .ed-lane-mute { display: none; }
.ed-keys-btn { width: 30px; height: 30px; border-radius: 50%; font-weight: 800; margin-left: auto; }
.ed-keys dl { display: grid; grid-template-columns: 1fr auto; gap: 7px 18px; margin: 0; font-size: 0.82rem; }
.ed-keys dt { color: var(--text-secondary); }
.ed-keys dd { margin: 0; font-family: 'JetBrains Mono', monospace; font-size: 0.74rem; text-align: right; }
.ed-keys { width: min(520px, 100%); }
.ed-shell .ed-music:not(.is-empty) > .ed-track-tag { display: none; }
/* Lyrics & subtitles menu (2026-10-04) */
/* .vx-card is injected by account.js into <head> at runtime, so it comes after this file and
   would otherwise win on width/gap; two classes keep the wider lyrics card. */
.vx-card.lyr-card { width: min(760px, 100%); gap: 14px; }
.lyr-sec { display: grid; gap: 8px; }
.lyr-h { font: 700 0.72rem/1 var(--font-body); letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-secondary); }
.lyr-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); gap: 8px; max-height: 300px; overflow-y: auto; padding: 2px; }
.lyr-tile { display: grid; gap: 5px; padding: 0; border: 0; background: none; color: var(--text-primary); cursor: pointer; text-align: left; }
.lyr-prev { aspect-ratio: 16 / 9; border-radius: 9px; display: flex; flex-direction: column; justify-content: center; gap: 2px; padding: 0 10px; overflow: hidden; border: 2px solid transparent; font-size: 15px; }
.lyr-prev b { white-space: nowrap; line-height: 1.15; transform-origin: center; }
.lyr-dim { opacity: 0.35; font-size: 11px; font-weight: 600; }
.lyr-tile.is-on .lyr-prev { border-color: var(--accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 35%, transparent); }
.lyr-tile:hover .lyr-prev { border-color: color-mix(in srgb, var(--accent) 50%, transparent); }
.lyr-name { font-size: 0.72rem; font-weight: 600; color: var(--text-secondary); }
.lyr-tile.is-on .lyr-name { color: var(--text-primary); }
.lyr-tabs { display: inline-flex; flex-wrap: wrap; gap: 4px; }
.lyr-tabs button { border: 1px solid var(--border); background: transparent; color: var(--text-secondary); border-radius: 999px; padding: 6px 12px; font: 600 0.76rem/1 var(--font-body); cursor: pointer; }
.lyr-tabs button.is-on { background: var(--accent); border-color: var(--accent); color: #fff; }
.lyr-pane { display: grid; gap: 8px; }
.lyr-pane[hidden] { display: none; }
.lyr-text { height: auto; min-height: 120px; padding: 10px 12px; font-family: var(--font-body); line-height: 1.45; resize: vertical; }
.lyr-row { display: flex; flex-wrap: wrap; gap: 10px 16px; align-items: center; font-size: 0.8rem; }
.lyr-row select, .lyr-row input[type=number] { background: var(--bg-primary); color: var(--text-primary); border: 1px solid var(--border); border-radius: 8px; padding: 5px 8px; font-size: 0.8rem; margin-left: 6px; }
.lyr-row input[type=number] { width: 64px; }
.lyr-note { color: var(--text-secondary); font-size: 0.74rem; }
.lyr-actions { display: flex; justify-content: flex-end; }
.lyr-actions .vx-primary { padding: 0 22px; }
.tx-lyrics { margin-left: 6px; }

/* Captions panel spacing (2026-10-04). The speech-to-text controls used to pile up with no
   rhythm — full-width selects, labels glued to their controls, and action buttons carrying
   their own cramped inline padding. Scoped to the Captions section so nothing else moves. */
section[data-exjump="captions"] .control-group { margin: 14px 0; }
section[data-exjump="captions"] .control-group > label:first-child { display: block; font-weight: 600; margin-bottom: 6px; color: var(--text-primary); }
section[data-exjump="captions"] select.dropdown { width: 100%; }
section[data-exjump="captions"] .row-group { display: flex; flex-wrap: wrap; gap: 8px; }
section[data-exjump="captions"] .hint-text { display: block; margin-top: 7px; line-height: 1.55; }
/* The Generate / Import / Lyrics row: equal, tappable buttons instead of 4px-padded chips.
   !important is required to win over the inline font-size/padding still on those buttons. */
section[data-exjump="captions"] #subtitlesFileGroup .row-group > .btn,
section[data-exjump="captions"] #subtitlesFileGroup .row-group > .file-upload-label {
  flex: 1 1 150px; min-height: 38px; justify-content: center;
  font-size: 12px !important; padding: 8px 14px !important;
}
section[data-exjump="captions"] .subtitles-shift-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 12px; }

/* Community section on the landing page (2026-10-04), plus a tighter small-screen rhythm.
   The homepage is one long column of sections; on a phone the fixed bottom padding on every
   section and the wide gaps added up to a lot of thumb-scrolling between things worth seeing. */
.lp-community-grid { list-style: none; margin: 0 auto; padding: 0; max-width: 1040px; display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 14px; }
.lp-community-card { margin: 0; }
.lp-community-card a { display: block; height: 100%; border: 1px solid var(--border); border-radius: 14px; overflow: hidden; background: var(--bg-secondary); color: inherit; text-decoration: none; transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease; }
.lp-community-card img { display: block; width: 100%; aspect-ratio: 16 / 10; object-fit: cover; }
.lp-community-card strong { display: block; padding: 10px 12px 2px; font-size: 0.92rem; }
.lp-community-card span { display: block; padding: 0 12px 12px; font-size: 0.78rem; line-height: 1.45; color: var(--text-secondary); }
@media (hover: hover) { .lp-community-card a:hover { transform: translateY(-2px); border-color: var(--accent); box-shadow: var(--shadow-sm); } }
.lp-community-cta { text-align: center; margin-top: 18px; }
@media (max-width: 700px) {
  .lp-section { padding-bottom: 26px; }
  .lp-section-head { margin-bottom: 18px; }
  .lp-section-head p { font-size: 0.86rem; }
  .lp-community-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  .lp-community-card strong { padding: 8px 9px 1px; font-size: 0.82rem; }
  .lp-community-card span { padding: 0 9px 9px; font-size: 0.72rem; }
  .lp-community-cta .btn { width: 100%; justify-content: center; }
}

/* Trim-on-import dialog (2026-10-04). Reuses the vx-dlg shell (the Lyrics menu / account
   dialog share it); these are just the two range rows and their actions. */
.trim-offer-row { display: grid; grid-template-columns: 46px 1fr 58px; align-items: center; gap: 10px; margin: 10px 0 0; font-size: 0.78rem; font-weight: 600; color: var(--text-secondary); }
.trim-offer-row input[type="range"] { width: 100%; }
.trim-offer-row b { font-variant-numeric: tabular-nums; text-align: right; color: var(--text-primary); }
.trim-offer-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 14px; }

/* Desktop editor polish (2026-10-04). The ed-shell skin (the CapCut-style surface the editor
   actually renders) re-declared the lane heights and dock floor AFTER the base rules, so the
   timeline enlargement had to land here too — the video lane must be at least as tall as a
   .ed-clip or every clip overhangs its track. Plus a little breathing room in the chrome. */
.ed-shell { grid-template-rows: 44px 1fr var(--ed-dock-h, 360px); }
.ed-shell .ed-track { margin-top: 8px; }
.ed-shell .ed-video { height: 80px; }
.ed-shell .ed-toolbar { gap: 4px; }
.ed-shell .ed-tool:active:not(:disabled) { background: #34353f; }
.ed-shell .ed-right { padding: 12px 14px; }
.ed-shell .ed-media { padding: 12px; }

/* Effect strength (2026-10-04) — master mix + envelope shapes at the top of Applied. */
.fx-strength { padding: 10px 12px; margin: 0 0 12px; border: 1px solid var(--border); border-radius: 12px; }
.fx-strength.is-animated .value-display::after { content: ' ◆'; color: var(--accent); font-size: .8em; }
.fx-env-shapes { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.fx-env-btn { font: inherit; font-size: 12px; padding: 6px 10px; min-height: 32px; border-radius: 999px; border: 1px solid var(--border); background: transparent; color: inherit; cursor: pointer; }
.fx-env-btn:hover { border-color: var(--accent); }
#fxStrengthHint.is-warning { color: #f5b14c; }
@media (max-width: 600px) { .fx-env-btn { min-height: 40px; padding: 8px 12px; } }

/* Mode presets (2026-10-04) — one-tap tracking looks under the video. */
.mode-preset-row { display: flex; align-items: center; gap: 10px; margin: 10px 0 0; min-width: 0; }
.mode-preset-row[hidden] { display: none; }
.mode-preset-label { flex: none; font-size: 12px; font-weight: 600; color: var(--text-secondary); letter-spacing: .02em; }
.mode-preset-chips { display: flex; gap: 6px; overflow-x: auto; scrollbar-width: none; min-width: 0; padding: 2px; -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent); mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent); }
.mode-preset-chips::-webkit-scrollbar { display: none; }
.mode-preset-chip { flex: none; display: inline-flex; align-items: center; gap: 6px; font: inherit; font-size: 12.5px; padding: 6px 12px 6px 9px; min-height: 34px; border-radius: 999px; border: 1px solid var(--border); background: var(--bg-secondary); color: var(--text-primary); cursor: pointer; transition: border-color var(--transition, .15s), background var(--transition, .15s); }
.mode-preset-chip svg { width: 18px; height: 18px; color: var(--mp-tint, currentColor); }
.mode-preset-chip:hover { border-color: var(--mp-tint, var(--accent)); }
.mode-preset-chip.is-on { border-color: var(--mp-tint, var(--accent)); background: color-mix(in srgb, var(--mp-tint, var(--accent)) 18%, var(--bg-secondary)); }
.mode-preset-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Post-FX chips carry a live thumbnail instead of an icon, so they stack. */
.fx-preset-row { margin-top: 8px; }
/* (2026-10-06) A thumbnail tile, not a pill: the frame fills it and the name sits on a shade
   along the bottom — the old pill radius turned every thumbnail into a circle. */
.fx-preset-chip { position: relative; flex-direction: column; align-items: stretch; gap: 0; padding: 0; width: 66px; height: 38px; min-height: 0; border-radius: 7px; overflow: hidden; background: #000; border: 1px solid color-mix(in srgb, var(--text-primary) 14%, transparent); }
.fx-preset-chip canvas { width: 100%; height: 100%; display: block; transition: filter .15s; }
.fx-preset-chip span { position: absolute; left: 0; right: 0; bottom: 0; padding: 9px 5px 2px; font-size: 9.5px; font-weight: 600; line-height: 1.2; letter-spacing: .01em; color: #fff; text-align: left; text-shadow: 0 1px 2px rgba(0,0,0,.6); background: linear-gradient(to bottom, transparent, rgba(0,0,0,.78)); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; pointer-events: none; }
.fx-preset-chip:hover { border-color: var(--mp-tint, var(--accent)); }
.fx-preset-chip:hover canvas { filter: brightness(1.12); }
.fx-preset-chip.is-on { border-color: var(--mp-tint, var(--accent)); box-shadow: 0 0 0 1.5px var(--mp-tint, var(--accent)); background: #000; }
.fx-chip-check { position: absolute; top: 3px; right: 3px; width: 13px; height: 13px; border-radius: 50%; background: var(--mp-tint, var(--accent)); opacity: 0; transform: scale(.6); transition: opacity .12s, transform .12s; pointer-events: none; }
.fx-chip-check::after { content: ""; position: absolute; left: 4.5px; top: 2.4px; width: 3px; height: 6px; border: solid #000; border-width: 0 1.6px 1.6px 0; transform: rotate(45deg); }
.fx-preset-chip.is-on .fx-chip-check { opacity: 1; transform: none; }
/* Group tags inside the effects row (Motion | Post FX): set on their side, so they cost ~12 px. */
.fx-group-tag { flex: none; align-self: center; writing-mode: vertical-rl; transform: rotate(180deg); font-size: 8.5px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--text-secondary); opacity: .8; user-select: none; }
.fx-group-sep { flex: none; align-self: stretch; width: 1px; margin: 3px 4px; background: var(--border); }
@media (max-width: 768px) {
  .mode-preset-row { margin: 8px 12px 0; gap: 8px; }
  .mode-preset-label { display: none; }
  /* Compact on phones (2026-10-05): one slim swipe row, so the strip covers as little of the
     picture as possible while still being a comfortable tap (≥32px tall). */
  .mode-preset-chips { gap: 5px; }
  .mode-preset-chip { min-height: 32px; padding: 4px 10px 4px 7px; gap: 4px; font-size: 12px; }
  .mode-preset-chip svg { width: 15px; height: 15px; }
}
@media (min-width: 769px) { .mode-preset-row { padding-right: 150px; } } /* clear the fixed theme/font toggles */
/* Phones (2026-10-06): both strips live in one dock under the picture, above the transport
   bar (placeModePresetRow in main.js); syncMobileChromeInsets reserves its height out of the
   canvas area, so a look is picked under the video instead of on top of it. */
.mobile-preset-dock { position: absolute; left: 8px; right: 8px; bottom: var(--mp-dock-bottom, 60px); z-index: 8; display: flex; flex-direction: column; gap: 6px; }
.mobile-preset-dock:not(:has(> .mode-preset-row:not([hidden]))) { display: none; }
.mobile-preset-dock > .mode-preset-row { margin: 0; gap: 6px; }
/* A short tag says which strip is which — the full labels are hidden on phones for room. */
.mobile-preset-dock > .mode-preset-row .mode-preset-label { display: block; flex: none; width: 34px; font-size: 9.5px; line-height: 1.15; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: rgba(255,255,255,.55); white-space: normal; }
.mobile-preset-dock .mode-preset-chips { flex: 1; scroll-snap-type: x proximity; scroll-padding-inline: 2px; }
.mobile-preset-dock .mode-preset-chip { scroll-snap-align: start; background: rgba(255,255,255,.07); border-color: rgba(255,255,255,.14); color: #fff; }
.mobile-preset-dock .mode-preset-chip.is-on { background: color-mix(in srgb, var(--mp-tint, #fff) 30%, rgba(0,0,0,.6)); border-color: var(--mp-tint, #fff); }
.mobile-preset-dock .fx-preset-chip { width: 72px; height: 38px; padding: 0; border-radius: 8px; background: #000; border-color: rgba(255,255,255,.16); }
.mobile-preset-dock .fx-preset-chip.is-on { background: #000; box-shadow: 0 0 0 2px var(--mp-tint, #fff); }
.mobile-preset-dock .fx-group-tag { color: rgba(255,255,255,.55); }
.mobile-preset-dock .fx-group-sep { background: rgba(255,255,255,.2); }
/* Just above the transport bar is now the dock, so the toast would hide the looks you are
   picking from; it drops over the top of the picture instead (under the quick-style bar). */
@media (max-width: 768px) {
  body.has-source:not(.sidebar-open):has(#mobilePresetDock .mode-preset-row:not([hidden])) #undoToast { top: calc(var(--header-h, 60px) + 92px); bottom: auto; }
  /* Same second step for the Settings nudge: with the dock up, the band above the transport
     bar is the mode/FX chips, and the nudge lifted there covered those instead (that is
     exactly what the rule above leaves free). It keeps the toast's destination — over the
     top of the picture — the one band on a phone holding no control either bubble can
     swallow. The sidebar-open guard mirrors the toast's but is belt-and-braces here: every
     phone route into the sheet runs through openSidebar(), which dismisses this nudge
     outright, so the bubble and an open sheet are never on screen together (asserted in the
     probe, not assumed). */
  body.has-source:not(.sidebar-open):has(#mobilePresetDock .mode-preset-row:not([hidden])) .settings-nudge { top: calc(var(--header-h, 60px) + 92px); bottom: auto; }
}

/* VHS tape notice (2026-10-04) — a template that tapes the whole picture says so. */
.tx-tape-notice { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin: 8px 0; padding: 10px 12px; border-radius: 10px; border: 1px solid color-mix(in srgb, #f5b14c 55%, var(--border)); background: color-mix(in srgb, #f5b14c 12%, var(--bg-secondary)); font-size: 13px; }
.tx-tape-notice[hidden] { display: none; }
.tx-tape-acts { display: inline-flex; gap: 6px; flex-wrap: wrap; }
.tx-tape-acts .btn { padding: 5px 10px; min-height: 30px; font-size: 12.5px; }
.tx-tape-keep { background: none; border: 0; color: var(--text-secondary); font: inherit; font-size: 12.5px; cursor: pointer; text-decoration: underline; }
.tx-bar-vhs { flex: none; font: 600 9px/1 ui-monospace, monospace; padding: 2px 4px; margin-right: 4px; border-radius: 4px; background: #f5b14c; color: #1a1205; letter-spacing: .04em; }
.tx-bar-vhs.is-text { background: rgba(245,177,76,.35); color: inherit; }

/* Transitions v2 (2026-10-04) — swatches and the per-transition sound chips. */
.ed-sw-whip { background: linear-gradient(90deg, #4a6 0 30%, rgba(80,160,110,.5) 40%, rgba(70,100,170,.5) 60%, #46a 70%); filter: blur(.4px); }
.ed-sw-push { background: linear-gradient(90deg, #4a6 50%, #46a 50%); box-shadow: inset 3px 0 0 rgba(255,255,255,.6); }
.ed-sw-zoom { background: radial-gradient(circle, #46a 30%, #4a6 70%); }
.ed-sw-glitch { background: repeating-linear-gradient(0deg, #4a6 0 3px, #46a 3px 5px, #e3f 5px 6px); }
.ed-sw-luma { background: linear-gradient(90deg, #4a6, #111 40%, #eee 60%, #46a); }
.ed-sw-flash { background: linear-gradient(90deg, #4a6, #fff 48% 52%, #46a); box-shadow: 0 0 6px rgba(255,255,255,.7); }
.ed-sw-burn { background: radial-gradient(circle at 70% 35%, #fff4d8, #ff9a2e 40%, #46a 85%); }
.ed-sw-swipe { background: linear-gradient(90deg, #4a6 0 42%, rgba(255,255,255,.55) 50%, #46a 58% 100%); }
.ed-sw-iris { background: radial-gradient(circle, #46a 0 30%, #12161c 32% 48%, #4a6 50% 100%); }
.ed-sw-blinds { background: repeating-linear-gradient(0deg, #4a6 0 3px, #46a 3px 6px); }
.ed-sw-spin { background: conic-gradient(from 210deg, #4a6 0 35%, #46a 50% 85%, #4a6 100%); }
.ed-sw-mosaic { background-color: #46a; background-image: linear-gradient(45deg, #4a6 25%, transparent 25% 75%, #4a6 75%), linear-gradient(45deg, #4a6 25%, transparent 25% 75%, #4a6 75%); background-size: 8px 8px; background-position: 0 0, 4px 4px; }
.ed-sw-shake { background: linear-gradient(90deg, #4a6 0 46%, #fff 48% 52%, #46a 54% 100%); box-shadow: inset 0 0 0 1px rgba(255,255,255,.35); }
.ed-trans-sfx { display: flex; flex-wrap: wrap; gap: 4px; padding: 6px 8px 8px; border-top: 1px solid var(--border); margin-top: 4px; }
.ed-trans-sfx-label { width: 100%; font-size: 0.72rem; font-weight: 600; color: var(--text-secondary); }
.ed-trans-chip { font: inherit; font-size: 0.74rem; padding: 3px 8px; border-radius: 999px; border: 1px solid var(--border); background: var(--bg-secondary); color: var(--text-primary); cursor: pointer; }
.ed-trans-chip.is-on { background: var(--accent); border-color: var(--accent); color: var(--on-accent, #fff); }
/* Transition sound + J/L menu, v2 (2026-10-05) */
.ed-trans-menu { max-height: calc(100vh - 16px); overflow-y: auto; }
.ed-trans-menu.is-wide { width: 330px; }
.ed-tsfx { display: flex; flex-direction: column; gap: 8px; margin: 6px 0 4px; padding: 8px; border-radius: 10px; background: var(--bg-secondary); }
.ed-tsfx-top { display: flex; align-items: center; justify-content: space-between; }
.ed-tsfx-top .ed-trans-head { padding: 0; }
.ed-tsfx-play { display: inline-flex; align-items: center; gap: 5px; height: 24px; padding: 0 10px; border-radius: 999px; border: 0; background: var(--accent); color: var(--on-accent, #fff); font: 600 0.72rem/1 var(--font-body); cursor: pointer; }
.ed-tsfx-play svg { width: 10px; height: 10px; }
.ed-tsfx-play:disabled { opacity: .4; cursor: default; }
.ed-tsfx-group { display: grid; grid-template-columns: 54px minmax(0, 1fr); gap: 6px; align-items: start; }
.ed-tsfx-group > span { padding-top: 4px; font-size: 0.68rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--text-secondary); }
.ed-tsfx-row { display: flex; flex-wrap: wrap; gap: 4px; }
.ed-trans-chip.is-sugg:not(.is-on) { border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); color: var(--accent); }
.ed-tsfx-slider { display: grid; grid-template-columns: 54px minmax(0, 1fr) 76px; gap: 8px; align-items: center; font-size: 0.74rem; color: var(--text-secondary); }
.ed-tsfx-slider input { width: 100%; accent-color: var(--accent); }
.ed-tsfx-slider b { font: 600 0.72rem/1 ui-monospace, monospace; color: var(--text-primary); text-align: right; font-variant-numeric: tabular-nums; }
.ed-tsfx-slider input:disabled + b { opacity: .4; }
.ed-tsfx-all { height: 28px; border-radius: 8px; border: 1px solid var(--border); background: var(--bg-primary); color: var(--text-primary); font: 600 0.74rem/1 var(--font-body); cursor: pointer; }
.ed-tsfx-all:hover { border-color: var(--accent); }
.ed-trans-blend { display: flex; align-items: center; gap: 4px; padding: 4px 8px 6px; }
.ed-trans-blend > span { margin-right: 4px; font-size: 0.72rem; font-weight: 600; color: var(--text-secondary); cursor: help; }
.ed-trans-tip { padding: 6px 8px 4px; border-top: 1px solid var(--border); margin-top: 4px; font-size: 0.68rem; line-height: 1.4; color: var(--text-secondary); }

/* Sound-cut handles on the Sound lane (2026-10-05): drag for a J or L cut. */
#edAudioTrack { position: relative; }
.ed-acut-span { position: absolute; top: 0; bottom: 0; z-index: 1; pointer-events: none; }
.ed-acut-span.is-j { background: repeating-linear-gradient(135deg, color-mix(in srgb, #bf5af2 28%, transparent) 0 6px, color-mix(in srgb, #bf5af2 12%, transparent) 6px 12px); }
.ed-acut-span.is-l { background: repeating-linear-gradient(45deg, color-mix(in srgb, #ff9f0a 28%, transparent) 0 6px, color-mix(in srgb, #ff9f0a 12%, transparent) 6px 12px); }
.ed-acut-h { position: absolute; top: 0; bottom: 0; width: 14px; margin-left: -7px; z-index: 3; cursor: ew-resize; touch-action: none; }
.ed-acut-line { position: absolute; left: 6px; top: 0; bottom: 0; width: 2px; border-radius: 1px; background: color-mix(in srgb, var(--text-primary) 35%, transparent); transition: background .12s ease, width .12s ease, left .12s ease; }
.ed-acut-h::before { content: ""; position: absolute; left: 3px; top: 50%; width: 8px; height: 14px; margin-top: -7px; border-radius: 3px; background: var(--bg-primary); box-shadow: 0 0 0 1px var(--border), 0 1px 3px rgba(0,0,0,.3); opacity: 0; transition: opacity .12s ease; }
#edAudioTrack:hover .ed-acut-h::before, .ed-acut-h.is-split::before, .ed-acut-h.is-drag::before { opacity: 1; }
.ed-acut-h:hover .ed-acut-line, .ed-acut-h.is-drag .ed-acut-line { left: 5px; width: 4px; background: var(--accent); }
.ed-acut-h.is-split .ed-acut-line { background: var(--accent); }
.ed-acut-pill { position: absolute; top: 2px; left: 12px; padding: 1px 6px; border-radius: 999px; background: var(--accent); color: var(--on-accent, #fff); font: 700 10px/1.4 ui-monospace, monospace; white-space: nowrap; pointer-events: none; }
.ed-acut-pill:empty { display: none; }
.ed-acut-h.has-blend:not(.is-split) .ed-acut-pill { background: var(--bg-primary); color: var(--text-primary); box-shadow: 0 0 0 1px var(--border); }

/* Editor overview strip (2026-10-04) — whole edit + a draggable/resizable view window. */
.ed-overview { position: relative; height: 22px; margin: 0 10px 4px; border-radius: 6px; background: color-mix(in srgb, var(--bg-secondary) 80%, #000); border: 1px solid var(--border); cursor: pointer; user-select: none; touch-action: none; overflow: hidden; }
.ed-ov-clips { position: absolute; inset: 4px 0; }
.ed-ov-clip { position: absolute; top: 0; bottom: 0; border-radius: 2px; background: color-mix(in srgb, var(--accent) 45%, transparent); }
.ed-ov-clip.is-card { background: rgba(255,255,255,.35); }
.ed-ov-ph { position: absolute; top: 0; bottom: 0; width: 2px; margin-left: -1px; background: #ff3b4a; pointer-events: none; }
.ed-ov-win { position: absolute; top: 1px; bottom: 1px; border: 1.5px solid rgba(255,255,255,.85); border-radius: 4px; background: rgba(255,255,255,.08); cursor: grab; }
.ed-ov-win:active { cursor: grabbing; }
.ed-ov-win.is-fit { border-color: rgba(255,255,255,.35); }
.ed-ov-h { position: absolute; top: -1px; bottom: -1px; width: 8px; cursor: ew-resize; }
.ed-ov-h[data-edge="l"] { left: -5px; } .ed-ov-h[data-edge="r"] { right: -5px; }
.ed-ov-h::after { content: ''; position: absolute; top: 4px; bottom: 4px; left: 3px; width: 2px; border-radius: 1px; background: #fff; }
/* Past the end of the edit: a darker band, so the headroom reads as empty space. */
.ed-content { background-image: linear-gradient(90deg, transparent var(--ed-end, 100%), rgba(0,0,0,.22) var(--ed-end, 100%)); }
/* Titles toolbar inside the full-screen editor (2026-10-04): only with a title selected. */
#edShell .tx-tl-tools.is-ed-idle { display: none; }
#edShell .tx-tl-tools.is-in-editor::before { content: 'Title'; align-self: center; margin-right: 4px; padding: 3px 8px; border-radius: 6px; font-size: 0.72rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; background: color-mix(in srgb, var(--accent) 22%, transparent); color: var(--text-primary); }
#edShell .tx-tl-tools #textZoomOut, #edShell .tx-tl-tools #textZoomIn, #edShell .tx-tl-tools #textZoomLabel, #edShell .tx-tl-tools .kf-zoom { display: none; }

/* DAW-style automation (2026-10-04): lanes under the clips, menus, LFO, control markers. */
.ed-auto-lanes { display: flex; flex-direction: column; gap: 2px; margin-top: 2px; }
.ed-auto-lanes[hidden] { display: none; }
.ed-auto-lane { position: relative; height: 48px; background: rgba(255,80,80,.05); border-top: 1px solid rgba(255,90,90,.25); }
.ed-auto-lane.has-mod { background: rgba(160,110,255,.06); border-top-color: rgba(176,132,255,.35); }
.ed-auto-lane canvas { display: block; max-height: none; object-fit: fill; }
.ed-auto-tag { position: sticky; left: 6px; position: absolute; top: 3px; left: 6px; display: inline-flex; align-items: center; gap: 4px; padding: 2px 4px 2px 8px; border-radius: 6px; background: rgba(0,0,0,.6); font-size: 0.7rem; color: #fff; pointer-events: auto; }
.ed-auto-tag b { font-weight: 600; }
.ed-auto-tag i { font-style: normal; font-size: 0.62rem; padding: 1px 5px; border-radius: 4px; background: #8a5cff; }
.ed-auto-tag button { border: 0; background: none; color: #fff; opacity: .7; cursor: pointer; font: inherit; padding: 0 4px; }
.ed-auto-tag button:hover { opacity: 1; }
.kf-auto-menu, .kf-mod-pop { position: fixed; z-index: 3000; min-width: 220px; max-width: 320px; padding: 6px; border-radius: 12px; background: var(--bg-secondary); border: 1px solid var(--border); box-shadow: 0 12px 32px rgba(0,0,0,.45); }
.kf-auto-pick { width: 300px; }
.kf-auto-pick .text-input { width: 100%; margin: 4px 0 6px; }
.kf-auto-list { max-height: 320px; overflow-y: auto; }
.kf-auto-head { padding: 4px 8px 6px; font-size: 0.75rem; font-weight: 700; color: var(--text-secondary); }
.kf-auto-group { padding: 6px 8px 2px; font-size: 0.66rem; text-transform: uppercase; letter-spacing: .05em; color: var(--text-secondary); }
.kf-auto-item { display: block; width: 100%; text-align: left; padding: 7px 10px; border: 0; border-radius: 8px; background: none; color: var(--text-primary); font: inherit; font-size: 0.82rem; cursor: pointer; }
.kf-auto-item:hover { background: color-mix(in srgb, var(--accent) 18%, transparent); }
.kf-auto-item.is-on { color: #ff6b6b; }
.kf-auto-item.is-danger { color: #ff6b6b; }
.kf-mod-pop { width: 300px; padding: 10px; }
.kf-mod-row { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 8px; }
.kf-mod-label { display: block; font-size: 0.72rem; color: var(--text-secondary); margin: 2px 0 4px; }
.kf-mod-label input { width: 100%; }
.kf-mod-chip { font: inherit; font-size: 0.76rem; padding: 4px 9px; border-radius: 999px; border: 1px solid var(--border); background: none; color: var(--text-primary); cursor: pointer; }
.kf-mod-chip.is-on { background: #8a5cff; border-color: #8a5cff; color: #fff; }
.kf-mod-chip.is-danger { color: #ff6b6b; }
/* Ableton's LED: automated controls go red, modulated ones violet. */
input[type=range].is-automated::-webkit-slider-thumb { background: #ff4d4d; box-shadow: 0 0 0 3px rgba(255,77,77,.25); }
input[type=range].is-modulated::-webkit-slider-thumb { background: #8a5cff; box-shadow: 0 0 0 3px rgba(138,92,255,.3); }
select.is-automated { box-shadow: inset 3px 0 0 #ff4d4d; }
#edAutoRecBtn.is-rec { color: #ff3b4a; }
#edAutoRecBtn.is-rec svg { animation: edRecBlink 1s steps(2) infinite; }
@keyframes edRecBlink { 50% { opacity: .35; } }
.ed-shell .ed-content > .ed-auto-lanes { order: 2; } /* right under the clips (same order as V1, later in the DOM) */

/* Node view (2026-10-05 redesign, after Apple's HIG): a calm canvas, material cards, one column
   per phase with a small tinted header, SF-Symbol-style icons in tinted tiles, switches instead of
   power buttons, and an inspector laid out as grouped inset sections. Light and dark. */
.node-view {
  --nv-bg: #f2f2f7; --nv-canvas: #ffffff; --nv-dot: rgba(60,60,67,.10); --nv-card: rgba(255,255,255,.92); --nv-card-solid: #fff;
  --nv-line: rgba(60,60,67,.14); --nv-line-strong: rgba(60,60,67,.24); --nv-tx: #1d1d1f; --nv-tx2: rgba(60,60,67,.62); --nv-tx3: rgba(60,60,67,.42);
  --nv-fill: rgba(120,120,128,.12); --nv-fill2: rgba(120,120,128,.20); --nv-blue: #007aff; --nv-shadow: 0 1px 2px rgba(0,0,0,.06), 0 6px 18px rgba(0,0,0,.06);
  --nv-shadow-hi: 0 2px 6px rgba(0,0,0,.08), 0 16px 36px rgba(0,0,0,.12); --nv-red: #ff3b30; --nv-green: #34c759; --nv-flow: rgba(0,0,0,.55);
  position: fixed; inset: 0; z-index: 2500; display: flex; flex-direction: column; background: var(--nv-bg); color: var(--nv-tx);
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Inter", var(--font-body), sans-serif; -webkit-font-smoothing: antialiased;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .node-view {
    --nv-bg: #000; --nv-canvas: #1c1c1e; --nv-dot: rgba(235,235,245,.08); --nv-card: rgba(44,44,46,.86); --nv-card-solid: #2c2c2e;
    --nv-line: rgba(84,84,88,.55); --nv-line-strong: rgba(120,120,128,.6); --nv-tx: #f5f5f7; --nv-tx2: rgba(235,235,245,.62); --nv-tx3: rgba(235,235,245,.36);
    --nv-fill: rgba(120,120,128,.24); --nv-fill2: rgba(120,120,128,.36); --nv-blue: #0a84ff; --nv-shadow: 0 1px 2px rgba(0,0,0,.4), 0 8px 22px rgba(0,0,0,.35);
    --nv-shadow-hi: 0 2px 8px rgba(0,0,0,.5), 0 18px 40px rgba(0,0,0,.5); --nv-red: #ff453a; --nv-green: #30d158; --nv-flow: #fff;
  }
}
:root[data-theme="dark"] .node-view {
  --nv-bg: #000; --nv-canvas: #1c1c1e; --nv-dot: rgba(235,235,245,.08); --nv-card: rgba(44,44,46,.86); --nv-card-solid: #2c2c2e;
  --nv-line: rgba(84,84,88,.55); --nv-line-strong: rgba(120,120,128,.6); --nv-tx: #f5f5f7; --nv-tx2: rgba(235,235,245,.62); --nv-tx3: rgba(235,235,245,.36);
  --nv-fill: rgba(120,120,128,.24); --nv-fill2: rgba(120,120,128,.36); --nv-blue: #0a84ff; --nv-shadow: 0 1px 2px rgba(0,0,0,.4), 0 8px 22px rgba(0,0,0,.35);
  --nv-shadow-hi: 0 2px 8px rgba(0,0,0,.5), 0 18px 40px rgba(0,0,0,.5); --nv-red: #ff453a; --nv-green: #30d158; --nv-flow: #fff;
}
.node-view[hidden] { display: none; }
.node-view svg { flex: none; }

/* Toolbar: a unified title bar — title and one-line hint, a legend, plain bordered buttons, close. */
.nv-top { display: flex; align-items: center; gap: 16px; padding: 12px 20px; border-bottom: 1px solid var(--nv-line); background: color-mix(in srgb, var(--nv-bg) 82%, transparent); backdrop-filter: saturate(180%) blur(20px); -webkit-backdrop-filter: saturate(180%) blur(20px); }
.nv-title { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.nv-title b { font-size: 15px; font-weight: 600; letter-spacing: -.01em; }
.nv-hint { font-size: 12px; color: var(--nv-tx2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nv-legend { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--nv-tx2); }
.nv-legend-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--nv-red); }
.nv-tools { display: flex; gap: 6px; }
.nv-tool { display: inline-flex; align-items: center; gap: 6px; height: 30px; padding: 0 12px; border-radius: 8px; border: 0; background: var(--nv-fill); color: var(--nv-tx); font: 500 13px/1 inherit; font-family: inherit; cursor: pointer; transition: background .15s ease; }
.nv-tool svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
.nv-tool:hover { background: var(--nv-fill2); }
.nv-tool.is-on { background: color-mix(in srgb, var(--nv-blue) 16%, transparent); color: var(--nv-blue); }
.nv-tool:focus-visible, .nv-close:focus-visible, .nv-node:focus-visible, .nv-phase:focus-visible, .nv-switch:focus-visible { outline: 3px solid color-mix(in srgb, var(--nv-blue) 55%, transparent); outline-offset: 2px; }
.nv-close { width: 30px; height: 30px; display: grid; place-items: center; border-radius: 50%; border: 0; background: var(--nv-fill); color: var(--nv-tx2); cursor: pointer; }
.nv-close svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; }
.nv-close:hover { background: var(--nv-fill2); color: var(--nv-tx); }

/* Body: canvas and inspector side by side. */
.nv-body { flex: 1; min-height: 0; display: grid; grid-template-columns: minmax(0, 1fr) 312px; gap: 14px; padding: 14px 18px 18px; }
.nv-canvas { position: relative; overflow: auto; border-radius: 16px; background-color: var(--nv-canvas); background-image: radial-gradient(var(--nv-dot) 1px, transparent 1.2px); background-size: 18px 18px; border: 1px solid var(--nv-line); }
.nv-wires { position: absolute; left: 0; top: 0; pointer-events: none; overflow: visible; }
.nv-wire { fill: none; stroke-width: 2; stroke-linecap: round; opacity: .85; }
.nv-flow { fill: none; stroke: var(--nv-flow); stroke-width: 2.4; stroke-linecap: round; stroke-dasharray: .1 22; opacity: .85; animation: nvflow 1.4s linear infinite; }
@keyframes nvflow { to { stroke-dashoffset: -23.5; } }
.nv-groups { position: absolute; left: 0; top: 0; right: 0; pointer-events: none; }
.nv-nodes { position: relative; }

/* Phase headers: Apple's small section titles, tinted with the phase colour. */
.nv-phase { position: absolute; top: 16px; pointer-events: auto; display: flex; align-items: center; gap: 6px; height: 24px; padding: 0 4px; border: 0; background: none; color: var(--nv-tx2); font: 600 11px/1 inherit; font-family: inherit; letter-spacing: .06em; text-transform: uppercase; cursor: pointer; text-align: left; }
.nv-phase span { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .nv-phase::before { content: ""; width: 8px; height: 8px; border-radius: 3px; background: var(--ph); }
.nv-phase small { font: 500 11px/1 inherit; font-family: inherit; letter-spacing: 0; color: var(--nv-tx3); }
.nv-phase-chev { width: 11px; height: 11px; order: 3; transition: transform .2s ease; }
.nv-phase.is-collapsed .nv-phase-chev { transform: rotate(-90deg); }
.nv-phase:hover { color: var(--nv-tx); }

/* Nodes: material cards. */
.nv-node { position: absolute; padding: 12px 12px 12px; border-radius: 14px; background: var(--nv-card); border: 1px solid var(--nv-line); color: var(--nv-tx); cursor: grab; box-shadow: var(--nv-shadow);
  backdrop-filter: saturate(180%) blur(16px); -webkit-backdrop-filter: saturate(180%) blur(16px); transition: box-shadow .2s ease, transform .2s ease, opacity .2s ease; user-select: none; }
.nv-node:hover { box-shadow: var(--nv-shadow-hi); }
.nv-node.is-selected { border-color: transparent; box-shadow: 0 0 0 2px var(--nv-blue), var(--nv-shadow-hi); }
.nv-node.is-dragging { cursor: grabbing; transform: scale(1.02); box-shadow: 0 0 0 2px var(--nv-blue), 0 24px 48px rgba(0,0,0,.25); transition: none; z-index: 6; }
.nv-head { display: flex; align-items: center; gap: 8px; }
.nv-icon { width: 28px; height: 28px; flex: none; display: grid; place-items: center; border-radius: 8px; color: #fff; background: linear-gradient(180deg, color-mix(in srgb, var(--ph) 82%, #fff), var(--ph)); box-shadow: inset 0 0 0 .5px rgba(0,0,0,.08); transition: filter .2s ease; }
.nv-icon svg { width: 16px; height: 16px; }
.nv-text { flex: 1; min-width: 0; }
.nv-text b { display: flex; align-items: center; gap: 5px; font-size: 13px; font-weight: 600; letter-spacing: -.01em; line-height: 1.2; }
.nv-sum { margin-top: 2px; font-size: 12px; color: var(--nv-tx2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nv-led { display: none; width: 7px; height: 7px; flex: none; border-radius: 50%; background: var(--nv-red); }
.nv-node.is-automated .nv-led { display: inline-block; }
/* A stage doing nothing reads as quiet: grey tile, secondary text. */
.nv-node:not(.is-on) .nv-icon { filter: grayscale(1) opacity(.55); }
.nv-node.is-bypassed { opacity: .55; }
.nv-node.is-bypassed .nv-icon { filter: grayscale(1) opacity(.45); }
.nv-node.is-bypassed .nv-sum::before { content: "Off · "; }
.nv-preview { display: block; width: 100%; height: auto; aspect-ratio: 16 / 9; margin-top: 10px; border-radius: 9px; background: #000; object-fit: cover; }
/* Ports: small rings in the phase colour on the middle of the top and bottom edges. */
.nv-port { position: absolute; left: 50%; width: 10px; height: 10px; margin-left: -5px; border-radius: 50%; background: var(--nv-card-solid); border: 2px solid var(--ph); pointer-events: none; }
.nv-port-in { top: -6px; } .nv-port-out { bottom: -6px; }
.nv-node[data-key="source"] .nv-port-in, .nv-node.is-output .nv-port-out { display: none; }
.nv-del { position: absolute; top: -8px; right: -8px; width: 20px; height: 20px; display: grid; place-items: center; border-radius: 50%; border: 0; background: var(--nv-card-solid); color: var(--nv-tx2); box-shadow: 0 0 0 1px var(--nv-line), 0 2px 6px rgba(0,0,0,.15); cursor: pointer; opacity: 0; transform: scale(.85); transition: opacity .15s ease, transform .15s ease; }
.nv-del svg { width: 9px; height: 9px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; }
.nv-node:hover .nv-del, .nv-node:focus-within .nv-del { opacity: 1; transform: none; }
.nv-del:hover { color: var(--nv-red); }

/* Switch: the iOS control (51×31, here scaled to 38×22 for a dense canvas). */
.nv-switch { position: relative; flex: none; width: 34px; height: 20px; padding: 0; border: 0; border-radius: 999px; background: var(--nv-fill2); cursor: pointer; transition: background .2s ease; }
.nv-switch i { position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.25), 0 0 0 .5px rgba(0,0,0,.04); transition: transform .22s cubic-bezier(.3,.7,.4,1); }
.nv-switch[aria-checked="true"] { background: var(--nv-green); }
.nv-switch[aria-checked="true"] i { transform: translateX(14px); }
.nv-switch:disabled { opacity: .4; cursor: default; }

/* Stages menu: a macOS menu — rounded, translucent, check rows. */
.nv-menu { position: absolute; z-index: 20; min-width: 220px; padding: 6px; border-radius: 12px; background: color-mix(in srgb, var(--nv-card-solid) 88%, transparent); backdrop-filter: saturate(180%) blur(24px); -webkit-backdrop-filter: saturate(180%) blur(24px); border: 1px solid var(--nv-line); box-shadow: 0 12px 40px rgba(0,0,0,.22); color: var(--nv-tx); }
.nv-menu[hidden] { display: none; }
.nv-menu-head { padding: 6px 8px 2px; font: 600 12px/1.2 inherit; font-family: inherit; color: var(--nv-tx2); }
.nv-menu-hint { padding: 0 8px 6px; font-size: 11px; color: var(--nv-tx3); }
.nv-menu-row { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 6px; font-size: 13px; cursor: pointer; }
.nv-menu-row:hover { background: var(--nv-blue); color: #fff; }
.nv-menu-row input { accent-color: var(--nv-blue); margin: 0; }

/* Inspector: grouped inset sections, like System Settings. */
.nv-insp { min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 14px; padding: 14px; border-radius: 16px; background: var(--nv-canvas); border: 1px solid var(--nv-line); color: var(--nv-tx); }
.nv-big { display: block; width: 100%; height: auto; aspect-ratio: 16 / 9; border-radius: 10px; background: #000; object-fit: cover; }
.nv-scope { display: grid; grid-template-columns: auto 1fr 1fr; align-items: center; gap: 2px; padding: 2px; border-radius: 9px; background: var(--nv-fill); font-size: 12px; }
.nv-scope[hidden] { display: none; }
.nv-scope span { padding: 0 8px; color: var(--nv-tx2); }
.nv-scope button { height: 26px; border-radius: 7px; border: 0; background: none; color: var(--nv-tx); font: 500 12px/1 inherit; font-family: inherit; cursor: pointer; transition: background .15s ease, box-shadow .15s ease; }
.nv-scope button.is-on { background: var(--nv-card-solid); box-shadow: 0 1px 3px rgba(0,0,0,.14), 0 0 0 .5px rgba(0,0,0,.05); font-weight: 600; }
.nv-insp-head { display: flex; align-items: center; gap: 10px; }
.nv-insp-icon { width: 36px; height: 36px; flex: none; display: grid; place-items: center; border-radius: 9px; color: #fff; background: linear-gradient(180deg, color-mix(in srgb, var(--ph, #8e8e93) 82%, #fff), var(--ph, #8e8e93)); }
.nv-insp-icon svg { width: 20px; height: 20px; }
.nv-insp-title { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.nv-insp-title b { font-size: 17px; font-weight: 600; letter-spacing: -.01em; }
.nv-insp-title small { font-size: 12px; color: var(--nv-tx2); }
.nv-insp-bypass[hidden] { display: none; }
.nv-params { display: flex; flex-direction: column; border-radius: 12px; background: var(--nv-bg); overflow: hidden; }
.nv-param { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 6px 10px; align-items: center; padding: 10px 12px; font-size: 13px; position: relative; }
.nv-param + .nv-param::before { content: ""; position: absolute; left: 12px; right: 0; top: 0; height: 1px; background: var(--nv-line); }
.nv-param > [data-for] { grid-column: 1 / -1; width: 100%; }
.nv-param.is-check { grid-template-columns: minmax(0, 1fr) auto; }
.nv-param.is-check > [data-for] { grid-column: 2; grid-row: 1; width: auto; accent-color: var(--nv-green); }
.nv-param.is-check .nv-param-name { grid-column: 1; grid-row: 1; }
.nv-param.is-check .nv-param-val { display: none; }
.nv-param-name { color: var(--nv-tx); }
.nv-param-val { font: 500 12px/1 ui-monospace, "SF Mono", monospace; color: var(--nv-tx2); font-variant-numeric: tabular-nums; }
.nv-param.is-automated .nv-param-name::after { content: " ●"; color: var(--nv-red); }
.nv-param.is-modulated .nv-param-name::after { content: " ∿"; color: #af52de; }
.nv-param select { width: 100%; height: 30px; border-radius: 8px; border: 1px solid var(--nv-line); background: var(--nv-card-solid); color: var(--nv-tx); font: 13px/1 inherit; font-family: inherit; padding: 0 8px; }
.nv-param input[type=range] { accent-color: var(--nv-blue); }
.nv-empty { margin: 0; padding: 12px; font-size: 13px; color: var(--nv-tx2); }
.nv-params .nv-open-titles { margin: 0 12px 12px; }
.nv-insp-more { margin-top: auto; height: 34px; border-radius: 9px; border: 0; background: var(--nv-fill); color: var(--nv-blue); font: 500 13px/1 inherit; font-family: inherit; cursor: pointer; }
.nv-insp-more:hover { background: var(--nv-fill2); }
@media (prefers-reduced-motion: reduce) { .nv-flow { animation: none; display: none; } .nv-node, .nv-switch i, .nv-phase-chev { transition: none; } }
@media (max-width: 900px) { .nv-body { grid-template-columns: 1fr; grid-template-rows: auto 1fr; padding: 10px; } .nv-insp { order: -1; max-height: 50vh; } .nv-hint, .nv-legend { display: none; } .nv-tool { padding: 0 9px; } }

/* Lyrics tap-along bar (2026-10-04) */
.lyr-tap { position: fixed; left: 50%; top: 84px; transform: translateX(-50%); z-index: 2600; width: min(720px, calc(100vw - 24px)); padding: 12px 14px; border-radius: 16px; background: rgba(12,14,20,.92); border: 1px solid var(--border); box-shadow: 0 18px 40px rgba(0,0,0,.5); color: #fff; }
.lyr-tap-lines { display: flex; flex-direction: column; gap: 4px; margin-bottom: 10px; }
.lyr-tap-cur { font-size: 1.05rem; font-weight: 700; }
.lyr-tap-next { font-size: 0.82rem; color: #aab3c2; }
.lyr-tap-acts { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.lyr-tap-count { font: 600 0.8rem/1 ui-monospace, monospace; color: #aab3c2; margin-right: auto; }
.lyr-tap-snap { font-size: 0.8rem; color: #cfd6e2; display: inline-flex; gap: 5px; align-items: center; }
.lyr-tap-go { min-width: 140px; }
@media (max-width: 768px) { .lyr-tap { top: 70px; } .lyr-tap-go { flex: 1 1 100%; min-height: 48px; font-size: 1rem; } }

.lyr-tap .vx-secondary, .lyr-tap .vx-primary { font: 600 0.85rem/1 var(--font-body); padding: 10px 14px; border-radius: 10px; cursor: pointer; border: 1px solid #39404f; background: #1b1f29; color: #fff; }
.lyr-tap .vx-primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent, #fff); }


/* Compact phone settings sheet (2026-10-05) */
.sidebar-search-toggle { display: none; }
@media (max-width: 768px) {
  .sidebar { padding-top: 14px; }
  .sidebar.content-expanded { max-height: min(40dvh, calc(100dvh - var(--header-h) - var(--bottom-nav-h) - 16px)); }
  .sidebar:not(.search-open) .sidebar-search-wrap { display: none; }
  .sidebar-search-toggle { display: inline-grid; place-items: center; flex: none; width: 34px; height: 34px; margin-left: 2px; border: 0; border-radius: 9px; background: transparent; color: var(--text-secondary); position: sticky; right: 0; }
  .sidebar-search-toggle svg { width: 18px; height: 18px; }
  .sidebar.search-open .sidebar-search-toggle { color: var(--accent); }
  .sidebar-tabs .sidebar-tab { min-height: 34px; padding-top: 0; padding-bottom: 0; font-size: 0.82rem; }
  .sidebar .control-section > h3 { padding-top: 10px; padding-bottom: 10px; }
}
@media (max-width: 768px) {
  .sidebar-search-toggle { background: var(--bg-secondary); box-shadow: -10px 0 10px -4px var(--bg-secondary); }
  /* The mode chips above already pick the mode — the dropdown under them repeats it. */
  .sidebar .control-group:has(> #modeSelect), .sidebar .control-group:has(> label + #modeSelect) { display: none; }
}

/* ═══ Apple-style settings & menus (2026-10-05) ═══════════════════════════════════════════
   Quieter, grouped, iOS/macOS-settings feel: sentence-case labels, a segmented tab control,
   grouped rounded cards per section, iOS switches, thin sliders with a white thumb, plain
   tabular values, info dots only on hover, frosted pop-up menus. Theme-agnostic: every fill
   is mixed from --text-primary, so light and dark both work. */
.sidebar {
  --ui-fill: color-mix(in srgb, var(--text-primary) 7%, transparent);
  --ui-fill-2: color-mix(in srgb, var(--text-primary) 11%, transparent);
  --ui-group: color-mix(in srgb, var(--text-primary) 3.5%, transparent);
  --ui-hair: color-mix(in srgb, var(--text-primary) 9%, transparent);
  font-feature-settings: 'tnum' 1, 'cv11' 1;
}
/* Segmented control tabs */
.sidebar .sidebar-tabs { gap: 2px; padding: 3px; border-radius: 11px; background: var(--ui-fill); margin-bottom: 14px; }
.sidebar .sidebar-tab { flex-direction: row; gap: 5px; padding: 7px 4px; border: 0; border-radius: 8px; background: transparent; font-size: 0.72rem; font-weight: 600; text-transform: none; letter-spacing: 0; color: var(--text-secondary); }
.sidebar .sidebar-tab .tab-icon { width: 14px; height: 14px; }
.sidebar .sidebar-tab:hover { background: transparent; color: var(--text-primary); }
.sidebar .sidebar-tab.active, .sidebar .sidebar-tab.active:hover { background: var(--bg-primary); color: var(--text-primary); box-shadow: 0 1px 3px rgba(0,0,0,.18), 0 0 0 0.5px var(--ui-hair); }
@media (min-width: 769px) and (max-width: 1500px) { .sidebar .sidebar-tab .tab-icon { display: none; } }
/* Modes: quiet chips, tinted when chosen, key hint only on hover */
.sidebar .mobile-mode-tabs { gap: 5px; }
.sidebar .mode-tab { text-transform: none; letter-spacing: 0; font-size: 0.78rem; font-weight: 550; padding: 7px 10px; border-radius: 9px; background: var(--ui-fill); color: var(--text-primary); }
.sidebar .mode-tab:hover { background: var(--ui-fill-2); }
.sidebar .mode-tab.active { background: color-mix(in srgb, var(--accent) 20%, transparent); color: var(--accent); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent); }
.sidebar .mode-tab-key { opacity: 0; transition: opacity .15s; }
.sidebar .mode-tab:hover .mode-tab-key { opacity: .5; }
/* Grouped sections */
.sidebar .control-section { background: var(--ui-group); border: 0; border-radius: 14px; padding: 12px 14px 14px; margin-bottom: 10px; gap: 10px; }
.sidebar .control-section.collapsed { padding-bottom: 12px; }
.sidebar .control-section h3 { text-transform: none; letter-spacing: -0.01em; font-size: 0.92rem; font-weight: 650; color: var(--text-primary); }
.sidebar .control-section h3::after { color: var(--text-secondary); font-weight: 400; }
/* Labels & values */
.sidebar .control-group > label, .sidebar .label-row > label { font-size: 0.8rem; font-weight: 500; color: var(--text-primary); }
.sidebar .value-display { background: none !important; border: 0 !important; padding: 0 !important; font: 500 0.78rem/1 var(--font-body); font-variant-numeric: tabular-nums; color: var(--text-secondary); min-width: 0; }
.sidebar .hint-text { color: var(--text-secondary); font-size: 0.74rem; line-height: 1.4; }
/* Info dots: there when you look for them */
@media (hover: hover) {
  .sidebar .hint-dot { opacity: 0; transition: opacity .15s; }
  .sidebar .control-group:hover .hint-dot, .sidebar .label-row:hover .hint-dot, .sidebar .toggle-control:hover .hint-dot, .sidebar .hint-dot:focus-visible { opacity: .6; }
}
/* Selects & text inputs: iOS fill, no outline box */
.sidebar select.dropdown, .sidebar .text-input, .sidebar input[type=text], .sidebar input[type=search] { background-color: var(--ui-fill); border: 0; border-radius: 9px; box-shadow: none; }
.sidebar select.dropdown:hover { background-color: var(--ui-fill-2); }
.sidebar select.dropdown:focus-visible, .sidebar .text-input:focus-visible { outline: 2px solid color-mix(in srgb, var(--accent) 60%, transparent); outline-offset: 1px; }
/* iOS switch for every toggle */
.sidebar .toggle-control { font-weight: 500; font-size: 0.8rem; }
.sidebar .control-indicator { width: 34px; height: 20px; border-radius: 999px; border: 0; background: var(--ui-fill-2); transition: background-color .2s ease; }
.sidebar .control-indicator::after { top: 2px; left: 2px; width: 16px; height: 16px; border: 0; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.3); transform: none; opacity: 1; transition: transform .22s cubic-bezier(.3,1.3,.5,1); }
.sidebar .toggle-control input:checked ~ .control-indicator { background: #34c759; }
.sidebar .toggle-control input:checked ~ .control-indicator::after { transform: translateX(14px); }
.sidebar .toggle-control:hover .control-indicator { border-color: transparent; }
/* Sliders: hairline track, white thumb */
.sidebar .slider::-webkit-slider-runnable-track { height: 4px; }
.sidebar .slider::-webkit-slider-thumb { width: 18px; height: 18px; margin-top: -7px; background: #fff; border: 0; box-shadow: 0 1px 4px rgba(0,0,0,.35), 0 0 0 0.5px rgba(0,0,0,.12); }
.sidebar .slider.is-automated::-webkit-slider-thumb { background: #ff4d4d; }
.sidebar .slider.is-modulated::-webkit-slider-thumb { background: #8a5cff; }
/* Frosted pop-up menus (automation, transitions, node & picker menus) */
.kf-auto-menu, .kf-mod-pop, .ed-trans-menu, .pv-picker-card, .opt-picker-pop {
  background: color-mix(in srgb, var(--bg-primary) 72%, transparent) !important;
  -webkit-backdrop-filter: saturate(180%) blur(24px); backdrop-filter: saturate(180%) blur(24px);
  border: 0.5px solid color-mix(in srgb, var(--text-primary) 14%, transparent) !important;
  border-radius: 13px !important;
  box-shadow: 0 12px 40px rgba(0,0,0,.35), 0 0 0 0.5px rgba(0,0,0,.2) !important;
}
.kf-auto-item, .ed-trans-opt { border-radius: 7px; font-size: 0.82rem; padding: 6px 10px; }
.kf-auto-item:hover, .ed-trans-opt:hover { background: var(--accent) !important; color: var(--on-accent, #fff) !important; }
.kf-auto-head, .ed-trans-head { font-size: 0.7rem; font-weight: 600; color: var(--text-secondary); text-transform: none; letter-spacing: 0; }
/* Phones: same language, a touch larger targets */
@media (max-width: 768px) {
  .sidebar .control-section { border-radius: 12px; padding: 10px 12px 12px; }
  .sidebar .mode-tab { padding: 6px 11px; }
  .sidebar .control-indicator { width: 40px; height: 24px; }
  .sidebar .control-indicator::after { width: 20px; height: 20px; }
  .sidebar .toggle-control input:checked ~ .control-indicator::after { transform: translateX(16px); }
}
.sidebar .sidebar-tab { font-size: 0.8rem; }
.sidebar .mode-tab.active .mode-tab-key { opacity: 0; }
.sidebar .mode-tab.active:hover .mode-tab-key { opacity: .5; }
/* App-wide: the iOS switch for every toggle (2026-10-05), not just the settings panel. */
.toggle-control .control-indicator { width: 34px; height: 20px; border-radius: 999px; border: 0; background: color-mix(in srgb, var(--text-primary) 14%, transparent); transition: background-color .2s ease; }
.toggle-control .control-indicator::after { top: 2px; left: 2px; width: 16px; height: 16px; border: 0; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.3); transform: none; opacity: 1; transition: transform .22s cubic-bezier(.3,1.3,.5,1); }
.toggle-control input:checked ~ .control-indicator { background: #34c759; border-color: transparent; }
.toggle-control input:checked ~ .control-indicator::after { transform: translateX(14px); }
.toggle-control:hover .control-indicator { border-color: transparent; }
/* Frosted picker (Post FX, grain, resolution…) */
.pv-picker { background: color-mix(in srgb, var(--bg-primary) 74%, transparent) !important; -webkit-backdrop-filter: saturate(180%) blur(26px); backdrop-filter: saturate(180%) blur(26px); border: 0.5px solid color-mix(in srgb, var(--text-primary) 14%, transparent) !important; border-radius: 16px !important; box-shadow: 0 18px 50px rgba(0,0,0,.4) !important; }
.pv-picker.is-sheet { border-radius: 18px 18px 0 0 !important; }
.pv-picker .pv-opt { border: 0 !important; border-radius: 11px !important; background: color-mix(in srgb, var(--text-primary) 6%, transparent) !important; font-weight: 550; }
.pv-picker .pv-opt:hover { background: color-mix(in srgb, var(--text-primary) 11%, transparent) !important; }
.pv-picker .pv-opt.is-on { background: color-mix(in srgb, var(--accent) 22%, transparent) !important; box-shadow: inset 0 0 0 1.5px var(--accent) !important; }
.sidebar .sidebar-tabs .sidebar-tab, .sidebar .sidebar-tabs .sidebar-tab span { font-size: 0.8rem; }
/* The mode chips belong to Tracking; elsewhere they were a block of noise above every tab. */
.sidebar[data-active-tab]:not([data-active-tab="tracking"]) .mobile-mode-tabs { display: none !important; }
.sidebar .sidebar-tabs .sidebar-tab { flex: 1 1 auto; min-width: max-content; padding: 6px 7px; font-size: 0.76rem; }
.sidebar .sidebar-tabs .sidebar-tab span { font-size: 0.76rem; overflow: visible; }

/* CapCut-style editor panes (2026-10-05) */
.ed-shell #mobileQuickStyleBar { display: none !important; }
.ed-lib { display: flex; flex-direction: column; gap: 8px; padding: 12px; overflow-y: auto; min-height: 0; }
.ed-lib[hidden] { display: none; }
.ed-lib-head { display: flex; flex-direction: column; gap: 2px; }
.ed-lib-head b { font-size: 0.95rem; }
.ed-lib-head small, .ed-props-h small { color: var(--text-secondary); font-size: 0.74rem; }
.ed-lib-h, .ed-props-sub { margin-top: 6px; font-size: 0.72rem; font-weight: 600; color: var(--text-secondary); }
.ed-lib-chips { display: flex; flex-wrap: wrap; gap: 5px; }
.ed-lib-chip { font: inherit; font-size: 0.76rem; font-weight: 550; padding: 5px 10px; border-radius: 8px; border: 0; background: color-mix(in srgb, var(--text-primary) 7%, transparent); color: var(--text-primary); cursor: pointer; }
.ed-lib-chip:hover { background: color-mix(in srgb, var(--text-primary) 12%, transparent); }
.ed-lib-chip.is-on { background: color-mix(in srgb, var(--accent) 22%, transparent); color: var(--accent); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 50%, transparent); }
.ed-lib-chip:disabled, .ed-lib-card:disabled { opacity: .4; cursor: default; }
.ed-lib-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(98px, 1fr)); gap: 6px; }
.ed-lib-card { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; min-height: 58px; padding: 8px; border-radius: 10px; border: 0; background: color-mix(in srgb, var(--text-primary) 6%, transparent); color: var(--text-primary); font: inherit; text-align: left; cursor: pointer; }
.ed-lib-card b { font-size: 0.76rem; font-weight: 600; line-height: 1.2; }
.ed-lib-card small { font-size: 0.66rem; color: var(--text-secondary); }
.ed-lib-card:hover { background: color-mix(in srgb, var(--text-primary) 11%, transparent); }
.ed-lib-card.is-on { background: color-mix(in srgb, var(--accent) 20%, transparent); box-shadow: inset 0 0 0 1.5px var(--accent); }
.ed-lib-sw { width: 18px; height: 18px; border-radius: 5px; background: var(--sw, var(--accent)); }
.ed-lib-card .ed-trans-swatch { width: 100%; height: 22px; border-radius: 5px; }
.ed-lib-more { margin-top: 6px; padding: 8px; border-radius: 9px; border: 0; background: none; color: var(--accent); font: 600 0.78rem/1 var(--font-body); cursor: pointer; text-align: left; }
.ed-props { padding: 12px; margin-bottom: 8px; border-bottom: 1px solid var(--border); display: flex; flex-direction: column; gap: 8px; }
.ed-props:empty { display: none; }
.ed-props-h { display: flex; flex-direction: column; gap: 1px; }
.ed-props-h b { font-size: 0.95rem; }
.ed-props-row { display: grid; grid-template-columns: 110px 1fr; align-items: center; gap: 8px; font-size: 0.78rem; }
.ed-props-row select { background: color-mix(in srgb, var(--text-primary) 7%, transparent); border: 0; border-radius: 8px; }
.ed-props-grid { display: grid; grid-template-columns: auto 1fr auto 1fr; gap: 4px 10px; font-size: 0.76rem; }
.ed-props-grid span { color: var(--text-secondary); }
.ed-props-grid b { font-weight: 600; font-variant-numeric: tabular-nums; }
.ed-props-tip { color: var(--text-secondary); font-size: 0.72rem; }
.ed-props-actions { display: flex; gap: 6px; flex-wrap: wrap; }
.ed-props-actions button { flex: 1; padding: 7px 8px; border-radius: 8px; border: 0; background: color-mix(in srgb, var(--text-primary) 7%, transparent); color: var(--text-primary); font: 600 0.74rem/1 var(--font-body); cursor: pointer; }
.ed-props-actions button:hover { background: color-mix(in srgb, var(--text-primary) 12%, transparent); }
.ed-props-actions .is-danger { color: #ff6b6b; }
.ed-props-actions button[disabled] { opacity: 0.45; cursor: default; }
/* Per-clip sound sliders: label, slider, value — the same three-column feel as the track rows. */
.ed-audio-row { display: grid; grid-template-columns: 64px 1fr 40px; align-items: center; gap: 8px; font-size: 0.74rem; }
.ed-audio-row span { color: var(--text-secondary); }
.ed-audio-row b { font-weight: 600; font-variant-numeric: tabular-nums; text-align: right; }
.ed-audio-row input[type="range"] { width: 100%; }
/* A muted clip reads as muted on the timeline, not just in the Properties card. */
.ed-clip.is-muted { opacity: 0.6; }
.ed-clip.is-muted .ed-clip-strip { filter: grayscale(0.7) brightness(0.72); }
.ed-clip.is-muted .ed-clip-label { font-style: italic; }
.ed-shell #modePresetRow { display: none !important; } /* the Looks pane carries these in the editor */
.sidebar #echoSection.is-live > h3::before { content: "●"; color: #34c759; font-size: 0.6rem; margin-right: 6px; }

/* Header → pill on scroll (2026-10-05). The page header condenses into a floating frosted pill
   once the page scrolls (site_nav.py's script toggles .is-pill). */
.lp-page-header { transition: width .4s cubic-bezier(.2,.8,.2,1), top .4s cubic-bezier(.2,.8,.2,1), border-radius .4s cubic-bezier(.2,.8,.2,1), box-shadow .4s ease, background-color .4s ease, padding .4s ease; }
.lp-page-header.is-pill {
  top: 10px; width: min(1080px, calc(100% - 24px)); margin-left: auto; margin-right: auto;
  border-radius: 999px; padding-left: 14px; padding-right: 10px;
  background: color-mix(in srgb, var(--bg-primary) 70%, transparent) !important;
  -webkit-backdrop-filter: saturate(180%) blur(22px); backdrop-filter: saturate(180%) blur(22px);
  border: 0.5px solid color-mix(in srgb, var(--text-primary) 14%, transparent) !important;
  box-shadow: 0 10px 32px rgba(0,0,0,.22);
}
.lp-page-header.is-pill .header-logo { width: 30px; height: 30px; }
@media (max-width: 768px) { .lp-page-header.is-pill { width: calc(100% - 16px); top: 8px; } }
/* App header, left side (2026-10-05): logo + wordmark only — the tagline and three badges
   crowded it (the landing pages carry that message). */
header.glass-panel .header-byline { display: none; }
/* Community looks (2026-10-05): media with hover-play, a clear "Try this look". */
.lp-community-card a { display: flex; flex-direction: column; border: 0; border-radius: 18px; background: color-mix(in srgb, var(--text-primary) 4%, transparent); box-shadow: 0 0 0 0.5px color-mix(in srgb, var(--text-primary) 12%, transparent); }
.lp-community-card a:hover { transform: translateY(-3px); box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 50%, transparent), 0 14px 34px rgba(0,0,0,.28); }
.lp-cc-media { position: relative; display: block; aspect-ratio: 16 / 10; overflow: hidden; }
.lp-cc-media img, .lp-cc-media video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.lp-cc-media video { opacity: 0; transition: opacity .3s ease; }
.lp-cc-media video.is-on { opacity: 1; }
.lp-community-card span:not(.lp-cc-media) { display: block; padding: 0 14px; color: var(--text-secondary); font-size: 0.82rem; }
.lp-community-card strong { padding: 12px 14px 2px; font-size: 0.98rem; }
.lp-cc-go { display: block; margin-top: auto; padding: 10px 14px 14px; font-style: normal; font-size: 0.8rem; font-weight: 600; color: var(--accent); }
.lp-community-cta { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }

/* ── Title timing card (2026-10-05): "On screen" — start / end / length, each with steps and a
   set-to-playhead button, and a live readout of where the playhead is relative to the title. */
.tx-inspector .tx-row.tx-timing { display: block; padding: 10px 12px 12px; border: 1px solid var(--border); border-radius: 12px; background: var(--bg-secondary); }
.tx-timing-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.tx-timing-title { font-weight: 700; font-size: 0.8rem; color: var(--text-primary); }
.tx-timing-ph { flex: 1 1 auto; min-width: 0; font-size: 0.72rem; color: var(--text-secondary); font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tx-timing-ph.is-in { color: var(--accent); }
.tx-timing-head .tx-chip { flex: 0 0 auto; }
.tx-timing-edges { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
.tx-tedge { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.tx-tedge > label { font-size: 0.68rem; font-weight: 600; color: var(--text-secondary); }
.tx-tstep { display: flex; align-items: stretch; height: 34px; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; background: var(--bg-primary, transparent); }
.tx-tstep button { flex: 0 0 28px; border: 0; background: transparent; color: var(--text-primary); font-size: 1.05rem; line-height: 1; cursor: pointer; }
.tx-tstep button:hover { background: color-mix(in srgb, var(--accent) 14%, transparent); }
.tx-tstep button:active { background: color-mix(in srgb, var(--accent) 26%, transparent); }
.tx-inspector .tx-tstep input.text-input { flex: 1 1 auto; min-width: 0; height: auto; border: 0; border-radius: 0; border-left: 1px solid var(--border); border-right: 1px solid var(--border); text-align: center; font-variant-numeric: tabular-nums; padding: 0 4px; background: transparent; }
.tx-tset { height: 28px; padding: 0 8px; border-radius: 7px; border: 1px solid var(--border); background: transparent; color: var(--text-primary); font-size: 0.7rem; font-weight: 600; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tx-tset:hover { border-color: var(--accent); color: var(--accent); }
.tx-tlen-btns { display: flex; gap: 5px; }
.tx-tlen-btns .tx-tset { flex: 1 1 0; min-width: 0; }
@media (max-width: 560px) {
  .tx-timing-edges { grid-template-columns: 1fr 1fr; }
  .tx-tedge.tx-tlen { grid-column: 1 / -1; }
  .tx-tstep { height: 40px; }
  .tx-tstep button { flex-basis: 36px; }
  .tx-tset { height: 34px; }
  .tx-timing-ph { white-space: normal; }
}
/* Entrance / exit grips on the selected title's bar (2026-10-05): a slim handle where the shaded
   animation zone ends — drag it to change how long the entrance / exit takes. */
.tx-bar-az { position: absolute; top: 3px; bottom: 3px; width: 8px; margin-left: -4px; cursor: ew-resize; z-index: 2; touch-action: none; }
.tx-bar-az.is-in { left: var(--tx-in, 0%); }
.tx-bar-az.is-out { left: calc(100% - var(--tx-out, 0%)); }
.tx-bar-az::after { content: ''; position: absolute; left: 3px; top: 2px; bottom: 2px; width: 2px; border-radius: 2px; background: currentColor; opacity: 0.55; }
.tx-bar-az:hover::after, .tx-bar-az.is-dragging::after { opacity: 1; width: 3px; left: 2.5px; }
@media (pointer: coarse) { .tx-bar-az { width: 18px; margin-left: -9px; } .tx-bar-az::after { left: 8px; } }
/* Presets within presets (2026-10-05): the Variations strip under a template just added. */
.tx-variants { flex-basis: 100%; display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding: 8px 10px; margin: 2px 0 6px; border-radius: 12px; border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--border)); background: color-mix(in srgb, var(--accent) 7%, var(--bg-secondary)); }
.tx-variants-cap { flex-basis: 100%; font: 700 0.62rem/1.2 var(--font-body, inherit); letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-secondary); }
.tx-variant { padding: 7px 12px; border-radius: 999px; border: 1px solid var(--border); background: var(--bg-primary, transparent); color: var(--text-primary); font-size: 0.78rem; cursor: pointer; }
.tx-variant:hover { border-color: var(--accent); }
.tx-variant.is-on { background: var(--accent); border-color: var(--accent); color: var(--on-accent, #fff); }
.tx-variants-close { margin-left: auto; width: 26px; height: 26px; border-radius: 50%; border: 1px solid var(--border); background: transparent; color: var(--text-secondary); cursor: pointer; }
.tx-templates.is-tabbed .tx-variants { display: flex; }
.tx-templates.is-tabbed .tx-variants { grid-column: 1 / -1; }
/* Font flip card (2026-10-05): set tiles in their own faces, per-font chips, speed / look / sound. */
.tx-inspector .tx-row.tx-flipcard { display: block; padding: 10px 12px 12px; border: 1px solid var(--border); border-radius: 12px; background: var(--bg-secondary); }
.tx-flipcard [hidden] { display: none !important; }
.tx-flip-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.tx-flip-head b { font-size: 0.82rem; }
.tx-flip-state { flex: 1; font-size: 0.72rem; color: var(--text-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tx-flip-sets { display: grid; grid-auto-flow: column; grid-auto-columns: 92px; gap: 6px; overflow-x: auto; padding-bottom: 4px; scrollbar-width: thin; }
.tx-flip-set { display: flex; flex-direction: column; align-items: stretch; gap: 4px; padding: 6px; border-radius: 10px; border: 1px solid var(--border); background: var(--bg-primary, transparent); color: var(--text-primary); cursor: pointer; }
.tx-flip-set:hover { border-color: color-mix(in srgb, var(--accent) 60%, var(--border)); }
.tx-flip-set.is-on { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }
.tx-flip-sample { display: flex; justify-content: center; gap: 4px; height: 26px; align-items: center; font-size: 17px; line-height: 1; border-radius: 6px; background: color-mix(in srgb, var(--text-primary) 6%, transparent); }
.tx-flip-sample i { font-size: 11px; color: var(--text-secondary); font-style: normal; }
.tx-flip-name { font-size: 0.64rem; font-weight: 600; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tx-flipcard.is-off .tx-flip-body, .tx-flipcard.is-off .tx-flip-hear { display: none; }
.tx-flip-sub { margin: 10px 0 6px; font-size: 0.68rem; font-weight: 700; color: var(--text-secondary); }
.tx-flip-sub small { font-weight: 500; opacity: 0.75; margin-left: 4px; }
.tx-flip-fonts { display: flex; flex-wrap: wrap; gap: 6px; }
.tx-flip-font { max-width: 140px; padding: 6px 10px; border-radius: 8px; border: 1px solid var(--border); background: transparent; color: var(--text-secondary); font-size: 15px; line-height: 1.1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer; opacity: 0.45; text-decoration: line-through; text-decoration-thickness: 1px; }
.tx-flip-font.is-on { opacity: 1; color: var(--text-primary); text-decoration: none; border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); }
.tx-flip-add { display: flex; gap: 6px; margin-top: 8px; }
.tx-flip-add select { flex: 1; min-width: 0; height: 32px; }
.tx-flip-grid { display: flex; flex-wrap: wrap; gap: 8px; align-items: flex-end; }
.tx-flip-grid > .tx-field { flex: 1 1 160px; }
/* Quick bar above the preview, tidied (2026-10-05): the visual-picker pills join their groups
   instead of all sitting unlabelled at the front, and the Post FX dropdown (the FX pill opens
   the same list with thumbnails) is dropped on desktop. */
@media (min-width: 769px) {
  .is-desktop-bar #mqPostFxSelect { display: none !important; }
  .is-desktop-bar #mqFxPill { order: 12; }
  .is-desktop-bar #mqFxAmountPill { order: 13; }
  .is-desktop-bar #mqGrainPill { order: 14; }
  .is-desktop-bar #mqAspectPill, .is-desktop-bar #mqReframePill { order: 15; }
  .is-desktop-bar #mqResPill { order: 16; }
  .is-desktop-bar #mqSpeedPill { order: 17; }
  .is-desktop-bar #mqTitlesPill { order: 22; }
  .is-desktop-bar #mqNodesBtn { order: 98; margin-left: auto; }
  .is-desktop-bar #mqMoreSettingsBtn { margin-left: 0; }
  .is-desktop-bar.mq-squash-active .mq-squash-item { order: -8; }
}
.tx-variant.is-surprise { border-style: dashed; }
#textTapeAmountField[hidden] { display: none; }
/* Scene card row inside the Variations strip (2026-10-06). */
.tx-variants-scene { flex-basis: 100%; display: flex; gap: 6px; align-items: stretch; overflow-x: auto; scrollbar-width: none; padding: 4px 0 2px; flex-wrap: nowrap; }
.tx-variants-scene::-webkit-scrollbar { display: none; }
.tx-variants-scene .tx-variants-cap { flex: none; flex-basis: auto; align-self: center; width: 44px; }
.tx-scene { flex: none; display: flex; flex-direction: column; gap: 3px; width: 70px; padding: 3px; border-radius: 9px; border: 1px solid var(--border); background: transparent; color: var(--text-primary); font-size: 10.5px; cursor: pointer; text-align: center; }
.tx-scene .sw { display: block; height: 38px; border-radius: 6px; background-size: cover; background-position: center; }
.tx-scene span:last-child { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tx-scene:hover { border-color: var(--accent); }
.tx-scene.is-on { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.tx-scene-more { justify-content: center; font-size: 12px; font-weight: 600; }
/* Titles lane (2026-10-06) — titles over the picture as blocks on the edit. */
.ed-titles { height: 34px; border-radius: 8px; border: 1.5px dashed var(--border); }
.ed-shell .ed-titles { height: 34px; border: 1px dashed #34353f; background: #16171c; }
.ed-titles.is-empty .ed-track-tag { opacity: 0.75; }
.ed-tclip { position: absolute; top: 3px; bottom: 3px; border-radius: 6px; cursor: grab; touch-action: none; overflow: hidden;
  background: color-mix(in srgb, hsl(var(--hue, 35) 70% 55%) 55%, #1a1b20); border: 1px solid hsl(var(--hue, 35) 70% 62%); color: #fff; }
.ed-tclip .ed-clip-label { position: absolute; left: 8px; right: 8px; top: 50%; transform: translateY(-50%); font-size: 0.66rem; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; pointer-events: none; }
.ed-tclip.is-selected { box-shadow: 0 0 0 2px #fff; }
.ed-tclip.is-hidden { opacity: 0.4; }
.ed-tclip.is-dragging { cursor: grabbing; z-index: 4; box-shadow: 0 4px 14px rgba(0,0,0,.45); }
.ed-tclip-tick { position: absolute; top: 0; bottom: 0; width: 1px; background: rgba(255,255,255,.55); pointer-events: none; }
.ed-tclip .ed-clip-edge { position: absolute; top: 0; bottom: 0; width: 10px; cursor: ew-resize; z-index: 2; }
.ed-tclip .ed-edge-l { left: 0; } .ed-tclip .ed-edge-r { right: 0; }
@media (pointer: coarse) { .ed-titles, .ed-shell .ed-titles { height: 44px; } .ed-tclip .ed-clip-edge { width: 18px; } }
.ed-tclip .ed-clip-label { bottom: auto; font-size: 0.66rem; line-height: 1.2; }
/* Editor inspector: the Text pane's add buttons wrapped into three lines in the narrow column. */
.ed-shell .tx-head { flex-wrap: wrap; row-gap: 6px; }
.ed-shell .tx-head .tx-add { white-space: nowrap; padding: 0 10px; }
/* Phone Clips button + sheet (2026-10-06). */
.mp-clips-btn { position: relative; display: none; }
@media (max-width: 768px), (max-width: 900px) and (max-height: 500px) and (orientation: landscape) { .mp-clips-btn { display: inline-flex; } }
.mp-clips-n { position: absolute; top: -3px; right: -3px; min-width: 16px; height: 16px; padding: 0 4px; border-radius: 8px; background: var(--accent, #3b82f6); color: #fff; font: 700 10px/16px var(--font-body, system-ui); text-align: center; }
.mp-clips-n[hidden] { display: none; }
.mcl-sheet { position: fixed; left: 0; right: 0; bottom: 0; z-index: 1200; max-height: 62dvh; display: flex; flex-direction: column; background: var(--bg-primary, #0d0e12); border-top: 1px solid var(--border); border-radius: 18px 18px 0 0; box-shadow: 0 -12px 40px rgba(0,0,0,.5); padding-bottom: env(safe-area-inset-bottom); animation: mclUp .22s cubic-bezier(.2,.8,.2,1); }
.mcl-sheet[hidden] { display: none; }
@keyframes mclUp { from { transform: translateY(24px); opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .mcl-sheet { animation: none; } }
.mcl-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; padding: 14px 16px 8px; border-bottom: 1px solid var(--border); }
.mcl-head strong { font-size: 1rem; }
.mcl-count { color: var(--text-secondary); font-size: 0.8rem; }
.mcl-hint { flex-basis: 100%; order: 3; color: var(--text-secondary); font-size: 0.72rem; }
.mcl-done { margin-left: auto; min-height: 36px; padding: 0 16px; border-radius: 999px; border: 0; background: var(--accent, #3b82f6); color: #fff; font: 600 0.85rem var(--font-body, system-ui); }
.mcl-body { overflow-y: auto; padding: 10px 16px 16px; -webkit-overflow-scrolling: touch; }
.mcl-body .clip-bin { margin: 0; }
.mcl-body .clip-bin-head .clip-bin-title { display: none; }
/* Cards in a sideways row, big enough to grab. */
.mcl-body .clip-strip { display: flex; flex-wrap: nowrap; gap: 10px; overflow-x: auto; padding: 4px 2px 8px; scroll-snap-type: x proximity; }
.mcl-body .clip-card { flex: none; width: 132px; scroll-snap-align: start; }
.mcl-body .clip-card-btn { min-width: 34px; min-height: 34px; }
.clip-add-loaded.is-nudge { box-shadow: 0 0 0 2px var(--accent, #3b82f6); }
/* While the sheet is up, the toast must not sit on top of it. */
body.mcl-open #undoToast { top: calc(var(--header-h, 60px) + 8px); bottom: auto; }
.mcl-body .clip-card { width: 184px; }
.mcl-body .clip-card-actions { display: flex; flex-wrap: nowrap; gap: 4px; }
.mcl-body .clip-card-btn { flex: 1; min-width: 0; padding: 0; }
.mcl-body .clip-bin.has-clips .clip-add-loaded { display: none; }
.mcl-body .clip-bin-head { display: flex; gap: 8px; align-items: center; }

/* ── Comparison pages (2026-10-06, scripts/guides/compare_pages.py) ── */
.cmp-section { max-width: 1080px; margin-inline: auto; }
.cmp-subhead { margin-top: 56px; }
.cmp-verdict { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
.cmp-verdict-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.cmp-verdict-card { padding: 22px 22px 18px; border: 1px solid var(--border); border-radius: 18px; background: var(--bg-secondary); }
.cmp-verdict-card h3 { margin: 0 0 10px; font-size: 1.02rem; }
.cmp-verdict-card p { margin: 0; color: var(--text-secondary); font-size: 0.92rem; line-height: 1.55; }
.cmp-verdict-card ul { margin: 0; padding-left: 1.1em; color: var(--text-secondary); font-size: 0.92rem; line-height: 1.55; }
.cmp-verdict-card li + li { margin-top: 6px; }
.cmp-verdict-us { border-color: color-mix(in srgb, var(--accent) 55%, transparent); background: color-mix(in srgb, var(--accent) 9%, var(--bg-secondary)); }
.cmp-table-wrap { overflow-x: auto; border: 1px solid var(--border); border-radius: 16px; -webkit-overflow-scrolling: touch; }
.cmp-table-wrap:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.cmp-table { width: 100%; border-collapse: collapse; font-size: 0.9rem; line-height: 1.45; }
.cmp-cols-5 { min-width: 760px; }
.cmp-table th, .cmp-table td { padding: 12px 14px; text-align: left; vertical-align: top; border-bottom: 1px solid var(--border); }
.cmp-table thead th { position: sticky; top: 0; font: 600 0.72rem/1.2 'JetBrains Mono', monospace; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-secondary); background: var(--bg-secondary); }
.cmp-table thead th:last-child { color: var(--accent); }
.cmp-table tbody th { font-weight: 650; color: var(--text-primary); width: 22%; }
.cmp-table td { color: var(--text-secondary); }
.cmp-table td:last-child { color: var(--text-primary); background: color-mix(in srgb, var(--accent) 6%, transparent); }
.cmp-table tbody tr:last-child > * { border-bottom: 0; }
.cmp-y, .cmp-n, .cmp-p { display: inline-block; padding: 1px 8px; border-radius: 999px; font-size: 0.78rem; font-weight: 650; }
.cmp-y { color: #22c55e; background: rgba(34,197,94,.12); }
.cmp-n { color: #f87171; background: rgba(248,113,113,.12); }
.cmp-p { color: #fbbf24; background: rgba(251,191,36,.12); }
.cmp-note { margin: 14px 4px 0; font-size: 0.78rem; color: var(--text-secondary); }
.cmp-map { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin: 0; }
.cmp-map > div { display: grid; grid-template-columns: 9.5em 1fr; gap: 12px; align-items: baseline; padding: 14px 16px; border: 1px solid var(--border); border-radius: 14px; background: var(--bg-secondary); }
.cmp-map dt { font: 600 0.74rem/1.3 'JetBrains Mono', monospace; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-secondary); }
.cmp-map dd { margin: 0; font-size: 0.9rem; line-height: 1.5; }
.cmp-together { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; }
.cmp-together-card { padding: 20px; border-radius: 16px; border: 1px solid var(--border); }
.cmp-together-card h3 { margin: 0 0 8px; font-size: 1rem; }
.cmp-together-card p { margin: 0; font-size: 0.9rem; line-height: 1.55; color: var(--text-secondary); }
@media (max-width: 900px) {
  .cmp-verdict-4, .cmp-together { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .cmp-map { grid-template-columns: 1fr; }
}
@media (max-width: 640px) {
  .cmp-verdict, .cmp-verdict-4, .cmp-together { grid-template-columns: 1fr; }
  .cmp-map > div { grid-template-columns: 1fr; gap: 4px; }
  .cmp-table-wrap { overflow: visible; border: 0; border-radius: 0; }
  .cmp-table, .cmp-cols-5 { min-width: 0; font-size: 0.88rem; }
  .cmp-table thead { display: none; }
  .cmp-table, .cmp-table tbody, .cmp-table tr, .cmp-table th, .cmp-table td { display: block; width: auto; }
  .cmp-table tr { margin-bottom: 10px; border: 1px solid var(--border); border-radius: 14px; overflow: hidden; background: var(--bg-secondary); }
  .cmp-table tbody th { width: auto; padding: 12px 14px 6px; border: 0; font-size: 0.95rem; }
  .cmp-table td { display: grid; grid-template-columns: 7.2em 1fr; gap: 10px; padding: 7px 14px; border: 0; }
  .cmp-table td::before { content: attr(data-label); font: 600 0.68rem/1.6 'JetBrains Mono', monospace; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-secondary); }
  .cmp-table td:last-child { padding-bottom: 12px; }
  .cmp-table td:last-child::before { color: var(--accent); }
}
/* The hero aurora reaches 20% past each side; on desktop that made the whole landing page
   scroll sideways by ~290px. Clip at the page (clip, not hidden: no scroll box, sticky still works). */
body.lp-home { overflow-x: clip; }
/* Editor: Text rows fold (2026-10-06). */
.ed-text-toggle { display: flex; align-items: center; gap: 6px; flex: none; width: 100%; padding: 6px 12px; border: 0; border-top: 1px solid var(--border); background: transparent; color: var(--text-secondary); font: 600 0.72rem/1.2 var(--font-body, system-ui); text-align: left; cursor: pointer; }
.ed-text-toggle:hover { color: var(--text-primary); background: rgba(255,255,255,0.03); }
.ed-text-toggle svg { transition: transform .15s; }
.ed-text-toggle[aria-expanded="true"] svg { transform: rotate(90deg); }
.ed-text-toggle-n:not(:empty) { padding: 0 6px; border-radius: 999px; background: rgba(255,255,255,0.08); color: var(--text-primary); }
.ed-text-toggle-hint { margin-left: 6px; font-weight: 400; opacity: 0.7; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ed-text-slot.is-collapsed { display: none; }
/* Landing: editor showcase (2026-10-06, home_page.py editor_html). */
.lp-editor { max-width: 1180px; margin-inline: auto; }
.lp-editor-shot { display: block; margin: 8px auto 0; max-width: 1080px; border-radius: 18px; overflow: hidden; border: 1px solid var(--border); box-shadow: 0 30px 80px -30px color-mix(in srgb, var(--accent) 45%, transparent), 0 20px 50px rgba(0,0,0,.45); transition: transform .35s cubic-bezier(.2,.8,.2,1), box-shadow .35s; }
.lp-editor-shot img { display: block; width: 100%; height: auto; }
.lp-editor-shot:hover { transform: translateY(-3px); }
.lp-editor-feats { list-style: none; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 22px 32px; max-width: 1080px; margin: 44px auto 0; padding: 0; }
.lp-editor-feats li { display: flex; flex-direction: column; gap: 6px; padding-top: 14px; border-top: 1px solid var(--border); }
.lp-editor-feats strong { font-size: 1rem; }
.lp-editor-feats span { color: var(--text-secondary); font-size: 0.92rem; line-height: 1.55; }
.lp-editor-cta { margin-top: 36px; }
.lp-editor-from { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; margin-top: 22px; }
.lp-editor-from a { display: inline-flex; align-items: center; gap: 6px; padding: 8px 14px; border-radius: 999px; border: 1px solid var(--border); color: var(--text-secondary); font-size: 0.86rem; text-decoration: none; transition: color .15s, border-color .15s; }
.lp-editor-from a:hover { color: var(--text-primary); border-color: color-mix(in srgb, var(--accent) 60%, var(--border)); }
@media (prefers-reduced-motion: reduce) { .lp-editor-shot { transition: none; } .lp-editor-shot:hover { transform: none; } }
@media (max-width: 900px) { .lp-editor-feats { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 640px) {
  .lp-editor-shot { border-radius: 12px; }
  .lp-editor-feats { grid-template-columns: 1fr; gap: 14px; margin-top: 28px; }
  .lp-editor-from { justify-content: flex-start; flex-wrap: nowrap; overflow-x: auto; margin-inline: -16px; padding-inline: 16px; scrollbar-width: none; }
  .lp-editor-from a { flex: none; }
}
.lp-editor-cta .btn-arrow { margin-left: 8px; }
.cmp-crosslink { margin: 28px 0 0; padding: 14px 18px; border: 1px solid var(--border); border-radius: 14px; color: var(--text-secondary); font-size: 0.92rem; line-height: 1.55; }

/* ── Photo mode (2026-10-06): body.is-photo while a still is loaded ── */
.photo-only { display: none !important; }
body.is-photo .photo-only { display: block !important; }
body.is-photo .video-only { display: none !important; }
.ph-card { display: flex; gap: 14px; align-items: center; padding: 4px 0 12px; }
.ph-thumb { width: 72px; height: 72px; flex: none; object-fit: cover; border-radius: 10px; border: 1px solid var(--border); background: var(--bg-secondary); }
.ph-info { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.ph-info strong { font-size: 0.95rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ph-meta { font: 500 0.74rem/1.3 'JetBrains Mono', monospace; color: var(--text-secondary); }
.ph-found { font-size: 0.82rem; color: var(--accent); }
.ph-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 10px; }
.ph-actions .btn { width: 100%; justify-content: center; text-align: center; }
.ph-actions .btn:first-child { grid-column: 1 / -1; }
.ph-seg { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 6px; }
.ph-seg button { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; padding: 9px 10px; border-radius: 10px; border: 1px solid var(--border); background: var(--bg-secondary); color: var(--text-primary); font: 650 0.84rem/1.2 var(--font-body, system-ui); text-align: left; cursor: pointer; }
.ph-seg button small { font-weight: 400; font-size: 0.68rem; color: var(--text-secondary); }
.ph-seg button[aria-checked="true"] { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, var(--bg-secondary)); }
.ph-seg button:disabled { opacity: 0.45; cursor: default; }
.ph-save-row { display: flex; gap: 8px; margin: 6px 0 8px; }
.ph-save-row .btn { justify-content: center; }
.ph-save-row #phSaveBtn { flex: 1; display: inline-flex; align-items: center; gap: 8px; }
#photoSaveSection .control-group { margin-top: 12px; }
.ph-save-row { margin-top: 14px; }
.ph-save-row .btn.secondary-btn { flex: none; padding-inline: 16px; }
.ph-save-row #phSaveBtn { white-space: nowrap; }
/* On a phone the text editor (#trimPanel) lives in the Playback section, which photo mode
   hides — while it is the focused editor, the section shows (its other rows stay hidden by
   the editor-focus rules), and the Photo card steps aside. */
body.is-photo .sidebar.editor-focus section.video-only.has-editor-focus { display: block !important; }
body.is-photo .sidebar.editor-focus .photo-only:not(.has-editor-focus) { display: none !important; }
body.is-photo .tx-itabs button[data-g="animate"], body.is-photo .tx-itabs button[data-g="sound"] { display: none !important; }

/* Photo roll (2026-10-05): several photos, one look — the strip under a photo. */
.photo-roll { display: grid; gap: 8px; margin: 10px auto 0; width: min(100%, 900px); padding: 10px; border-radius: 14px; background: var(--bg-secondary, #14161c); border: 1px solid var(--border, #2a2b33); }
.photo-roll[hidden] { display: none; }
.pr-strip { display: flex; gap: 8px; overflow-x: auto; scrollbar-width: none; padding: 2px; }
.pr-strip::-webkit-scrollbar { display: none; }
.pr-thumb, .pr-add { position: relative; flex: none; width: 64px; height: 64px; border-radius: 10px; border: 2px solid transparent; background: #222 center / cover no-repeat; color: #aaa; font: 700 11px/1 var(--font-body); cursor: pointer; }
.pr-thumb.is-on { border-color: var(--accent, #5b8cff); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent, #5b8cff) 35%, transparent); }
.pr-x { position: absolute; top: -6px; right: -6px; width: 20px; height: 20px; border-radius: 50%; background: #000; color: #fff; display: grid; place-items: center; font-size: 13px; border: 1px solid #444; }
.pr-add { border: 2px dashed var(--border, #3a3b44); background: none; font-size: 24px; font-weight: 300; color: var(--text-primary, #eee); }
.pr-actions { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.pr-note { font-size: 12px; color: var(--text-secondary, #aab); }
.pr-export { flex: none; }
.pr-export.pr-ready { background: #2fbf71; }

/* ── Look bar (desktop, 2026-10-06) ─────────────────────────────────────────────────────
   The mode looks and the effects share one bar the width of the picture (#lookBar, built by
   ensureLookBar in main.js): a Looks / Effects switch, one scrolling row, arrows at the ends.
   It replaced two full-width rows that took ~120 px from the video and overflowed both sides. */
.look-bar { position: relative; flex: none; display: flex; align-items: center; gap: 6px; box-sizing: border-box; max-width: 100%; min-width: min(100%, 600px); height: 50px; padding: 0 6px; border: 1px solid var(--border); border-radius: 12px; background: var(--bg-primary); box-shadow: 0 1px 2px rgba(0,0,0,.12); }
.look-bar[hidden] { display: none; }
.lb-tabs { flex: none; display: flex; gap: 2px; padding: 2px; border-radius: 9px; background: color-mix(in srgb, var(--text-primary) 6%, transparent); }
.lb-tab { position: relative; display: inline-flex; align-items: center; gap: 5px; height: 28px; padding: 0 11px; border: 0; border-radius: 7px; background: transparent; color: var(--text-secondary); font: 600 12px/1 var(--font-body, inherit); white-space: nowrap; cursor: pointer; transition: background .15s, color .15s; }
.lb-tab[hidden] { display: none; }
.lb-tab:hover { color: var(--text-primary); }
.lb-tab.is-on { background: var(--bg-secondary); color: var(--text-primary); box-shadow: 0 1px 2px rgba(0,0,0,.18); }
.lb-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.lb-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--accent); display: none; }
.lb-tab.has-on .lb-dot { display: block; }
.lb-rows { order: 2; flex: 1 1 auto; min-width: 0; }
.lb-prev { order: 1; }
.lb-next { order: 3; }
.lb-nav { flex: none; display: grid; place-items: center; width: 24px; height: 24px; padding: 0; border: 1px solid var(--border); border-radius: 50%; background: var(--bg-secondary); color: var(--text-primary); cursor: pointer; transition: opacity .15s; }
.lb-nav svg { width: 13px; height: 13px; }
.lb-nav:hover { border-color: var(--accent); }
.look-bar.at-start .lb-prev, .look-bar.at-end .lb-next { opacity: 0; pointer-events: none; }
.look-bar .mode-preset-row { display: none; margin: 0; padding: 0; gap: 0; }
.look-bar .mode-preset-row.lb-shown:not([hidden]) { display: flex; }
.look-bar .mode-preset-label { display: none; }
.look-bar .mode-preset-chips { flex: 1; align-items: center; gap: 5px; padding: 3px; scroll-behavior: smooth;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 24px, #000 calc(100% - 24px), transparent); mask-image: linear-gradient(to right, transparent, #000 24px, #000 calc(100% - 24px), transparent); }
.look-bar.at-start .mode-preset-chips { -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent); mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent); }
.look-bar.at-end .mode-preset-chips { -webkit-mask-image: linear-gradient(to right, transparent, #000 24px); mask-image: linear-gradient(to right, transparent, #000 24px); }
.look-bar.at-start.at-end .mode-preset-chips { -webkit-mask-image: none; mask-image: none; }
.look-bar .mode-preset-chip:not(.fx-preset-chip) { min-height: 0; height: 30px; padding: 0 11px 0 8px; gap: 6px; font-size: 12px; font-weight: 500; border-radius: 8px; background: transparent; border-color: transparent; color: var(--text-primary); white-space: nowrap; }
.look-bar .mode-preset-chip:not(.fx-preset-chip) svg { width: 15px; height: 15px; }
.look-bar .mode-preset-chip:not(.fx-preset-chip):hover { background: color-mix(in srgb, var(--text-primary) 7%, transparent); border-color: transparent; }
.look-bar .mode-preset-chip:not(.fx-preset-chip).is-on { background: color-mix(in srgb, var(--mp-tint, var(--accent)) 16%, transparent); border-color: color-mix(in srgb, var(--mp-tint, var(--accent)) 70%, transparent); }
.look-bar .fx-preset-chip { width: 74px; height: 38px; }
/* The editor has its own Looks pane on the left, and keeps the player clean. */
.ed-shell #lookBar { display: none !important; }
/* The floating theme / font / accent buttons sat on the bar's right end; with a source loaded
   they move into the sidebar's search row on desktop too, as they already do on phones. */
@media (min-width: 769px) {
  body.has-source:has(#lookBar:not([hidden])) .theme-font-toggle { display: none; }
  body.has-source:has(#lookBar:not([hidden])) .sheet-only-btn { display: inline-flex; align-items: center; justify-content: center; }
}
/* Editor Looks pane (2026-10-06): effects and motion picked by sight — each card shows the
   effect on the current frame (paintLookThumbs), like the look bar under the normal player. */
.ed-lib-h small { font-weight: 500; opacity: .7; margin-left: 4px; }
.ed-lib-grid.ed-lib-thumbs { grid-template-columns: repeat(auto-fill, minmax(92px, 1fr)); }
.ed-lib-thumbs .ed-lib-card { padding: 4px 4px 6px; min-height: 0; gap: 5px; }
.ed-lib-thumbs .ed-lib-card b { padding: 0 3px; font-size: 0.72rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
.ed-lib-thumb { display: block; width: 100%; aspect-ratio: 16 / 9; height: auto; border-radius: 6px; background: #000; }
.ed-lib-thumbs .ed-lib-card.is-on { box-shadow: inset 0 0 0 1.5px var(--mp-tint, var(--accent)); }
/* The time read-out wrapped onto two lines in the toolbar. */
.ed-time { white-space: nowrap; flex: none; }
/* Editor monitor bar (2026-10-06): transport + timecode under the preview (edSyncMonitor). */
.ed-center { flex-direction: column; }
.ed-center > main.video-area { order: 1; }
.ed-monitor { order: 2; flex: none; display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; height: 44px; padding: 0 14px; border-top: 1px solid var(--border); background: var(--bg-primary); }
.ed-mon-tc, .ed-mon-dur { font: 600 0.82rem/1 'JetBrains Mono', ui-monospace, monospace; font-variant-numeric: tabular-nums; letter-spacing: .02em; white-space: nowrap; }
.ed-mon-tc { color: var(--text-primary); }
.ed-mon-dur { justify-self: end; color: var(--text-secondary); }
.ed-mon-transport { display: flex; align-items: center; gap: 2px; }
.ed-mon-btn { display: grid; place-items: center; width: 30px; height: 30px; padding: 0; border: 0; border-radius: 7px; background: transparent; color: var(--text-secondary); cursor: pointer; transition: background .12s, color .12s; }
.ed-mon-btn svg { width: 16px; height: 16px; }
.ed-mon-btn:hover { color: var(--text-primary); background: color-mix(in srgb, var(--text-primary) 8%, transparent); }
.ed-mon-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.ed-mon-play { width: 36px; height: 36px; margin: 0 6px; border-radius: 50%; background: var(--text-primary); color: var(--bg-primary); }
.ed-mon-play:hover { background: var(--text-primary); color: var(--bg-primary); filter: brightness(.9); }
.ed-mon-play svg { width: 15px; height: 15px; }
/* Empty lanes (Titles, V2, Music, Sound effects) took ~150 px of the dock before anything was on
   them; they shrink to a slim drop strip and grow back as soon as they hold something or a drag
   is over them. Mouse only — touch keeps the taller targets. */
@media (pointer: fine) {
  .ed-shell .ed-track.is-empty:not(.is-drop) { height: 26px; }
  .ed-shell .ed-track.is-empty:not(.is-drop) .ed-track-tag { font-size: 9.5px; }
}

/* ── Editor v3 (2026-10-06): settings follow what you click, trimming, scrubbing, audio ─── */
/* Docked settings: the music / sound effect / overlay menus live in Properties now. */
.ed-props > .ed-trans-menu.is-docked { position: static; width: auto; max-height: none; padding: 0; border: 0; border-radius: 0; background: none; box-shadow: none; overflow: visible; display: flex; flex-direction: column; gap: 6px; }
.ed-props > .ed-trans-menu.is-docked .ed-trans-head { font-size: 0.95rem; font-weight: 700; padding: 0; }
.ed-props-h { position: relative; }
.ed-props-h .ed-props-mute { position: absolute; top: 0; right: 0; padding: 5px 9px; border-radius: 999px; border: 1px solid var(--border); background: color-mix(in srgb, var(--text-primary) 6%, transparent); color: var(--text-primary); font: 600 0.7rem/1 var(--font-body); cursor: pointer; }
.ed-props-h .ed-props-mute.is-muted { color: #ff6b6b; border-color: color-mix(in srgb, #ff6b6b 50%, transparent); }
.ed-time-in { height: 26px; padding: 0 7px; font: 600 0.74rem/1 'JetBrains Mono', ui-monospace, monospace; font-variant-numeric: tabular-nums; min-width: 0; width: 100%; border-radius: 6px; }
.ed-props-grid { align-items: center; }
.ed-trim-acts kbd { margin-left: 4px; padding: 1px 4px; border-radius: 4px; border: 1px solid var(--border); font: 600 0.62rem/1.2 'JetBrains Mono', monospace; color: var(--text-secondary); }
.ed-title-panel { display: flex; flex-direction: column; gap: 8px; }
/* Mute on the clip block. */
.ed-clip-mute { position: absolute; top: 4px; right: 6px; z-index: 3; display: grid; place-items: center; width: 22px; height: 22px; padding: 0; border: 0; border-radius: 6px; background: rgba(0,0,0,0.55); color: #fff; cursor: pointer; opacity: 0; transition: opacity .12s; }
.ed-clip-mute svg { width: 13px; height: 13px; }
.ed-clip:hover .ed-clip-mute, .ed-clip.is-selected .ed-clip-mute, .ed-clip-mute.is-muted { opacity: 1; }
.ed-clip-mute.is-muted { background: #ff4d5e; }
/* Trim read-out and ruler hover. */
.ed-trim-tip, .ed-hover-tip { position: fixed; z-index: 1200; pointer-events: none; display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 4px; border-radius: 9px; background: rgba(12,13,18,0.94); border: 1px solid rgba(255,255,255,0.12); box-shadow: 0 10px 28px rgba(0,0,0,0.45); color: #fff; }
.ed-trim-tip[hidden], .ed-hover-tip[hidden] { display: none; }
.ed-trim-tip img, .ed-hover-tip img { display: block; height: 54px; width: auto; min-width: 72px; border-radius: 6px; background: #000; }
.ed-hover-tip img[hidden] { display: none; }
.ed-trim-tip div { display: flex; flex-direction: column; align-items: center; gap: 1px; padding: 0 4px 2px; }
.ed-trim-tip b, .ed-hover-tip b { font: 600 0.72rem/1.1 'JetBrains Mono', ui-monospace, monospace; font-variant-numeric: tabular-nums; }
.ed-trim-tip span { font: 500 0.66rem/1.1 var(--font-body); color: rgba(255,255,255,0.7); }
.ed-hover-tip b { padding: 0 4px 2px; }
.ed-hover-line { position: absolute; top: 0; bottom: 0; width: 1px; background: rgba(255,255,255,0.35); pointer-events: none; z-index: 5; }
.ed-hover-line[hidden] { display: none; }
/* Playhead timecode while scrubbing. */
.ed-playhead.is-scrub::after { content: attr(data-tc); position: absolute; top: 2px; left: 8px; padding: 2px 5px; border-radius: 4px; background: #ff3b5c; color: #fff; font: 700 0.62rem/1.2 'JetBrains Mono', ui-monospace, monospace; white-space: nowrap; }
/* Music: fade ramps, corner handles, volume line. */
.ed-mclip.is-sel { box-shadow: inset 0 0 0 2px #fff, 0 0 0 2px var(--accent); }
.ed-mfade-shade { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.ed-mfade-shade path { fill: rgba(0,0,0,0.38); }
.ed-mfade { position: absolute; top: 2px; z-index: 3; width: 10px; height: 10px; border-radius: 50%; background: #fff; box-shadow: 0 0 0 1.5px rgba(0,0,0,0.5); cursor: ew-resize; opacity: 0; transition: opacity .12s; }
.ed-mvol { position: absolute; left: 0; right: 0; z-index: 2; height: 7px; margin-top: -3px; cursor: ns-resize; opacity: 0; transition: opacity .12s; }
.ed-mvol::before { content: ""; position: absolute; left: 0; right: 0; top: 3px; height: 1.5px; background: #ffe14a; }
.ed-mclip:hover .ed-mfade, .ed-mclip:hover .ed-mvol, .ed-mclip.is-sel .ed-mfade, .ed-mclip.is-sel .ed-mvol { opacity: 1; }
/* Sound effects: coloured by family, with their waveform. */
.ed-sclip { background: linear-gradient(180deg, hsl(var(--sh, 34) 80% 45%), hsl(var(--sh, 34) 80% 34%)); }
.ed-sclip.is-auto { background: repeating-linear-gradient(135deg, hsl(var(--sh, 34) 70% 40%) 0 6px, hsl(var(--sh, 34) 70% 33%) 6px 12px); }
.ed-sclip-wave { position: absolute; left: 0; top: 2px; width: 100%; height: 28px; pointer-events: none; }
.ed-sclip .ed-clip-label { position: relative; z-index: 1; text-shadow: 0 1px 2px rgba(0,0,0,0.6); }
/* Rebuild veil over the player. */
.ed-center { position: relative; }
.ed-build-veil { position: absolute; inset: 0 0 44px 0; z-index: 20; display: grid; place-items: center; background: rgba(0,0,0,0.45); backdrop-filter: blur(2px); }
.ed-build-veil[hidden] { display: none; }
.ed-bv-card { display: flex; flex-direction: column; gap: 6px; width: min(340px, 80%); padding: 14px 16px; border-radius: 12px; background: rgba(14,15,20,0.92); border: 1px solid rgba(255,255,255,0.1); color: #fff; }
.ed-bv-card b { font-size: 0.9rem; }
.ed-bv-card span { font-size: 0.74rem; color: rgba(255,255,255,0.7); }
.ed-bv-bar { height: 4px; border-radius: 4px; background: rgba(255,255,255,0.14); overflow: hidden; }
.ed-bv-bar[hidden] { display: none; }
.ed-bv-bar i { display: block; height: 100%; background: var(--accent); transition: width .25s; }
.ed-build-veil.is-error .ed-bv-card b { color: #ff8b8b; }
.ed-bv-card button { align-self: flex-start; padding: 6px 14px; border-radius: 8px; border: 0; background: var(--accent); color: #fff; font: 600 0.78rem/1 var(--font-body); cursor: pointer; }
.ed-bv-card button[hidden] { display: none; }

/* Compact settings in the editor (2026-10-06): the sidebar and the clip inspector were laid out
   for the roomy normal page — in the editor's narrow columns they ran several screens long. */
.ed-shell aside.sidebar { padding: 10px 12px !important; }
.ed-shell aside.sidebar .sidebar-tabs { gap: 4px; margin-bottom: 10px; }
.ed-shell aside.sidebar .sidebar-tab { padding: 5px 2px; font-size: 0.66rem; gap: 2px; }
.ed-shell .control-section { gap: 6px; padding-bottom: 8px; }
.ed-shell .control-section h3 { font-size: 0.7rem; letter-spacing: 0.06em; }
.ed-shell .control-group { gap: 3px; }
.ed-shell .toggle-control { min-height: 26px; font-size: 0.74rem; gap: 6px; }
.ed-shell .label-row { font-size: 0.72rem; }
.ed-shell .hint-text { font-size: 0.68rem; line-height: 1.35; }
.ed-shell .dropdown, .ed-shell .text-input { padding-top: 5px; padding-bottom: 5px; font-size: 0.76rem; }
.ed-shell .mt-2 { margin-top: 6px !important; }
.ed-shell .btn { padding-top: 6px; padding-bottom: 6px; font-size: 0.74rem; }
.ed-shell .ed-right { padding: 8px 10px; }
.ed-shell .ed-props { padding: 8px 4px 10px; gap: 6px; }
.ed-shell .ed-lib { gap: 6px; }
.ed-shell #trimPanel .tx-row { margin-top: 6px; }
.ed-shell .ed-time-in { background: color-mix(in srgb, var(--text-primary) 6%, transparent); border: 1px solid transparent; }
.ed-shell .ed-time-in:focus { border-color: var(--accent); background: var(--bg-primary); }

/* ── Node view v3 (2026-10-06): every setting, resets, Add, preview sizes ───────────────── */
.node-view[data-preview="s"] .nv-body { grid-template-columns: minmax(0, 1fr) 300px; }
.node-view[data-preview="m"] .nv-body, .node-view[data-preview="xl"] .nv-body { grid-template-columns: minmax(0, 1fr) 380px; }
.node-view[data-preview="l"] .nv-body { grid-template-columns: minmax(0, 1fr) 540px; }
.nv-tool-sep { width: 1px; height: 20px; background: var(--nv-line); margin: 0 2px; }
.nv-tool-primary { background: var(--nv-blue); color: #fff; }
.nv-tool-primary:hover { background: color-mix(in srgb, var(--nv-blue) 86%, #000); }
.nv-tool svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
/* Segmented control, the macOS kind. */
.nv-seg { display: inline-flex; padding: 2px; gap: 2px; border-radius: 9px; background: var(--nv-fill); }
.nv-seg button { min-width: 28px; height: 26px; padding: 0 9px; border: 0; border-radius: 7px; background: transparent; color: var(--nv-tx2); font: 600 12px/1 inherit; font-family: inherit; cursor: pointer; }
.nv-seg button.is-on { background: var(--nv-card-solid); color: var(--nv-tx); box-shadow: 0 1px 2px rgba(0,0,0,.18), 0 0 0 .5px rgba(0,0,0,.06); }
/* The preview, with an expand button; ⤢ floats it big over the board. */
.nv-big-wrap { position: relative; }
.nv-big-expand { position: absolute; right: 8px; bottom: 8px; width: 28px; height: 28px; display: grid; place-items: center; border: 0; border-radius: 8px; background: rgba(0,0,0,.5); color: #fff; cursor: pointer; opacity: 0; transition: opacity .15s; backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); }
.nv-big-expand svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.nv-big-wrap:hover .nv-big-expand, .node-view[data-preview="xl"] .nv-big-expand { opacity: 1; }
.node-view[data-preview="xl"] .nv-big-wrap { position: fixed; z-index: 30; left: 32px; top: 74px; right: calc(380px + 50px); bottom: 32px; display: grid; place-items: center; padding: 16px; border-radius: 18px; background: rgba(0,0,0,.88); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); box-shadow: 0 30px 80px rgba(0,0,0,.5); }
.node-view[data-preview="xl"] .nv-big { width: auto; max-width: 100%; max-height: 100%; height: auto; aspect-ratio: auto; }
/* Inspector: stage reset, per-setting reset, "N more settings". */
.nv-insp-reset { margin-left: auto; height: 26px; padding: 0 10px; border: 0; border-radius: 7px; background: var(--nv-fill); color: var(--nv-tx); font: 500 12px/1 inherit; font-family: inherit; cursor: pointer; }
.nv-insp-reset:hover { background: var(--nv-fill2); }
.nv-insp-reset[hidden] { display: none; }
.nv-param { grid-template-columns: minmax(0, 1fr) auto auto; }
.nv-param.is-check { grid-template-columns: minmax(0, 1fr) auto auto; }
.nv-param.is-check > [data-for] { grid-column: 3; }
.nv-param-reset { grid-row: 1; width: 22px; height: 22px; display: grid; place-items: center; padding: 0; border: 0; border-radius: 6px; background: transparent; color: var(--nv-blue); cursor: pointer; visibility: hidden; }
.nv-param.is-check .nv-param-reset { grid-column: 2; }
.nv-param-reset svg { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.nv-param.is-changed .nv-param-reset { visibility: visible; }
.nv-param.is-changed .nv-param-name { font-weight: 600; }
.nv-param-reset:hover { background: color-mix(in srgb, var(--nv-blue) 14%, transparent); }
.nv-more-toggle { display: flex; align-items: center; gap: 6px; width: 100%; padding: 10px 12px; border: 0; border-top: 1px solid var(--nv-line); background: transparent; color: var(--nv-blue); font: 500 13px/1 inherit; font-family: inherit; text-align: left; cursor: pointer; }
.nv-more-toggle svg { width: 11px; height: 11px; transition: transform .2s; }
.nv-more-toggle.is-open svg { transform: rotate(90deg); }
.nv-more { display: flex; flex-direction: column; border-top: 1px solid var(--nv-line); }
/* Menu items (Reset). */
.nv-menu-item { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; width: 100%; padding: 7px 10px; border: 0; border-radius: 7px; background: transparent; color: var(--nv-tx); font: 13px/1.2 inherit; font-family: inherit; text-align: left; cursor: pointer; }
.nv-menu-item small { font-size: 11px; color: var(--nv-tx2); }
.nv-menu-item:hover { background: var(--nv-blue); color: #fff; }
.nv-menu-item:hover small { color: rgba(255,255,255,.8); }
.nv-menu { min-width: 280px; }
/* Titles & intros inspector. */
.nv-tlist { display: flex; flex-direction: column; }
.nv-trow { display: flex; align-items: center; border-bottom: 1px solid var(--nv-line); }
.nv-trow-main { flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: flex-start; gap: 2px; padding: 9px 12px; border: 0; background: transparent; color: var(--nv-tx); font: 13px/1.2 inherit; font-family: inherit; text-align: left; cursor: pointer; }
.nv-trow-main b { font-weight: 600; max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nv-trow-main small { color: var(--nv-tx2); font-size: 11px; font-variant-numeric: tabular-nums; }
.nv-trow-main:hover { background: var(--nv-fill); }
.nv-trow-del { width: 30px; height: 30px; margin-right: 6px; border: 0; border-radius: 7px; background: transparent; color: var(--nv-tx3); cursor: pointer; }
.nv-trow-del:hover { background: color-mix(in srgb, var(--nv-red) 14%, transparent); color: var(--nv-red); }
.nv-intro-pick { padding: 4px 12px 8px; }
.nv-sub { padding: 10px 0 2px; font: 600 13px/1.2 inherit; font-family: inherit; }
.nv-sub2 { padding: 10px 0 6px; font: 600 11px/1.2 inherit; font-family: inherit; color: var(--nv-tx2); text-transform: uppercase; letter-spacing: .04em; }
.nv-tgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); gap: 8px; }
.nv-tcard { display: flex; flex-direction: column; gap: 5px; padding: 0; border: 0; background: transparent; color: var(--nv-tx); font: 500 11.5px/1.25 inherit; font-family: inherit; text-align: left; cursor: pointer; }
.nv-tcard img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; border-radius: 8px; background: #000; box-shadow: 0 0 0 1px var(--nv-line); transition: transform .15s, box-shadow .15s; }
.nv-tcard:hover img { transform: translateY(-1px); box-shadow: 0 0 0 2px var(--nv-blue), 0 6px 16px rgba(0,0,0,.25); }
.nv-tcard span { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
/* Add sheet. */
.nv-add { position: absolute; inset: 0; z-index: 40; display: grid; place-items: center; background: rgba(0,0,0,.32); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); }
.nv-add[hidden] { display: none; }
.nv-add-card { width: min(880px, calc(100vw - 48px)); max-height: calc(100vh - 96px); display: flex; flex-direction: column; border-radius: 18px; background: color-mix(in srgb, var(--nv-card-solid) 94%, transparent); backdrop-filter: saturate(180%) blur(30px); -webkit-backdrop-filter: saturate(180%) blur(30px); border: 1px solid var(--nv-line); box-shadow: 0 30px 80px rgba(0,0,0,.35); color: var(--nv-tx); overflow: hidden; }
.nv-add-head { display: flex; align-items: center; gap: 14px; padding: 14px 16px; border-bottom: 1px solid var(--nv-line); }
.nv-add-head > b { font-size: 15px; font-weight: 600; }
.nv-add-tabs { margin: 0 auto; }
.nv-add-body { overflow-y: auto; padding: 4px 18px 18px; }
.nv-add-body[hidden] { display: none; }
.nv-add-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 8px; }
.nv-add-item { display: flex; align-items: center; gap: 10px; padding: 9px 10px; border: 0; border-radius: 11px; background: var(--nv-fill); color: var(--nv-tx); font: 500 13px/1.2 inherit; font-family: inherit; text-align: left; cursor: pointer; transition: background .15s; }
.nv-add-item:hover { background: var(--nv-fill2); }
.nv-add-item .nv-icon { width: 26px; height: 26px; }
.nv-add-thumbs { grid-template-columns: repeat(auto-fill, minmax(124px, 1fr)); }
.nv-add-fx { flex-direction: column; align-items: stretch; gap: 6px; padding: 6px 6px 8px; }
.nv-add-fx canvas { width: 100%; aspect-ratio: 16 / 9; border-radius: 7px; background: #000; }
.nv-add-fx span { padding: 0 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nv-add .nv-tgrid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; }
/* The Undo toast sat behind the node view (z 2500), so a reset there could not be undone by click. */
body:has(#nodeView:not([hidden])) #undoToast { z-index: 2700; }
.nv-param .nv-param-name { grid-column: 1; grid-row: 1; }
.nv-param .nv-param-reset { grid-column: 2; grid-row: 1; }
.nv-param .nv-param-val { grid-column: 3; grid-row: 1; }
.nv-param:not(.is-check) > [data-for] { grid-column: 1 / -1; grid-row: 2; }
.nv-param.is-check > [data-for] { grid-column: 3; grid-row: 1; }

/* ── Text: Quick settings + flip sounds (2026-10-06) ─────────────────────────────────────── */
.tx-qs { display: flex; flex-direction: column; gap: 8px; padding: 10px 12px; border-radius: 12px; background: color-mix(in srgb, var(--accent) 7%, var(--bg-secondary)); border: 1px solid color-mix(in srgb, var(--accent) 22%, var(--border)); }
.tx-q-head { display: flex; align-items: baseline; gap: 8px; }
.tx-q-head b { font-size: 0.8rem; }
.tx-q-head small { font-size: 0.68rem; color: var(--text-secondary); }
.tx-q-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); gap: 8px 10px; align-items: end; }
.tx-q { display: flex; flex-direction: column; gap: 4px; min-width: 0; font-size: 0.72rem; color: var(--text-secondary); }
.tx-q span { display: flex; justify-content: space-between; }
.tx-q b { color: var(--text-primary); font-weight: 600; font-variant-numeric: tabular-nums; }
.tx-q .dropdown { padding-top: 5px; padding-bottom: 5px; font-size: 0.76rem; min-width: 0; width: 100%; }
.tx-q-size { grid-column: span 2; }
.tx-q-color input[type=color] { width: 100%; height: 30px; padding: 0 2px; border-radius: 8px; border: 1px solid var(--border); background: none; cursor: pointer; }
.tx-q[hidden] { display: none; }
.tx-q-time { display: flex; flex-wrap: wrap; align-items: end; gap: 6px 8px; }
.tx-q-time label { display: flex; flex-direction: column; gap: 3px; font-size: 0.7rem; color: var(--text-secondary); }
.tx-q-time input { width: 74px; padding: 5px 7px; font: 600 0.76rem/1 'JetBrains Mono', ui-monospace, monospace; font-variant-numeric: tabular-nums; }
.tx-q-time .tx-chip { height: 28px; }
.tx-q-pos { display: flex; align-items: center; gap: 10px; font-size: 0.72rem; color: var(--text-secondary); }
.tx-q-pos small { font-size: 0.68rem; }
.tx-q-grid9 { display: grid; grid-template-columns: repeat(3, 16px); gap: 3px; padding: 4px; border-radius: 7px; background: color-mix(in srgb, var(--text-primary) 6%, transparent); }
.tx-q-grid9 button { width: 16px; height: 11px; padding: 0; border: 0; border-radius: 3px; background: color-mix(in srgb, var(--text-primary) 18%, transparent); cursor: pointer; }
.tx-q-grid9 button:hover { background: color-mix(in srgb, var(--accent) 60%, transparent); }
.tx-q-grid9 button.is-on { background: var(--accent); }
@media (max-width: 768px) { .tx-q-time input { width: 64px; font-size: 16px; } .tx-q .dropdown { font-size: 16px; } }
/* Flip sound chips. */
.tx-flip-voices { display: flex; flex-wrap: wrap; gap: 5px; }
.tx-flip-voice { display: inline-flex; align-items: center; gap: 5px; height: 28px; padding: 0 10px; border-radius: 999px; border: 1px solid var(--border); background: var(--bg-secondary); color: var(--text-primary); font: 600 0.72rem/1 var(--font-body); cursor: pointer; }
.tx-flip-voice i { font-style: normal; font-size: 0.6rem; opacity: .6; }
.tx-flip-voice:hover { border-color: var(--accent); }
.tx-flip-voice.is-on { background: color-mix(in srgb, var(--accent) 18%, var(--bg-secondary)); border-color: var(--accent); }
.tx-flip-tune { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 6px; }
.tx-flip-tune[hidden] { display: none; }
.tx-flip-tune-l { font-size: 0.72rem; color: var(--text-secondary); }
.tx-flip-pitch { display: inline-flex; padding: 2px; gap: 2px; border-radius: 8px; background: color-mix(in srgb, var(--text-primary) 7%, transparent); }
.tx-flip-pitch button { height: 24px; padding: 0 9px; border: 0; border-radius: 6px; background: transparent; color: var(--text-secondary); font: 600 0.7rem/1 var(--font-body); cursor: pointer; }
.tx-flip-pitch button.is-on { background: var(--bg-primary); color: var(--text-primary); box-shadow: 0 1px 2px rgba(0,0,0,.2); }
.tx-itabs { flex-wrap: nowrap !important; overflow-x: auto; scrollbar-width: none; }
.tx-itabs::-webkit-scrollbar { display: none; }
.tx-itabs button { flex: none; white-space: nowrap; }
.ed-shell .tx-itabs button { padding-left: 8px; padding-right: 8px; font-size: 0.72rem; }
.tx-q-pos { justify-content: flex-start; width: 100%; }
/* Editor Soundtrack panel (2026-10-06). */
.ed-sound-panel { display: flex; flex-direction: column; gap: 7px; }
.ed-sound-chks { display: flex; gap: 14px; flex-wrap: wrap; font-size: 0.76rem; }
.ed-sound-chk { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; }
.ed-sound-chk input { accent-color: var(--accent); }
.ed-sound-panel .ed-lib-chips { gap: 5px; }
.sound-eq { display: flex; flex-direction: column; }

/* ── Look bar: cleanup + motion (2026-10-06) ─────────────────────────────────────────────── */
.look-bar { background: color-mix(in srgb, var(--bg-primary) 92%, transparent); backdrop-filter: saturate(160%) blur(14px); -webkit-backdrop-filter: saturate(160%) blur(14px); border-color: color-mix(in srgb, var(--text-primary) 10%, transparent); box-shadow: 0 1px 0 rgba(255,255,255,.03) inset, 0 6px 24px rgba(0,0,0,.18); }
.look-bar:not([hidden]) { animation: lbIn .38s cubic-bezier(.2,.8,.2,1) both; }
@keyframes lbIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
/* Sliding pill behind the active tab. */
.lb-tabs { position: relative; isolation: isolate; }
.lb-tabs.has-pill::before { content: ""; position: absolute; z-index: -1; top: 2px; bottom: 2px; left: 0; width: var(--lb-w, 0); transform: translateX(var(--lb-x, 0)); border-radius: 7px; background: var(--bg-secondary); box-shadow: 0 1px 2px rgba(0,0,0,.22), 0 0 0 .5px rgba(255,255,255,.04) inset; transition: transform .32s cubic-bezier(.3,.9,.3,1), width .32s cubic-bezier(.3,.9,.3,1); }
.lb-tabs.has-pill .lb-tab.is-on { background: transparent; box-shadow: none; }
.lb-tab { transition: color .2s; }
/* Chips come in one after another. */
.look-bar .mode-preset-row.lb-anim .mode-preset-chips > * { animation: lbChipIn .34s cubic-bezier(.2,.8,.2,1) both; animation-delay: calc(var(--i, 0) * 22ms); }
@keyframes lbChipIn { from { opacity: 0; transform: translateY(5px) scale(.97); } to { opacity: 1; transform: none; } }
/* Hover lift, press, and a pop when something turns on. */
.look-bar .mode-preset-chip { transition: transform .18s cubic-bezier(.3,.9,.3,1), box-shadow .18s, background .18s, border-color .18s; }
.look-bar .mode-preset-chip:hover { transform: translateY(-1px); }
.look-bar .fx-preset-chip:hover { box-shadow: 0 6px 14px rgba(0,0,0,.28); }
.look-bar .mode-preset-chip:active { transform: scale(.96); transition-duration: .08s; }
.look-bar .mode-preset-chip.is-pop { animation: lbPop .5s cubic-bezier(.3,1.4,.4,1); }
@keyframes lbPop { 0% { transform: scale(.94); } 45% { transform: scale(1.06); } 100% { transform: none; } }
.look-bar .fx-preset-chip.is-on.is-pop { box-shadow: 0 0 0 1.5px var(--mp-tint, var(--accent)), 0 0 0 6px color-mix(in srgb, var(--mp-tint, var(--accent)) 22%, transparent); }
.look-bar .fx-chip-check { transition: opacity .18s, transform .28s cubic-bezier(.3,1.5,.4,1); }
.lb-nav { transition: opacity .2s, transform .15s; }
.lb-nav:active { transform: scale(.9); }
/* Cleaner group tags: a hairline either side instead of a bare vertical word. */
.look-bar .fx-group-tag { font-size: 8px; letter-spacing: .14em; opacity: .55; }
.look-bar .fx-group-sep { margin: 6px 6px; opacity: .7; }
@media (prefers-reduced-motion: reduce) {
  .look-bar:not([hidden]), .look-bar .mode-preset-row.lb-anim .mode-preset-chips > *, .look-bar .mode-preset-chip.is-pop { animation: none !important; }
  .lb-tabs.has-pill::before { transition: none; }
  .look-bar .mode-preset-chip:hover { transform: none; }
}
/* The Undo toast sat under the full-screen editor (z 900), so its Undo could not be clicked there. */
body.editor-full #undoToast { z-index: 1100; }
/* Text editor (2026-10-06): what the open tab is for, and a phone-friendly inspector. */
.tx-itab-hint { margin: -2px 2px 6px; font-size: 0.7rem; color: var(--text-secondary); }
@media (max-width: 768px) {
  #textInspector .tx-itabs { position: sticky; top: 0; z-index: 4; background: var(--bg-primary); padding-top: 4px; padding-bottom: 4px; }
  #textInspector .tx-itabs button { min-height: 38px; padding: 0 12px; }
  .tx-qs { padding: 10px; }
  .tx-q-grid { grid-template-columns: 1fr 1fr; }
  .tx-q-size { grid-column: 1 / -1; }
  .tx-q-time { display: grid; grid-template-columns: repeat(3, 1fr); }
  .tx-q-time input { width: 100%; }
  .tx-q-time .tx-chip { min-height: 38px; }
  .tx-q-grid9 { grid-template-columns: repeat(3, 26px); gap: 4px; }
  .tx-q-grid9 button { width: 26px; height: 18px; }
  .tx-flip-voice { height: 34px; }
}
.clip-read-pill { position: fixed; left: 50%; top: calc(var(--header-h, 60px) + 14px); transform: translateX(-50%); z-index: 3000; padding: 9px 16px; border-radius: 999px; background: rgba(20,20,24,.92); color: #fff; font: 600 13px/1.2 var(--font-body); box-shadow: 0 8px 24px rgba(0,0,0,.35); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); }
/* The desktop Editor button showed on phones over the first mode pill (Blob); phones open the
   editor from the player bar's ✂ / Clips buttons instead. */
@media (max-width: 999px) { #edOpenBtn.ed-open-btn { display: none !important; } }

/* ── App header as a pill (2026-10-06) ─────────────────────────────────────────────────────
   The app's header was a full-width bar; it is now an inset frosted capsule (the guide pages'
   header already condenses into one on scroll). The flyouts drop from it as rounded sheets
   with a small gap, and the hover bridge spans only that gap — it used to reach 46 px up over
   the other triggers, so with one menu open, hovering the next kept the first open. */
@media (min-width: 769px) {
  .app-container > header {
    margin: 8px 12px 0; height: calc(var(--header-h) - 12px);
    padding: 0 10px 0 16px; border-radius: 999px;
    border: 1px solid color-mix(in srgb, var(--text-primary) 11%, transparent);
    background: color-mix(in srgb, var(--bg-primary) 84%, transparent);
    -webkit-backdrop-filter: saturate(180%) blur(20px); backdrop-filter: saturate(180%) blur(20px);
    box-shadow: 0 1px 0 rgba(255,255,255,.04) inset, 0 10px 30px rgba(0,0,0,.22);
  }
  .app-container > header::after { display: none; }
  .app-container .site-nav .site-nav-panel {
    top: calc(100% + 10px); border-radius: 22px; overflow: hidden;
    border: 1px solid color-mix(in srgb, var(--text-primary) 10%, transparent);
  }
  .app-container .site-nav .site-nav-panel::before { top: -30px; height: 30px; }
}
@media (max-width: 768px) {
  .app-container > header {
    margin: max(6px, env(safe-area-inset-top)) 8px 0; padding-top: 0;
    height: calc(var(--header-h) - 10px); border-radius: 999px;
    border: 1px solid color-mix(in srgb, var(--text-primary) 12%, transparent);
    background: color-mix(in srgb, var(--bg-primary) 86%, transparent);
    -webkit-backdrop-filter: saturate(180%) blur(20px); backdrop-filter: saturate(180%) blur(20px);
  }
}
/* Tools flyout: the drop card had been squeezed to 150 px by a later .site-nav-visual width,
   so its formats ran into the dashed edge and the caption wrapped. */
.site-nav .site-nav-visual.site-nav-drop { width: 250px; }
@media (max-width: 1100px) { .site-nav .site-nav-visual.site-nav-drop { width: 210px; } }
.site-nav .site-nav-drop-box { padding: 14px 16px; text-align: center; }
.site-nav .site-nav-drop-box i { line-height: 1.4; }
@media (max-width: 768px) { .app-container > header #sidebarToggle { border-radius: 999px; border-color: color-mix(in srgb, var(--text-primary) 14%, transparent); } }
/* Overlay settings on phones (2026-10-06): a bottom sheet rather than a menu pinned to a lane. */
@media (max-width: 999px) {
  body > .ed-ov-menu { left: 8px !important; right: 8px; width: auto !important; top: auto !important; bottom: calc(env(safe-area-inset-bottom) + 76px); max-height: 40vh; border-radius: 16px; z-index: 3000; }
}
/* Crop on the picture (2026-10-06). */
.crop-editor { position: absolute; z-index: 40; touch-action: none; }
.crop-editor .ce-box { position: absolute; box-shadow: 0 0 0 9999px rgba(0,0,0,.58); outline: 1.5px solid #fff; cursor: move; }
.crop-editor .ce-grid { position: absolute; inset: 0; pointer-events: none; background:
  linear-gradient(to right, transparent calc(33.33% - .5px), rgba(255,255,255,.45) calc(33.33% - .5px), rgba(255,255,255,.45) calc(33.33% + .5px), transparent calc(33.33% + .5px), transparent calc(66.66% - .5px), rgba(255,255,255,.45) calc(66.66% - .5px), rgba(255,255,255,.45) calc(66.66% + .5px), transparent calc(66.66% + .5px)),
  linear-gradient(to bottom, transparent calc(33.33% - .5px), rgba(255,255,255,.45) calc(33.33% - .5px), rgba(255,255,255,.45) calc(33.33% + .5px), transparent calc(33.33% + .5px), transparent calc(66.66% - .5px), rgba(255,255,255,.45) calc(66.66% - .5px), rgba(255,255,255,.45) calc(66.66% + .5px), transparent calc(66.66% + .5px)); }
.crop-editor .ce-h { position: absolute; width: 22px; height: 22px; margin: -11px 0 0 -11px; }
.crop-editor .ce-h::after { content: ""; position: absolute; inset: 6px; border-radius: 3px; background: #fff; box-shadow: 0 1px 4px rgba(0,0,0,.5); }
.crop-editor .ce-nw { left: 0; top: 0; cursor: nwse-resize; } .crop-editor .ce-n { left: 50%; top: 0; cursor: ns-resize; } .crop-editor .ce-ne { left: 100%; top: 0; cursor: nesw-resize; }
.crop-editor .ce-e { left: 100%; top: 50%; cursor: ew-resize; } .crop-editor .ce-se { left: 100%; top: 100%; cursor: nwse-resize; } .crop-editor .ce-s { left: 50%; top: 100%; cursor: ns-resize; }
.crop-editor .ce-sw { left: 0; top: 100%; cursor: nesw-resize; } .crop-editor .ce-w { left: 0; top: 50%; cursor: ew-resize; }
@media (pointer: coarse) { .crop-editor .ce-h { width: 34px; height: 34px; margin: -17px 0 0 -17px; } .crop-editor .ce-h::after { inset: 11px; } }
.crop-editor .ce-size { position: absolute; left: 50%; bottom: 8px; transform: translateX(-50%); padding: 3px 8px; border-radius: 999px; background: rgba(0,0,0,.6); color: #fff; font: 600 11px/1.2 'JetBrains Mono', ui-monospace, monospace; pointer-events: none; }
.crop-editor .ce-bar { position: absolute; right: 8px; top: 8px; display: flex; gap: 6px; z-index: 2; }
.crop-editor .ce-bar button { height: 30px; padding: 0 12px; border: 0; border-radius: 999px; background: rgba(20,20,24,.85); color: #fff; font: 600 12px/1 var(--font-body); cursor: pointer; backdrop-filter: blur(8px); }
.crop-editor .ce-bar .ce-done { background: var(--accent); }
.crop-edit-btn.is-on { filter: brightness(1.15); }
.crop-fine > summary { cursor: pointer; font-size: 0.76rem; color: var(--text-secondary); }
/* Handles sit just inside the box: at the frame's edge a centred handle was half outside the
   preview card, which clips, so it could not be grabbed. */
.crop-editor .ce-h { margin: 0; }
.crop-editor .ce-nw { left: 0; top: 0; } .crop-editor .ce-ne { left: auto; right: 0; top: 0; }
.crop-editor .ce-se { left: auto; right: 0; top: auto; bottom: 0; } .crop-editor .ce-sw { left: 0; top: auto; bottom: 0; }
.crop-editor .ce-n { left: 50%; top: 0; margin-left: -11px; } .crop-editor .ce-s { left: 50%; top: auto; bottom: 0; margin-left: -11px; }
.crop-editor .ce-e { left: auto; right: 0; top: 50%; margin-top: -11px; } .crop-editor .ce-w { left: 0; top: 50%; margin-top: -11px; }
@media (pointer: coarse) { .crop-editor .ce-n, .crop-editor .ce-s { margin-left: -17px; } .crop-editor .ce-e, .crop-editor .ce-w { margin-top: -17px; } }
/* Smart Calibrate suggestion (2026-10-06). */
.calib-suggest { position: absolute; left: 50%; top: 12px; transform: translateX(-50%); z-index: 30; display: flex; align-items: center; gap: 10px; max-width: calc(100% - 24px); padding: 8px 8px 8px 14px; border-radius: 16px; background: rgba(16,17,22,.92); border: 1px solid rgba(255,255,255,.12); color: #fff; box-shadow: 0 10px 30px rgba(0,0,0,.4); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); animation: csIn .35s cubic-bezier(.2,.8,.2,1) both; }
.calib-suggest.is-out { opacity: 0; transform: translate(-50%, -6px); transition: opacity .2s, transform .2s; }
@keyframes csIn { from { opacity: 0; transform: translate(-50%, -8px); } to { opacity: 1; transform: translateX(-50%); } }
.calib-suggest .cs-ico { color: var(--accent); font-size: 15px; }
.calib-suggest .cs-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.calib-suggest .cs-text b { font-size: 13px; }
.calib-suggest .cs-text small { font-size: 11px; color: rgba(255,255,255,.7); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 360px; }
.calib-suggest .cs-go { flex: none; height: 32px; padding: 0 14px; border: 0; border-radius: 999px; background: var(--accent); color: #fff; font: 600 12.5px/1 var(--font-body); cursor: pointer; }
.calib-suggest .cs-no { flex: none; width: 28px; height: 28px; border: 0; border-radius: 50%; background: rgba(255,255,255,.1); color: #fff; cursor: pointer; }
@media (max-width: 768px) { .calib-suggest { top: auto; bottom: 150px; } .calib-suggest .cs-text small { display: none; } }
/* Thumbnail canvases inside chips (2026-10-06): the global `canvas { touch-action: pan-y }` meant
   for the preview also caught these, so a sideways swipe on the effects strip never scrolled it
   on a phone. Taps go to the chip; the strip scrolls. */
button canvas, .mode-preset-chips canvas, .ed-lib-thumbs canvas, .nv-add-grid canvas, .lut-swatch-row canvas { touch-action: auto; pointer-events: none; }
/* Phones (2026-10-06): the preview card was forced to 100% of the area's height, so anything
   under it (the photo roll strip) made the column taller than the area — and centring it then
   pushed the top (the quick controls over the photo) above the edge, where nothing could scroll
   to it. The card now shares the height with what sits below it, from the top. */
@media (max-width: 768px) {
  body.has-source .video-area { justify-content: flex-start; }
  body.has-source .video-area > .canvas-wrapper { flex: 1 1 0; min-height: 0; height: auto; }
  /* The photo's result line: one quiet line in the UI font, not two lines of 12 px mono. */
  #photoResultBar { font: 600 12px/1.3 var(--font-body); padding: 7px 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; border-top-width: 1px; background: rgba(0,0,0,.72); }
}
/* Photo roll on phones (2026-10-06): one compact row — thumbnails, +, and Save photo — instead
   of a ~200 px card that shrank the photo. */
@media (max-width: 768px) {
  .photo-roll { grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 8px; margin-top: 8px; padding: 6px 8px; border-radius: 14px; }
  .photo-roll .pr-strip { gap: 6px; }
  .photo-roll .pr-thumb, .photo-roll .pr-add { width: 44px; height: 44px; border-radius: 9px; font-size: 20px; }
  .photo-roll .pr-actions { gap: 6px; }
  .photo-roll .pr-note { display: none; }
  .photo-roll .pr-export { height: 40px; padding: 0 16px; font-size: 14px; border-radius: 999px; }
}
/* Photo mode on phones: the video transport (T, fullscreen) has nothing to do and showed faintly
   behind the result line. */
@media (max-width: 768px) { body.is-photo .mobile-player-bar { display: none !important; } }
/* Preview picker on phones: three effects across, a compact sheet. */
@media (max-width: 768px) {
  .pv-picker.is-sheet { max-height: 46vh; }
  .pv-grid.has-thumbs { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; padding: 2px 10px 12px; }
  .pv-grid.has-thumbs .pv-opt { padding: 4px; border-radius: 10px; font-size: 11.5px; }
  .pv-grid.has-thumbs .pv-opt canvas { border-radius: 7px; }
}

/* ── Resets where the setting is (2026-10-07) ─────────────────────────────── */
/* A slider that's off its default shows its value in the accent colour, with a ↺ button
   just before it (main.js syncQuickResetMarks). */
.value-display.qr-changed { color: var(--accent); }
.qr-reset {
  border: 0; background: transparent; color: var(--accent);
  width: 20px; height: 20px; margin-left: auto; margin-right: 2px; padding: 0;
  border-radius: 50%; cursor: pointer; font-size: 0.8rem; line-height: 20px; flex: none;
}
.qr-reset::before { content: '\21BA'; }
.qr-reset:hover, .qr-reset:focus-visible { background: color-mix(in srgb, var(--accent) 16%, transparent); }
.qr-reset[hidden] { display: none; }
.qr-reset + .value-display { margin-left: 0; }
/* "↺ 3" on a section heading: resets what was changed in that section. The count comes from
   data-n through ::after, so the heading's textContent (read as the section name) stays clean. */
.control-section > h3 .qr-section {
  margin-left: auto;
  margin-right: 8px;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--accent);
  border-radius: 999px;
  font: 600 0.68rem/1 inherit;
  letter-spacing: 0;
  padding: 3px 8px;
  cursor: pointer;
  text-transform: none;
}
.control-section > h3 .qr-section::before { content: '\21BA  ' attr(data-n); white-space: pre; }
.control-section > h3 .qr-section:hover { background: color-mix(in srgb, var(--accent) 24%, transparent); }
.control-section > h3 .qr-section[hidden] { display: none; }

/* ── Applied: what's on (2026-10-07) ──────────────────────────────────────── */
.applied-stack {
  display: flex; flex-direction: column; gap: 4px;
  padding: 10px; border: 1px solid var(--border); border-radius: var(--radius-md, 10px);
  background: var(--bg-secondary);
}
.applied-stack[hidden] { display: none; }
.applied-stack-head { display: flex; align-items: center; gap: 8px; margin-bottom: 2px; }
.applied-stack-head > span { font-size: 0.74rem; font-weight: 700; color: var(--text-primary); }
.applied-stack-head > b {
  font: 600 0.64rem/1 'JetBrains Mono', monospace; color: var(--text-secondary);
  background: var(--bg-tertiary); border-radius: 999px; padding: 3px 7px;
}
.applied-compare {
  margin-left: auto; border: 1px solid var(--border); background: var(--bg-primary);
  color: var(--text-primary); border-radius: 999px; padding: 5px 11px;
  font: 600 0.7rem/1 inherit; cursor: pointer; touch-action: none; user-select: none; -webkit-user-select: none;
}
.applied-compare:hover:not(:disabled) { border-color: var(--accent); }
.applied-compare.is-active { background: var(--accent); border-color: var(--accent); color: #fff; }
.applied-compare:disabled { opacity: 0.45; cursor: default; }
.applied-stack-row {
  display: flex; align-items: center; gap: 9px; padding: 6px 6px 6px 8px;
  border-radius: var(--radius-sm); border: 1px solid transparent; cursor: pointer;
}
.applied-stack-row:hover { background: var(--bg-tertiary); border-color: var(--border); }
.applied-stack-row:focus-visible { outline: none; border-color: var(--accent); }
.applied-stack-ico {
  flex: none; width: 26px; height: 26px; border-radius: 7px; display: grid; place-items: center;
  background: color-mix(in srgb, var(--accent) 14%, var(--bg-tertiary)); color: var(--accent); font-size: 0.82rem;
}
.applied-stack-text { display: flex; flex-direction: column; min-width: 0; flex: 1; line-height: 1.25; }
.applied-stack-text b { font-size: 0.76rem; font-weight: 600; color: var(--text-primary); }
.applied-stack-text small { font-size: 0.68rem; color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.applied-stack-eye, .applied-stack-reset {
  flex: none; width: 28px; height: 28px; border-radius: 50%; display: grid; place-items: center;
  border: 1px solid var(--border); background: var(--bg-primary); color: var(--text-secondary); cursor: pointer; font-size: 14px; line-height: 1;
}
.applied-stack-eye[aria-pressed="true"] { color: var(--accent); }
.applied-stack-eye:hover, .applied-stack-reset:hover { color: var(--accent); border-color: var(--accent); }
.applied-stack-row.is-bypassed .applied-stack-ico { background: var(--bg-tertiary); color: var(--text-secondary); }
.applied-stack-row.is-bypassed .applied-stack-text b { color: var(--text-secondary); text-decoration: line-through; text-decoration-color: color-mix(in srgb, var(--text-secondary) 60%, transparent); }
.applied-stack-all {
  align-self: flex-start; margin-top: 4px; border: 0; background: none; padding: 4px 2px;
  color: var(--text-secondary); font: 600 0.7rem/1 inherit; cursor: pointer;
}
.applied-stack-all:hover { color: var(--accent); }
.applied-stack .applied-empty { padding: 4px 2px; }
/* A clear "you're looking at the original" while the compare is held. */
.canvas-wrapper.is-comparing::after {
  content: 'Original'; position: absolute; top: 12px; left: 50%; transform: translateX(-50%);
  padding: 4px 10px; border-radius: 999px; background: rgba(0, 0, 0, 0.7); color: #fff;
  font: 600 0.72rem/1.2 inherit; letter-spacing: 0.04em; pointer-events: none; z-index: 5;
}
/* Rows: the setting's name gets room before the value does ("For…" was Format). */
.applied-row-label { flex: 1 1 auto; min-width: 5.5em; }
.applied-row-value { flex: 0 1 auto; max-width: 46%; min-width: 0; }
.applied-row-value > * { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── Sound: voice clean-up + EQ curve (2026-10-07) ────────────────────────── */
.sound-clean { margin-top: 10px; padding: 10px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg-secondary); }
.sound-clean-head, .sound-eq-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.sound-clean-head b, .sound-eq-head b { font-size: 0.76rem; color: var(--text-primary); }
.sound-clean-auto { padding: 5px 10px; font-size: 0.72rem; width: auto; flex: none; margin: 0; }
.sound-eq { margin-top: 10px; }
.sound-eq-curve { display: block; width: 100%; height: 74px; margin-top: 6px; border-radius: var(--radius-sm); background: var(--bg-secondary); border: 1px solid var(--border); }
.qr-inline { border: 0; background: none; color: var(--accent); font: 600 0.72rem/1 inherit; cursor: pointer; padding: 4px 2px; }
.qr-inline[hidden] { display: none; }

/* ── Editor: Applied pane, monitor Compare, no calibrate pill over the picture (2026-10-07) ── */
.ed-mon-right { justify-self: end; display: flex; align-items: center; gap: 10px; }
.ed-mon-compare {
  height: 26px; padding: 0 10px; border-radius: 999px; border: 1px solid var(--border);
  background: var(--bg-secondary); color: var(--text-secondary); font: 600 0.7rem/1 inherit;
  cursor: pointer; touch-action: none; user-select: none; -webkit-user-select: none;
}
.ed-mon-compare:hover { color: var(--text-primary); border-color: var(--accent); }
.ed-mon-compare.is-active { background: var(--accent); border-color: var(--accent); color: #fff; }
.ed-applied { display: flex; flex-direction: column; gap: 10px; overflow-y: auto; }
.ed-applied[hidden] { display: none; }
.ed-applied .applied-stack { background: transparent; border: 0; padding: 0; }
/* The Smart Calibrate suggestion sat right over the editor monitor's picture; it is still
   one click away in All settings. */
body.editor-full .calib-suggest { display: none !important; }

/* ── Desktop settings tabs: all six always on screen (2026-10-07) ─────────────
   With Applied as a sixth tab, the one-row segmented control (min-width: max-content per
   tab) needed 356–470 px in a 354 px sidebar (274 px at 1024 wide). It scrolled sideways with
   no scrollbar, so Applied sat past the edge: unreachable with a mouse. One row can't hold six
   labels without cutting "Advanced", so desktop gets a 3×2 grid with icons back. Phones keep
   their swipeable row (max-width: 768px rules above). */
@media (min-width: 769px) {
  .sidebar .sidebar-tabs { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); overflow: visible; }
  .sidebar .sidebar-tabs .sidebar-tab { min-width: 0; }
  .sidebar .sidebar-tab .tab-icon { display: block; }
}

/* ── Audio › Beat sync (2026-10-07) ───────────────────────────────────────── */
.beat-status { font-size: 0.72rem; color: var(--text-secondary); padding: 7px 9px; border-radius: var(--radius-sm); background: var(--bg-secondary); border: 1px solid var(--border); }
.beat-status.is-found { color: var(--text-primary); border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); }
.beat-status.is-found::before { content: '\25CF  '; color: var(--accent); white-space: pre; }
.beat-presets { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.beat-presets .btn { width: auto; flex: 1 1 auto; padding: 6px 10px; font-size: 0.72rem; margin: 0; }

/* ── Source › Stabilisation presets (2026-10-07) ──────────────────────────── */
.stab-presets { display: flex; flex-wrap: wrap; gap: 6px; }
.stab-preset {
  border: 1px solid var(--border); background: var(--bg-secondary); color: var(--text-secondary);
  border-radius: 999px; padding: 6px 11px; font: 600 0.72rem/1 inherit; cursor: pointer;
}
.stab-preset:hover { color: var(--text-primary); border-color: color-mix(in srgb, var(--accent) 50%, var(--border)); }
.stab-preset.is-on { background: color-mix(in srgb, var(--accent) 18%, var(--bg-secondary)); border-color: var(--accent); color: var(--text-primary); }

/* ── Editor › Soundtrack: hear / A-B / meter / curve (2026-10-07) ────────── */
.ed-sound-monitor { display: grid; grid-template-columns: auto auto 1fr; align-items: center; gap: 8px; margin: 6px 0 4px; }
.ed-sound-ab {
  height: 26px; padding: 0 10px; border-radius: 999px; border: 1px solid var(--border);
  background: var(--bg-secondary); color: var(--text-secondary); font: 600 0.7rem/1 inherit; cursor: pointer;
  touch-action: none; user-select: none; -webkit-user-select: none;
}
.ed-sound-ab:hover:not(:disabled) { color: var(--text-primary); border-color: var(--accent); }
.ed-sound-ab.is-active { background: var(--accent); border-color: var(--accent); color: #fff; }
.ed-sound-ab:disabled { opacity: 0.45; cursor: default; }
.ed-sound-meter { position: relative; height: 8px; border-radius: 4px; background: var(--bg-secondary); border: 1px solid var(--border); overflow: hidden; }
.ed-meter-rms { position: absolute; inset: 0 auto 0 0; width: 0; background: linear-gradient(90deg, #22c55e 0 70%, #eab308 70% 88%, #ef4444 88%); background-size: 100% 100%; transition: width 60ms linear; }
.ed-meter-peak { position: absolute; top: 0; bottom: 0; left: 0; width: 2px; background: var(--text-primary); }
.ed-sound-meter.is-hot { border-color: #ef4444; }
.ed-eq-curve { height: 60px; margin: 2px 0 6px; }
