  :root {
    --bg: #0a0a0a;
    --surface: #111318;
    --surface-2: #161922;
    --border: #1e2433;
    --border-strong: #2a3350;
    --text: #f5f5f5;
    --text-dim: #94a3b8;
    --text-faint: #64748b;
    --brand: #2563eb;
    --brand-hover: #3b82f6;
    --brand-light: #60a5fa;
    --good: #4ade80;
    --bad:  #f87171;
    --warn: #fbbf24;
    --r: 12px;
    --r-sm: 8px;
    /* Two-tier max-widths.
       - --max-w-app  caps the wide surfaces (header, results grid)
         so a 1080p / 1440p monitor doesn't sprawl edge-to-edge but
         still gets meaningful real estate for clip cards.
       - --max-w-form caps form-shaped screens (idle + progress +
         error). Forms read better narrow; readers track shorter
         line lengths. .narrow centers these inside the wider main.
    */
    --max-w-app:  1200px;
    --max-w-form: 640px;
  }
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
  html, body { background: var(--bg); color: var(--text); }
  /* Mobile safety net — without this, any single child element
     wider than the viewport (long unbroken project names from
     iOS PHAssetID-style filenames, runaway badges, etc.) lets
     the user scroll horizontally and the headline / lead text
     get cut off on the left. */
  html, body { overflow-x: hidden; max-width: 100%; }
  body {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    line-height: 1.55;
    -webkit-font-smoothing: antialiased;
    min-height: 100vh;
    min-height: 100svh;
    /* viewport-fit=cover extends content edge-to-edge on iPhones,
       which means the page draws UNDER the notch / status bar /
       home indicator without these compensating insets. Bottom
       was already handled; top was missing — lead paragraph was
       sitting behind the time/signal icons on the iPhone screenshot. */
    padding-top: env(safe-area-inset-top);
    padding-bottom: env(safe-area-inset-bottom);
  }
  a { color: var(--brand-light); text-decoration: none; }
  a:hover { color: var(--brand-hover); }
  button { font: inherit; cursor: pointer; border: 0; background: none; color: inherit; }
  input, select, textarea { font: inherit; color: inherit; }

  /* ── Header ────────────────────────────────────────────────────── */
  .hdr {
    display: flex; align-items: center; justify-content: space-between;
    max-width: var(--max-w-app); margin: 0 auto;
    padding: 18px 20px;
  }
  /* Brand: inline icon + "FrameIQ" text. The icon mark is the same
     visual language as /brand/frameiq_icon.svg + horizontal.svg
     (the social/favicon assets); rendered inline + currentColor so
     it adapts to any theme. The text wrapper is a single <span>,
     not anonymous-text + .iq, so flex doesn't gap them as separate
     items. */
  .hdr-brand {
    display: inline-flex; align-items: center; gap: 9px;
    font-weight: 700; letter-spacing: -0.02em; font-size: 18px;
    color: var(--text);
  }
  .hdr-brand:hover { color: var(--text); }
  .hdr-brand .iq { color: var(--brand-light); }
  .hdr-brand .brand-mark {
    width: 22px; height: 22px;
    flex: 0 0 auto;
    color: var(--brand-light);
    transition: transform 0.25s cubic-bezier(0.2, 0.8, 0.2, 1);
  }
  .hdr-brand:hover .brand-mark { transform: rotate(-6deg); }
  .hdr-tag { font-size: 11px; color: var(--text-faint); padding: 3px 7px;
    border: 1px solid var(--border); border-radius: 999px; }
  .hdr-right { display: flex; align-items: center; gap: 10px; }

  /* ── Auth slot (header) ─────────────────────────────────── */
  .auth-signin {
    font-size: 13px; font-weight: 500;
    padding: 7px 12px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 999px;
    color: var(--text-dim);
    cursor: pointer;
    transition: border-color 0.15s, color 0.15s, background 0.15s;
  }
  .auth-signin:hover { border-color: var(--brand-border); color: var(--brand-light); background: var(--brand-dim); }
  .auth-user {
    position: relative;
  }
  .auth-user-trigger {
    display: inline-flex; align-items: center; gap: 8px;
    padding: 4px 10px 4px 4px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 999px;
    cursor: pointer;
    color: var(--text-dim);
    font-size: 13px;
    transition: border-color 0.15s, background 0.15s;
  }
  .auth-user-trigger:hover { border-color: var(--border-strong); background: var(--surface-2); }
  .auth-avatar {
    width: 24px; height: 24px; border-radius: 50%;
    background: var(--brand);
    color: #fff;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 11px; font-weight: 600;
    overflow: hidden;
  }
  .auth-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
  .auth-name { max-width: 130px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .auth-menu {
    position: absolute; right: 0; top: calc(100% + 6px);
    min-width: 200px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
    padding: 6px;
    box-shadow: 0 8px 24px rgba(0,0,0,0.35);
    z-index: 50;
  }
  .auth-menu-item {
    display: block; width: 100%;
    padding: 8px 10px;
    font-size: 13px; color: var(--text-dim);
    text-align: left;
    background: none; border: 0; border-radius: 6px;
    cursor: pointer;
  }
  .auth-menu-item:hover { background: var(--surface-2); color: var(--text); }
  .auth-menu-meta {
    padding: 8px 10px 10px;
    border-bottom: 1px solid var(--border);
    margin-bottom: 4px;
  }
  .auth-menu-meta .am-name { font-size: 13px; color: var(--text); font-weight: 500; }
  .auth-menu-meta .am-email { font-size: 11px; color: var(--text-faint); margin-top: 2px; word-break: break-all; }

  @media (max-width: 540px) {
    .auth-name { display: none; } /* phone: avatar only */
    .hdr-tag { display: none; }   /* free up space */
  }

  /* ── Layout ────────────────────────────────────────────────────── */
  /* main caps at the wider tier so the results grid can spread on
     desktop. Form-shaped screens (idle/progress/error) wear .narrow,
     which re-centers them inside main at the form max-width. */
  main {
    max-width: var(--max-w-app);
    margin: 0 auto;
    padding: 12px 20px 80px;
  }
  .narrow {
    max-width: var(--max-w-form);
    margin-left: auto;
    margin-right: auto;
  }
  .card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r);
    padding: 22px;
  }
  .card + .card { margin-top: 14px; }
  .row { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }

  h1 {
    /* clamp scales smoothly: 26px on phones up to 44px on 1440px+ */
    font-size: clamp(26px, 4.2vw, 44px);
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1.15;
    margin-bottom: 12px;
  }
  h2 { font-size: clamp(18px, 1.8vw, 22px); font-weight: 600; letter-spacing: -0.01em; margin-bottom: 8px; }
  h3 { font-size: 15px; font-weight: 600; }
  .lead { color: var(--text-dim); font-size: clamp(15px, 1.2vw, 17px); margin-bottom: 18px; }
  .small { font-size: 13px; color: var(--text-dim); }
  .smaller { font-size: 12px; color: var(--text-faint); }

  /* ── Tabs ──────────────────────────────────────────────────────── */
  .tabs {
    display: grid; grid-template-columns: 1fr 1fr; gap: 6px;
    background: var(--surface-2);
    padding: 4px;
    border-radius: var(--r-sm);
    margin-bottom: 16px;
  }
  .tab {
    padding: 11px 12px; border-radius: 6px;
    font-size: 14px; font-weight: 500;
    color: var(--text-dim);
    text-align: center;
    transition: background 0.15s, color 0.15s;
  }
  .tab[aria-selected="true"] {
    background: var(--surface); color: var(--text);
    box-shadow: 0 0 0 1px var(--border);
  }

  /* ── Form bits ─────────────────────────────────────────────────── */
  .label { font-size: 13px; color: var(--text-dim); margin-bottom: 6px; display: block; }
  .label-hint { color: var(--text-faint); font-size: 12px; font-weight: 400; }
  .label-hint b { color: var(--text-dim); font-weight: 500; }
  .input, .file-zone {
    width: 100%;
    padding: 13px 14px;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
    color: var(--text);
    font-size: 15px;
    transition: border-color 0.15s;
  }
  .input:focus, .file-zone:focus-within {
    outline: 0; border-color: var(--brand);
  }
  .file-zone {
    display: flex; align-items: center; justify-content: center;
    flex-direction: column; gap: 8px;
    padding: 28px 16px;
    text-align: center;
    cursor: pointer;
    border-style: dashed;
    min-height: 120px;
  }
  .file-zone.has-file { border-style: solid; border-color: var(--good); }
  /* Drag-hover affordance — shows the drop zone is active while a
     file is being dragged over the page. Without this the dragover
     UX is silent and users wonder if dropping will actually work. */
  .file-zone.is-dragover {
    border-style: solid;
    border-color: var(--brand);
    background: color-mix(in oklab, var(--brand) 10%, transparent);
  }
  .file-zone .file-name { color: var(--text); font-weight: 500; }
  .file-zone .file-hint { color: var(--text-faint); font-size: 13px; }
  .file-zone input[type="file"] { display: none; }

  .helper {
    font-size: 12px; color: var(--text-faint);
    margin-top: 8px; line-height: 1.5;
  }
  .helper b { color: var(--text-dim); font-weight: 500; }

  .name-input { margin-bottom: 14px; }
  .name-input .input { padding: 11px 12px; font-size: 14px; }

  /* ── CTA button ────────────────────────────────────────────────── */
  .btn {
    display: inline-flex; align-items: center; justify-content: center;
    gap: 8px;
    min-height: 46px;
    padding: 12px 18px;
    border-radius: var(--r-sm);
    font-size: 15px; font-weight: 600;
    background: var(--brand); color: white;
    transition: background 0.15s, transform 0.05s, opacity 0.15s;
    width: 100%;
  }
  .btn:hover:not(:disabled) { background: var(--brand-hover); }
  .btn:active:not(:disabled) { transform: scale(0.99); }
  .btn:disabled { opacity: 0.5; cursor: not-allowed; }
  .btn-ghost {
    background: var(--surface-2); color: var(--text);
    box-shadow: 0 0 0 1px var(--border);
  }
  .btn-ghost:hover:not(:disabled) { background: var(--border); }
  .btn-sm { min-height: 38px; padding: 8px 14px; font-size: 13px; width: auto; }
  .btn-row { display: flex; gap: 8px; flex-wrap: wrap; }
  .btn-row .btn { flex: 1; }

  /* ── Progress screen ────────────────────────────────────────── */
  .progress-wrap {
    background: var(--surface-2);
    border-radius: 999px;
    height: 8px;
    overflow: hidden;
    margin: 14px 0 8px;
  }
  .progress-bar {
    height: 100%;
    background: linear-gradient(90deg, var(--brand) 0%, var(--brand-light) 100%);
    width: 0%;
    transition: width 0.4s ease;
  }
  .stage-list { display: grid; gap: 6px; margin-top: 10px; }
  /* Manual-mode flow skips transcription + clip selection — hide
     those rows entirely so the user doesn't see two stages they
     never enter. JS toggles body.is-manual-mode at submit time
     (see _applyModeToProgress). */
  body.is-manual-mode .stage[data-ai-only] { display: none; }
  .stage {
    display: flex; align-items: center; gap: 10px;
    font-size: 14px; color: var(--text-faint);
    padding: 6px 0;
  }
  .stage .dot {
    width: 8px; height: 8px; border-radius: 50%;
    background: var(--border-strong);
    flex-shrink: 0;
  }
  .stage.done { color: var(--text-dim); }
  .stage.done .dot { background: var(--good); }
  .stage.active { color: var(--text); font-weight: 500; }
  .stage.active .dot {
    background: var(--brand-light);
    box-shadow: 0 0 0 4px rgba(96,165,250,0.18);
  }

  /* ── Results — clip cards ──────────────────────────────────── */
  .results-head {
    display: flex; align-items: baseline; justify-content: space-between;
    flex-wrap: wrap; gap: 8px;
    margin-bottom: 4px;
  }
  .results-head-text { min-width: 0; flex: 1 1 auto; }
  .results-subtitle {
    margin: 2px 0 0;
    color: var(--text-dim);
    overflow: hidden; text-overflow: ellipsis;
    white-space: nowrap; max-width: 100%;
  }
  .clip-grid { display: grid; gap: 14px; }
  .clip-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r);
    overflow: hidden;
  }
  .clip-card video {
    display: block;
    width: 100%; max-width: 100%;
    background: #000;
    aspect-ratio: 9 / 16;
    object-fit: contain;
    max-height: 70vh;
  }
  .clip-meta { padding: 14px 16px 16px; }
  .clip-title {
    font-size: 15px; font-weight: 600;
    margin-bottom: 6px;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  .clip-bits {
    display: flex; gap: 8px; flex-wrap: wrap;
    margin-bottom: 12px;
    font-size: 12px;
    color: var(--text-faint);
  }
  .badge {
    display: inline-flex; align-items: center;
    padding: 2px 8px; border-radius: 999px;
    background: var(--surface-2);
    border: 1px solid var(--border);
    font-size: 11px;
    color: var(--text-dim);
  }
  .badge.good {
    background: rgba(74, 222, 128, 0.10);
    border-color: rgba(74, 222, 128, 0.30);
    color: var(--good);
  }
  /* Score badge — exposes the existing combinedScore (0-1) as a
     human-readable 0–10 with a star glyph. Not a vanity metric:
     directly drives which clip the user picks first. */
  .badge.score {
    background: rgba(96, 165, 250, 0.10);
    border-color: rgba(96, 165, 250, 0.30);
    color: var(--brand-light);
    font-variant-numeric: tabular-nums;
    font-weight: 600;
  }
  .badge.score::before {
    content: "★";
    margin-right: 4px;
    opacity: 0.85;
  }
  .badge.score.high {
    background: rgba(251, 191, 36, 0.12);
    border-color: rgba(251, 191, 36, 0.35);
    color: #fcd34d;
  }
  /* Top-pick badge — gold-tinted, applied to a single clip in
     the batch so users have a clear "start here" cue when
     several clips would otherwise read as equally Recommended. */
  .badge.top-pick {
    background: rgba(251, 191, 36, 0.16);
    border-color: rgba(251, 191, 36, 0.50);
    color: #fde68a;
    font-weight: 600;
    letter-spacing: 0.01em;
  }
  .badge.score .score-suffix {
    opacity: 0.55;
    font-weight: 500;
    margin-left: 1px;
  }
  /* Inline label so mobile users (no hover → no tooltip) still
     get context. Visually subordinate to the number itself. */
  .badge.score .score-label {
    font-weight: 500;
    opacity: 0.8;
    margin-right: 4px;
    letter-spacing: 0.02em;
  }

  /* ── Touch tooltip popover ──────────────────────────────────
     Browsers don't show title= tooltips on touch — long-press
     surfaces the OS context menu instead. We show the title text
     in a small popover when the user taps an element marked with
     [data-tip] (mirrors the title attribute). One popover at a
     time, dismisses on the next tap or after 3 seconds. */
  .tip-popover {
    position: fixed;
    z-index: 200;
    max-width: min(280px, calc(100vw - 24px));
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: 8px;
    padding: 10px 12px;
    font-size: 13px;
    line-height: 1.45;
    color: var(--text);
    box-shadow: 0 12px 36px rgba(0,0,0,0.45);
    animation: tip-pop-in 0.14s ease-out both;
    pointer-events: none;
  }
  @keyframes tip-pop-in {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: translateY(0);    }
  }

  /* ── Error / generic banner ─────────────────────────────────── */
  .banner {
    border-radius: var(--r-sm);
    padding: 12px 14px;
    font-size: 14px;
    margin-bottom: 14px;
    line-height: 1.45;
  }
  .banner.err {
    background: rgba(248, 113, 113, 0.08);
    border: 1px solid rgba(248, 113, 113, 0.30);
    color: #fecaca;
  }
  .banner.warn {
    background: rgba(251, 191, 36, 0.08);
    border: 1px solid rgba(251, 191, 36, 0.30);
    color: #fde68a;
  }
  .banner b { color: inherit; }

  /* ── AI effects disclosure (results-settings card) ───────────
     Four independent toggles for the AI-polish features the
     export pipeline applies. Defaults derive from project.mode
     and reflect into the checkboxes once the project loads.
     Manual-mode users see this open by default with all four
     off; auto-mode users see it collapsed with "All on".      */
  .fx-disclosure {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
  }
  .fx-summary {
    list-style: none;
    cursor: pointer;
    padding: 10px 12px;
    display: flex; align-items: center; justify-content: space-between;
    gap: 10px;
    user-select: none;
    font-size: 13px;
  }
  .fx-summary::-webkit-details-marker { display: none; }
  .fx-title  { font-weight: 600; }
  .fx-status {
    font-size: 11px;
    color: var(--text-dim);
    text-transform: uppercase;
    letter-spacing: 0.5px;
  }
  .fx-list {
    padding: 4px 12px 12px;
    display: grid; gap: 6px;
    border-top: 1px solid var(--border);
  }
  .fx-row {
    display: flex; align-items: flex-start; gap: 10px;
    padding: 6px 0;
    cursor: pointer;
  }
  .fx-toggle { margin-top: 2px; flex-shrink: 0; cursor: pointer; }
  .fx-label  { display: flex; flex-direction: column; gap: 2px; }
  .fx-name   { font-size: 13px; color: var(--text); }
  .fx-help   { font-size: 11px; color: var(--text-dim); }

  /* ── Settings (caption toggle, etc.) ────────────────────────── */
  .settings { display: grid; gap: 10px; margin-top: 6px; }
  .toggle-row {
    display: flex; align-items: center; justify-content: space-between;
    gap: 14px;
    padding: 10px 12px;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
  }
  .toggle-row .t-text { font-size: 14px; }
  .toggle-row .t-sub { font-size: 12px; color: var(--text-faint); margin-top: 2px; }
  .switch {
    position: relative; display: inline-block;
    width: 42px; height: 24px;
    flex-shrink: 0;
  }
  .switch input { opacity: 0; width: 0; height: 0; }
  .switch .slider {
    position: absolute; inset: 0;
    background: var(--border-strong);
    border-radius: 999px;
    transition: 0.2s;
  }
  .switch .slider::before {
    content: ""; position: absolute;
    width: 18px; height: 18px;
    left: 3px; top: 3px;
    background: var(--text);
    border-radius: 50%;
    transition: 0.2s;
  }
  .switch input:checked + .slider { background: var(--brand); }
  .switch input:checked + .slider::before { transform: translateX(18px); }

  /* ── Segmented control (aspect ratio) ───────────────────────── */
  .seg {
    display: grid; grid-template-columns: repeat(3, 1fr);
    gap: 4px;
    background: var(--surface-2);
    padding: 4px;
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
  }
  .seg-btn {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 2px;
    padding: 9px 6px;
    border-radius: 6px;
    font-size: 14px; font-weight: 600;
    color: var(--text-dim);
    line-height: 1.1;
    transition: background 0.15s, color 0.15s;
    min-height: 48px;
  }
  .seg-btn .seg-sub {
    font-size: 10px; font-weight: 400;
    color: var(--text-faint);
    letter-spacing: 0.02em;
  }
  .seg-btn[aria-checked="true"] {
    background: var(--surface);
    color: var(--text);
    box-shadow: 0 0 0 1px var(--border);
  }
  .seg-btn[aria-checked="true"] .seg-sub { color: var(--text-dim); }
  .seg-btn:not([aria-checked="true"]):hover { color: var(--text); }

  /* ── Download arrow icon (post-export primary button) ───── */
  .dl-icon {
    display: inline-block;
    width: 13px; height: 13px;
    margin-right: 6px;
    background: currentColor;
    -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 3v12m0 0l-4-4m4 4l4-4M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2' fill='none' stroke='currentColor' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/></svg>") center/contain no-repeat;
            mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M12 3v12m0 0l-4-4m4 4l4-4M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2' fill='none' stroke='currentColor' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/></svg>") center/contain no-repeat;
    vertical-align: -2px;
  }

  /* ── Copy-link icon (post-export ghost button) ─────────── */
  .cl-icon {
    display: inline-block;
    width: 12px; height: 12px;
    margin-right: 5px;
    background: currentColor;
    -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M10 13a5 5 0 0 0 7 0l3-3a5 5 0 0 0-7-7l-1 1m1 9a5 5 0 0 0-7 0l-3 3a5 5 0 0 0 7 7l1-1' fill='none' stroke='currentColor' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/></svg>") center/contain no-repeat;
            mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M10 13a5 5 0 0 0 7 0l3-3a5 5 0 0 0-7-7l-1 1m1 9a5 5 0 0 0-7 0l-3 3a5 5 0 0 0 7 7l1-1' fill='none' stroke='currentColor' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/></svg>") center/contain no-repeat;
    vertical-align: -1px;
  }

  /* ── Caption-edit affordance ─────────────────────────────── */
  .clip-caption {
    font-size: 13px;
    color: var(--text-dim);
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
    padding: 9px 11px;
    margin-bottom: 12px;
    line-height: 1.4;
  }
  .clip-caption .cc-preview {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    margin-bottom: 6px;
  }
  .clip-caption .cc-preview.empty { color: var(--text-faint); font-style: italic; }
  /* "Edit clip" trigger — sits between the caption preview and the
     export row. Bumped from a quiet text-dim chip (X10 audit
     flagged it as easily missed) to a brand-tinted pill so users
     understand that editing is the recommended next action before
     they hit Export. Same affordance, more visual weight. */
  .clip-edit-btn {
    display: inline-flex; align-items: center; gap: 7px;
    font-size: 13px; font-weight: 600;
    color: var(--brand-light);
    background: var(--brand-dim);
    border: 1px solid var(--brand-border);
    border-radius: 999px;
    padding: 7px 14px 7px 12px;
    margin-bottom: 12px;
    cursor: pointer;
    transition: border-color 0.15s, color 0.15s, background 0.15s, transform 0.05s;
  }
  .clip-edit-btn::before {
    content: ""; display: inline-block;
    width: 12px; height: 12px;
    background: currentColor;
    -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a1 1 0 0 0 0-1.41l-2.34-2.34a1 1 0 0 0-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z'/></svg>") center/contain no-repeat;
            mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a1 1 0 0 0 0-1.41l-2.34-2.34a1 1 0 0 0-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z'/></svg>") center/contain no-repeat;
    flex-shrink: 0;
  }
  .clip-edit-btn:hover {
    color: var(--text);
    border-color: var(--brand);
    background: rgba(37, 99, 235, 0.18);
  }
  .clip-edit-btn:active { transform: scale(0.98); }

  /* ── Unified edit panel (per clip) ───────────────────────── */
  /* Three sections — caption text, caption position, trim — with
     Save/Cancel at the bottom. Stacks single-column on mobile;
     trim inputs go side-by-side at >= 540px so a phone in
     landscape gets the desktop layout too. */
  .clip-edit-panel {
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
    padding: 12px;
    margin-bottom: 12px;
  }
  .ep-section { margin-bottom: 12px; }
  .ep-section:last-of-type { margin-bottom: 0; }
  .ep-label {
    display: block;
    font-size: 12px; color: var(--text-dim);
    margin-bottom: 6px;
    font-weight: 500;
  }
  /* Header row for sections that need a label + auxiliary action
     (e.g. Trim + Reset). Keeps the label-baseline alignment of
     the standalone label form by inheriting margin-bottom. */
  /* Replay-clip button next to the Caption text label. Small
     inline ghost so it doesn't compete with Save/Cancel. Lets
     users hear what was said without scrolling up to the video. */
  .ep-replay-btn {
    background: var(--surface-2);
    border: 1px solid var(--border);
    color: var(--text-dim);
    font: inherit; font-size: 11px; font-weight: 500;
    padding: 4px 10px;
    border-radius: 999px;
    cursor: pointer;
    transition: border-color 0.15s, color 0.15s, background 0.15s;
  }
  .ep-replay-btn:hover {
    color: var(--brand-light);
    border-color: var(--brand-border);
    background: var(--brand-dim);
  }
  .ep-replay-btn:active { transform: scale(0.97); }
  .ep-replay-btn.playing {
    color: var(--brand-light);
    border-color: var(--brand-border);
    background: var(--brand-dim);
  }

  .ep-row-between {
    display: flex; align-items: baseline; justify-content: space-between;
    margin-bottom: 6px;
  }
  .ep-row-between .ep-label { margin-bottom: 0; }
  /* Small inline action — used by "Reset to original" inside the
     Trim section. Visible only when the clip has actual trim edits
     (hasTrimEdits=true on the clip record). */
  .ep-reset-trim-btn {
    background: none; border: 0; cursor: pointer;
    color: var(--brand-light);
    font: inherit; font-size: 12px; font-weight: 500;
    padding: 2px 4px;
    border-radius: 4px;
    transition: color 0.15s, background 0.15s;
  }
  .ep-reset-trim-btn:hover { color: var(--brand-hover); background: var(--brand-dim); }
  .ep-reset-trim-btn:disabled { opacity: 0.5; cursor: not-allowed; color: var(--text-faint); background: none; }
  .ep-textarea {
    width: 100%;
    min-height: 80px;
    padding: 8px 10px;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 6px;
    color: var(--text);
    font: inherit;
    font-size: 13px;
    line-height: 1.45;
    resize: vertical;
  }
  .ep-textarea:focus { outline: 0; border-color: var(--brand); }
  /* Position segmented control reuses .seg styles from the aspect-
     ratio picker, just smaller. Each button carries a tiny visual
     diagram (a frame with a horizontal "caption bar" at the
     position the button represents) so users can see what they're
     picking without exporting first. */
  .ep-pos-seg .seg-btn {
    min-height: 56px;
    padding: 6px 4px 4px;
    font-size: 11px;
    flex-direction: column; gap: 4px;
  }
  .ep-pos-seg .seg-btn .seg-sub { display: none; } /* simpler than the aspect picker */
  .ep-pos-vis {
    display: block;
    width: 28px; height: 18px;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 3px;
    position: relative;
    color: var(--text-faint);
    transition: border-color 0.15s, color 0.15s;
  }
  .ep-pos-seg .seg-btn[aria-checked="true"] .ep-pos-vis {
    border-color: var(--brand);
    color: var(--brand-light);
  }
  .ep-pos-vis::after {
    content: "";
    position: absolute;
    left: 4px; right: 4px;
    height: 2px;
    background: currentColor;
    border-radius: 1px;
  }
  .ep-pos-vis-top::after    { top: 3px; }
  .ep-pos-vis-middle::after { top: 50%; transform: translateY(-50%); }
  .ep-pos-vis-bottom::after { bottom: 3px; }

  /* ── Caption-style preview chips ─────────────────────────
     Each Default / Bold / Clean button carries a mini "Aa"
     mockup showing what the caption will actually look like
     burnt into the video. The mockups try to match the ffmpeg
     drawtext params (background, outline, color) so users
     pick a style that looks how they expect. */
  /* 6 styles → fixed 3-col grid that wraps to 2 rows. Without
     this override the parent .seg's repeat(3, 1fr) handles it
     anyway, but pinning here keeps the layout stable when more
     presets land later. */
  .ep-style-seg { grid-template-columns: repeat(3, 1fr); }
  .ep-style-seg .seg-btn {
    min-height: 56px;
    padding: 6px 4px 4px;
    font-size: 11px;
    flex-direction: column; gap: 4px;
  }
  .ep-style-seg .seg-btn .seg-sub { display: none; }
  .ep-style-vis {
    display: inline-flex; align-items: center; justify-content: center;
    width: 44px; height: 22px;
    border-radius: 4px;
    background: #0a0a0a;
    overflow: hidden;
  }
  .ep-style-vis span {
    font-weight: 800;
    font-size: 11px;
    line-height: 1;
    letter-spacing: 0.02em;
  }
  /* Default — black box, white text, soft padding (matches the
     ffmpeg "box=1,boxcolor=black@0.55,boxborderw=20" preset). */
  .ep-style-vis-default span {
    color: white;
    background: rgba(0,0,0,0.78);
    padding: 4px 6px;
    border-radius: 3px;
  }
  /* Bold — yellow text on transparent, thick black outline
     (matches "fontcolor=yellow,borderw=6,bordercolor=black"). */
  .ep-style-vis-bold span {
    color: #fbbf24;
    -webkit-text-stroke: 1.4px #000;
    text-stroke: 1.4px #000;
  }
  /* Clean — white text with thin dark outline, no bg
     (matches "fontcolor=white,borderw=3,bordercolor=black@0.85"). */
  .ep-style-vis-clean span {
    color: white;
    -webkit-text-stroke: 0.7px rgba(0,0,0,0.85);
    text-stroke: 0.7px rgba(0,0,0,0.85);
  }
  /* Viral — high-impact yellow-on-black-pill. Mini cue mirrors
     the boxed-yellow look the export produces. */
  .ep-style-vis-viral span {
    color: #fbbf24;
    background: rgba(0,0,0,0.85);
    padding: 4px 7px;
    border-radius: 3px;
    -webkit-text-stroke: 0.5px #000;
    font-weight: 800;
  }

  /* Pop — drawtext per-word highlight cue. White phrase with a
     yellow "current-word" badge to signal the discrete colour
     switch the export produces (vs Highlight's smooth fill). */
  .ep-style-vis-pop span {
    color: #fff;
    background: rgba(0,0,0,0.78);
    padding: 4px 6px;
    border-radius: 3px;
    -webkit-text-stroke: 0.5px #000;
    box-shadow: inset 18px 0 0 -1px #fbbf24;
    font-weight: 800;
  }

  /* Highlight — phrase karaoke, current word fills yellow → white.
     Visual cue uses a half-yellow / half-white "Aa" to hint at
     the colour-transition behaviour the export produces. */
  .ep-style-vis-highlight span {
    background: linear-gradient(90deg, #ffffff 0%, #ffffff 50%, #fbbf24 50%, #fbbf24 100%);
    -webkit-background-clip: text;
            background-clip: text;
    color: transparent;
    -webkit-text-stroke: 0.6px #000;
    font-weight: 800;
  }

  /* Animated — yellow karaoke-per-word. Same visual cue as Bold
     but with an animated "pulse" on the Aa to signal motion. */
  .ep-style-vis-animated span {
    color: #fbbf24;
    background: rgba(0,0,0,0.78);
    padding: 4px 6px;
    border-radius: 3px;
    -webkit-text-stroke: 0.6px #000;
    animation: ep-style-pulse 1.4s ease-in-out infinite;
  }
  @keyframes ep-style-pulse {
    0%, 100% { transform: scale(1);    }
    50%      { transform: scale(1.12); }
  }
  @media (prefers-reduced-motion: reduce) {
    .ep-style-vis-animated span { animation: none; }
  }
  /* Big — ALL CAPS, white, chunky black stroke. MrBeast-style.
     (matches "uppercase: true" + "borderw: 8" in CAPTION_STYLES) */
  .ep-style-vis-big span {
    color: white;
    -webkit-text-stroke: 1.8px #000;
    text-stroke: 1.8px #000;
    font-size: 12px;
    font-weight: 900;
    letter-spacing: 0.04em;
  }
  /* Subtitle — small off-white with thin dark stroke
     (matches "fontcolor=#e6e6e6,borderw=2" in CAPTION_STYLES). */
  .ep-style-vis-subtitle span {
    color: #e6e6e6;
    -webkit-text-stroke: 0.5px rgba(0,0,0,0.85);
    font-size: 10px;
    font-weight: 600;
  }
  /* Visual scrubber for trim — draggable handles over a timeline
     representing the source-video duration. Stacks above the
     number inputs, which become precision adjustments. */
  .ep-scrubber {
    grid-column: 1 / -1;
    margin-bottom: 6px;
    user-select: none;
    -webkit-user-select: none;
  }
  .ep-scrub-track {
    position: relative;
    height: 36px;                   /* tall hit-target zone */
    background: linear-gradient(var(--surface-2), var(--surface-2)) center / 100% 6px no-repeat;
    border-radius: 4px;
    cursor: pointer;
    touch-action: none;             /* prevents scroll while dragging on mobile */
  }
  .ep-scrub-track::before {
    /* Visible track centered vertically. */
    content: "";
    position: absolute; left: 0; right: 0; top: 50%;
    transform: translateY(-50%);
    height: 6px;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 999px;
    pointer-events: none;
  }
  .ep-scrub-region {
    position: absolute; top: 50%; transform: translateY(-50%);
    height: 6px;
    background: linear-gradient(90deg, var(--brand) 0%, var(--brand-light) 100%);
    border-radius: 999px;
    pointer-events: none;
    box-shadow: 0 0 0 1px rgba(96,165,250,0.30);
    transition: left 0.05s linear, width 0.05s linear;
  }
  .ep-scrub-handle {
    position: absolute;
    top: 0; bottom: 0;
    width: 28px;                    /* 28px hit target — comfy on touch */
    background: none; border: 0;
    cursor: ew-resize;
    transform: translateX(-50%);    /* centre handle on its left% position */
    padding: 0;
    z-index: 2;
    display: flex; align-items: center; justify-content: center;
  }
  .ep-scrub-handle:focus-visible { outline: none; }
  .ep-scrub-thumb {
    /* Visible thumb — narrower than the hit zone for precision. */
    width: 4px; height: 24px;
    background: var(--brand-light);
    border-radius: 4px;
    box-shadow:
      0 0 0 2px var(--bg),
      0 0 0 3.5px var(--brand-light),
      0 4px 14px rgba(96,165,250,0.45);
    transition: transform 0.12s, height 0.12s;
  }
  .ep-scrub-handle:hover .ep-scrub-thumb,
  .ep-scrub-handle:active .ep-scrub-thumb {
    transform: scaleX(1.2);
    height: 28px;
  }
  .ep-scrub-handle:active { z-index: 3; }
  /* On phones, fingers cover the 4px-wide thumb — bumping the
     visible width + the hit-target makes the trim handles actually
     grabbable. The hit zone widens to 38px (vs 28px desktop) and
     the thumb itself is 7×30 so the user sees something through
     their fingertip. */
  @media (max-width: 600px) {
    .ep-scrub-handle { width: 38px; }
    .ep-scrub-thumb  { width: 7px; height: 30px; border-radius: 5px; }
    .ep-scrub-handle:hover .ep-scrub-thumb,
    .ep-scrub-handle:active .ep-scrub-thumb { height: 34px; }
  }
  .ep-scrub-labels {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 12px;
    margin-top: 8px;
    font-size: 11px;
    color: var(--text-faint);
    font-variant-numeric: tabular-nums;
  }
  .ep-scrub-labels .ep-scrub-l-end { text-align: right; }
  .ep-scrub-labels .ep-scrub-selection {
    font-weight: 600;
    color: var(--brand-light);
    background: var(--brand-dim);
    border: 1px solid var(--brand-border);
    border-radius: 999px;
    padding: 3px 10px;
    white-space: nowrap;
  }
  /* When the source duration is unknown, hide the scrubber and
     fall back to the number inputs. liveAttachScrubber sets this. */
  .ep-scrubber[hidden] { display: none; }

  /* Trim row */
  .ep-trim {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
    align-items: end;
  }
  .ep-trim-input {
    display: flex; align-items: center; gap: 6px;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 6px 10px;
  }
  .ep-trim-input .ep-trim-tag {
    font-size: 11px; color: var(--text-faint); flex-shrink: 0;
  }
  .ep-trim-input input[type="number"] {
    width: 100%;
    background: transparent;
    border: 0;
    color: var(--text);
    font: inherit;
    font-size: 13px;
    padding: 2px 0;
    -moz-appearance: textfield;
  }
  .ep-trim-input input[type="number"]::-webkit-outer-spin-button,
  .ep-trim-input input[type="number"]::-webkit-inner-spin-button {
    -webkit-appearance: none; margin: 0;
  }
  .ep-trim-input input[type="number"]:focus { outline: 0; }
  .ep-trim-input.invalid { border-color: rgba(248,113,113,0.55); }
  .ep-trim-dur {
    grid-column: 1 / -1;
    font-size: 12px; color: var(--text-faint);
  }
  .ep-trim-dur b { color: var(--text-dim); font-weight: 500; }
  .ep-trim-dur.invalid { color: var(--bad); }
  .ep-trim-dur.invalid b { color: var(--bad); }
  /* Mobile drawer header — hidden on desktop (the inline card
     is the header), visible only inside the bottom-sheet. Tells
     users which clip they're editing without closing the drawer. */
  .ep-mobile-header { display: none; }
  .emh-eyebrow {
    display: block;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-faint);
  }
  .emh-title {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    font-size: 15px;
    font-weight: 600;
    color: var(--text);
    margin-top: 2px;
  }
  @media (max-width: 600px) {
    .clip-edit-panel:not([hidden]) > .ep-mobile-header {
      display: block;
      padding-bottom: 12px;
      margin-bottom: 14px;
      border-bottom: 1px solid var(--border);
    }
  }

  /* Caption character counter — only visible when user is within
     200 chars of the cap. No noise on empty / typical-length
     captions. */
  .ep-caption-counter {
    display: none;
    margin-top: 4px;
    font-size: 11px;
    color: var(--text-faint);
    text-align: right;
    font-variant-numeric: tabular-nums;
  }
  .ep-caption-counter.show { display: block; }
  .ep-caption-counter.warn { color: var(--warn, #fde68a); }
  .ep-caption-counter.danger { color: var(--bad); font-weight: 600; }

  /* Hook-style 2-button toggle. Same seg-grid as the other
     panel toggles, two columns. */
  .ep-hook-seg { grid-template-columns: repeat(2, 1fr); }
  .ep-hook-seg .seg-btn {
    min-height: 38px;
    padding: 7px 10px;
    font-size: 13px;
  }
  .ep-hook-preview {
    margin-top: 8px;
    font-size: 12px;
    color: var(--text-dim);
    line-height: 1.45;
    padding: 8px 10px;
    background: var(--surface-2);
    border-radius: 6px;
    min-height: 14px;
  }
  .ep-hook-preview:empty { display: none; }
  .ep-hook-preview-label {
    color: var(--brand-light);
    font-weight: 600;
    margin-right: 4px;
  }
  .ep-hook-status {
    margin-top: 6px;
    font-size: 11px;
    color: var(--text-faint);
    min-height: 14px;
  }
  .ep-hook-status.error { color: var(--bad); }
  .ep-hook-status.busy::before {
    content: "";
    display: inline-block;
    width: 8px; height: 8px;
    margin-right: 6px; vertical-align: middle;
    border: 1.5px solid var(--text-faint);
    border-top-color: transparent;
    border-radius: 50%;
    animation: ep-translate-spin 0.7s linear infinite;
  }

  /* Subtitle-language dropdown. Same visual weight as the other
     edit-panel inputs — full-width, bordered, dark surface. */
  .ep-translate-select {
    width: 100%;
    background: var(--surface-2);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
    padding: 9px 12px;
    font: inherit; font-size: 13px;
    cursor: pointer;
    appearance: none;
    -webkit-appearance: none;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none'><path d='M1 1l5 5 5-5' stroke='%2394a3b8' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/></svg>");
    background-repeat: no-repeat;
    background-position: right 12px center;
    background-size: 12px 8px;
    padding-right: 34px;
  }
  .ep-translate-select:focus {
    outline: 0;
    border-color: var(--brand-border);
  }
  .ep-translate-status {
    margin-top: 6px;
    font-size: 11px;
    color: var(--text-faint);
    min-height: 14px;
  }
  .ep-translate-status.error { color: var(--bad); }
  .ep-translate-status.busy::before {
    content: "";
    display: inline-block;
    width: 8px; height: 8px;
    margin-right: 6px; vertical-align: middle;
    border: 1.5px solid var(--text-faint);
    border-top-color: transparent;
    border-radius: 50%;
    animation: ep-translate-spin 0.7s linear infinite;
  }
  @keyframes ep-translate-spin { to { transform: rotate(360deg); } }

  /* Preview-cut button sits inside the trim section now (only
     meaningful in trim context). Small inline ghost button
     anchored to the right. */
  .ep-trim-preview-row {
    display: flex;
    justify-content: flex-end;
    margin-top: 6px;
  }
  .ep-trim-preview-row .ep-preview-trim-btn {
    width: auto;
    flex: 0 0 auto;
    min-height: 32px;
    padding: 4px 12px;
    font-size: 12px;
  }

  /* Trim collapsible — disclosure style. Click summary to
     expand the scrubber + inputs. Native <details> so it
     keeps keyboard + screen-reader semantics. */
  .ep-trim-details > summary {
    cursor: pointer;
    list-style: none;
    display: flex; align-items: center;
    gap: 10px;
    padding: 6px 0;
    user-select: none;
    -webkit-user-select: none;
  }
  .ep-trim-details > summary::-webkit-details-marker { display: none; }
  .ep-trim-summary-hint {
    font-size: 11px;
    color: var(--text-faint);
    font-weight: 400;
    margin-right: auto;
  }
  .ep-trim-details > summary::after {
    content: "▾";
    color: var(--text-faint);
    font-size: 12px;
    transition: transform 0.15s ease, color 0.15s;
  }
  .ep-trim-details[open] > summary::after { transform: rotate(180deg); color: var(--text-dim); }
  .ep-trim-details > summary:hover::after { color: var(--text-dim); }
  /* Inner trim content gets a small top margin to space it from
     the summary line on expand. */
  .ep-trim-details[open] > .ep-trim { margin-top: 6px; }
  /* Reset-to-original button inside the summary stays right of
     the hint — no auto-margin since the chevron is the auto-margin. */
  .ep-trim-details > summary > .ep-reset-trim-btn { margin-right: 4px; }

  .ep-trim-original {
    grid-column: 1 / -1;
    font-size: 11px;
    color: var(--text-faint);
    font-variant-numeric: tabular-nums;
  }
  .ep-trim-original b { color: var(--text-dim); font-weight: 500; }

  .ep-actions { display: flex; gap: 6px; margin-top: 4px; align-items: center; }
  .ep-actions .btn { width: auto; flex: 0 0 auto; min-height: 34px; padding: 6px 14px; font-size: 13px; }
  /* Tells users they have an uncommitted change before they hit
     Save. Quiet by default — JS adds .show + the count text as
     soon as anything in the panel diverges from the saved clip. */
  .ep-dirty-indicator {
    display: none;
    margin-left: auto;
    font-size: 12px;
    color: var(--brand-light);
    font-weight: 500;
  }
  .ep-dirty-indicator::before {
    content: "•";
    margin-right: 5px;
    color: var(--brand-light);
  }
  .ep-dirty-indicator.show { display: inline; }
  .ep-error {
    margin-top: 8px;
    font-size: 12px; color: var(--bad);
  }

  /* ── Inline caption preview overlay ─────────────────────────
     A DOM caption rendered on top of the inline <video>, mirroring
     the ffmpeg drawtext output well enough that users can dial in
     position + style before exporting. The wrapper is the relative
     anchor so absolute children stay glued to the video element
     even as it resizes (it has its own aspect-ratio rule). The
     overlay never steals clicks (pointer-events: none) so the user
     can still play/pause through it. */
  .clip-video-wrap {
    position: relative;
    display: block;
    line-height: 0; /* kills the inline-baseline gap below <video> */
  }
  .clip-caption-overlay {
    position: absolute;
    left: 0; right: 0;
    z-index: 4;                      /* under select/delete (z=5) */
    pointer-events: none;
    display: flex;
    justify-content: center;
    padding: 0 6%;
    text-align: center;
    line-height: 1.2;
    letter-spacing: 0.01em;
    /* Font scales with card width so the preview reads at any
       aspect ratio. Container queries let us tune by the card's
       inline size, not the viewport — important since clips
       render in a multi-column grid on desktop. */
    font-size: clamp(11px, 2.2cqi, 18px);
    font-weight: 700;
    container-type: inline-size;
  }
  .clip-caption-overlay[data-pos="top"]    { top: 7%; }
  .clip-caption-overlay[data-pos="middle"] { top: 50%; transform: translateY(-50%); }
  .clip-caption-overlay[data-pos="bottom"] { bottom: 9%; }
  .clip-caption-overlay .ccov-text {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    max-width: 100%;
    word-break: break-word;
    /* Approximations of the three ffmpeg presets in modules/clip/captionBurn.js.
       2-line clamp because export paces captions through in chunks
       (a few words at a time, timed to speech) — showing the full
       transcript statically would be misleading and dominate the
       video preview. The clamp signals "preview" while keeping the
       chosen position + style legible. */
  }
  .clip-caption-overlay[data-style="default"] .ccov-text {
    color: #fff;
    background: rgba(0,0,0,0.65);
    padding: 6px 14px;
    border-radius: 4px;
  }
  .clip-caption-overlay[data-style="bold"] .ccov-text {
    color: #fbbf24;
    -webkit-text-stroke: 2px #000;
    text-shadow:
      0 0 2px #000, 0 0 2px #000, 0 0 2px #000,
      0 1px 0 #000, 1px 0 0 #000, 0 -1px 0 #000, -1px 0 0 #000;
  }
  .clip-caption-overlay[data-style="clean"] .ccov-text {
    color: #fff;
    -webkit-text-stroke: 0.9px rgba(0,0,0,0.85);
    text-shadow: 0 1px 2px rgba(0,0,0,0.85), 0 0 1px rgba(0,0,0,0.85);
  }
  /* Viral — boxed yellow text, heavier shadow than default. Mirrors
     the export look. Static, works on every clip. */
  .clip-caption-overlay[data-style="viral"] .ccov-text {
    color: #fbbf24;
    background: rgba(0,0,0,0.72);
    padding: 8px 16px;
    border-radius: 4px;
    -webkit-text-stroke: 0.6px #000;
    text-shadow: 0 2px 4px rgba(0,0,0,0.7), 0 0 2px rgba(0,0,0,0.85);
    font-weight: 800;
    font-size: clamp(13px, 2.8cqi, 22px);
    letter-spacing: 0.01em;
  }

  /* Pop — same yellow trailing gradient as Highlight since the
     drawtext export look is conceptually the same (per-word
     highlight). Real export differs in that the colour switch
     is discrete instead of a smooth fill. */
  .clip-caption-overlay[data-style="pop"] .ccov-text {
    background: linear-gradient(90deg, #ffffff 60%, #fbbf24 100%);
    -webkit-background-clip: text;
            background-clip: text;
    color: transparent;
    -webkit-text-stroke: 1.2px #000;
    text-shadow: 0 0 3px #000;
    text-transform: uppercase;
    letter-spacing: 0.02em;
    font-weight: 900;
    font-size: clamp(13px, 2.6cqi, 22px);
  }

  /* Highlight — preview shows white text with a yellow trailing
     gradient hinting at the per-word fill behaviour the export
     produces (real animation requires libass; preview is a static
     approximation). */
  .clip-caption-overlay[data-style="highlight"] .ccov-text {
    background: linear-gradient(90deg, #ffffff 60%, #fbbf24 100%);
    -webkit-background-clip: text;
            background-clip: text;
    color: transparent;
    -webkit-text-stroke: 1.2px #000;
    text-shadow: 0 0 3px #000;
    text-transform: uppercase;
    letter-spacing: 0.02em;
    font-weight: 900;
    font-size: clamp(13px, 2.6cqi, 22px);
  }

  /* Animated — preview is a yellow box with a soft pulse to hint
     "this animates on export". The actual burn-in runs per-word. */
  .clip-caption-overlay[data-style="animated"] .ccov-text {
    color: #fbbf24;
    background: rgba(0,0,0,0.65);
    padding: 6px 14px;
    border-radius: 4px;
    -webkit-text-stroke: 0.8px #000;
    text-shadow: 0 0 2px #000;
    animation: ccov-animated-pulse 1.6s ease-in-out infinite;
  }
  @keyframes ccov-animated-pulse {
    0%, 100% { transform: scale(1);    opacity: 0.95; }
    50%      { transform: scale(1.05); opacity: 1;    }
  }
  @media (prefers-reduced-motion: reduce) {
    .clip-caption-overlay[data-style="animated"] .ccov-text { animation: none; }
  }
  /* When word-timestamp data is available, JS adds .ccov-animating
     and swaps .ccov-text to one word at a time per video frame.
     Override the preview-pulse styling: a single word should be
     much larger and not clamped to 2 lines. The single-word
     replacement on each timeupdate IS the visible animation —
     the CSS pulse becomes redundant noise. */
  .clip-caption-overlay.ccov-animating[data-style="animated"] .ccov-text {
    font-size: clamp(20px, 6cqi, 56px);
    font-weight: 900;
    letter-spacing: 0.04em;
    line-height: 1;
    -webkit-line-clamp: 1;
    padding: 8px 18px;
    animation: none;
  }
  /* Big — ALL CAPS, white, chunky black stroke + drop shadow. */
  .clip-caption-overlay[data-style="big"] .ccov-text {
    color: #fff;
    -webkit-text-stroke: 2.4px #000;
    text-shadow: 0 0 3px #000, 0 2px 0 #000, 2px 0 0 #000, 0 -2px 0 #000, -2px 0 0 #000;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    font-weight: 900;
    font-size: clamp(13px, 2.6cqi, 22px);
  }
  /* Subtitle — small off-white with thin dark stroke. */
  .clip-caption-overlay[data-style="subtitle"] .ccov-text {
    color: #e6e6e6;
    -webkit-text-stroke: 0.6px rgba(0,0,0,0.85);
    text-shadow: 0 1px 2px rgba(0,0,0,0.85);
    font-size: clamp(9px, 1.7cqi, 14px);
    font-weight: 500;
  }

  /* ── Multi-select checkbox (top-right corner of clip card) ─ */
  .clip-card { position: relative; }
  .clip-select {
    position: absolute;
    top: 8px; right: 8px;
    z-index: 5;
    width: 30px; height: 30px;
    border-radius: 50%;
    background: rgba(0,0,0,0.55);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    border: 1.5px solid rgba(255,255,255,0.50);
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    transition: background 0.15s, border-color 0.15s;
  }
  .clip-select:hover { background: rgba(0,0,0,0.75); border-color: rgba(255,255,255,0.85); }
  .clip-select::after {
    content: ""; display: block;
    width: 14px; height: 14px;
    background: transparent;
    -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M5 12l5 5L20 7' fill='none' stroke='currentColor' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/></svg>") center/contain no-repeat;
            mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M5 12l5 5L20 7' fill='none' stroke='currentColor' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/></svg>") center/contain no-repeat;
    transition: background 0.15s;
  }
  .clip-card.selected .clip-select {
    background: var(--brand);
    border-color: var(--brand-light);
  }
  .clip-card.selected .clip-select::after { background: white; }
  .clip-card.selected { box-shadow: 0 0 0 2px var(--brand); }
  /* Hide the multi-select circle when there's only one clip — bulk
     ops are meaningless. Paired with the bulk-shortcuts row, which
     renderResults already hides when clips.length < 2. The grid
     gets the .single-clip class set in the same place. */
  .clip-grid.single-clip .clip-select { display: none; }

  /* ── Bulk action bar (floats above content when 1+ selected) ─ */
  .bulk-bar {
    position: fixed;
    left: 50%;
    /* Safe-area aware bottom margin so the floating bar clears the
       iOS home indicator on iPhone X+ models. env() returns 0 on
       devices that don't have a safe area (Android, desktop), so
       the max() falls back to the original 16px there. */
    bottom: max(16px, env(safe-area-inset-bottom));
    transform: translateX(-50%);
    background: var(--surface);
    border: 1px solid var(--border-strong);
    box-shadow: 0 8px 32px rgba(0,0,0,0.55);
    border-radius: 999px;
    padding: 8px 8px 8px 16px;
    display: flex; align-items: center; gap: 10px;
    z-index: 80;
    max-width: calc(100vw - 24px);
  }
  .bulk-bar .bb-count {
    font-size: 13px; font-weight: 500;
    color: var(--text);
    white-space: nowrap;
  }
  .bulk-bar .bb-cancel {
    background: none; border: 0; cursor: pointer;
    color: var(--text-faint);
    font-size: 12px;
    padding: 6px 10px;
    border-radius: 999px;
  }
  .bulk-bar .bb-cancel:hover { color: var(--text); background: var(--surface-2); }
  .bulk-bar .bb-export {
    background: var(--brand); color: white;
    border: 0; cursor: pointer;
    padding: 8px 16px;
    font-size: 13px; font-weight: 600;
    border-radius: 999px;
    transition: background 0.15s;
  }
  .bulk-bar .bb-export:hover { background: var(--brand-hover); }
  .bulk-bar .bb-export:disabled { opacity: 0.6; cursor: not-allowed; }

  /* Sticky settings card on the results screen. Keeps aspect
     ratio reachable while the user scrolls through 5+ clips —
     no scroll-back to flip 9:16 vs 1:1 vs 16:9. The page header
     isn't sticky, so top: 0 lands at the viewport edge cleanly. */
  #screen-results .card.results-settings {
    position: sticky;
    top: 0;
    z-index: 6;
    /* Translucent surface + blur reads cleanly over the clip
       grid scrolling underneath. Falls back to opaque on
       browsers without backdrop-filter. */
    background: rgba(20, 20, 22, 0.85);
    backdrop-filter: blur(10px) saturate(1.1);
    -webkit-backdrop-filter: blur(10px) saturate(1.1);
    border-color: var(--border-strong);
  }

  /* ── Mobile drawer for the clip edit panel ──────────────────
     Inline-expanded panels can crowd a narrow viewport; on
     phones, a slide-up bottom sheet is more native and frees
     the page for the user's tap targets. The panel DOM stays
     inside .clip-edit-wrap so all its existing handlers
     (scrubber, segmented controls, save/cancel) keep working
     unchanged — only the visual position flips. */
  #drawer-backdrop {
    display: none;
    position: fixed; inset: 0;
    /* Lighter dim than a typical modal — we want the video above
       the drawer to stay readable for live-preview while editing,
       not fully eclipsed. */
    background: rgba(0,0,0,0.32);
    z-index: 99;
    animation: drawer-backdrop-fade 0.18s ease-out;
  }
  @keyframes drawer-backdrop-fade {
    from { opacity: 0; } to { opacity: 1; }
  }
  @keyframes drawer-slide-up {
    from { transform: translateY(100%); }
    to   { transform: translateY(0);    }
  }
  @media (max-width: 600px) {
    body.has-open-panel #drawer-backdrop { display: block; }
    /* iOS Safari ignores `overflow: hidden` on body in many cases —
       the page still rubber-band-scrolls under the drawer. The
       position-fixed + width pattern is bulletproof; the JS that
       toggles .has-open-panel also stashes/restores scrollY so
       closing the drawer doesn't snap to the top. */
    body.has-open-panel {
      position: fixed;
      width: 100%;
      overflow: hidden;
    }
    .clip-edit-panel:not([hidden]) {
      position: fixed;
      left: 0; right: 0; bottom: 0;
      z-index: 100;
      /* Cap drawer height so the source video stays visible above
         the sheet — the whole point of the inline overlay was
         "live preview while editing", and we'd lose that if the
         drawer covered the video. 60vh leaves ~40vh for the
         video + the dimmed-but-still-readable card header. */
      max-height: 60vh;
      height: 60vh;
      overflow-y: auto;
      background: var(--surface);
      border-top: 1px solid var(--border-strong);
      border-radius: 16px 16px 0 0;
      padding: 16px 18px 20px;
      padding-bottom: max(20px, env(safe-area-inset-bottom));
      margin: 0;
      box-shadow: 0 -16px 48px rgba(0,0,0,0.55);
      animation: drawer-slide-up 0.28s cubic-bezier(0.2, 0.8, 0.2, 1);
      -webkit-overflow-scrolling: touch;
    }
    /* Drag-handle visual cue — hints "swipe / tap-to-close" in
       the same way native sheets do. Ornamental only; doesn't
       drive close behavior. */
    .clip-edit-panel:not([hidden])::before {
      content: "";
      display: block;
      width: 44px; height: 4px;
      background: var(--text-faint);
      border-radius: 999px;
      margin: 0 auto 14px;
      opacity: 0.5;
    }
  }

  /* Quick-select shortcuts row above the clip grid (only shown
     once results are visible). */
  .bulk-shortcuts {
    display: flex; align-items: center; gap: 8px;
    flex-wrap: wrap;
    font-size: 13px;
    color: var(--text-faint);
    margin: 6px 0 0;
  }
  .bulk-shortcut-btn {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 999px;
    padding: 5px 12px;
    cursor: pointer;
    color: var(--text-dim);
    font-size: 12px;
    transition: border-color 0.15s, color 0.15s;
  }
  .bulk-shortcut-btn:hover { border-color: var(--border-strong); color: var(--text); }

  /* ── Results toolbar: sort + filter ─────────────────────────
     Sits above the clip-grid. Sort changes the visual order of
     cards (no re-render — keeps in-flight edits intact). Filter
     toggles a .filtered-out class. */
  .results-toolbar {
    display: flex; align-items: center; gap: 14px;
    flex-wrap: wrap;
    margin: 12px 0 0;
    font-size: 12px;
    color: var(--text-faint);
  }
  .toolbar-group {
    display: inline-flex; align-items: center; gap: 6px;
  }
  .toolbar-group .tg-label {
    color: var(--text-faint);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-size: 10px;
    margin-right: 4px;
  }
  .tool-chip {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 999px;
    padding: 5px 11px;
    cursor: pointer;
    color: var(--text-dim);
    font-size: 12px;
    transition: border-color 0.15s, color 0.15s, background 0.15s;
  }
  .tool-chip:hover { border-color: var(--border-strong); color: var(--text); }
  .tool-chip[aria-pressed="true"] {
    background: var(--brand-dim);
    border-color: var(--brand-border);
    color: var(--brand-light);
  }
  /* Filter hides cards entirely (display:none). The grid is a
     CSS grid container — display:none on a child takes it out
     of flow so the layout reflows around it. */
  .clip-card.filtered-out { display: none !important; }

  /* "Apply to all clips" link under the caption-style picker.
     Tiny ghost button — cheap to ignore if you don't need it. */
  .apply-style-all-btn {
    margin-top: 8px;
    background: none; border: 0; cursor: pointer;
    color: var(--text-faint);
    font: inherit; font-size: 12px;
    padding: 4px 6px;
    border-radius: 4px;
    transition: color 0.15s;
  }
  .apply-style-all-btn:hover { color: var(--brand-light); text-decoration: underline; }
  .apply-style-all-btn:disabled { opacity: 0.55; cursor: default; text-decoration: none; }

  /* First-visit welcome hint above the upload form. Auto-hides
     once the user has created at least one project (tracked in
     localStorage). One-line, dismissible. */
  .welcome-hint {
    display: flex; align-items: flex-start; gap: 10px;
    background: linear-gradient(135deg, rgba(96,165,250,0.08), rgba(37,99,235,0.05));
    border: 1px solid var(--brand-border);
    border-radius: var(--r-sm);
    padding: 11px 14px;
    margin-bottom: 14px;
    font-size: 13px;
    color: var(--text-dim);
    line-height: 1.5;
  }
  .welcome-hint .wh-icon { flex: 0 0 auto; opacity: 0.85; }
  .welcome-hint b { color: var(--text); }
  .welcome-hint .wh-close {
    background: none; border: 0; cursor: pointer;
    color: var(--text-faint);
    font-size: 18px; line-height: 1;
    padding: 0 4px;
    margin-left: auto;
    transition: color 0.15s;
  }
  .welcome-hint .wh-close:hover { color: var(--text); }

  /* ── In-app feedback widget ────────────────────────────────────
     Floating button bottom-left (clear of the bulk-bar bottom-center
     and any chat-style widgets users expect bottom-right). Clicking
     opens the existing modal-back / modal pattern, repurposed. */
  .fb-trigger {
    position: fixed;
    left: 16px;
    bottom: max(16px, env(safe-area-inset-bottom));
    z-index: 70;
    display: inline-flex; align-items: center; gap: 6px;
    padding: 9px 14px;
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: 999px;
    box-shadow: 0 6px 18px rgba(0,0,0,0.4);
    color: var(--text-dim);
    cursor: pointer;
    font: inherit; font-size: 13px; font-weight: 500;
    transition: background 0.15s, color 0.15s, border-color 0.15s, transform 0.05s;
  }
  .fb-trigger:hover { color: var(--text); border-color: var(--brand-border); background: var(--surface-2); }
  .fb-trigger:active { transform: translateY(1px); }
  /* On phones the icon-only variant saves space; the modal still
     opens identically. Hide the label when narrow. */
  @media (max-width: 480px) {
    .fb-trigger { padding: 11px 12px; }
    .fb-trigger-label { display: none; }
  }
  @media (pointer: coarse) {
    .fb-trigger { min-width: 44px; min-height: 44px; }
  }

  /* ── In-flight project banner ──────────────────────────────────
     Shown above the upload card on idle when localStorage holds a
     project still in 'processing' state from a recent session. Lets
     users recover from a refresh / tab-close without hunting Recent. */
  .inflight-banner {
    display: flex; align-items: center; gap: 12px;
    padding: 12px 14px;
    margin-bottom: 14px;
    background: rgba(96,165,250,0.08);
    border: 1px solid rgba(96,165,250,0.28);
    border-radius: 10px;
  }
  .inflight-banner .ifb-icon { flex: 0 0 auto; font-size: 18px; }
  .inflight-banner .ifb-text { flex: 1; min-width: 0; }
  .inflight-banner .ifb-title { font-size: 13px; font-weight: 600; color: var(--text); }
  .inflight-banner .ifb-name { font-size: 12px; color: var(--text-faint); margin-top: 2px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
  .inflight-banner .btn-sm { flex: 0 0 auto; }
  .inflight-banner .ifb-dismiss {
    flex: 0 0 auto; background: none; border: 0; cursor: pointer;
    color: var(--text-faint); font-size: 18px; line-height: 1;
    padding: 0 4px; transition: color 0.15s;
  }
  .inflight-banner .ifb-dismiss:hover { color: var(--text); }
  @media (pointer: coarse) {
    .inflight-banner .ifb-dismiss { min-width: 44px; min-height: 44px; padding: 10px; }
  }

  /* ── Per-clip caption pill ──────────────────────────────── */
  .cap-pill {
    display: inline-flex; align-items: center; gap: 7px;
    font-size: 12px;
    padding: 6px 12px;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 999px;
    cursor: pointer;
    color: var(--text-dim);
    margin-bottom: 12px;
    transition: border-color 0.15s, color 0.15s, background 0.15s;
  }
  .cap-pill:hover { border-color: var(--border-strong); }
  .cap-pill .cp-dot {
    width: 8px; height: 8px; border-radius: 50%;
    background: var(--border-strong);
    flex-shrink: 0;
    transition: background 0.15s, box-shadow 0.15s;
  }
  .cap-pill[data-on="true"] {
    border-color: rgba(74,222,128,0.30);
    background: rgba(74,222,128,0.08);
    color: var(--good);
  }
  .cap-pill[data-on="true"] .cp-dot {
    background: var(--good);
    box-shadow: 0 0 0 3px rgba(74,222,128,0.18);
  }
  .cap-pill b { font-weight: 600; }
  /* Mobile: the cap-pill wording is dense in narrow card grids on
     phones. Tighten padding + font size so the status dot + ON/OFF
     badge stay the primary signal. The pill stays tappable as a
     toggle, just visually compact. */
  @media (max-width: 480px) {
    .cap-pill { padding: 6px 10px; font-size: 11px; }
  }

  /* Caption-edit draft restore banner. Surfaces above the textarea
     when localStorage holds an unsaved draft from a previous session
     (browser crash / tab kill mid-edit). Lets the user opt to
     restore or discard. Auto-prunes after 7 days. */
  .ep-draft-banner {
    display: flex; align-items: center; gap: 10px;
    margin-bottom: 8px; padding: 8px 12px;
    background: rgba(96,165,250,0.08);
    border: 1px solid rgba(96,165,250,0.28);
    border-radius: 8px;
    font-size: 12px; color: var(--text-dim);
  }
  .ep-draft-banner > span:first-child { flex: 1; min-width: 0; }
  .ep-draft-banner .ep-draft-restore {
    background: var(--brand); color: #fff; border: 0; cursor: pointer;
    padding: 5px 10px; border-radius: 6px;
    font-size: 12px; font-weight: 500;
  }
  .ep-draft-banner .ep-draft-discard {
    background: none; border: 0; cursor: pointer;
    color: var(--text-faint); font-size: 16px; line-height: 1;
    padding: 0 4px;
  }
  .ep-draft-banner .ep-draft-discard:hover { color: var(--text); }

  /* Mobile-only dictation hint shown under the caption-edit textarea.
     Hidden on desktop (no relevance — physical keyboards don't have
     mic shortcuts in the same way). Subtle enough to not crowd the
     panel but visible enough that thumb-typing users notice it. */
  .ep-mobile-hint {
    display: none;
    font-size: 12px; color: var(--text-faint);
    margin: 6px 0 0; line-height: 1.4;
    opacity: 0.85;
  }
  @media (pointer: coarse) {
    .ep-mobile-hint { display: block; }
  }

  /* ── Tap-target hardening (touch devices only) ────────────────
     Apple HIG recommends 44×44pt minimum for touch targets. The
     audit flagged several spots where small text-buttons could
     fat-finger neighbors on phones: dismiss × buttons, copy-link,
     small bulk-bar controls. We pad them up via a touch-only
     @media query so desktop layouts stay tight while phones get
     forgiving tap zones. */
  @media (pointer: coarse) {
    /* Dismiss × buttons — currently ~22×22px hit areas, way under
       HIG. Bump padding so the pseudo-area covers 44×44 without
       changing the visible × glyph size. */
    .welcome-hint .wh-close,
    .baked-caption-hint .bch-dismiss,
    .signin-nudge .sn-dismiss {
      min-width: 44px; min-height: 44px;
      padding: 10px;
      display: inline-flex; align-items: center; justify-content: center;
    }
    /* Caption pill on phones — the previous compaction made it more
       readable but the tap target is still only ~26px tall. Bump
       padding so the pill clears 44×44. */
    .cap-pill {
      min-height: 44px;
      padding: 10px 14px;
    }
    /* Bulk-bar controls — Cancel + Export both compact for desktop;
       on phones the close-quarters fat-finger risk is real. */
    .bulk-bar .bb-cancel,
    .bulk-bar .bb-export {
      min-height: 44px;
      padding-left: 16px; padding-right: 16px;
    }
    /* Copy-link button — small ghost button in the export action row;
       gets fat-fingered with Download on phones. */
    .copy-link-btn {
      min-height: 44px;
    }
    /* Tool chips (sort / filter) and bulk shortcuts — single tap on
       phones, often near each other. Bump min-height. */
    .tool-chip, .bulk-shortcut-btn {
      min-height: 44px;
      padding-top: 10px; padding-bottom: 10px;
    }
    /* Small btn variant — ensure all 'btn-sm' buttons hit the
       minimum on touch. Desktop keeps the 38px height. */
    .btn-sm { min-height: 44px; }
    /* Mode-link (text-link in the idle screen) — easy to tap but
       small target. Pad it up. */
    .mode-link { padding: 10px 14px; min-height: 44px; }
    /* Locale switch buttons in the footer — already small. */
    .locale-switch .locale-btn { min-width: 44px; min-height: 44px; padding: 10px 12px; }
  }

  /* ── Mode picker (AI vs Manual entry) ────────────────────── */
  /* Two big tappable buttons at the top of the idle screen.
     Mobile: stacked. Desktop ≥640: side-by-side 2-col grid.
     Selected state highlights with brand color + subtle ring. */
  .mode-picker {
    display: grid;
    grid-template-columns: 1fr;
    gap: 10px;
    margin-bottom: 22px;
  }
  @media (min-width: 640px) {
    .mode-picker { grid-template-columns: 1fr 1fr; }
  }
  .mode-btn {
    display: flex; align-items: flex-start; gap: 12px;
    padding: 16px 16px;
    background: var(--surface);
    border: 1.5px solid var(--border);
    border-radius: 12px;
    cursor: pointer;
    text-align: left;
    transition: border-color 0.18s, background 0.18s, transform 0.05s;
  }
  .mode-btn:hover { border-color: var(--border-strong); background: var(--surface-2); }
  .mode-btn[aria-checked="true"] {
    border-color: var(--brand);
    background: var(--brand-dim);
    box-shadow: 0 0 0 3px rgba(37,99,235,0.10);
  }
  .mode-btn .mode-icon {
    flex-shrink: 0;
    width: 36px; height: 36px;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 18px;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 9px;
  }
  .mode-btn[aria-checked="true"] .mode-icon {
    background: var(--brand-dim);
    border-color: var(--brand-border);
  }
  .mode-btn .mode-text { flex: 1; min-width: 0; line-height: 1.3; }
  .mode-btn .mode-title {
    display: block;
    font-size: 14px; font-weight: 600;
    color: var(--text);
    margin-bottom: 3px;
    letter-spacing: -0.005em;
  }
  .mode-btn .mode-sub {
    display: block;
    font-size: 12px; color: var(--text-faint);
  }
  .mode-btn[aria-checked="true"] .mode-sub { color: var(--text-dim); }

  /* ── Idle settings (language + translate) ───────────────── */
  /* Wrapped in a <details> disclosure — collapsed by default to
     reduce decision fatigue. Most users won't touch these; the
     ones who need them (Pidgin / accented audio / non-English)
     find them under "Advanced settings". */
  .idle-settings-disclosure {
    margin-top: 14px;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
    overflow: hidden;
  }
  .idle-settings-disclosure[open] { border-color: var(--border-strong); }
  .adv-summary {
    list-style: none;
    cursor: pointer;
    padding: 11px 14px;
    display: flex; align-items: center; justify-content: space-between;
    gap: 12px;
    font-size: 13px;
    transition: background 0.15s;
  }
  .adv-summary::-webkit-details-marker { display: none; }
  .adv-summary::after {
    content: "+";
    color: var(--text-faint);
    font-size: 16px; font-weight: 500;
    flex-shrink: 0;
    transition: transform 0.2s, color 0.2s;
  }
  .idle-settings-disclosure[open] .adv-summary::after { content: "−"; color: var(--brand-light); }
  .adv-summary:hover { background: var(--bg); }
  .adv-summary-text { font-weight: 500; color: var(--text); }
  .adv-summary-hint { color: var(--text-faint); font-size: 12px; }
  .idle-settings-disclosure .idle-settings {
    margin-top: 0;
    padding: 4px 14px 14px;
  }

  .idle-settings { display: grid; gap: 10px; margin-top: 14px; }
  .idle-settings .field-row {
    display: flex; align-items: center; justify-content: space-between;
    gap: 12px;
    padding: 10px 12px;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
    flex-wrap: wrap;
  }
  .idle-settings .fr-text { font-size: 14px; flex: 1 1 auto; }
  .idle-settings .fr-sub  { font-size: 12px; color: var(--text-faint); margin-top: 2px; }
  .idle-settings select {
    appearance: none; -webkit-appearance: none;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 6px;
    color: var(--text);
    padding: 8px 30px 8px 10px;
    font-size: 14px;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path d='M1 1l4 4 4-4' fill='none' stroke='%2394a3b8' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/></svg>");
    background-repeat: no-repeat;
    background-position: right 10px center;
    min-height: 38px;
  }
  .idle-settings select:focus { outline: 0; border-color: var(--brand); }

  /* ── Recent projects (idle screen) ──────────────────────── */
  .recent-card { padding: 22px 22px 18px; }
  .recent-head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 14px; }
  .recent-head h2 { font-size: 17px; margin: 0; letter-spacing: -0.01em; }
  .recent-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 8px; }

  /* Each row: a thin coloured platform bar on the left edge for
     instant visual sorting (YouTube red, Twitch purple, Vimeo
     teal, Upload neutral, etc). Bigger name as the primary text.
     Hover-reveal actions on desktop, always-visible on touch. */
  .recent-row {
    display: flex; align-items: center; gap: 14px;
    padding: 14px 14px 14px 16px;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 10px;
    cursor: pointer;
    position: relative;
    transition: border-color 0.18s, background 0.18s, transform 0.05s;
  }
  .recent-row:hover {
    border-color: var(--border-strong);
    background: #1a1f2c;
  }
  .recent-row:active { transform: scale(0.997); }

  /* Platform-coloured rail on the left edge. Set via inline
     style.--rail per row in renderRow() based on sourceType. */
  .recent-row::before {
    content: "";
    position: absolute; top: 10px; bottom: 10px; left: 0;
    width: 3px;
    border-radius: 0 3px 3px 0;
    background: var(--rail, var(--brand));
    opacity: 0.85;
  }

  .recent-row .r-main { flex: 1; min-width: 0; }
  .recent-row .r-name {
    font-size: 15px; font-weight: 600;
    color: var(--text);
    letter-spacing: -0.005em;
    line-height: 1.3;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    /* Insurance for the rare case where nowrap+ellipsis loses
       to a really long unbroken string (iOS Safari edge case). */
    min-width: 0;
    word-break: break-all;
  }
  .recent-row .r-meta {
    font-size: 12px; color: var(--text-faint);
    margin-top: 4px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    display: flex; align-items: center; gap: 6px;
  }
  .recent-row .r-meta .r-meta-sep {
    width: 3px; height: 3px; border-radius: 50%;
    background: var(--text-faint); opacity: 0.55;
    flex-shrink: 0;
  }
  .recent-row .r-platform {
    font-weight: 500; color: var(--text-dim);
  }
  .recent-row .r-status {
    font-size: 11px; font-weight: 500;
    padding: 4px 10px; border-radius: 999px;
    background: var(--bg); color: var(--text-dim);
    border: 1px solid var(--border);
    white-space: nowrap;
    flex-shrink: 0;
  }
  .recent-row .r-status.ready  {
    color: var(--good);
    border-color: rgba(74,222,128,0.35);
    background: rgba(74,222,128,0.08);
  }
  .recent-row .r-status.failed {
    color: var(--bad);
    border-color: rgba(248,113,113,0.35);
    background: rgba(248,113,113,0.08);
  }
  /* In-flight status — matches the brand palette so users see
     "your project is still cooking" rather than the raw pipeline
     stage names ("packaging", "analyzing", etc.). */
  .recent-row .r-status.working {
    color: var(--brand-light);
    border-color: var(--brand-border);
    background: var(--brand-dim);
  }
  .recent-row .r-remove,
  .recent-row .r-rename,
  .recent-row .r-delete {
    border: 0; background: none; cursor: pointer;
    padding: 7px 8px; line-height: 1;
    border-radius: 6px;
    color: var(--text-faint);
    flex-shrink: 0;
    font-size: 15px;
    transition: color 0.15s, background 0.15s;
  }
  .recent-row .r-remove:hover { color: var(--bad); background: rgba(248,113,113,0.12); }
  .recent-row .r-rename:hover { color: var(--brand-light); background: var(--brand-dim); }
  .recent-row .r-delete:hover { color: var(--bad); background: rgba(248,113,113,0.12); }
  .recent-row .r-actions {
    display: flex; align-items: center; gap: 2px;
    flex-shrink: 0;
    /* Hover-reveal on desktop, always-visible on touch. Touch
       devices report (hover: none); we use that to keep the
       buttons visible since there's no hover state to expose them. */
    opacity: 1;
    transition: opacity 0.18s;
  }
  @media (hover: hover) {
    .recent-row .r-actions { opacity: 0; }
    .recent-row:hover .r-actions,
    .recent-row:focus-within .r-actions { opacity: 1; }
  }

  /* Empty-state for My Projects (signed-in user with zero projects).
     Rendered as a single .my-empty <li> in place of the row list. */
  .my-empty {
    display: flex; align-items: center; gap: 14px;
    padding: 18px 16px;
    background: var(--surface-2);
    border: 1px dashed var(--border-strong);
    border-radius: 10px;
    list-style: none;
  }
  .my-empty .me-icon {
    font-size: 22px;
    width: 38px; height: 38px;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--brand-dim);
    border: 1px solid var(--brand-border);
    border-radius: 50%;
    flex-shrink: 0;
  }
  .my-empty .me-text { flex: 1; min-width: 0; }
  .my-empty .me-title {
    font-size: 14px; font-weight: 600; color: var(--text);
    margin-bottom: 2px;
  }
  .my-empty .me-sub {
    font-size: 12px; color: var(--text-faint); line-height: 1.5;
  }

  /* Inline rename mode — name swaps to a small text input. Enter
     or blur saves; Esc cancels. */
  .recent-row.renaming { cursor: default; }
  .recent-row.renaming .r-name { display: none; }
  .recent-row .r-name-input {
    display: none;
    width: 100%;
    background: var(--bg);
    border: 1px solid var(--brand);
    border-radius: 4px;
    color: var(--text);
    font: inherit; font-size: 14px;
    padding: 4px 8px;
  }
  .recent-row .r-name-input:focus { outline: 0; }
  .recent-row.renaming .r-name-input { display: block; }
  .recent-row.renaming .r-meta,
  .recent-row.renaming .r-status { opacity: 0.55; }

  /* ── Sign-in nudge banner (idle screen) ─────────────────── */
  .signin-nudge {
    display: flex; align-items: center; justify-content: space-between;
    gap: 14px;
    flex-wrap: wrap;
    padding: 14px 16px;
    background: linear-gradient(135deg, rgba(37,99,235,0.10), rgba(37,99,235,0.04));
    border: 1px solid var(--brand-border);
  }
  .signin-nudge .sn-text { font-size: 14px; color: var(--text); }
  .signin-nudge .sn-text b { font-weight: 600; }
  .signin-nudge .sn-text .sn-sub {
    display: block; font-size: 12px; color: var(--text-dim); margin-top: 2px;
  }
  .signin-nudge .sn-cta { flex: 0 0 auto; min-width: 100px; width: auto; }
  .signin-nudge .sn-dismiss {
    flex: 0 0 auto;
    background: none; border: 0; cursor: pointer;
    color: var(--text-faint);
    font-size: 18px; line-height: 1;
    padding: 4px 8px;
    border-radius: 6px;
    transition: color 0.15s, background 0.15s;
  }
  .signin-nudge .sn-dismiss:hover {
    color: var(--text);
    background: var(--surface-2);
  }

  /* ── Progress-screen elapsed-time chip ───────────────────── */
  .prog-elapsed {
    display: inline-flex; align-items: baseline; gap: 6px;
    margin-top: 8px;
    padding: 3px 10px;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 999px;
    font-size: 12px;
    color: var(--text-dim);
    font-variant-numeric: tabular-nums;
  }
  .prog-elapsed-sub { color: var(--text-faint); font-size: 11px; }
  /* ETA badge inside the elapsed chip — separated by a thin divider
     so users don't read "0:42 elapsed about 1 min remaining" as a
     run-on. Same color tone as the elapsed text — secondary, not
     attention-grabbing. */
  .prog-eta {
    margin-left: 4px; padding-left: 8px;
    border-left: 1px solid var(--border);
    color: var(--brand-light);
    font-size: 11px; font-weight: 500;
  }
  /* Tab-wake banner — appears for 2.5 seconds when the user returns
     from a backgrounded tab while polling is active. Quick reassurance
     that the system caught up. Auto-fades via JS. */
  .prog-tabwake {
    margin-top: 10px; padding: 8px 14px;
    background: rgba(96,165,250,0.10);
    border: 1px solid rgba(96,165,250,0.28);
    border-radius: 8px;
    font-size: 12px; color: var(--brand-light);
    text-align: center;
    transition: opacity 0.4s;
  }
  .prog-tabwake.fade { opacity: 0; }

  /* ── Progress-screen footer (run-in-background link) ─────── */
  .prog-foot {
    margin-top: 18px;
    padding-top: 14px;
    border-top: 1px solid var(--border);
    text-align: center;
    font-size: 12px;
    color: var(--text-faint);
    line-height: 1.5;
  }
  .prog-foot-link {
    background: none; border: 0; cursor: pointer;
    font: inherit; color: var(--brand-light);
    padding: 2px 4px;
  }
  .prog-foot-link:hover { color: var(--brand-hover); text-decoration: underline; }
  .prog-foot-sub b { color: var(--text-dim); font-weight: 500; }

  /* ── Clip-card celebration entrance ─────────────────────── */
  /* Staggered fade-up animation when results first render —
     applied via a class so we can opt into it once (the first
     time the cards appear) and skip on subsequent re-renders.
     Each card's delay scales with its index via inline style.  */
  .clip-card.celebrate {
    opacity: 0;
    transform: translateY(8px);
    animation: clip-card-in 0.45s ease-out both;
  }
  @keyframes clip-card-in {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: translateY(0);    }
  }

  /* ── Try-with-sample link ──────────────────────────────── */
  .try-sample-link {
    background: none; border: 0; cursor: pointer;
    color: var(--text-dim);
    font: inherit; font-size: 13px;
    padding: 6px 12px;
    border-radius: 999px;
    transition: color 0.15s, background 0.15s;
  }
  .try-sample-link:hover { color: var(--brand-light); background: var(--brand-dim); }

  /* Inline mode-switch link below the Generate button. Default
     entry is AI clipping; this lets users who already know what
     they want to cut drop into manual-edit mode without a big
     two-button picker dominating the idle screen. */
  /* Daily-quota badge near the Generate button. Quiet by default
     (text-faint colour, just informational), amber when the user
     is approaching the cap, red + bolder when at the cap so they
     understand why Generate is disabled. */
  .quota-badge {
    margin-top: 10px;
    text-align: center;
    font-size: 12px;
    color: var(--text-faint);
  }
  .quota-badge.warn   { color: #fde68a; }
  .quota-badge.danger { color: var(--bad); font-weight: 600; }

  /* Baked-caption hint — appears above the clip grid when the
     vision detector flagged the source as already having burned-
     in subtitles. Subtle amber tint, dismissible, with a primary
     "turn off captions everywhere" action. */
  .baked-caption-hint {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    margin-top: 14px;
    padding: 12px 14px;
    background: rgba(251, 191, 36, 0.08);
    border: 1px solid rgba(251, 191, 36, 0.30);
    border-radius: var(--r-sm);
    color: #fde68a;
    font-size: 13px;
    line-height: 1.5;
  }
  .baked-caption-hint .bch-icon { flex: 0 0 auto; font-size: 18px; }
  .baked-caption-hint b { color: #fde68a; }
  .baked-caption-hint .bch-text { flex: 1 1 auto; min-width: 0; }
  .baked-caption-hint .bch-cta {
    flex: 0 0 auto;
    width: auto;
    background: rgba(251, 191, 36, 0.16);
    border: 1px solid rgba(251, 191, 36, 0.45);
    color: #fde68a;
    padding: 6px 14px;
    font-size: 12px; font-weight: 600;
  }
  .baked-caption-hint .bch-cta:hover {
    background: rgba(251, 191, 36, 0.26);
    border-color: rgba(251, 191, 36, 0.65);
  }
  .baked-caption-hint .bch-dismiss {
    flex: 0 0 auto;
    background: none; border: 0; cursor: pointer;
    color: var(--text-faint);
    font-size: 18px; line-height: 1;
    padding: 0 4px;
  }
  .baked-caption-hint .bch-dismiss:hover { color: var(--text); }
  @media (max-width: 600px) {
    .baked-caption-hint { flex-wrap: wrap; }
    .baked-caption-hint .bch-cta { margin-left: auto; }
  }

  .mode-link-wrap { text-align: center; margin-top: 12px; }
  .mode-link {
    /* Text-faint was too low-contrast — users were missing this
       as the only visible doorway to manual-edit mode. Bumped to
       text-dim with the brand-light hover so it actually reads as
       a clickable affordance. */
    background: var(--surface-2);
    border: 1px solid var(--border);
    cursor: pointer;
    color: var(--text-dim);
    font: inherit; font-size: 13px;
    font-weight: 500;
    padding: 7px 14px;
    border-radius: 999px;
    transition: color 0.15s, border-color 0.15s, background 0.15s;
  }
  .mode-link:hover {
    color: var(--brand-light);
    border-color: var(--brand-border);
    background: var(--brand-dim);
  }

  /* Post-export feedback row. Tucked under the btn-row inside
     each clip card. Only shown after the user has actually
     exported + presumably watched the result — that's the
     moment opinion is most informed. Three visual phases
     controlled by data-phase. */
  .clip-feedback {
    margin-top: 12px;
    padding-top: 10px;
    border-top: 1px dashed var(--border);
  }
  .clip-feedback .cf-ask {
    display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
    font-size: 13px; color: var(--text-dim);
  }
  .clip-feedback .cf-prompt { flex: 1 1 auto; min-width: 0; }
  .clip-feedback .cf-rating-btn {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 999px;
    padding: 5px 12px;
    font-size: 12px; font-weight: 500;
    color: var(--text-dim);
    cursor: pointer;
    transition: border-color 0.15s, color 0.15s, background 0.15s;
  }
  .clip-feedback .cf-rating-btn:hover {
    color: var(--text);
    border-color: var(--brand-border);
    background: var(--brand-dim);
  }
  .clip-feedback[data-phase="note"] .cf-ask    { display: none; }
  .clip-feedback[data-phase="note"] .cf-note   { display: flex; }
  .clip-feedback[data-phase="thanks"] .cf-ask  { display: none; }
  .clip-feedback[data-phase="thanks"] .cf-note { display: none; }
  .clip-feedback[data-phase="thanks"] .cf-thanks {
    display: block;
    font-size: 12px; color: var(--good);
  }
  .clip-feedback .cf-note {
    align-items: center; gap: 6px; flex-wrap: wrap;
  }
  .clip-feedback .cf-note-input {
    flex: 1 1 180px;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 7px 10px;
    font-size: 13px;
    color: var(--text);
    min-width: 0;
  }
  .clip-feedback .cf-note-input:focus {
    outline: 0;
    border-color: var(--brand-border);
  }
  .clip-feedback .cf-note-submit,
  .clip-feedback .cf-note-skip {
    flex: 0 0 auto;
    background: var(--brand);
    color: white;
    border: 0;
    border-radius: 999px;
    padding: 6px 14px;
    font-size: 12px; font-weight: 600;
    cursor: pointer;
  }
  .clip-feedback .cf-note-submit:hover { background: var(--brand-hover); }
  .clip-feedback .cf-note-skip {
    background: none;
    border: 1px solid var(--border);
    color: var(--text-faint);
  }
  .clip-feedback .cf-note-skip:hover { color: var(--text); border-color: var(--border-strong); }

  /* ── Per-clip delete (×) button ─────────────────────────────
     Top-LEFT of the clip card so it can't be confused with the
     multi-select chip in the top-right. Hover-reveal on
     hover-capable devices, always-visible on touch (otherwise
     unreachable). Click flow: confirm → DELETE endpoint →
     remove the card from the DOM. */
  .clip-delete-btn {
    position: absolute;
    top: 8px; left: 8px;
    z-index: 5;
    width: 30px; height: 30px;
    border-radius: 50%;
    background: rgba(0,0,0,0.55);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    border: 1.5px solid rgba(255,255,255,0.40);
    color: rgba(255,255,255,0.85);
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    font-size: 18px; line-height: 1;
    transition: background 0.15s, border-color 0.15s, opacity 0.15s;
    opacity: 0;
  }
  .clip-card:hover .clip-delete-btn,
  .clip-delete-btn:focus-visible { opacity: 1; }
  @media (hover: none) {
    .clip-delete-btn { opacity: 1; }
  }
  .clip-delete-btn:hover {
    background: rgba(220, 38, 38, 0.85);
    border-color: rgba(252, 165, 165, 0.85);
    color: #fff;
  }

  /* ── Toast (shared) ─────────────────────────────────────────── */
  /* Floats up from the bottom-center of the viewport; auto-fades.
     Used for inline confirmations (Saved) and soft errors (Sign-in
     failed). One toast at a time — newer ones replace older. */
  .toast-host {
    position: fixed; left: 50%; bottom: 24px;
    transform: translateX(-50%);
    z-index: 200;
    display: flex; flex-direction: column; gap: 8px;
    align-items: center;
    pointer-events: none;
  }
  .toast {
    pointer-events: auto;
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: 999px;
    padding: 10px 18px;
    font-size: 13px; font-weight: 500;
    color: var(--text);
    box-shadow: 0 8px 32px rgba(0,0,0,0.55);
    animation: toast-in 0.18s ease-out both, toast-out 0.30s ease-in 1.6s both;
    display: inline-flex; align-items: center; gap: 8px;
    max-width: calc(100vw - 32px);
  }
  .toast.success { border-color: rgba(74,222,128,0.40); }
  .toast.success::before {
    content: ""; width: 7px; height: 7px; border-radius: 50%;
    background: var(--good); box-shadow: 0 0 0 3px rgba(74,222,128,0.20);
  }
  .toast.error   { border-color: rgba(248,113,113,0.40); color: #fecaca; }
  .toast.error::before {
    content: "!"; width: 16px; height: 16px; border-radius: 50%;
    background: var(--bad); color: white;
    font-size: 11px; font-weight: 700;
    display: inline-flex; align-items: center; justify-content: center;
  }
  @keyframes toast-in  { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
  @keyframes toast-out { from { opacity: 1; transform: translateY(0); } to { opacity: 0; transform: translateY(-4px); } }
  /* Undo toast — variant with an inline button. Stays visible
     until the user clicks Undo, dismisses it, or the commit
     timer fires. No auto-fade animation. */
  .toast.undo {
    animation: toast-in 0.18s ease-out both;
    border-color: var(--brand-border);
  }
  .toast.undo .toast-undo-btn {
    background: var(--brand);
    color: white;
    border: 0;
    border-radius: 999px;
    padding: 4px 12px;
    font-size: 12px; font-weight: 600;
    cursor: pointer;
    transition: background 0.15s;
  }
  .toast.undo .toast-undo-btn:hover { background: var(--brand-hover); }
  .toast.undo .toast-undo-progress {
    display: block;
    height: 2px;
    background: var(--brand-light);
    border-radius: 999px;
    margin-top: 6px;
    transform-origin: left center;
    animation: toast-undo-bar 5s linear forwards;
  }
  @keyframes toast-undo-bar { from { transform: scaleX(1); } to { transform: scaleX(0); } }
  .toast.undo .toast-undo-stack { display: flex; flex-direction: column; gap: 0; }

  /* ── Modal (beta gate) ──────────────────────────────────────── */
  .modal-back {
    position: fixed; inset: 0;
    background: rgba(0,0,0,0.78);
    display: flex; align-items: center; justify-content: center;
    z-index: 100;
    padding: 20px;
  }
  .modal {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r);
    padding: 22px;
    max-width: 380px;
    width: 100%;
  }

  /* ── Footer ────────────────────────────────────────────────── */
  footer {
    margin-top: 30px;
    padding: 18px 20px;
    text-align: center;
    color: var(--text-faint);
    font-size: 12px;
  }

  /* ── Responsive tuning ─────────────────────────────────────── */
  /* Base: phones — single column, tight padding, video can take
     most of the viewport height since it's the focus. */
  @media (max-width: 540px) {
    .card { padding: 18px; border-radius: 10px; }
    main { padding: 8px 14px 60px; }
    .hdr { padding: 14px 16px; }
    .clip-card video { aspect-ratio: 9 / 16; max-height: 80vh; }
  }
  /* Tablet portrait + small laptop — 2 columns. Card width is now
     ~330px, so the 9:16 video keeps its ratio without letterboxing.
     Forms (.narrow) stay capped at form-width and centred. */
  @media (min-width: 700px) {
    .clip-grid { grid-template-columns: 1fr 1fr; }
    main { padding: 18px 24px 80px; }
    .card { padding: 26px; }
  }
  /* Laptop / desktop — 3 columns at 380–400px each, a comfortable
     readable size for vertical clip previews. */
  @media (min-width: 1100px) {
    .clip-grid { grid-template-columns: repeat(3, 1fr); }
    .clip-card video { max-height: 70vh; }
  }
  /* Wide desktop — keep 3 columns, just give the grid breathing
     room rather than stretching cards to absurd widths. The
     centred narrow forms benefit from the same max-width. */
  @media (min-width: 1500px) {
    .clip-grid { gap: 18px; }
  }

  [hidden] { display: none !important; }
