/* App-only extras. Ink, border and surface tokens come from ds.css — declaring
   them here again would win the cascade and undo the token layer (T003). */
:root{ color-scheme:light dark;

  --bg:#ffffff; --placeholder:#5b6472;
  --hover:#f5f5f5; --callout:#f4f4f5; --black:#000000;
}
/* App-only extras for the dark theme (spec 007, T007). Ink/border/surface stay
   in ds.css — redeclaring them here would win the cascade and undo the token
   layer. --black is the "inverse chip" role (active pills, play button, primary
   modal button, slider fills) and is always painted with #fff labels, so on
   dark it becomes a RAISED neutral, not a hole: #fff on #23262d = 12.8:1. */
:root[data-theme="dark"]{
  color-scheme:dark;
  --bg:#15161a; --placeholder:#9aa3b0;
  --hover:#1d1f24; --callout:#1a1c21; --black:#23262d;
}
*{ box-sizing:border-box; margin:0; padding:0; }
button,input,select,textarea{ font-family:inherit; font-size:inherit; line-height:inherit; }
html,body{ height:100%; }
body{
  font-family:'Plus Jakarta Sans','Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
  font-weight:400; font-size:15px; line-height:1.5; color:var(--text); background:var(--bg);
  -webkit-font-smoothing:antialiased; overflow-x:hidden; opacity:1; transition:opacity .18s ease;
}
html.i18n-loading body{ opacity:0; }
.layout{ display:flex; height:100vh; height:100dvh; overflow:hidden; background:var(--canvas); }
.sr-only{ position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden;
  clip:rect(0,0,0,0); white-space:nowrap; border:0; }
.app{ display:flex; flex-direction:column; height:100%; flex:1; min-width:0; }
/* ---------- SIDEBAR ---------- */
.sidebar{ width:280px; flex:none; background:transparent;
  display:flex; flex-direction:column; overflow:hidden; contain:layout paint; will-change:width;
  transition:width .26s cubic-bezier(.22,1,.36,1); }
.sidebar.collapsed{ width:72px; }
.sb-scroll{ flex:1; overflow-y:auto; padding:8px 12px; position:relative; }
/* The sidebar scrollbar is an auto-hiding overlay: the thumb is invisible at
   rest and only painted while the list is actually being scrolled (JS toggles
   .is-scrolling on every scroll event — wheel, touch, keyboard and thumb drag
   all fire it — and clears it shortly after the scrolling settles). Slim: a
   4px gutter with a 1px blend border, so the visible thumb is a 2px sliver.
   The gutter is reserved in every state and the track stays transparent, so
   the nav content never shifts and nothing shows when the list is at rest. */
.sb-scroll{ scrollbar-width:thin; scrollbar-color:transparent transparent; }
.sb-scroll::-webkit-scrollbar{ width:4px; }
.sb-scroll::-webkit-scrollbar-track{ background:transparent; }
.sb-scroll::-webkit-scrollbar-thumb{ background:transparent; border:1px solid transparent; border-radius:8px; transition:background .18s ease, border-color .18s ease; }
.sb-scroll.is-scrolling{ scrollbar-color:#e0e0e0 transparent; }
.sb-scroll.is-scrolling::-webkit-scrollbar-thumb{ background:var(--surface-hover); border-color:#fbfbfb; }
@media (prefers-reduced-motion:reduce){ .sb-scroll::-webkit-scrollbar-thumb{ transition:none; } }
/* Sidebar brand — the same element as the site header (owner, 2026-11):
   a.brand-name.site-brand > img.brand-logo, the 118×20 wordmark. The rail keeps
   its own padding/scale; the narrow rail swaps the SAME <img> to the mark with
   content:url(), so the markup never carries a second logo. */
.sidebar .site-brand{ display:flex; align-items:center; gap:9px; padding:8px 14px; margin-bottom:26px;
  font-size:20px; font-weight:800; letter-spacing:-.03em; white-space:nowrap; color:inherit;
  text-decoration:none; cursor:pointer; }
.sidebar .site-brand .brand-logo{ width:auto; height:20px; display:block; transition:transform .18s;
  filter:drop-shadow(0 3px 10px color-mix(in srgb, var(--brand-coral) 30%, transparent)); }
.sidebar .site-brand:hover .brand-logo{ transform:rotate(-4deg); }
.sidebar.collapsed .site-brand{ justify-content:center; padding:18px 0 12px; }
.sidebar.collapsed .site-brand .brand-logo{ content:url("/static/saydivoice-mark.svg"); width:24px; height:24px; }

@media (max-width: 1240px) {
  .sidebar .site-brand .brand-logo{ height: 18px; }
}
.workspace{ display:flex; align-items:center; gap:10px; margin:0 12px 10px; padding:8px 10px;
  border-radius:12px; border:1px solid var(--border-2); background:var(--surface); cursor:pointer; white-space:nowrap; }
.workspace:hover{ background:var(--hover); }
.ws-dot{ width:18px; height:18px; border-radius:999px; flex:none;
  background:radial-gradient(circle at 40% 35%,#fed35d,#f97316); }
.ws-name{ font-size:15px; font-weight:600; flex:1; overflow:hidden; text-overflow:ellipsis; }
.ws-chev{ color:var(--text-3); flex:none; }
.sidebar.collapsed .workspace{ justify-content:center; padding:8px; }
.sidebar.collapsed .ws-name,.sidebar.collapsed .ws-chev{ display:none; }
.s-item[data-mode="home"]{ margin-top:6px; }
.s-item{ display:flex; align-items:center; gap:10px; min-height:40px; margin:6px 0; padding:6px 8px; border-radius:12px;
  color:#7d7d7d; text-decoration:none; cursor:pointer; white-space:nowrap; position:relative; z-index:1;
  font-size:15px; font-weight:500; letter-spacing:-.01em; -webkit-tap-highlight-color:transparent; touch-action:manipulation;
  width:100%; text-align:left; background:none; border:none; font-family:inherit;
  transition:background .15s ease, color .15s ease, padding .22s ease, gap .22s ease, min-height .22s ease; }
.s-item:hover{ color:var(--text); }
.s-item.active{ font-weight:600; background:var(--canvas); border-radius:12px; }
.s-ico{ flex:none; display:flex; align-items:center; justify-content:center; width:22px; height:22px; color:var(--text-2); }
/* Icon (and its wrapper) must not swallow clicks: an inline <svg> is only
   clickable on its painted strokes by default (pointer-events:visiblePainted),
   so clicking the icon's transparent interior did nothing. Passing pointer
   events through makes the entire .s-item the click target. */
.s-item .s-ico,
.s-item .s-ico svg,
.s-item > svg,
.s-item .s-label{ pointer-events:none; }
.s-item.active .s-ico{ color:var(--text); }
.s-item.active .s-label{ color:var(--text); }
.s-label{ flex:1 1 auto; min-width:0; width:100%; overflow:hidden; text-overflow:ellipsis; font-size:14px;
  transition:opacity .18s ease, transform .18s ease; }
/* Nav rows are label-only: the rail stays short and scannable. */
/* Grouped nav sections (Product / Playground) + "Soon" badges */
.sb-group{ font-size:12px; font-weight:700; letter-spacing:.06em; text-transform:uppercase; color:var(--text-3);
  padding:0 12px; margin:16px 0 10px; max-height:24px; overflow:hidden;
  transition:opacity .15s ease, max-height .22s ease, margin .22s ease, padding .22s ease; }
/* Gate hint on nav rows that need an account (T036). */
.s-item .s-hint{ margin-left:auto; font-size:12px; font-weight:700; letter-spacing:.01em;
  color:var(--text-3); background:var(--surface-hover); border:1px solid var(--border);
  border-radius:var(--r-full); padding:1px 8px; white-space:nowrap; }
.sidebar.collapsed .s-item .s-hint{ display:none; }

.s-badge{ margin-left:auto; flex:none; font-size:12px; font-weight:700; padding:2px 7px; border-radius:999px;
  background:var(--violet-soft); color:#fe655d; letter-spacing:.02em; }
/* Not-yet-shipped tools (e.g. Voice Changer "Soon") are visibly de-emphasized
   so the nav's real, actionable tools stand out and users don't feel teased. */
.s-item[data-action="soon"]{ opacity:.5; }
.s-item[data-action="soon"]:hover{ opacity:.7; background:none; }
.s-section{ font-size:13px; font-weight:600; color:var(--text-3); padding:16px 12px 6px; white-space:nowrap;
  max-height:42px; overflow:hidden; transition:opacity .15s ease, max-height .22s ease, margin .22s ease, padding .22s ease; }
.sidebar.collapsed .s-item{ justify-content:center; padding:0; gap:0; min-height:42px; }
.sidebar.collapsed .s-label{ display:block; flex:0 0 0; width:0; min-width:0; opacity:0; transform:translateX(-4px); pointer-events:none; }
.sidebar.collapsed .s-badge{ display:inline-flex; width:0; min-width:0; max-width:0; padding:0; margin-left:0; opacity:0; overflow:hidden; pointer-events:none; }
.sidebar.collapsed .s-section,
.sidebar.collapsed .sb-group{ max-height:0; opacity:0; margin:0; padding-top:0; padding-bottom:0; pointer-events:none; }
.sb-bottom{ flex:none; padding:10px 12px 14px; }
.invite-card{ border:1px solid var(--border-2); border-radius:12px; background:var(--surface); padding:14px; margin-bottom:10px; }
.invite-card .ic-top{ display:flex; align-items:center; justify-content:center; width:30px; height:30px;
  border-radius:8px; background:var(--surface-hover); color:var(--text); margin-bottom:10px; }
.invite-card h4{ font-size:15px; font-weight:600; margin-bottom:5px; }
.invite-card p{ font-size:13px; line-height:1.45; color:var(--text-2); }
.sidebar.collapsed .invite-card{ display:none; }
.sidebar.collapsed .dev-label{ display:none; }
.sb-foot{ font-size:12px; color:var(--text-3); text-align:center; padding:10px 4px 2px; white-space:nowrap; }

.sb-foot a{ color:var(--text-2); text-decoration:none; font-weight:600; }
.sb-foot a:hover{ color:var(--text); text-decoration:underline; }
.sidebar.collapsed .sb-foot{ display:none; }

/* saydi.ai promo card (sidebar) */
.promo-card{ display:block; text-decoration:none; color:var(--text); border:1px solid var(--border-2);
  border-radius:16px; padding:14px; margin-bottom:10px; background:linear-gradient(135deg,#fff7ed,#eef2ff);
  transition:box-shadow .15s, transform .15s; }
.promo-card:hover{ box-shadow:var(--e-2); transform:translateY(-1px); }
.promo-top{ display:flex; align-items:center; gap:8px; margin-bottom:6px; }
.promo-mark{ font-size:16px; line-height:1; }
.promo-logo{ display:block; flex:none; }
.promo-name{ font-size:15px; font-weight:700; letter-spacing:-.01em; }
.promo-body{ font-size:13px; line-height:1.45; color:var(--text-2); margin-bottom:10px; }
.promo-cta{ display:inline-flex; align-items:center; gap:5px; font-size:13px; font-weight:700; color:var(--accent); }
.sidebar.collapsed .promo-card{ display:none; }
/* Ko-fi tip nudge: small, dismissible card shown bottom-right after value. */
.tip-nudge{ position:fixed; right:18px; bottom:18px; z-index:70; width:min(330px, calc(100vw - 36px));
  display:flex; align-items:flex-start; gap:11px; padding:15px 34px 14px 15px;
  background:var(--surface,#fff); color:var(--text,#111827);
  border:1px solid var(--border-2,#e5e7eb); border-radius:12px; box-shadow:var(--e-3);
  opacity:0; transform:translateY(14px) scale(.98); pointer-events:none;
  transition:opacity .22s ease, transform .22s ease; }
.tip-nudge.show{ opacity:1; transform:none; pointer-events:auto; }
.tip-nudge[hidden]{ display:none; }
.tip-emoji{ font-size:24px; line-height:1.1; flex:none; }
.tip-main{ display:flex; flex-direction:column; gap:3px; }
.tip-main strong{ font-size:13px; }
.tip-main > span{ font-size:12px; line-height:1.45; color:var(--muted,#6b7280); }
.tip-actions{ display:flex; align-items:center; gap:10px; margin-top:11px; }
.tip-cta{ display:inline-flex; align-items:center; text-decoration:none; font-size:12px; font-weight:700;
  color:#fff; background:#fe655d; padding:8px 13px; border-radius:8px; transition:filter .15s; }
.tip-cta:hover{ filter:brightness(1.06); }
.tip-later{ background:none; border:0; cursor:pointer; font-size:12px; font-weight:600; color:var(--muted,#6b7280); padding:6px 4px; }
.tip-later:hover{ color:var(--text,#111827); }
.tip-close{ position:absolute; top:8px; right:8px; background:none; border:0; cursor:pointer; color:var(--muted,#9ca3af); padding:4px; line-height:0; border-radius:8px; }
.tip-close:hover{ color:var(--text,#111827); background:var(--border-2,#f0f0f2); }

/* get started chips */
.gs-chips{ display:flex; flex-wrap:wrap; gap:10px; margin-top:14px; }
.gs-chip{ display:inline-flex; align-items:center; gap:9px; height:42px; padding:0 16px; border-radius:12px;
  border:1px solid var(--border-2); background:var(--surface); font-size:15px; font-weight:500; color:var(--text);
  cursor:pointer; font-family:inherit; }
.gs-chip:hover{ background:var(--hover); }
.gs-chip svg{ color:var(--text-2); }

/* ---------- TOP BAR ---------- */
.topbar{ display:flex; align-items:center; justify-content:space-between; gap:10px; padding:8px 22px; flex:none; }
.app > .topbar{ background:var(--canvas); padding:8px 22px 8px 8px; }
.topbar-left{ display:flex; align-items:center; gap:4px; flex:1 1 auto; min-width:0; }
.topbar-breadcrumb{ display:inline-flex; align-items:center; gap:6px; min-width:0; }
.topbar-crumb-home{ color:var(--text-2); font-size:14px; font-weight:600; text-decoration:none; white-space:nowrap;
  padding:4px 6px; border-radius:8px; transition:color .15s ease, background .15s ease; }
.topbar-crumb-home:hover{ color:#fe655d; background:var(--brand-coral-soft); text-decoration:none; }
.topbar-crumb-home[hidden], .topbar-crumb-sep[hidden]{ display:none; }
.topbar-crumb-sep{ color:var(--text-3); flex:none; }
.topbar-breadcrumb .topbar-title{ min-width:0; }
.icon-btn{ display:flex; align-items:center; justify-content:center; width:36px; height:36px; border-radius:8px;
  border:none; background:none; cursor:pointer; color:var(--text); flex:none; }
.icon-btn:hover{ background:var(--hover); }
#sidebarToggle{ width:44px; height:44px; }
#sidebarToggle:focus-visible{ outline:3px solid #245fcc; outline-offset:2px; }
/* pointer-events:none so a click on the icon (including its transparent
   interior — SVG defaults to visiblePainted, so gaps between strokes are not
   clickable) passes through to the button. The whole toggle stays clickable. */
#sidebarToggle > svg{ color:var(--text-2); width:18px; height:18px; pointer-events:none; }
/* Swap the toggle icon by sidebar state: show "collapse" (chevron toward the
   sidebar) when expanded, and "expand" (chevron away) when collapsed. */
#sidebarToggle .sb-ic-expand,
#sidebarToggle .sb-ic-close{ display:none; }
body:has(.sidebar.collapsed) #sidebarToggle .sb-ic-collapse{ display:none; }
body:has(.sidebar.collapsed) #sidebarToggle .sb-ic-expand{ display:inline; }
.topbar-title{ font-size:14px; font-weight:600; letter-spacing:-.01em; color:var(--text);
  min-width:0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.topbar-right{ display:flex; align-items:center; gap:12px; flex:none; }
.pill-btn{ display:inline-flex; align-items:center; gap:8px; height:34px; padding:0 16px; border-radius:16px;
  border:1px solid var(--border-2); background:var(--surface); font-size:13px; font-weight:600; color:var(--text);
  cursor:pointer; font-family:inherit; }
.pill-btn:hover{ background:var(--hover); }
.lang-picker{ position:relative; display:inline-flex; align-items:center; }
.lang-picker-btn{ display:flex; align-items:center; gap:6px; height:32px; padding:0 10px 0 12px;
  border-radius:16px; border:1px solid var(--border-2); background:var(--surface); color:var(--text); cursor:pointer;
  font-family:inherit; font-size:13px; font-weight:500; }
.lang-picker-btn:hover{ background:var(--hover); }
.lang-picker-btn svg{ color:var(--text-2); flex:none; }
.lang-picker-flag{ font-size:16px; line-height:1; display:none; }
.lang-picker-panel{ display:none; position:absolute; top:100%; right:0; margin-top:8px; background:var(--surface);
  border-radius:12px; box-shadow:var(--e-3); padding:8px; min-width:160px; z-index:100;
  max-height:320px; overflow-y:auto; }
.lang-picker-panel.open{ display:block; }
/* Sliding hover indicator — same technique as the pricing toggle's pill. app.js
   (buildLangSelect) sets --lp-y/--lp-h from the hovered item; this ::before
   glides to match. JS-measured because the list is long and scrollable. The
   active language keeps its own pink highlight below. */
.lang-picker-panel::before{ content:""; position:absolute; left:8px; right:8px; top:0;
  height:var(--lp-h,0); transform:translateY(var(--lp-y,0)); background:var(--hover, #f5f5f7);
  border-radius:8px; opacity:var(--lp-ind,0); pointer-events:none; z-index:0;
  transition:transform .18s cubic-bezier(.4,0,.2,1), height .18s cubic-bezier(.4,0,.2,1), opacity .12s ease; }
.lang-picker-panel span{ display:block; padding:8px 12px; border-radius:8px; text-decoration:none;
  color:var(--text); font-size:14px; cursor:pointer; position:relative; z-index:1; }
.lang-picker-panel span:not(:first-child){ margin-top:2px; }
.lang-picker-panel span.lang-active{ background:var(--coral-tint); color:#fe655d; font-weight:600; }

/* ---- Appearance controls (spec 007) --------------------------------------
   Three ways in, one state: the avatar-menu radiogroup (signed in), the topbar
   icon (signed out) and a row in the language panel. All three are wired by
   assets/theme.js, which stamps [data-theme-current] and aria-checked, so none
   of these rules needs a per-theme branch beyond the icon swap. */
.theme-btn{ width:36px; height:36px; color:var(--text-2); }
.theme-btn:hover{ color:var(--text); }
.theme-btn[hidden]{ display:none; }
.theme-ic{ display:none; width:18px; height:18px; pointer-events:none; }
.theme-ic svg{ width:18px; height:18px; display:block; }
.theme-btn[data-theme-current="light"] [data-theme-icon="light"],
.theme-btn[data-theme-current="dark"] [data-theme-icon="dark"],
.theme-btn[data-theme-current="system"] [data-theme-icon="system"]{ display:block; }
.lang-picker-panel span.lang-theme{ display:flex; align-items:center; justify-content:space-between; gap:10px;
  margin-top:6px; padding-top:10px; border-top:1px solid var(--border); }
.lang-theme-key{ color:var(--text-2); font-size:12px; font-weight:600; letter-spacing:.01em; }
.lang-theme-val{ color:var(--accent-text); font-size:13px; font-weight:650; }
.icon-pill{ width:42px; height:40px; border-radius:12px; border:1px solid var(--border-2); background:var(--surface);
  display:flex; align-items:center; justify-content:center; cursor:pointer; color:var(--text); }
.icon-pill:hover{ background:var(--hover); }
.bell{ position:relative; }
.bell svg{ width:16px; height:16px; }
.bell::after{ content:""; position:absolute; top:5px; right:7px; width:5px; height:5px; border-radius:999px;
  background:#3b82f6; border:1.5px solid #fff; }
.bell.read::after{ display:none; }

/* Notifications dropdown */
.notif-wrap{ position:relative; display:inline-flex; height:32px; }
.notif-wrap .icon-pill{ width:32px; height:32px; }
.notif-panel{ position:absolute; top:36px; right:0; width:340px; max-width:90vw; background:var(--surface);
  border:1px solid var(--border-2); border-radius:16px; box-shadow:var(--e-3);
  display:none; z-index:80; overflow:hidden; }
.notif-panel.show{ display:block; }
.notif-head{ display:flex; align-items:center; justify-content:space-between; padding:14px 16px;
  border-bottom:1px solid var(--border); font-size:15px; font-weight:700; letter-spacing:-.01em; }
.notif-read{ border:none; background:none; cursor:pointer; font-family:inherit; font-size:13px;
  font-weight:600; color:var(--text-2); padding:0; }
.notif-read:hover{ color:var(--text); }
.notif-list{ max-height:60vh; overflow-y:auto; }
.notif-item{ display:flex; gap:12px; padding:13px 16px; border-bottom:1px solid var(--border);
  text-decoration:none; color:var(--text); cursor:pointer; }
.notif-item:last-child{ border-bottom:none; }
.notif-item:hover{ background:var(--hover); }
.notif-ico{ flex:none; width:34px; height:34px; border-radius:8px; display:flex; align-items:center;
  justify-content:center; font-size:16px; background:var(--surface-hover); }
.notif-t{ font-size:14px; font-weight:600; letter-spacing:-.01em; }
.notif-b{ font-size:13px; color:var(--text-2); line-height:1.45; margin-top:2px; }
.notif-time{ font-size:12px; color:var(--text-3); margin-top:4px; }
.avatar-top{ width:34px; height:34px; border-radius:999px; margin-left:4px;
  background:conic-gradient(from 210deg,#f59e0b,#ef4444,#8b5cf6,#3b82f6,#f59e0b); padding:2px; cursor:pointer; }
.avatar-top .ph{ width:100%; height:100%; border-radius:999px; display:block;
  background:radial-gradient(circle at 42% 36%,#caa97f,#7a5230); }
.avatar-top svg{ width:100%; height:100%; border-radius:999px; display:block; }
.avatar-top:hover{ transform:scale(1.06); transition:transform .15s; }

.avatar-wrap{ position:relative; display:inline-flex; }
/* The wrapper ships `hidden` on builds where accounts are disabled. An author
   `display:inline-flex` rule beats the UA stylesheet's [hidden] rule, so without
   this the profile control was visible but its JS guard made the click a no-op
   ("profile button ... doesn't work at all"). */
.avatar-wrap[hidden]{ display:none; }
.avatar-menu{ position:absolute; top:calc(100% + 10px); right:0; width:304px; max-width:calc(100vw - 24px); background:var(--surface);
  border:1px solid var(--border-2); border-radius:16px; box-shadow:var(--e-3);
  padding:10px; z-index:80; }
.avatar-menu[hidden]{ display:none; }
/* Sliding hover/focus indicator — same technique as the pricing toggle's pill.
   app.js sets --am-y/--am-h (and --am-bg for the red logout tint) from the
   hovered/focused actionable item; this ::before glides to match. JS-measured
   because items have different heights and some are hidden dynamically. The
   account row and divider are skipped (not actionable). */
.avatar-menu{ position:absolute; top:calc(100% + 10px); right:0; width:304px; max-width:calc(100vw - 24px); background:var(--surface);
  border:1px solid var(--border-2); border-radius:16px; box-shadow:var(--e-3);
  padding:10px; z-index:80; }
.avatar-menu::before{ content:""; position:absolute; left:10px; right:10px; top:0;
  height:var(--am-h,0); transform:translateY(var(--am-y,0)); background:var(--am-bg, var(--hover, #f5f5f7));
  border-radius:12px; opacity:var(--am-ind,0); pointer-events:none; z-index:0;
  transition:transform .18s cubic-bezier(.4,0,.2,1), height .18s cubic-bezier(.4,0,.2,1), opacity .12s ease, background-color .12s ease; }
.avatar-menu-item{ display:flex; align-items:center; gap:11px; width:100%; margin-top:2px; padding:9px 10px;
  border:none; background:none; cursor:pointer; font-family:inherit; font-size:13px; font-weight:500;
  color:var(--text); border-radius:12px; text-align:left; position:relative; z-index:1; transition:color .14s ease; }
/* MUST out-specify `.avatar-menu-item{display:flex}` above: a class beats the UA
   stylesheet's [hidden]{display:none}, so without this an item that app.js hides
   with el.hidden=true stays visible. Attribute+class = higher specificity. */
.avatar-menu-item[hidden], .avatar-menu-who[hidden]{ display:none; }
.avatar-menu-item:focus-visible{ outline:2px solid var(--focus); outline-offset:1px; }

/* Appearance row inside the avatar menu (spec 007, FR-002/AR-002). Sit it on
   its own line above the Help divider: a 3-up segmented control reads as one
   decision ("which theme"), not as three more menu items. */
.avatar-theme{ margin:6px 0 4px; padding:8px 10px 10px; position:relative; z-index:1; }
.avatar-theme-label{ display:block; color:var(--text-3); font-size:11px; font-weight:650;
  letter-spacing:.04em; text-transform:uppercase; margin-bottom:6px; }
.avatar-theme-opts{ display:grid; grid-template-columns:repeat(3,1fr); gap:3px; padding:3px;
  border-radius:12px; background:var(--surface-hover); border:1px solid var(--border); }
.avatar-theme-opt{ min-height:30px; border:1px solid transparent; border-radius:var(--r-sm); background:transparent;
  color:var(--text-2); font:inherit; font-size:12px; font-weight:600; cursor:pointer;
  transition:background .14s ease, color .14s ease, border-color .14s ease; }
.avatar-theme-opt:hover{ color:var(--text); }
.avatar-theme-opt[aria-checked="true"]{ background:var(--surface); border-color:var(--border-strong);
  color:var(--text); box-shadow:var(--e-1); }
.avatar-theme-opt:focus-visible{ outline:2px solid var(--focus); outline-offset:1px; }

/* ---- Dark theme fixes for the two topbar surfaces (spec 007) ---------------
   1. The segmented track must sit BELOW the selected pill, or the checked option
      reads as a hole instead of a choice (light: --surface-hover over --surface;
      dark needs --canvas under --surface).
   2. The upgrade row is a pastel gradient by design; its dark twin is the same
      coral→amber idea at low luminance, not a white pill.
   3. The appearance row is the last item in the language panel and the panel
      scrolls (max-height 320px): pin it to the bottom so it never needs a hunt. */
:root[data-theme="dark"] .avatar-theme-opts{ background:var(--canvas); }
:root[data-theme="dark"] .avatar-theme-opt[aria-checked="true"]{ background:var(--surface); border-color:var(--border-strong); }
:root[data-theme="dark"] .avatar-menu-upgrade{
  background:linear-gradient(135deg,var(--coral-tint) 0%,var(--warn-tint) 100%);
  border-color:rgba(255,138,128,.26); color:var(--accent-text); }
:root[data-theme="dark"] .avatar-menu-upgrade svg{ color:var(--accent-text); }
:root[data-theme="dark"] .avatar-menu-upgrade:hover{ background:linear-gradient(135deg,#33181a 0%,#33291a 100%); }
:root[data-theme="dark"] .avatar-menu-upgrade[data-cta="manage"]{ border-color:var(--border); color:var(--text); }
.lang-picker-panel .lang-theme{ position:sticky; bottom:-8px; background:var(--surface);
  padding-bottom:0; margin-bottom:-8px; }
.avatar-menu-item svg{ color:var(--text-2); flex:none; }
/* Standardize the leading action icon size on menu items. */
.avatar-menu-item > svg{ width:17px; height:17px; }
.avatar-menu-divider{ height:1px; background:var(--border); margin:8px 2px; }
.avatar-menu-divider[hidden]{ display:none; }
.avatar-menu-logout{ color:#ef4444; }
.avatar-menu-logout svg{ color:#ef4444; }
/* Cross-link row ("Use in ChatGPT & Claude"). The two platform marks overlap so
   the pair reads as one glyph; the row ends with a new-tab arrow. This row is an
   <a> where every other row is a <button>, so it has to re-state the reset
   (.avatar-menu-item) does not cover: no underline, pointer cursor. */
.avatar-menu-ai{ gap:9px; text-decoration:none; cursor:pointer; }
.avatar-menu-ai-duo{ display:inline-flex; flex:none; align-items:center; }
.avatar-menu-ai-tile{ display:inline-flex; align-items:center; justify-content:center;
  width:18px; height:18px; border-radius:8px; box-shadow:0 0 0 2px #fff; }
.avatar-menu-ai-tile + .avatar-menu-ai-tile{ margin-left:-6px; }
.avatar-menu-ai-tile svg{ width:12px; height:12px; }
.avatar-menu-ai-tile--chatgpt{ background:#111111; }
.avatar-menu-ai-tile--claude{ background:#d97757; }
.avatar-menu-ai-ext{ display:inline-flex; margin-left:auto; color:var(--text-3); }
.avatar-menu-ai-ext svg{ width:14px; height:14px; color:inherit; }
.avatar-menu-ai:hover .avatar-menu-ai-ext{ color:var(--text); }

/* Account header: identity + plan/supporter badges, with a quiet card surface. */
.avatar-menu-account[hidden]{ display:none; }
.avatar-menu-account{ display:flex; align-items:center; gap:12px; cursor:default;
  margin:0 0 6px; padding:11px 10px; border-radius:12px;
  background:linear-gradient(135deg,#fafafa 0%,#f4f4f5 100%);
  border:1px solid rgba(15,23,42,.05); }
.avatar-menu-account:hover{ background:linear-gradient(135deg,#fafafa 0%,#f4f4f5 100%); }
.avatar-menu-account .avatar-menu-who-info{ text-align:left; }
.avatar-menu-who-pic{ flex:none; width:42px; height:42px; border-radius:999px; overflow:hidden;
  display:flex; align-items:center; justify-content:center; background:var(--surface);
  border:1px solid rgba(15,23,42,.06); font-size:16px; font-weight:750; color:var(--text-2); text-transform:uppercase; }
.avatar-menu-who-pic img{ width:100%; height:100%; object-fit:cover; display:block; }
.avatar-menu-who-info{ display:flex; flex-direction:column; min-width:0; flex:1 1 auto; }
.avatar-menu-who-name{ font-size:14px; font-weight:700; color:var(--text); letter-spacing:-.01em;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.avatar-menu-who-email{ font-size:12px; color:var(--text-2); margin-top:1px;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.avatar-menu-who-badges{ display:flex; align-items:center; gap:5px; margin-top:5px; flex-wrap:wrap; min-width:0; }

/* Plan badge mirrors the sidebar tier colors so account context is immediate. */
.avatar-plan-badge{ display:inline-flex; align-items:center; max-width:120px; height:18px; padding:0 8px;
  border-radius:999px; background:var(--surface-hover); border:1px solid #e4e4e7; color:var(--text-2);
  font-size:12px; font-weight:750; line-height:1; letter-spacing:.01em;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.avatar-plan-badge[hidden]{ display:none; }
.avatar-plan-badge[data-plan="starter"]{ background:var(--success-tint); border-color:#bbf7d0; color:#15803d; }
.avatar-plan-badge[data-plan="creator"]{ background:var(--info-soft); border-color:var(--info-border); color:var(--info-ink); }
.avatar-plan-badge[data-plan="pro"]{ background:var(--warn-soft); border-color:var(--warn-border); color:var(--warn-ink); }
.avatar-plan-badge[data-plan="business"]{ background:var(--violet-soft); border-color:#ddd6fe; color:#6d28d9; }

/* Upgrade is the one promotional row: warm coral, but still calm enough for a menu. */
.avatar-menu-upgrade{ background:linear-gradient(135deg,#fff1f2 0%,#fff7ed 100%); color:#be123c;
  border:1px solid #ffe0e3; font-weight:650; }
.avatar-menu-upgrade svg{ color:#e11d48; }
.avatar-menu-upgrade:hover{ background:linear-gradient(135deg,#ffe4e6 0%,#ffedd5 100%); }
.avatar-menu-upgrade[data-cta="manage"]{ background:var(--surface-hover); border-color:#e4e4e7; color:#3f3f46; }
.avatar-menu-upgrade[data-cta="manage"] svg{ color:var(--text-3); }
.avatar-menu-upgrade[data-cta="manage"]:hover{ background:var(--surface-hover); }

/* Sign-in pill shown to the left of the avatar when signed out. */
/* Sign-in is a SECONDARY action: the solid coral gradient is reserved for the
   primary Generate button, so this uses a coral outline (ghost) instead — on
   brand, clearly clickable, but visually recessive next to Generate. */
.signin-btn{ height:34px; padding:0 16px; font-size:13px; font-weight:650; border-radius:16px;
  background:var(--surface); color:#e11d48; border:1px solid #fecdd3; box-shadow:none; }
.signin-btn:hover{ background:var(--danger-soft); border-color:#fda4af; color:#be123c; }
.signin-btn[hidden]{ display:none; }

/* Soft gradient sign-in card, with light typography for a quieter look. */
.sb-signin-btn{
  position:relative; display:flex; align-items:center; gap:9px; width:100%;
  margin:0 0 10px; padding:11px 12px; border-radius:12px; overflow:hidden;
  border:1px solid rgba(254,101,93,.16); cursor:pointer; text-align:left;
  font-family:inherit; color:#64748b;
  background:linear-gradient(135deg,#ffffff 0%,#fffaf5 58%,#fff1f2 100%);
  box-shadow:var(--e-2);
  transition:border-color .18s ease, box-shadow .18s ease, transform .18s ease;
}
.sb-signin-btn::before{
  content:""; position:absolute; width:96px; height:96px; right:-38px; top:-46px;
  background:radial-gradient(circle,rgba(251,113,133,.22),transparent 70%);
  pointer-events:none;
}
.sb-signin-btn::after{
  content:""; position:absolute; left:-30px; bottom:-52px; width:110px; height:110px;
  background:radial-gradient(circle,rgba(251,191,36,.12),transparent 70%);
  pointer-events:none;
}
.sb-signin-btn:hover{ transform:translateY(-1px); border-color:rgba(244,63,94,.28);
  box-shadow:var(--e-2); }
.sb-signin-btn[hidden]{ display:none; }
.sidebar.collapsed .sb-signin-btn{ display:none; }
.sb-signin-main{ position:relative; z-index:1; display:flex; flex-direction:column; min-width:0; }
.sb-signin-main strong{ font-size:14px; font-weight:650; line-height:1.25; color:#475569; letter-spacing:-.005em; }
.sb-signin-main small{ font-size:12px; color:#64748b; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.sb-signin-arrow{
  position:relative; z-index:1; margin-left:auto; flex:none; width:28px; height:28px;
  border-radius:999px; display:grid; place-items:center;
  background:rgba(148,163,184,.16); color:#64748b;
  transition:background .18s ease, color .18s ease;
}
.sb-signin-btn:hover .sb-signin-arrow{ background:#cbd5e1; color:#334155; }
.signin-btn[hidden]{ display:none; }

/* ---------- MAIN ---------- */
.app > main{ display:flex; flex-direction:column; flex:1; min-height:0; padding:4px 16px 8px 8px; }
.app > main > .main{ background:var(--surface); border:1px solid #f2f2f2; border-radius:16px; box-shadow:var(--e-2); overflow:hidden; }
.main{ display:grid; grid-template-columns:1fr auto; grid-template-rows:1fr auto; flex:1; min-height:0; }
.editor{ grid-column:1; grid-row:1; min-width:0; display:flex; flex-direction:column; overflow-y:auto; }
.workspace-bar{ display:flex; align-items:center; justify-content:space-between; gap:12px; flex:none;
  min-height:60px; padding:10px 16px; border-bottom:1px solid var(--border-2); background:var(--surface); }
.workspace-bar[hidden]{ display:none !important; }
.wsb-left{ display:flex; align-items:center; gap:10px; min-width:0; }
.wsb-title{ font-size:14px; font-weight:750; letter-spacing:-.01em; color:var(--text); white-space:nowrap; }
.wsb-save{ font-size:12px; font-weight:600; color:#16a34a; opacity:0; transform:translateY(2px);
  transition:opacity .18s ease, transform .18s ease; white-space:nowrap; }
.wsb-save.show{ opacity:1; transform:none; }
.wsb-voice{ display:inline-flex; align-items:center; gap:8px; min-height:34px; padding:5px 11px 5px 6px;
  border:1px solid var(--border-2); border-radius:12px; background:var(--surface); color:var(--text); cursor:pointer;
  font-family:inherit; font-size:12px; font-weight:650; max-width:min(260px, 42vw); }
.wsb-voice:hover{ background:var(--hover); border-color:#d1d5db; }
.wsb-voice-av{ flex:none; width:24px; height:24px; border-radius:999px; background:var(--hover); }
.wsb-voice-name{ min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.wsb-voice svg{ flex:none; color:var(--text-3); }
.wsb-preview{ display:none; flex:none; align-items:center; justify-content:center; width:36px; height:36px;
  border:1px solid var(--border-2); border-radius:12px; background:var(--surface); color:var(--text); cursor:pointer;
  font-family:inherit; touch-action:manipulation; }
.wsb-preview:hover{ background:var(--hover); }
.wsb-preview[hidden]{ display:none !important; }
.wsb-preview.playing{ background:var(--accent,#fe655d); border-color:transparent; color:#fff; }
@media (max-width:600px){
  .workspace-bar{ padding:8px 12px; }
  .wsb-title{ display:none; }
  /* One clear voice control at the top of the editor, not a lonely chip on the
     right edge. */
  .wsb-voice{ max-width:none; flex:1 1 auto; min-width:0; }
  .wsb-voice-name{ flex:1 1 auto; text-align:left; }
  .wsb-preview{ display:inline-flex; }
}

.editor .editor-footer{ margin-top:auto; }
/* Fluid so the editor column never gets crushed between ~820-1440px. */
.settings{ grid-column:2; grid-row:1; width:clamp(320px, 28vw, 480px); border-left:1px solid var(--border); overflow-y:auto; padding:14px 16px; }
.settings-backdrop{ display:none; }
.settings-sheet-trigger{ display:none; }
.st-sheet-close{ display:none; }

/* Trust strip — quick reassurance (free / no sign-up / no watermark). Shown on
   mobile at the very top of the page where the studio otherwise led with knobs
   and no marketing signal. */
.trust-strip{ display:none; align-items:center; flex-wrap:wrap; gap:8px 14px; margin:0 0 20px;
  padding:11px 14px; border:1px solid #fde0dd; border-radius:12px; background:var(--coral-tint-2); }
.trust-item{ display:inline-flex; align-items:center; gap:6px; font-size:13px; font-weight:600;
  letter-spacing:-.01em; color:#c8324a; white-space:nowrap; }
.trust-item svg{ color:#fc3e57; flex:none; }
.editor-scroll{ flex:1; overflow-y:auto; padding:28px 40px calc(48px + var(--gen-strip-h, 0px)); }
.editor-inner{ max-width:1080px; margin:0 auto; }
.onboard{ max-width:1080px; margin:0 auto 18px; display:flex; align-items:center; gap:14px;
  padding:13px 16px; border-radius:12px; border:1px solid #ffd9c2;
  background:linear-gradient(135deg,#fff7ed,#eef2ff); font-size:14px; color:var(--text); }
.onboard-text{ flex:1; line-height:1.5; }
.onboard-try{ flex:none; display:inline-flex; align-items:center; gap:7px; height:34px; padding:0 14px;
  border-radius:8px; border:none; background:var(--accent,#fe655d); color:#fff; font-size:13px; font-weight:700;
  cursor:pointer; font-family:inherit; }
.onboard-try:hover{ filter:brightness(.96); }
.onboard-ok{ flex:none; height:34px; padding:0 14px; border-radius:8px; border:1px solid var(--border-2);
  background:var(--surface); color:var(--text); font-size:13px; font-weight:600; cursor:pointer; font-family:inherit; }
.onboard-ok:hover{ background:var(--hover); }

/* Unverified-account hint in the plan card. Deliberately quiet: it explains a
   smaller number, it is not an upsell. */
.sb-plan-verify{ display:flex; align-items:center; gap:8px; margin:2px 0 8px; padding:8px 10px;
  border:1px solid var(--warn-border); background:var(--warn-soft); border-radius:8px; font-size:12px; line-height:1.4; color:var(--text); }
.sb-plan-verify[hidden]{ display:none; }
.sb-plan-verify-txt{ flex:1; }
.sb-plan-verify-btn{ flex:none; height:26px; padding:0 10px; border-radius:8px; border:1px solid var(--border-2);
  background:var(--surface); font-size:12px; font-weight:600; color:var(--text); cursor:pointer; font-family:inherit; }
.sb-plan-verify-btn:hover{ background:var(--hover); }
.sb-plan-verify-btn[disabled]{ opacity:.6; cursor:default; }

/* Post-payment activation bar (P1, 2026-11-01). Deliberately its own class and
   NOT `.onboard`: `.onboard` sets `display:flex`, which beats the `hidden`
   attribute (attribute selectors lose to a class selector), so reusing it would
   leave this bar permanently visible. The rule below pins the hidden state. */
/* Sits in the same flow as .editor-inner (a sibling inside .editor-scroll) and
   fills the same width, so the bar and the script under it share both edges. */
.first-audio{ max-width:none; margin:0 0 18px; display:flex; align-items:center; gap:14px;
  padding:13px 16px; border-radius:12px; border:1px solid #bbf7d0;
  background:linear-gradient(135deg,#f0fdf4,#eef2ff); font-size:14px; color:var(--text); }
.first-audio[hidden]{ display:none; }
.first-audio-ic{ flex:none; font-size:16px; line-height:1; }
.first-audio-txt{ flex:1; line-height:1.5; }
.first-audio-go{ flex:none; display:inline-flex; align-items:center; gap:7px; height:34px; padding:0 14px;
  border-radius:8px; border:none; background:var(--accent,#fe655d); color:#fff; font-size:13px; font-weight:700;
  cursor:pointer; font-family:inherit; white-space:nowrap; }
.first-audio-go:hover{ filter:brightness(.96); }
.first-audio-x{ flex:none; width:28px; height:28px; border-radius:8px; border:1px solid var(--border-2);
  background:var(--surface); color:var(--muted,#6b7280); font-size:16px; line-height:1; cursor:pointer; font-family:inherit; }
.first-audio-x:hover{ background:var(--hover); }
@media (max-width:640px){
  .first-audio{ flex-wrap:wrap; gap:10px; }
  .first-audio-txt{ flex:1 1 100%; }
  .first-audio-go{ flex:1 1 auto; justify-content:center; }
}

/* Result card: the clear "here's your audio" surface after Generate. */
.result-card{ max-width:1080px; margin:0 auto 20px; display:flex; align-items:center; gap:14px;
  padding:14px 16px; border-radius:16px; border:1px solid #d7f0dd;
  background:linear-gradient(135deg,#f0fdf4,#eef4ff); }
.rc-av{ width:40px; height:40px; min-width:40px; border-radius:999px; flex:none;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.25), 0 1px 4px rgba(0,0,0,.16); }
.rc-info{ display:flex; flex-direction:column; min-width:0; flex:1; }
.rc-title{ font-size:15px; font-weight:700; color:var(--text); display:flex; align-items:center; gap:7px; }
.rc-title::before{ content:"✓"; display:inline-grid; place-items:center; width:19px; height:19px; border-radius:999px;
  background:#16a34a; color:#fff; font-size:12px; font-weight:800; }
.rc-name{ font-size:13px; color:var(--text-2); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.rc-play{ flex:none; width:38px; height:38px; border-radius:999px; border:none; cursor:pointer;
  background:var(--black); color:#fff; display:inline-flex; align-items:center; justify-content:center; }
.rc-play:hover{ background:#262626; }
.rc-btn{ flex:none; display:inline-flex; align-items:center; gap:7px; height:40px; padding:0 15px; border-radius:12px;
  font-size:14px; font-weight:600; cursor:pointer; font-family:inherit; border:1px solid var(--border-2); background:var(--surface); color:var(--text); }
.rc-btn:hover{ background:var(--hover); }
.rc-download{ background:var(--accent,#fe655d); color:#fff; border-color:transparent; }
.rc-download:hover{ filter:brightness(.96); background:var(--accent,#fe655d); }
.rc-actions{ display:flex; align-items:center; gap:8px; flex:none; }
@media (max-width:600px){
  .result-card{ flex-wrap:wrap; }
  .rc-info{ order:-1; flex-basis:100%; }
  .rc-actions{ width:100%; }
  .rc-actions .rc-btn{ flex:1; justify-content:center; }
}
.editor-footer{ flex:none; display:flex; flex-direction:column; gap:12px;
  padding:14px 16px;
  /* Containing block for #genProgress, which floats above this edge instead of
     taking a row inside it (see .gen-progress). relative, never static: the
     strip's bottom:100% must resolve against the footer at every breakpoint. */
  position:relative; }
.editor-footer .el-top{ display:flex; align-items:center; justify-content:space-between; gap:22px; width:100%;
  /* These two rows wrap rather than overflow. The composer's host column is not
     always wide: on a phone in landscape the studio's editor column measured 218px,
     where "53 / 100,000 characters" + "Generate speech" (293px of min-content width)
     pushed the button 101px outside the footer, over the neighbouring panel. Wrapping
     changes nothing when there is room, which is every desktop case. */
  flex-wrap:wrap; row-gap:8px; }
.editor-footer .el-top .ef-left{ display:flex; align-items:center; min-width:0; flex-wrap:wrap; }
.editor-footer .el-top .ef-right{ display:flex; align-items:center; gap:16px; margin-left:auto; flex-wrap:wrap; min-width:0; }
.editor-footer .el-top .char-count{ min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.editor-footer .el-top .generate-btn{ height:40px; padding:0 20px; font-size:14px; }
/* History tab takes over the settings panel, so the editor footer has nothing
   to show — hide it whenever history is active. */
body.st-history .editor-footer{ display:none; }
.credits{ display:flex; align-items:center; gap:18px; font-size:14px; color:var(--text-2); min-width:0; }
.stat-line{ display:inline-flex; flex-direction:column; align-items:flex-start; gap:1px; line-height:1.2; padding-left:20px; position:relative; flex:none; }
.stat-line::before{ content:"💛"; font-size:12px; position:absolute; left:0; top:2px; }
.stat-line .stat-num{ font-size:14px; font-weight:800; color:var(--text); letter-spacing:-.02em; }
.stat-line .stat-label{ font-size:12px; font-weight:400; color:var(--text-3); }
/* Second social-proof figure (characters synthesized) sits beside the audio
   count and reuses .stat-line, so only the leading icon differs. */
.stat-line-chars::before{ content:"✍️"; }
.credits-divider{ width:1px; height:28px; background:var(--border); flex:none; }
.char-count{ display:inline-flex; align-items:baseline; gap:5px; white-space:nowrap; }
.char-count .cc-num{ font-size:15px; font-weight:700; color:var(--text); letter-spacing:-.01em; }
.char-count .cc-label{ font-size:14px; font-weight:400; color:var(--text-2); }
.spinner{ width:22px; height:22px; border-radius:999px; border:2px solid var(--border); border-top-color:#9ca3af;
  animation:spin 1.1s linear infinite; flex:none; }
@keyframes spin{ to{ transform:rotate(360deg); } }
.el-top{ display:flex; align-items:center; gap:22px; flex:none; width:100%; }
.el-top.over-limit .char-count .cc-num{ color:#dc2626; }
/* Over the limit the button stays pressable on purpose: pressing it opens the paywall
   (see the over-limit branch of the Generate handler). It only loses a little weight so
   the red counter still reads as the warning. */
.el-top.over-limit .generate-btn{ opacity:.92; }
/* Compact saydi.ai promo in the editor footer (measurable via data-promo). */
.ef-promo{ display:inline-flex; align-items:center; gap:7px; flex:none; text-decoration:none;
  font-size:12px; font-weight:600; line-height:1; color:var(--muted,#6b7280);
  padding:6px 11px; border:1px solid var(--border-2,#e5e7eb); border-radius:999px;
  transition:color .15s, border-color .15s, background .15s; white-space:nowrap; }
.ef-promo:hover{ color:var(--text,#111827); border-color:#fe655d; background:#fe655d14; }
.ef-promo img{ border-radius:8px; display:block; }
.ef-promo svg{ opacity:.7; }
@media (max-width: 560px){ .ef-promo{ display:none; } }
.char-count{ font-size:14px; color:var(--text-2); white-space:nowrap; }
/* Primary brand action — owns the one coral colour so it's always the star.
   Download (in the playbar) is solid black so the two never compete. */
.generate-btn{ height:48px; padding:0 26px; border-radius:12px; border:none;
  background:var(--accent); /* solid action colour (T003/T006) */
  color:#fff; font-size:15px; font-weight:700; cursor:pointer; font-family:inherit; letter-spacing:-.01em;
  display:inline-flex; align-items:center; gap:10px; flex:none; white-space:nowrap;
  transition:filter .12s, box-shadow .12s, transform .12s; }
.generate-btn:hover{ filter:brightness(1.04); box-shadow:var(--e-2); }
.generate-btn:active{ transform:translateY(1px); }
.generate-btn:disabled{ opacity:.65; cursor:default; }
.btn-spin{ display:inline-block; width:18px; height:18px; border-radius:999px; border:2.5px solid rgba(255,255,255,.35);
  border-top-color:#fff; animation:spin .8s linear infinite; vertical-align:middle; }

/* ---------- PLAYBACK / PREVIEW BAR ---------- */
.playbar{ flex:none; border-top:1px solid var(--border); display:none;
  grid-template-columns:1fr auto 1fr;
  grid-template-areas:"left center right" "prog prog prog";
  align-items:center; padding:12px 6px; column-gap:24px; row-gap:14px; background:var(--surface); }
.playbar.show{ display:grid; animation:playbar-in .22s ease-out both; }
/* The playbar is the one layer that genuinely needs the space it takes (unlike the
   generation strip, which floats), so it eases in rather than snapping open. */
@keyframes playbar-in{ from{ opacity:0; transform:translateY(8px); } to{ opacity:1; transform:none; } }
.pb-left{ grid-area:left; display:flex; align-items:center; gap:12px; min-width:0; }
.pb-left .av{ width:30px; height:30px; border-radius:999px; flex:none;
  background:radial-gradient(circle at 40% 35%,#3a3a3a,#111); }
.pb-left .nm{ font-size:15px; font-weight:600; letter-spacing:-.01em;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.pb-edit{ width:28px; height:28px; color:var(--text-3); }
.pb-center{ grid-area:center; display:flex; align-items:center; gap:22px; justify-content:center; }
.skip-btn{ position:relative; width:36px; height:36px; border-radius:8px; }
.skip-btn .num{ display:none; }
.play-main{ width:40px; height:40px; border-radius:999px; background:var(--black); border:none; cursor:pointer;
  display:flex; align-items:center; justify-content:center; color:#fff; }
.play-main:hover{ background:#262626; }
.pb-right{ grid-area:right; display:flex; align-items:center; gap:10px; justify-content:flex-end; }
/* Prominent, labelled download button — the payoff action in the player.
   Solid black (not coral) so it reads as a clear button without competing with
   the coral Generate CTA or clashing with the chat/translate accents. */
.pb-dl{ display:inline-flex; align-items:center; justify-content:center; gap:8px; height:36px; padding:0 16px;
  border:none; border-radius:12px; cursor:pointer; font-family:inherit; font-size:15px; font-weight:700;
  letter-spacing:-.01em; color:#fff; background:var(--black); white-space:nowrap; flex:none;
  box-shadow:var(--e-2); transition:transform .12s, box-shadow .12s, background .12s; }
.pb-dl svg{ flex:none; }
.pb-dl:hover{ background:#262626; box-shadow:var(--e-2); }
.pb-dl:active{ transform:translateY(1px); box-shadow:var(--e-2); }
.pb-dl:focus-visible{ outline:2px solid #111; outline-offset:2px; }
.pb-dl-label{ line-height:1; }

/* ---------- VOICE EFFECT PICKER (a popup, not a row) ----------
   Lives in the player rather than in Settings because that is where the user
   already is once audio exists: generate, hear it, try a character, keep the one
   you like. Pills (not a slider, not a dropdown) because there are five fixed
   presets and the choice is single-select — the same shape the reference
   products use.

   It is a POPUP rather than a permanent row on purpose: five presets are an
   occasional choice, but as a row they cost the player a whole grid band in
   every session — 34px of pills plus the row gap, i.e. 48px desktop / 43px
   phone — including the many sessions that never touch them. The trigger shows
   the state instead (coral while a preset is applied), so hiding the pills
   costs nothing but a click. */
/* ---------- LISTEN-TIME TOOLS (speed + effect) ----------
   One flex group inside .pb-right on desktop; on a phone .pb-right is
   `display:contents` and this group is the single grid item in the `fx` area, so
   the two triggers share the row that already existed instead of pushing the
   Download button down a band of its own. */
.pb-tools{ display:flex; align-items:center; gap:8px; flex:none; grid-area:fx; }

/* ---------- PLAYBACK SPEED ----------
   A value button with a popover, not a slider: the six steps are fixed and
   discrete, and the current value must be readable WITHOUT opening anything
   (the whole point of the feature is comparing steps). Shaped like the effect
   trigger next to it so the player keeps one control language.
   It is deliberately NOT in the settings rail: Generation Speed changes what the
   model renders (and costs a render); this changes only local playback of audio
   that already exists. Mixing the two in one column is how users learn to fear
   both. */
.pb-rate{ position:relative; display:flex; flex:none; }
.pb-rate-btn{ display:inline-flex; align-items:center; justify-content:center; gap:4px;
  height:36px; min-width:56px; padding:0 10px;
  border-radius:12px; border:1px solid var(--border); background:var(--surface); color:var(--text-2);
  font-family:inherit; font-size:13px; font-weight:700; letter-spacing:-.01em;
  font-variant-numeric:tabular-nums; cursor:pointer; flex:none;
  transition:border-color .12s, background .12s, color .12s; }
.pb-rate-btn:hover{ border-color:#c6c6c6; background:var(--canvas); color:var(--text); }
/* Open, or "not at 1.0x": the button reports both, because a speed the user set
   five minutes ago is otherwise invisible — and the way back to normal has to be
   findable.
   The TEXT stays dark on purpose: coral-on-coral-soft is 3.19:1, which is fine
   for the effect trigger next door (its face is an icon nobody has to read) and
   a colour-contrast failure for the one string this button exists to show. The
   state is carried by the surface; the value stays legible (axe: 0 violations). */
.pb-rate-btn[aria-expanded="true"],
.pb-rate-btn[data-active="true"]{ border-color:#f4b1ac; background:var(--brand-coral-soft); color:var(--text); }
.pb-rate-btn:focus-visible{ outline:2px solid #111; outline-offset:2px; }
.pb-rate-val{ line-height:1; }

.pb-rate-pop{ position:absolute; bottom:calc(100% + 10px); right:0; z-index:80;
  width:max-content; min-width:17.5rem; max-width:min(21rem, calc(100vw - 24px));
  padding:12px; border-radius:16px; border:1px solid var(--border); background:var(--surface);
  box-shadow:var(--e-3); animation:fx-pop-in .14s ease-out both; }
.pb-rate-pop[hidden]{ display:none; }
/* Header row: label on the left, the live value on the right. */
.pb-rate-head{ display:flex; align-items:baseline; justify-content:space-between; gap:12px; margin-bottom:10px; }
.pb-rate-head .fx-label{ margin:0; }
.pb-rate-now{ font-size:15px; font-weight:800; letter-spacing:-.01em; font-variant-numeric:tabular-nums;
  color:var(--text); }
/* The range input IS the fine control: 0.05 steps let 1.0 -> 1.25 be a five-notch
   move instead of a jump to the next preset. Styled with accent-color rather than
   a hand-rolled thumb so the native hit area, keyboard behaviour and focus ring
   stay intact on every platform. */
.pb-rate-slider{ display:block; width:100%; margin:0 0 12px; height:22px; padding:0;
  accent-color:var(--brand-coral-deep); cursor:pointer; }
.pb-rate-slider:focus-visible{ outline:2px solid #111; outline-offset:3px; border-radius:999px; }
/* Three columns of two: the six presets fit as one small square, so the popover
   never covers the transport controls it is auditioning. */
/* Compound selectors on purpose: the pills reuse .fx-list/.fx-pill, defined
   further down this file, so a bare `.rate-list` (same specificity, earlier in
   the sheet) would lose and the grid would fall back to the effect picker's two
   columns — the presets then take three rows and push the popover taller than
   the player it belongs to. */
.fx-list.rate-list{ grid-template-columns:repeat(3, minmax(0,1fr)); }
.fx-list.rate-list .fx-pill{ justify-content:center; padding:0 6px; }
.rate-hint{ margin:10px 2px 0; font-size:11px; line-height:1.45; color:var(--text-3); }

.pb-fx-btn{ display:inline-flex; align-items:center; justify-content:center; width:36px; height:36px;
  border-radius:12px; border:1px solid var(--border); background:var(--surface); color:var(--text-2);
  cursor:pointer; flex:none; padding:0;
  transition:border-color .12s, background .12s, color .12s; }
.pb-fx-btn:hover{ border-color:#c6c6c6; background:var(--canvas); color:var(--text); }
.pb-fx-btn[hidden]{ display:none; }
/* Open or "a preset is on": the button itself reports the state, since the pills
   are out of sight most of the time. */
.pb-fx-btn[aria-expanded="true"],
.pb-fx-btn[data-active="true"]{ border-color:#f4b1ac; background:var(--brand-coral-soft); color:var(--brand-coral-deep); }
.pb-fx-btn:focus-visible{ outline:2px solid #111; outline-offset:2px; }

.pb-fx-pop{ position:absolute; bottom:calc(100% + 10px); right:0; z-index:80;
  width:max-content; max-width:min(22rem, calc(100vw - 24px));
  padding:12px; border-radius:16px; border:1px solid var(--border); background:var(--surface);
  box-shadow:var(--e-3); animation:fx-pop-in .14s ease-out both; }
.pb-fx-pop[hidden]{ display:none; }
@keyframes fx-pop-in{ from{ opacity:0; transform:translateY(4px); } to{ opacity:1; transform:none; } }
.fx-label{ font-size:12px; font-weight:700; letter-spacing:.06em; text-transform:uppercase;
  color:var(--text-3); margin:0 0 10px; }
/* Two columns: five presets with their names stay readable without the popup
   growing wider than a phone. */
.fx-list{ display:grid; grid-template-columns:repeat(2, minmax(0,1fr)); gap:8px; }
.fx-list .fx-pill{ width:100%; justify-content:flex-start; }
.fx-pill{ display:inline-flex; align-items:center; gap:7px; height:32px; padding:0 12px;
  border-radius:999px; border:1px solid var(--border); background:var(--surface); color:var(--text-2);
  font:inherit; font-size:13px; font-weight:600; letter-spacing:-.01em; cursor:pointer;
  white-space:nowrap; transition:border-color .12s, background .12s, color .12s; }
.fx-pill:hover{ border-color:#c6c6c6; background:var(--canvas); }
.fx-pill:focus-visible{ outline:2px solid #111; outline-offset:2px; }
.fx-pill[aria-checked="true"]{ border-color:var(--black); background:var(--black); color:#fff; }
.fx-pill[aria-checked="true"]:hover{ background:#262626; }
/* A pill that is rendering: it stays clickable-looking but cannot be re-fired,
   and it says so with a spinner instead of a disabled grey. */
.fx-pill.is-busy{ cursor:progress; }
.fx-pill[aria-disabled="true"]{ opacity:.6; cursor:progress; }
.fx-ico{ display:inline-flex; width:15px; height:15px; flex:none; }
.fx-ico svg{ width:15px; height:15px; }
.fx-spin{ width:12px; height:12px; flex:none; border-radius:999px;
  border:2px solid currentColor; border-top-color:transparent; animation:spin .8s linear infinite; }
.fx-err{ font-size:12px; color:#b3261e; }
@media (max-width:760px){
  /* On a phone the tools sit at the LEFT of the Download row, so anchoring a
     panel to the trigger's right edge would push it off the screen. Drop the
     local anchor and let the popup position against the fixed playbar instead:
     full width above the bar, always inside the viewport. Both popovers use the
     same rule, so neither one can escape the screen on a narrow phone. */
  .pb-fx{ position:static; }
  .pb-fx-pop{ left:12px; right:12px; bottom:calc(100% + 8px);
    width:auto; max-width:none; max-height:min(60vh, 320px); overflow:auto; }
  .pb-rate{ position:static; }
  .pb-rate-pop{ left:12px; right:12px; bottom:calc(100% + 8px);
    width:auto; max-width:none; max-height:min(60vh, 320px); overflow:auto; }
  /* Enough digits for "1.75x" on one line without widening the tool group. */
  .pb-rate-btn{ min-width:54px; height:34px; }
}

/* Compact playbar controls (relocated into the editor footer). */
.playbar #pbName{ font-size:14px; font-weight:500; line-height:1.4;
  display:-webkit-box; -webkit-line-clamp:1; -webkit-box-orient:vertical; overflow:hidden; white-space:normal; }
.playbar #pbDownload{ height:auto; font-size:12px; padding:9px 14px; box-shadow:none; }
.playbar #pbDownload:hover, .playbar #pbDownload:active{ box-shadow:none; }
.playbar #pbDownload svg{ width:14px; height:14px; }
.playbar #pbClose{ width:32px; height:32px; }
.playbar #pbClose svg{ width:14px; height:14px; }
.playbar #pbPlay{ width:36px; height:36px; }
.playbar #pbPlay svg{ width:18px; height:18px; }
.playbar .skip-btn{ width:32px; height:32px; }
.playbar .skip-btn svg{ width:18px; height:18px; }
/* Lift the live-chat bubble above the playbar when it's showing (same custom
   props Crisp's dashboard offset writes). Mobile gets a bigger lift below. */
body.has-playbar .crisp-client{ --crisp-customization-default-button-vertical:120px!important; }
.pb-progress{ grid-area:prog; display:flex; align-items:center; gap:14px; min-width:0; }
.pb-time{ font-size:12px; color:var(--text-3); font-variant-numeric:tabular-nums; flex:none; }
/* The seek bar is `.pb-bar`, defined once in ds.css because the studio playbar,
   the home page cards and the landing panels all render that same component. */
/* Live recording meter (clone / design screens). This one is NOT the progress
   bar: app.js feeds these bars from the analyser while the mic is open. */
.waveform{ flex:1; height:34px; display:flex; align-items:center; gap:2px; cursor:pointer; overflow:hidden; }
.wbar{ flex:1 1 0; min-width:2px; border-radius:8px; background:#d1d5db; transition:background-color .1s;
  align-self:center; }
.wbar.on{ background:var(--black); }

/* ---------- SPEAKER BLOCKS ---------- */
.speaker-block{ padding-bottom:26px; margin-bottom:26px; border-bottom:1px solid var(--border); }
.speaker-block:last-of-type{ border-bottom:none; }
.speaker-head{ display:flex; align-items:center; gap:10px; }
.speaker-chip{ display:inline-flex; align-items:center; gap:10px; height:42px; padding:6px;
  border-radius:12px; border:1px solid var(--border-2); background:var(--surface); cursor:pointer; font-size:14px;
  font-weight:600; letter-spacing:-.01em; color:var(--text); font-family:inherit; }
.speaker-chip:hover{ background:var(--hover); }
.speaker-chip .sp-av{ width:24px; height:24px; border-radius:999px; flex:none;
  background:radial-gradient(circle at 40% 35%,#3a3a3a,#111); }
.speaker-chip .sp-chev{ display:flex; color:var(--text-3); margin-left:2px; }
.speaker-del{ display:flex; align-items:center; justify-content:center; width:38px; height:38px;
  border-radius:8px; border:none; background:none; cursor:pointer; color:var(--text-3); }
.speaker-del:hover{ background:var(--coral-tint-2); color:#e11d48; }
.speaker-copy{ display:flex; align-items:center; justify-content:center; width:38px; height:38px;
  border-radius:8px; border:none; background:none; cursor:pointer; color:var(--text-3); flex:none; }
.speaker-copy:hover{ background:var(--hover); color:var(--text); }
.speaker-copy[hidden]{ display:none; }
/* The script itself is the biggest click target on the screen, and it looked
   inert: the only hover cue was the format toolbar fading in BELOW the text, so
   pointing at the writing surface changed nothing and nothing told you the area
   was an input (owner report 2026-11-08). It now behaves like the text field it
   is: a tinted, hairline-ringed surface on hover and a stronger ring while the
   caret is inside — which also restores a focus indicator this element had none
   of, because `.speaker-text` sets `outline:none` for the caret.
   Geometry: `border:1px solid transparent` is always painted (so nothing shifts
   on hover) and the negative inline margin + matching padding keep the text at
   exactly the same x as before, giving the tint 12px of breathing room each side
   and 10px above/below. The placeholder overlay is offset by the same padding so
   it still sits on the first text line. */
.speaker-textwrap{ position:relative; margin:8px -12px 0; padding:9px 11px; min-height:68px ;
  border:1px solid transparent; border-radius:12px; cursor:text;
  transition:background-color .14s ease, border-color .14s ease; }
.speaker-textwrap:hover{ background:var(--hover); border-color:var(--border); }
.speaker-content:focus-within .speaker-textwrap{ background:var(--hover); border-color:var(--border-strong); }
.speaker-text{ font-size:16px; line-height:1.8; letter-spacing:-.005em; color:var(--text);
  outline:none; min-height:34px; }
.ph-overlay{ position:absolute; top:0; left:0; right:0; pointer-events:none; color:var(--placeholder);
  font-size:20px; line-height:1.55; letter-spacing:-.005em; }
.speaker-textwrap .ph-overlay{ top:9px; left:11px; right:11px; }
.tag-chip{ display:inline-flex; align-items:center; padding:1px 10px; margin:0 2px; border:1px solid var(--border-2);
  border-radius:8px; color:var(--text-3); font-size:16px; background:var(--surface); }
/* Icon variant: the placeholder points at the 😊 toolbar button, so it shows
   that button's own glyph instead of a bracketed tag name. */
.tag-chip-ico{ padding:2px 7px; vertical-align:-4px; }
.tag-chip-ico svg{ display:block; }
.add-speaker-row{ display:flex; align-items:center; gap:22px; margin:14px 0 30px; }
.add-speaker-row .ln{ flex:1; height:1px; background:var(--border); }
/* flex:none + nowrap: as flex children these buttons would otherwise shrink
   below their label and wrap it — one character per line in CJK, where there are
   no spaces to break on. The .ln hairlines absorb the spare width instead. */
.add-speaker{ display:inline-flex; align-items:center; gap:10px; padding:10px 16px; border:none;
  background:none; cursor:pointer; font-family:inherit; font-size:14px; font-weight:600; color:var(--text); border-radius:8px;
  flex:none; white-space:nowrap; }
.add-speaker > svg{ width:18px; height:18px; }
.add-speaker:hover{ background:var(--hover); }

/* ---------- PER-SPEAKER TOOL ROW ---------- */
/* A subtle row of formatting icons under each speaker's text. Faded until the
   block is hovered or its text is focused, so it stays out of the way. */
.blk-tools{ display:flex; align-items:center; gap:1px; margin:5px 2px 0; height:28px;
  opacity:0; transform:translateY(-2px); transition:opacity .15s ease, transform .15s ease;
  pointer-events:none; }
.speaker-block .speaker-content:hover .blk-tools,
.speaker-block:focus-within .blk-tools{ opacity:1; transform:none; pointer-events:auto; }
.blk-tool{ display:inline-flex; align-items:center; justify-content:center; width:28px; height:28px;
  border:none; background:none; border-radius:8px; cursor:pointer; color:var(--text-3);
  transition:background .12s ease, color .12s ease; }
.blk-tool:hover:not(:disabled){ background:var(--hover); color:var(--text); }
.blk-tool:active:not(:disabled){ background:var(--border); }
.blk-tool:disabled{ color:var(--border-2); cursor:default; }
/* --border-2 is a hairline colour, not ink: as a disabled ICON colour it made the
   toolbar glyphs #2a2d34 on #15161a (1.31:1) in dark. Light keeps its look. */
:root[data-theme="dark"] .blk-tool:disabled{ color:var(--text-3); }
.blk-tool[aria-expanded="true"]{ background:var(--hover); color:var(--text); }
.blk-div{ width:1px; height:15px; background:var(--border); margin:0 5px; flex:none; }

/* ---------- INSERT PAUSE PICKER (shared, floating) ---------- */
.pause-menu{ position:fixed; z-index:60; width:236px;
  background:var(--surface); border:1px solid var(--border-2); border-radius:12px; padding:12px;
  box-shadow:var(--e-3); }
.pause-tip{ font-size:12px; line-height:1.5; color:var(--text-3); margin-bottom:10px; }
.pause-grid{ display:grid; grid-template-columns:repeat(3, 1fr); gap:8px; }
.pause-opt{ border:1px solid var(--border-2); background:var(--surface); border-radius:8px; height:36px;
  cursor:pointer; font-family:inherit; font-size:14px; font-weight:600; color:var(--text); }
.pause-opt:hover{ background:var(--hover); border-color:var(--text-3); }
/* ---------- INSERT EXPRESSION PICKER (shared, floating) ---------- */
/* A short, grouped menu. Each row is emoji + name + the sound it makes, so the
   choice is made by ear-in-the-head rather than by decoding a tag name. */
.emo-menu{ position:fixed; z-index:60; width:292px; max-height:min(70vh, 460px); overflow-y:auto;
  background:var(--surface); border:1px solid var(--border-2); border-radius:12px; padding:12px;
  box-shadow:var(--e-3); }
/* The AI row leads the menu: judging which of five near-identical question tags
   belongs after a given clause is the tedious part, and this does it in one
   click. Styled as the one filled/primary item so it reads as the shortcut
   rather than as a fourteenth tag. */
.emo-auto{ display:flex; align-items:flex-start; gap:9px; width:100%; margin-bottom:10px;
  padding:9px 10px; border:1px solid #d9cffb; border-radius:8px; background:var(--violet-soft);
  cursor:pointer; font-family:inherit; text-align:left;
  transition:background .12s ease, border-color .12s ease; }
.emo-auto:hover{ background:var(--violet-soft); border-color:#c4b4f7; }
.emo-auto:focus-visible{ outline:2px solid #6c5ce7; outline-offset:1px; }
.emo-auto[disabled]{ cursor:default; opacity:.75; }
.emo-auto-ico{ flex:none; display:flex; color:#5b46c9; margin-top:1px; }
.emo-auto-text{ display:flex; flex-direction:column; gap:2px; min-width:0; }
.emo-auto-name{ font-size:13px; font-weight:700; color:#3d2f8f; }
.emo-auto-sub{ font-size:12px; line-height:1.45; color:#6a5ea8; }
.emo-spin{ animation:emo-spin .8s linear infinite; }
@keyframes emo-spin{ to{ transform:rotate(360deg); } }
@media (prefers-reduced-motion:reduce){ .emo-spin{ animation-duration:2.4s; } }
.emo-tip{ font-size:12px; line-height:1.5; color:var(--text-3); margin-bottom:10px; }
.emo-group + .emo-group{ margin-top:10px; }
.emo-group-label{ font-size:12px; font-weight:700; letter-spacing:.04em; text-transform:uppercase;
  color:var(--text-3); margin:0 2px 5px; }
.emo-opt{ display:flex; align-items:center; gap:10px; width:100%; padding:7px 8px;
  border:1px solid transparent; background:none; border-radius:8px; cursor:pointer;
  font-family:inherit; text-align:left; transition:background .12s ease, border-color .12s ease; }
.emo-opt:hover{ background:var(--hover); border-color:var(--border-2); }
.emo-opt:focus-visible{ outline:2px solid var(--text-3); outline-offset:1px; }
.emo-emoji{ font-size:20px; line-height:1; flex:none; width:24px; text-align:center; }
.emo-text{ display:flex; flex-direction:column; gap:1px; min-width:0; }
.emo-name{ font-size:14px; font-weight:600; color:var(--text); }
/* The sound the model actually makes — quoted so it reads as audio, not a label. */
.emo-sfx{ font-size:12px; color:var(--text-3); }
.emo-sfx::before{ content:"\201C"; }
.emo-sfx::after{ content:"\201D"; }
/* ---------- EXPRESSION CHIP (inside the text box) ---------- */
/* An expression reads as a chip, not as "[laughter]". contenteditable="false"
   makes it atomic: the caret can't get inside, so it cannot be half-deleted
   into something the voice would read out as a word. */
.emo-pill{ display:inline-flex; align-items:center; gap:4px; vertical-align:baseline;
  margin:0 1px; padding:1px 6px 1px 5px; border-radius:999px;
  background:var(--violet-soft); border:1px solid #d9cffb; color:var(--violet-ink);
  font-size:12px; font-weight:600; line-height:1.5; white-space:nowrap;
  cursor:default; user-select:none; -webkit-user-select:none; }
.emo-pill .ep-emoji{ font-size:16px; line-height:1; }
.emo-pill .ep-x{ display:inline-flex; align-items:center; justify-content:center;
  width:13px; height:13px; margin-left:1px; border-radius:999px;
  font-size:12px; font-weight:700; line-height:1; color:#8a7bd0;
  cursor:pointer; opacity:0; transition:opacity .12s ease, background .12s ease, color .12s ease; }
.emo-pill:hover .ep-x{ opacity:1; }
.emo-pill .ep-x:hover{ background:#4b3ba8; color:#fff; }
/* Browser selection of the whole chip (click, or Backspace beside it). */
.speaker-text .emo-pill::selection,
.speaker-text .emo-pill *::selection{ background:#c9b8ff; }

/* ---------- CLONE / DESIGN PANELS ---------- */
.clone-card{ border:1px solid var(--border-2); border-radius:16px; background:var(--surface); padding:20px 22px; margin-top:6px; }
.clone-label{ font-size:16px; font-weight:600; color:var(--text); margin:14px 0 8px; }
.clone-label:first-child{ margin-top:0; }
.dropzone{ border:2px dashed #c7cad2; border-radius:12px; padding:22px; text-align:center; cursor:pointer;
  font-size:16px; color:var(--text-2); background:var(--surface); }
.dropzone.has{ border-color:#34d399; color:var(--text); }
.dropzone.drag{ border-color:var(--accent); background:var(--info-soft); color:var(--text); }
.clone-ta{ width:100%; min-height:80px; border-radius:12px; border:1px solid var(--border-2); padding:12px 16px;
  font-size:16px; font-family:inherit; color:var(--text); resize:vertical; outline:none; }
.clone-ta:focus,.clone-name:focus{ border-color:#d1d5db; }
.clone-save{ display:flex; align-items:center; gap:8px; font-size:15px; color:var(--text); margin-top:12px; cursor:pointer; }
.clone-save input{ width:auto; }
.clone-name{ width:100%; height:46px; border-radius:12px; border:1px solid var(--border-2); padding:0 16px;
  font-size:16px; font-family:inherit; color:var(--text); outline:none; margin-top:10px; }

/* ---------- SETTINGS PANEL ---------- */
.st-tabs{ display:flex; align-items:center; gap:30px; border-bottom:1px solid var(--border); }
.st-tab{ position:relative; padding:4px 2px 12px; font-size:15px; font-weight:600; letter-spacing:-.01em;
  color:var(--text-3); cursor:pointer; background:none; border:none; font-family:inherit; }
.st-tab.active{ color:var(--text); }
.st-tab.active::after{ content:""; position:absolute; left:0; right:0; bottom:-1px; height:2.5px; background:var(--text); border-radius:8px; }
.st-section{ margin-top:34px; }
.st-label{ font-size:14px; font-weight:500; letter-spacing:-.01em; color:var(--text); margin-bottom:14px; }
.st-label.dotted{ text-decoration:underline dotted; text-underline-offset:6px; text-decoration-color:#d1d5db; }
.st-label-row{ display:flex; align-items:center; justify-content:space-between; margin-bottom:8px; }
/* A plain-language hint shown under a control label (replaces hover-only tips,
   which touch users can't reach). */
.st-hint{ font-size:12px; line-height:1.5; color:var(--text-2); margin:0 0 14px; }
/* Advanced settings: optional controls tucked into a collapsible so first-time
   users only see Voice + Generate by default. */
.st-adv{ margin-top:30px; border-top:1px solid var(--border); }
.st-adv > summary{ list-style:none; cursor:pointer; display:flex; align-items:center; justify-content:space-between;
  gap:10px; padding:16px 2px 0; font-size:15px; font-weight:600; color:var(--text-2); }
.st-adv > summary::-webkit-details-marker{ display:none; }
.st-adv > summary:hover{ color:var(--text); }
.st-adv-chev{ transition:transform .2s; flex:none; }
.st-adv[open] > summary .st-adv-chev{ transform:rotate(180deg); }
.st-adv[open] > summary{ color:var(--text); }
.st-adv .st-section:first-of-type{ margin-top:20px; }
.selector{ display:flex; align-items:center; gap:14px; width:100%; height:60px; padding:0 18px; border-radius:16px;
  border:1px solid var(--border-2); background:var(--surface); cursor:pointer; font-size:16px; font-weight:600;
  letter-spacing:-.01em; color:var(--text); font-family:inherit; text-align:left; }
.selector:hover{ background:var(--hover); }
/* Inline "hear this voice" preview button under the Voice selector. */
.voice-preview{ display:inline-flex; align-items:center; gap:8px; margin-top:10px; padding:8px 14px;
  border-radius:999px; border:1px solid var(--border-2); background:var(--surface); cursor:pointer;
  font-size:13px; font-weight:600; color:var(--text); font-family:inherit; transition:background .15s,border-color .15s; }
.voice-preview:hover{ background:var(--hover); border-color:#d1d5db; }
.voice-preview .voice-preview-ic{ display:inline-flex; align-items:center; justify-content:center;
  width:22px; height:22px; border-radius:999px; background:var(--accent,#fe655d); color:#fff; flex:none; }
.voice-preview .voice-preview-ic svg{ width:13px; height:13px; }
/* Inline "popular voices" quick-pick strip under the Voice card. */
.pop-voices{ margin-top:16px; }
.pop-voices-label{ font-size:12px; font-weight:700; letter-spacing:.04em; text-transform:uppercase;
  color:var(--text-3); margin-bottom:10px; }
.pop-voices-row{ display:flex; flex-direction:column; gap:8px; }
.pv-chip{ display:flex; align-items:center; gap:10px; width:100%; padding:8px 10px; border-radius:12px;
  border:1px solid var(--border-2); background:var(--surface); cursor:pointer; font-family:inherit; text-align:left;
  transition:background .15s,border-color .15s; }
.pv-chip:hover{ background:var(--hover); }
.pv-chip.sel{ border-color:var(--accent,#fe655d); box-shadow:inset 0 0 0 1px var(--accent,#fe655d); }
.pv-av{ width:28px; height:28px; border-radius:999px; flex:none;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.25), 0 1px 3px rgba(0,0,0,.16); }
.pv-name{ flex:1; min-width:0; font-size:14px; font-weight:600; color:var(--text);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.pv-play{ flex:none; display:inline-flex; align-items:center; justify-content:center; width:30px; height:30px;
  border-radius:999px; background:var(--hover); color:var(--text); }
.pv-play:hover{ background:var(--surface-hover); }
.pv-play svg{ width:14px; height:14px; }
.selector .sel-av{ width:32px; height:32px; border-radius:999px; flex:none;
  background:radial-gradient(circle at 40% 35%,#3a3a3a,#111); }
.selector .chev{ margin-left:auto; color:var(--text-3); }
.v3-badge{ display:inline-flex; align-items:center; justify-content:center; min-width:42px; height:28px; padding:0 10px;
  border:1.5px solid var(--text); border-radius:999px; font-size:13px; font-weight:700; letter-spacing:.02em; }
.selector-grad{ padding:2px; border-radius:16px; background:linear-gradient(95deg,#34d399,#60a5fa,#fe655d,#fc3e57,#fb923c); }
.selector-grad .selector{ border-color:transparent; width:100%; height:56px; border-radius:16px; }
.callout{ display:flex; gap:16px; background:var(--callout); border-radius:16px; padding:20px 22px; margin-top:16px; }
.callout .ic{ flex:none; color:var(--text-2); margin-top:2px; }
.callout p{ font-size:14px; line-height:1.5; color:var(--text); letter-spacing:-.005em; }
.slider-ends{ display:flex; justify-content:space-between; font-size:13px; font-weight:400; color:var(--text); margin-bottom:10px; }
.slider-val{ font-size:12px; font-weight:600; color:var(--text); font-variant-numeric:tabular-nums;
  background:var(--canvas); padding:2px 7px; border-radius:8px; }
/* Range slider (Voice stability / Speaking speed).
   A real <input type="range">, so touch dragging, keyboard (arrows / Home / End)
   and role="slider" + aria-valuenow come from the platform instead of hand-rolled
   mouse handlers. The visual is deliberately unchanged: 2px rail, black filled
   portion, 12px white knob. The input box is 44px tall so the touch target meets
   the guideline while the rail itself stays 2px — the old version was a 2px-tall
   <div> that could not be dragged on a phone at all.
   JS keeps --slider-pct in sync; WebKit has no ::-range-progress, so the fill is
   a gradient on the track there, while Firefox uses ::-moz-range-progress. */
.slider{ -webkit-appearance:none; appearance:none; display:block; width:100%;
  height:44px; margin:0; padding:0; border:none; background:transparent;
  cursor:pointer; --slider-pct:60%; }
.slider:focus-visible{ outline:2px solid var(--focus); outline-offset:2px; border-radius:8px; }
.slider::-webkit-slider-runnable-track{ height:2px; border-radius:8px;
  background:linear-gradient(to right, var(--black) 0 var(--slider-pct), #e5e7eb var(--slider-pct) 100%); }
.slider::-webkit-slider-thumb{ -webkit-appearance:none; appearance:none; width:12px; height:12px;
  margin-top:-5px; border-radius:999px; background:var(--surface); border:2px solid var(--black);
  box-shadow:var(--e-1); }
.slider::-moz-range-track{ height:2px; border-radius:8px; background:var(--surface-hover); }
.slider::-moz-range-progress{ height:2px; border-radius:8px; background:var(--black); }
.slider::-moz-range-thumb{ width:12px; height:12px; border-radius:999px; background:var(--surface);
  border:2px solid var(--black); box-shadow:var(--e-1); }
.slider:disabled{ opacity:.5; cursor:default; }
.dropdown{ display:flex; align-items:center; width:100%; height:60px; padding:0 18px; border-radius:16px;
  border:1px solid var(--border-2); background:var(--surface); cursor:pointer; font-size:16px; font-weight:600;
  letter-spacing:-.01em; color:var(--text); font-family:inherit; }
.dropdown:hover{ background:var(--hover); }
.dropdown .chev{ margin-left:auto; color:var(--text-3); }
/* Accordion (break settings): the header is a .dropdown; its chevron rotates
   when open and the inline panel holds the controls. */
.accordion .accordion-head .chev{ transition:transform .18s ease; }
.accordion.open .accordion-head .chev{ transform:rotate(180deg); }
.accordion-panel{ padding:14px 2px 4px; }
.accordion-panel[hidden]{ display:none; }

/* Break-settings controls (moved off inline styles). */
.brk-levels{ display:flex; flex-wrap:wrap; gap:6px; margin:0 0 8px; }
/* Two columns, not four: "Không ngắt" / "No breaks" does not fit on one line in
   a narrow settings sidebar, and a truncated label is worse than a second row. */
.brk-level{ flex:1 1 calc(50% - 3px); min-width:0; height:34px; padding:0 8px;
  border:1px solid var(--border-2); background:transparent; color:var(--text-2);
  border-radius:8px; cursor:pointer; font-family:inherit; font-size:12px; font-weight:500;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.brk-level:hover{ background:var(--hover); }
.brk-level.is-active{ background:#1e1e1e; border-color:#1e1e1e; color:#fff; }
.brk-summary{ margin:0 0 14px; font-size:12px; color:var(--text-3,var(--text-2));
  font-variant-numeric:tabular-nums; }
.brk-adv-row{ margin-bottom:10px; }
.brk-advanced{ margin:0 0 4px; }
.brk-advanced[hidden]{ display:none; }
.brk-clause-row{ margin-bottom:6px; }
.brk-clause-hint{ margin:0 0 14px; }
.brk-enable-row{ display:flex; align-items:center; gap:8px; margin:0 0 14px; cursor:pointer; font-size:12px; }
.brk-grid{ display:grid; grid-template-columns:repeat(auto-fit,minmax(200px,1fr)); gap:14px 22px; }
.brk-row{ display:flex; align-items:center; justify-content:space-between; gap:10px; min-width:0; }
.brk-row > label{ flex:1 1 auto; min-width:0; display:flex; align-items:center; gap:6px;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; font-size:12px; }
.brk-row-name{ overflow:hidden; text-overflow:ellipsis; }
.brk-row-emoji{ opacity:.55; }
.brk-step{ display:flex; align-items:center; gap:6px; flex:0 0 auto; }
.brk-step .brk-val{ min-width:52px; text-align:center; font-variant-numeric:tabular-nums; font-size:12px; }
.brk-step > button{ width:32px; height:32px; flex:0 0 auto; border-radius:8px;
  border:1px solid var(--border-2); background:transparent; font-size:14px; line-height:1; cursor:pointer; }
.brk-step > button:hover{ background:var(--hover); }
.brk-actions{ display:flex; justify-content:flex-end; margin-top:16px; }
.brk-reset{ border:none; cursor:pointer; font-family:inherit; font-size:13px; font-weight:600;
  color:#fff; background:#1e1e1e; padding:8px 16px; border-radius:8px; }
.brk-reset:hover{ background:var(--black); }

/* Output format — segmented tab group (WAV / MP3 / FLAC / OGG). */
.fmt-tabs{ display:flex; gap:4px; padding:4px; background:var(--canvas); border-radius:12px; }
.fmt-tab{ flex:1; height:34px; border:none; border-radius:8px; cursor:pointer; background:none;
  color:var(--text-2); font-family:inherit; font-size:13px; font-weight:500; letter-spacing:.01em; }
.fmt-tab:hover{ color:var(--text); }
.fmt-tab.active{ background:var(--surface); color:var(--text); box-shadow:var(--e-1); }

/* ---------- VOICE PICKER VIEW ---------- */
/* Voice picker is a centered modal (ant-modal style) overlaying the studio. */
#viewVoice{ display:none; }
#viewVoice.vp-open{ display:flex; position:fixed; inset:0; z-index:2000; align-items:flex-start;
  justify-content:center; padding:5vh 16px; overflow:auto; background:rgba(20,14,45,.5);
  -webkit-backdrop-filter:blur(2px); backdrop-filter:blur(2px); }
.vp-dialog{ width:min(760px,100%); max-height:90vh; display:flex; flex-direction:column;
  background:var(--bg); border:1px solid var(--border-2); border-radius:16px;
  box-shadow:var(--e-3); padding:20px 22px; animation:vppop .18s ease; }
@keyframes vppop{ from{ transform:translateY(10px) scale(.985); opacity:.5 } to{ transform:none; opacity:1 } }
body.vp-locked{ overflow:hidden; }
.vp-head{ display:flex; align-items:center; justify-content:space-between; gap:14px; margin-bottom:14px; }
.vp-head h2{ font-size:18px; font-weight:700; letter-spacing:-.015em; }
.vp-close{ width:34px; height:34px; border-radius:999px; border:none; background:var(--hover); color:var(--text-2);
  display:grid; place-items:center; cursor:pointer; flex:none; }
.vp-close:hover{ background:var(--surface-hover); color:var(--text); }
/* Tabs: Explore / My Voices / Favorites */
.vp-tabs{ display:flex; gap:20px; border-bottom:1px solid var(--border); margin-bottom:12px; }
.vp-tab{ border:none; background:none; cursor:pointer; font-family:inherit; font-size:14px; font-weight:600;
  color:var(--text-3); padding:0 0 9px; margin-bottom:-1px; border-bottom:2px solid transparent; }
.vp-tab:hover{ color:var(--text); }
.vp-tab.is-active{ color:var(--text); border-bottom-color:var(--text); }
.vp-back{ display:flex; align-items:center; justify-content:center; width:38px; height:38px; border-radius:8px;
  border:none; background:none; cursor:pointer; color:var(--text); }
.vp-back:hover{ background:var(--hover); }
.vp-search{ position:relative; display:flex; align-items:center; margin-bottom:12px; }
.vp-search .search-ico{ position:absolute; left:16px; color:var(--text-3); pointer-events:none; }
.vp-search input{ flex:1; height:46px; border-radius:12px; border:1px solid var(--border); padding:0 16px 0 46px;
  font-size:15px; font-family:inherit; color:var(--text); outline:none; background:var(--surface); }
.vp-search input::placeholder{ color:var(--text-3); }
.vp-search input:focus{ border-color:#d1d5db; }
.vp-filters{ display:flex; flex-wrap:wrap; gap:8px; margin-bottom:10px; }
.vp-filter{ flex:1 1 0; min-width:104px; height:40px; border-radius:8px; border:1px solid var(--border-2);
  background-color:var(--surface); color:var(--text); padding:0 32px 0 12px; font-size:14px; font-weight:500;
  font-family:inherit; cursor:pointer; outline:none; appearance:none; -webkit-appearance:none; -moz-appearance:none;
  white-space:nowrap; text-overflow:ellipsis;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%239ca3af' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right 10px center; background-size:16px;
  transition:border-color .15s, background-color .15s; }
.vp-filter:hover{ background-color:var(--hover); }
.vp-filter:focus{ border-color:var(--text); }
/* The picker's filters use the shared ov-listbox combobox UI (ovSelectListbox
   in app.js) instead of native <select>s; keep the same box as .vp-filter so
   the row — and everything around it — keeps its spacing. */
/* The picker's filters are combobox BUTTONS. The generic `.ov-field` wrapper would
   add its own border + padding around each one — a 62px double box per row, two
   rows deep on a phone — so the wrapper is neutralised here and the select keeps
   the only visible box. */
.vp-filters .ov-field.ov-lib-field{ flex:1 1 0; min-width:104px; padding:0; border:none; border-radius:0;
  background:none; min-height:0; }
.vp-filters .ov-select.ov-lib-select{ height:40px; padding:0 12px; border-radius:8px; font-size:14px; font-weight:500; }
/* Language + gender + use case are one flat row on desktop; the phone-only
   toggle and the wrappers that fold the last two away do nothing here. */
.vp-filter-main, .vp-filter-extra{ display:contents; }
.vp-filter-more{ display:none; }
.vp-count{ font-size:12px; color:var(--text-3); margin:0 2px 10px; }
.vp-count:empty{ display:none; }
.vp-list-wrap{ display:flex; flex-direction:column; gap:12px; overflow-y:auto; }
/* No footer under the list: page numbers are gone and so is the "load more"
   button — the list appends rows as you scroll (app.js vpLoadMore). */
/* A full catalogue is 1 300 rows in one scroller. Without this the browser lays
   out and paints every one of them on each append (measured: 36 fps, 133 ms worst
   frame while sweeping); content-visibility skips the offscreen rows — they stay
   in the DOM, in the a11y tree and reachable by keyboard — and the uniform row
   height keeps the scrollbar honest. */
.vp-list-wrap .mv-item{ content-visibility:auto; contain-intrinsic-size:auto 61px; }  /* rows measure 61px at every breakpoint */
/* The picker reads as a LIST, not a stack of cards. Three rules do the work:
   1. a hairline that starts where the text starts (inset), not at the sheet edge;
   2. exactly one accent, on the row that is the current voice — a check before the
      name, a whisper of coral behind it. Everything else is monochrome;
   3. one control per row (preview). Choosing is tapping the row, so the repeated
      "Dùng" pill carries no information and is not rendered at all. */
.vp-list-wrap .mv-item{ position:relative; border:none; border-radius:10px; background:none;
  box-shadow:none; margin-bottom:0; padding:10px 8px; }
.vp-list-wrap .mv-item::after{ content:""; position:absolute; left:56px; right:8px; bottom:0;
  height:1px; background:var(--border); pointer-events:none; }
.vp-list-wrap .mv-item:last-child::after,
.vp-list-wrap .mv-item:hover::after,
.vp-list-wrap .mv-item.selected::after{ opacity:0; }
.vp-list-wrap .mv-item:hover{ background:var(--hover); }
.vp-list-wrap .mv-item.selected{ background:var(--coral-tint-2); }
.vp-list-wrap .mv-item.selected .mv-name::before{ content:"✓"; color:var(--accent);
  font-weight:800; margin-right:7px; }
.vp-list-wrap .mv-name{ font-size:15px; }
.vp-list-wrap .mv-meta{ font-size:12px; color:var(--text-3); }
.vp-list-wrap{ scrollbar-width:thin; }
.vp-list-wrap .mv-av-wrapper{ box-shadow:inset 0 0 0 1px rgba(255,255,255,.22), 0 0 0 1px var(--border-2); }
.vp-list-wrap .mv-item.selected .mv-av-wrapper,
.vp-list-wrap .mv-item.playing .mv-av-wrapper{
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.22), 0 0 0 2px var(--accent); }
.vp-list-wrap .mv-btn.mv-use{ display:none; }   /* the row is the choice */
/* "Xoá" on the current voice is a quiet text action — unpicking a voice is not
   destructive, and a red pill was the loudest thing in an otherwise calm list. */
.vp-list-wrap .mv-btn.mv-remove{ background:none; border:none; color:var(--text-3);
  font-weight:600; padding:0 10px; }
.vp-list-wrap .mv-btn.mv-remove:hover{ background:var(--hover); color:var(--text); }
.vp-list-wrap .mv-btn.mv-play{ opacity:.35; }
.vp-list-wrap .mv-item:hover .mv-btn.mv-play,
.vp-list-wrap .mv-item:focus-within .mv-btn.mv-play,
.vp-list-wrap .mv-item.selected .mv-btn.mv-play,
.vp-list-wrap .mv-btn.mv-play.playing{ opacity:1; }
@media (hover:none){
  .vp-list-wrap .mv-btn.mv-play{ opacity:.5; }
}
.voice-item{ display:flex; align-items:center; gap:14px; padding:12px; border-radius:12px; cursor:pointer; }
.voice-item:hover{ background:var(--hover); }
.voice-item:focus-visible{ outline:2px solid var(--focus); outline-offset:-2px; }
button:focus-visible, .selector:focus-visible, .dropdown:focus-visible,
.vp-filter:focus-visible, [tabindex]:focus-visible{ outline:2px solid var(--focus); outline-offset:2px; }
.voice-item.selected{ background:var(--surface-hover); }
.vi-avatar{ position:relative; flex:none; }
.vi-avatar .av{ width:46px; height:46px; border-radius:999px; display:block; position:relative; overflow:hidden; }
.av-img{ position:absolute; inset:0; width:100%; height:100%; border-radius:inherit; object-fit:cover; display:block; }
/* Favorite (star) toggle on each voice row */
.vi-fav{ width:34px; height:34px; border-radius:8px; border:none; background:none; flex:none; cursor:pointer;
  color:var(--text-3); font-size:16px; line-height:1; display:grid; place-items:center; }
.vi-fav:hover{ background:var(--hover); color:#f59e0b; }
.vi-fav.on{ color:#f59e0b; }
.vi-select{ height:30px; padding:0 12px; border-radius:8px; border:none; background:var(--black);
  color:#fff; font-size:12px; font-weight:600; cursor:pointer; font-family:inherit;
  flex:none; white-space:nowrap; opacity:0; transition:opacity .12s; }
.voice-item:hover .vi-select,.voice-item:focus-within .vi-select{ opacity:1; }
.voice-item.selected .vi-select{ background:#16a34a; opacity:1; }
.vi-select:hover{ background:#262626; }
#voiceList{ overflow:auto; }
.vp-dialog #voiceList{ flex:1; min-height:0; }
.vi-info{ flex:1; min-width:0; }
.vi-name{ font-size:15px; font-weight:600; letter-spacing:-.01em; }
.vi-badge{ display:inline-block; margin-left:6px; color:#f59e0b; font-size:14px; vertical-align:middle; }

/* ---------- HISTORY ---------- */
#historyList{ margin-top:14px; }
#historyPagination{ margin-top:12px; display:flex; justify-content:center; }
.hist-header{ display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; row-gap:6px; margin:14px 0 4px; }
.hist-count{ font-size:14px; font-weight:600; color:var(--text); }
/* Hai nút hành động (Tải tất cả / Xoá) đi cùng nhau ở bên phải; count ở bên
   trái. Tách khỏi .hist-header để space-between không dàn rời 3 phần tử. */
.hist-actions{ display:inline-flex; align-items:center; gap:8px; flex:none; }
/* Tải hàng loạt là hành động bình thường, KHÔNG phải hành động phá huỷ: nút Xoá
   (.hist-clear) được style đỏ ở dưới, nên nút này có class riêng thay vì mượn. */
.hist-dl-all{ display:inline-flex; align-items:center; gap:10px; height:40px; padding:0 16px;
  border:1px solid var(--border-2); background:var(--surface); color:var(--text); border-radius:8px;
  font-size:14px; font-weight:600; font-family:inherit; cursor:pointer; flex:none; white-space:nowrap; }
.hist-dl-all:hover{ background:var(--hover); }
.hist-dl-all:disabled{ opacity:.55; cursor:default; }
.hist-clear{ display:inline-flex; align-items:center; gap:10px; height:42px; padding:0 20px; border:none;
  background:none; cursor:pointer; font-family:inherit; font-size:15px; font-weight:600; color:var(--text); border-radius:8px;
  flex:none; white-space:nowrap; }
.hist-clear:hover{ background:var(--hover); }
.hist-empty{ color:var(--text-3); font-size:15px; text-align:center; padding:28px 12px; }
.hist-item{ display:flex; align-items:center; gap:12px; padding:10px; border-radius:12px; cursor:pointer; }
.hist-item:hover{ background:var(--hover); }
.hist-av-wrap{ position:relative; flex:none; width:38px; height:38px; display:flex; align-items:center; justify-content:center; }
.hist-av{ width:100%; height:100%; border-radius:999px; flex:none; }
/* Play overlay over the avatar — mirrors the featured-card .vf-play affordance:
   centered, hidden until the row is hovered/playing, click to preview. The
   .hist-av-play override block lives AFTER .vf-play (below) so it wins the
   cascade over the shared .vf-play base. */
.hist-info{ flex:1; min-width:0; display:flex; flex-direction:column; gap:4px; }
.hist-name{ font-size:14px; font-weight:500; line-height:1.4;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
/* Opening words of the script, so entries sharing one voice are tellable
   apart. One line; the full text is in the row's Detail modal. */
.hist-snippet{ font-size:12px; font-weight:400; color:var(--text-2); line-height:1.4;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.hist-time{ font-size:12px; font-weight:400; color:var(--text-3); }
.hist-btn{ width:32px; height:32px; border-radius:8px; border:none; background:none; flex:none; cursor:pointer;
  display:flex; align-items:center; justify-content:center; color:var(--text); }
.hist-btn:hover{ background:rgba(0,0,0,.06); }
/* Clicks must always land on the button, not the (lucide) svg/path inside it. */
.hist-btn svg{ pointer-events:none; }

/* History item dropdown menu */
.hist-menu-wrap{ position:relative; flex:none; }
.hist-dropdown{ display:none; position:absolute; top:36px; right:0; z-index:20; min-width:160px;
  background:var(--surface); border:1px solid var(--border-2); border-radius:12px; box-shadow:var(--e-2);
  padding:6px; }
.hist-dropdown.show{ display:block; }
.hist-dd-item{ display:flex; align-items:center; gap:10px; width:100%; padding:9px 12px; border:none;
  background:none; border-radius:8px; font-size:14px; font-weight:500; color:var(--text);
  cursor:pointer; font-family:inherit; text-align:left; }
.hist-dd-item:hover{ background:var(--hover); }
.hist-dd-item.danger{ color:#dc2626; }
.hist-dd-item.danger:hover{ background:var(--danger-soft); }
.hist-dd-item svg{ width:16px; height:16px; flex:none; }
.hist-clear{height:40px; border-radius:8px;
  background:var(--surface); color:#dc2626; font-size:14px; font-family:inherit; cursor:pointer; }
.hist-clear:hover{ background:var(--danger-soft); }
.hist-modal{ text-align:left; max-width:560px; }
.hist-detail-name{ text-align:left; margin-bottom:4px; }
.hist-detail-meta{ text-align:left; margin:0 0 14px; font-size:13px; color:var(--text-3); }
.hist-detail-text{ white-space:pre-wrap; word-break:break-word; max-height:48vh; overflow:auto;
  background:var(--hover,#f6f6f7); border:1px solid var(--border-2); border-radius:12px;
  padding:14px 16px; font-size:15px; line-height:1.55; color:var(--text); margin-bottom:16px; }
.vi-desc{ font-size:13px; color:var(--text-3); margin-top:2px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.vi-play{ width:34px; height:34px; border-radius:999px; border:none; background:none; flex:none; display:flex;
  align-items:center; justify-content:center; cursor:pointer; color:var(--text); position:relative; }
.vi-play.playing{ color:#fc3e57; }
.vi-play.playing::before{ content:""; position:absolute; inset:-2px; border-radius:999px;
  border:2.5px solid transparent; border-top-color:#fc3e57;
  animation:vi-spin .8s linear infinite; }
@keyframes vi-spin{ to{ transform:rotate(360deg); } }
.voice-item:hover .vi-play,.voice-item.selected .vi-play{ background:rgba(0,0,0,.05); }

/* ---------- TOAST ---------- */
/* Quiet, paper-like feedback: a small system note, not a loud alert card.
   Desktop: bottom-right. Phones: docked under the topbar — see the media query
   below for why the bottom edge is unusable there. */
.toast{ position:fixed; right:20px; bottom:20px; z-index:9000;
  max-width:min(420px, calc(100vw - 32px)); display:flex; align-items:flex-start; gap:10px;
  padding:12px 13px 12px 14px; border:1px solid rgba(15,23,42,.08); border-radius:16px;
  background:var(--glass-bg-strong); color:var(--text, #111);
  box-shadow:var(--e-3);
  -webkit-backdrop-filter:blur(10px) saturate(1.2); backdrop-filter:blur(10px) saturate(1.2);
  font-size:14px; line-height:1.45; font-weight:500;
  opacity:0; transform:translateY(10px) scale(.985); pointer-events:none;
  transition:opacity .2s ease, transform .2s ease; }
.toast.show{ opacity:1; transform:translateY(0) scale(1); pointer-events:auto; }
.toast-ico{ flex:none; display:flex; align-items:center; justify-content:center; width:22px; height:22px;
  border-radius:999px; color:#fff; margin-top:1px; }
.toast-msg{ flex:1; min-width:0; overflow-wrap:anywhere; }
.toast-close{ flex:none; display:flex; align-items:center; justify-content:center; width:26px; height:26px;
  margin:-3px -3px -3px 0; border:none; border-radius:8px; background:transparent; color:inherit;
  opacity:.45; cursor:pointer; transition:opacity .15s, background .15s; }
.toast-close:hover{ opacity:1; background:rgba(15,23,42,.06); }
.toast.ok .toast-ico{ background:#16a34a; }
.toast.info .toast-ico{ background:#2563eb; }
.toast.err{ background:var(--coral-tint-2); border-color:rgba(225,29,72,.18); color:var(--danger); }
.toast.err .toast-ico{ background:#e11d48; }
@media (max-width:820px){
  /* Dock the toast under the topbar, not above the footer.
     Measured on a 390x844 viewport: the old `bottom: 144px` put the toast at
     y=617/h=83 and it overlapped "Add speaker" (93x22), "Import script"
     (93x22) and "Subtitles to speech" (195x36) — i.e. almost the whole button —
     plus a close control. The reserved 144px was a hard-coded guess that no
     longer matches .editor-footer's real height, so it was wrong on every
     screen with a taller toolbar. The strip directly under the topbar is empty
     on every studio screen, so dock there instead.
     It is also pointer-events:none here: on touch there is no hover to pause for,
     and a toast must never eat the tap meant for the control it sits over. Only
     the error toast's close button stays tappable. */
  .toast{ left:50%; right:auto; top:calc(env(safe-area-inset-top, 0px) + 68px); bottom:auto;
    transform:translate(-50%, -8px) scale(.985); width:auto; max-width:calc(100vw - 24px);
    padding:11px 12px 11px 13px; border-radius:12px; font-size:13px;
    pointer-events:none; }
  .toast.show{ transform:translate(-50%, 0) scale(1); pointer-events:none; }
  .toast-close{ pointer-events:auto; }
}

/* ---------- DOWNLOAD LOCATION NOTICE ---------- */
/* Light, warm and on-brand for a white/pastel page: the old black card read
   like a system alert and out-shouted the primary coral CTA. This one keeps
   the same hierarchy while feeling like part of SaydiVoice. */
.dl-notice{ position:fixed; right:24px; bottom:calc(24px + env(safe-area-inset-bottom, 0px)); z-index:9001;
  width:min(420px,calc(100vw - 48px)); display:flex; align-items:flex-start; gap:12px;
  padding:16px 48px 16px 16px; border-radius:16px;
  background:linear-gradient(180deg,#fff 0%,#fff8f7 100%);
  border:1px solid #f1d9d6; color:var(--text);
  box-shadow:var(--e-3);
  opacity:0; transform:translateY(16px); pointer-events:none;
  transition:opacity .22s ease, transform .22s ease; }
.dl-notice.show{ opacity:1; transform:translateY(0); pointer-events:auto; }
.dl-notice[hidden]{ display:none; }
body.has-playbar .dl-notice{ bottom:calc(120px + env(safe-area-inset-bottom, 0px)); }
.dl-notice-ico{ flex:none; display:flex; align-items:center; justify-content:center; width:34px; height:34px;
  border-radius:999px; background:var(--success-tint); color:#16a34a; }
.dl-notice-body{ min-width:0; flex:1; }
.dl-notice-title{ display:block; font-size:15px; line-height:1.25; font-weight:800; letter-spacing:-.01em; color:var(--text); }
.dl-notice-file{ display:block; margin-top:4px; font-size:14px; line-height:1.35; color:var(--text-2);
  overflow-wrap:anywhere; word-break:normal; }
.dl-notice-hint{ display:flex; align-items:flex-start; gap:7px; margin-top:8px; font-size:13px;
  line-height:1.5; color:var(--text-2); }
.dl-notice-folder{ flex:none; margin-top:2px; color:#fe655d; opacity:.95; }
.dl-notice-actions{ display:flex; flex-wrap:wrap; gap:10px; margin-top:12px; }
.dl-notice-btn{ flex:1 1 auto; min-height:38px; padding:0 14px; border-radius:12px;
  border:1px solid #f1d9d6; background:var(--surface); color:var(--text); font:inherit; font-size:13px; font-weight:700;
  cursor:pointer; transition:background .15s ease, border-color .15s ease, box-shadow .15s ease, filter .15s ease; }
.dl-notice-btn:first-child{ border-color:transparent; color:#fff;
  background:linear-gradient(135deg,#ff9e99,#fe655d 55%,#fc3e57);
  box-shadow:var(--e-2); }
.dl-notice-btn:hover{ background:var(--coral-tint-2); border-color:#f1c9c5; }
.dl-notice-btn:first-child:hover{ background:linear-gradient(135deg,#ff9e99,#fe655d 55%,#fc3e57);
  border-color:transparent; filter:brightness(1.04); }
.dl-notice-btn:active{ transform:translateY(1px); }
.dl-notice-btn:focus-visible,.dl-notice-close:focus-visible{ outline:2px solid var(--focus); outline-offset:2px; }
.dl-notice-close{ position:absolute; top:10px; right:10px; width:32px; height:32px; border:none; border-radius:8px;
  background:var(--surface); color:var(--text-2); display:flex; align-items:center; justify-content:center;
  cursor:pointer; transition:background .15s ease, color .15s ease; }
.dl-notice-close:hover{ background:var(--canvas); color:var(--text); }
@media (max-width:640px){
  .dl-notice{ left:12px; right:12px; bottom:calc(12px + env(safe-area-inset-bottom, 0px)); width:auto;
    padding:16px 48px 18px 16px; border-radius:16px; }
  body.has-playbar .dl-notice{ bottom:calc(216px + env(safe-area-inset-bottom, 0px)); }
  .dl-notice-title{ font-size:16px; }
  .dl-notice-file{ font-size:14px; }
  .dl-notice-hint{ font-size:13px; }
  .dl-notice-actions{ gap:10px; }
  .dl-notice-btn{ min-height:46px; font-size:14px; }
  .dl-notice-close{ width:40px; height:40px; top:8px; right:8px; }
}

.editor::-webkit-scrollbar,.settings::-webkit-scrollbar{ width:10px; }
.editor::-webkit-scrollbar-thumb,.settings::-webkit-scrollbar-thumb{ background:var(--surface-hover); border-radius:8px; border:2px solid #fff; }

/* Voice avatars: render the generated SVG orb cleanly with a subtle ring. */
.sel-av, .sp-av, .vi-avatar .av, .pb-left .av {
  background-size: cover; background-position: center; background-repeat: no-repeat;
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.22), 0 1px 4px rgba(0,0,0,.16);
}

@media (max-width:1300px){
  .speaker-text,.ph-overlay{ font-size:16px; }
  .st-label{ font-size:16px; }
  .selector,.dropdown{ font-size:15px; }
}
/* Mid-range (tablet / small laptop): trim generous 40px gutters so the editor
   column keeps a comfortable width before the layout stacks at 820px. */
@media (max-width:1180px){
  .editor-scroll{ padding:26px 24px calc(20px + var(--gen-strip-h, 0px)); }
  .settings{ padding:26px 22px 48px; }
  .editor-footer{ padding:14px 24px 16px; }
}

/* ---------- DOCS / ABOUT MODAL ---------- */
.modal-backdrop{ position:fixed; inset:0; background:rgba(10,10,10,.45); display:flex;
  align-items:center; justify-content:center; z-index:8000; padding:20px;
  /* Fade in/out: stay in flow (display:flex) and toggle visibility/opacity via
     .show so both fade transitions can run (display:none can't be animated). */
  opacity:0; visibility:hidden; pointer-events:none; transition:opacity .2s ease, visibility .2s ease; }
.modal-backdrop.show{ opacity:1; visibility:visible; pointer-events:auto; }
.modal{ position:relative; width:100%; max-width:460px; background:var(--surface); border-radius:16px;
  padding:34px 32px 28px; text-align:center; box-shadow:var(--e-3); }
.modal-x{ position:absolute; top:14px; right:14px; width:36px; height:36px; border-radius:8px; border:none;
  background:none; cursor:pointer; color:var(--text-3); display:flex; align-items:center; justify-content:center; }
.modal-x:hover{ background:var(--hover); color:var(--text); }
.modal-logo{ line-height:1; margin-bottom:10px; }
.modal-logo img{ width:48px; height:48px; display:inline-block; filter:drop-shadow(0 4px 10px rgba(254,101,93,.28)); }
.modal-title{ font-size:20px; font-weight:800; letter-spacing:-.02em; margin-bottom:8px; }
.modal-tagline{ font-size:15px; line-height:1.5; color:var(--text-2); margin-bottom:20px; }
.modal-list{ list-style:none; text-align:left; display:flex; flex-direction:column; gap:12px; margin-bottom:24px; }
.modal-list li{ display:flex; align-items:center; gap:12px; font-size:15px; font-weight:500; }
.modal-ico{ display:flex; align-items:center; justify-content:center; width:28px; height:28px; border-radius:999px; flex:none; color:#fff; }
.modal-ico.free{ background:#16a34a; }
.modal-ico.eco{ background:linear-gradient(135deg,#f59e0b,#ef4444); }
.modal-promo{ display:flex; align-items:center; justify-content:space-between; gap:12px; text-decoration:none;
  border:1px solid var(--border-2); border-radius:16px; padding:14px 16px; margin-bottom:16px; text-align:left;
  background:linear-gradient(135deg,#fff7ed,#eef2ff); color:var(--text); transition:box-shadow .15s, transform .15s; }
.modal-promo:hover{ box-shadow:var(--e-2); transform:translateY(-1px); }
.modal-promo-text{ display:flex; flex-direction:column; flex:1; min-width:0; }
.modal-promo-logo{ display:block; flex:none; }
.modal-promo-text strong{ font-size:15px; font-weight:700; }
.modal-promo-text span{ font-size:13px; color:var(--text-2); }
.modal-promo-go{ font-size:13px; font-weight:700; color:#4f46e5; white-space:nowrap; }
.modal-btn{ width:100%; height:48px; border-radius:12px; border:none; background:var(--black); color:#fff;
  font-size:16px; font-weight:600; cursor:pointer; font-family:inherit; }
.modal-btn:hover{ background:#262626; }

/* Confirm modal (delete voice etc.) */
.confirm-modal{ max-width:380px; padding:48px 28px 24px; text-align:left;
  opacity:0; transform:translateY(6px); transition:opacity .22s ease, transform .22s ease; }
.confirm-modal .modal-x{ top:8px; right:8px; }
.modal-backdrop.show .confirm-modal{ opacity:1; transform:translateY(0); }
.confirm-msg{ font-size:16px; font-weight:600; color:var(--text); line-height:1.5; margin-bottom:24px; }
.confirm-actions{ display:flex; gap:10px; justify-content:flex-end; margin-top:24px; }
.confirm-actions > button{ height:36px; display:inline-flex; align-items:center; justify-content:center; gap:7px; }
.confirm-actions > button svg{ width:18px; height:18px; flex:none; }
/* New-API-key dialog (mirrors the sign-out confirm modal): title -> once-only
   note -> the secret itself -> the two actions. */
.confirm-secret-note{ font-size:13px; color:var(--text-2); line-height:1.5; margin:-12px 0 14px; }
.confirm-secret-box{ background:var(--callout); border:1px solid var(--border-2); border-radius:8px;
  padding:12px; overflow-wrap:anywhere; }
.confirm-secret-box code{ font-family:ui-monospace, SFMono-Regular, Menlo, monospace; font-size:13px; color:var(--text); }
/* The key dialog is wider than the confirm modal — long keys stay readable. */
.confirm-modal.secret-modal{ max-width:520px; }
.prompt-input{ width:100%; height:44px; border:1px solid var(--border-2); border-radius:8px; padding:0 14px;
  font-size:14px; font-family:inherit; color:var(--text); outline:none; margin-bottom:20px; }
.prompt-input:focus{ border-color:#fe655d; box-shadow:0 0 0 3px rgba(254,101,93,.12); }

/* Share modal: a public link for a dubbed video. */
.share-modal{ max-width:440px; padding:40px 26px 22px; text-align:left;
  opacity:0; transform:translateY(6px); transition:opacity .22s ease, transform .22s ease; }
.share-modal .modal-x{ top:8px; right:8px; }
.modal-backdrop.show .share-modal{ opacity:1; transform:translateY(0); }
.share-title{ font-size:16px; font-weight:700; margin:0 0 6px; }
.share-sub{ font-size:13px; line-height:1.55; color:var(--text-2); margin:0 0 16px; }
/* Title + comparison: both edit the LIVE link through PATCH, so they read as
   fields of the link rather than as a second step. */
.share-field{ display:block; margin:0 0 14px; }
.share-field span{ display:block; font-size:12px; font-weight:650; color:var(--text-2); margin-bottom:6px; }
.share-field input{ width:100%; height:42px; border:1px solid var(--border-2); border-radius:8px;
  padding:0 12px; font-size:14px; font-family:inherit; color:var(--text); background:var(--surface,#fff); outline:none; }
.share-field input:focus{ border-color:#fe655d; box-shadow:0 0 0 3px rgba(254,101,93,.12); }
.share-compare{ display:flex; gap:9px; align-items:flex-start; font-size:14px; font-weight:600;
  color:var(--text); cursor:pointer; margin:0 0 6px; }
.share-compare input{ width:17px; height:17px; margin:2px 0 0; accent-color:#fc3e57; flex:none; }
.share-compare-hint{ margin:0 0 14px; font-size:12px; line-height:1.5; color:var(--text-2); }
.share-compare-hint.is-off{ color:var(--text-3); }
.share-link{ display:flex; gap:8px; align-items:center; }
/* The link is read-only and select-on-focus: it is meant to be copied, and a
   truncated long URL would make "select all by hand" fail on mobile. */
.share-link input{ flex:1; min-width:0; height:42px; border:1px solid var(--border-2); border-radius:8px;
  padding:0 12px; font-size:13px; font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  color:var(--text); background:var(--surface,#fff); outline:none; }
.share-link input:focus{ border-color:#fe655d; box-shadow:0 0 0 3px rgba(254,101,93,.12); }
.share-link button{ flex:none; height:42px; padding:0 16px; border:none; border-radius:8px; cursor:pointer;
  background:linear-gradient(90deg,#fe655d,#fc3e57); color:#fff; font-weight:650; font-size:14px;
  font-family:inherit; }
.share-link button[disabled]{ opacity:.6; cursor:default; }
.share-note{ font-size:12px; line-height:1.55; color:var(--text-3); margin:14px 0 0; }
.share-actions{ display:flex; gap:10px; justify-content:space-between; align-items:center; margin-top:18px; }
.share-actions .share-revoke{ border:none; background:none; padding:0; cursor:pointer; font-family:inherit;
  font-size:13px; font-weight:600; color:#dc2626; }
.share-actions .share-revoke:hover{ text-decoration:underline; }
.share-actions .share-close{ height:36px; padding:0 18px; border-radius:8px; border:none; cursor:pointer;
  background:var(--black,#111); color:#fff; font-family:inherit; font-size:14px; font-weight:600; }
.share-ok{ color:#16a34a; }
.confirm-cancel{ height:40px; padding:0 18px; border-radius:8px; border:1px solid var(--border-2);
  background:var(--surface); color:var(--text); font-size:14px; font-weight:600; cursor:pointer; font-family:inherit; }
.confirm-cancel:hover{ background:var(--hover); }
.confirm-ok{ height:40px; padding:0 18px; border-radius:8px; border:none;
  background:var(--black); color:#fff; font-size:14px; font-weight:600; cursor:pointer; font-family:inherit; }
.confirm-ok:hover{ background:#262626; }

/* Cloud-sync state above the history list. Quiet by design: it answers "is my
   work safe?" at a glance and otherwise stays out of the way. */
.cloud-status{ display:flex; align-items:center; flex-wrap:wrap; gap:7px; padding:8px 10px; margin:10px 0;
  border-radius:8px; font-size:12px; line-height:1.4; background:var(--hover, #f5f5f7);
  color:var(--muted, #6b6b76); }
.cloud-status[hidden]{ display:none; }
.cloud-status svg{ flex:none; }
.cloud-ok{ background:var(--success-tint); color:#15803d; }
.cloud-err{ background:var(--danger-soft); color:#b91c1c; }
.cloud-cta{ border:none; background:none; padding:0; cursor:pointer; font-family:inherit; flex:none;
  font-size:12px; font-weight:600; color:#fe655d; }
.cloud-cta:hover{ text-decoration:underline; }
.cloud-spin{ width:12px; height:12px; border:2px solid rgba(0,0,0,.15);
  border-top-color:#6b6b76; border-radius:999px; display:inline-block;
  animation:cloud-spin .7s linear infinite; }
@keyframes cloud-spin{ to{ transform:rotate(360deg); } }

/* Free-trial counter in the editor footer. Deliberately understated: it informs
   while you work so the sign-in gate is never a surprise, and it must not read as
   a nag. Turns amber on the last one. */
.trial-hint{ display:inline-flex; align-items:center; gap:6px; font-size:12px; color:var(--muted, #6b6b76); }
.trial-hint[hidden]{ display:none; }
/* Ring on the left of the trial hint: the filled arc = creations already used
   (driven by --used-frac, 0..1). Inner white disc leaves a thin ring. */
.trial-ring{ position:relative; flex:none; width:18px; height:18px; border-radius:999px;
  background:conic-gradient(#fe655d calc(var(--used-frac, 0) * 360deg), var(--border-2, #e5e7eb) 0); }
.trial-ring::after{ content:""; position:absolute; inset:3px; border-radius:999px; background:var(--surface); }
.trial-hint-last{ color:var(--warn-ink); }
.trial-hint-cta{ border:none; background:none; padding:0; cursor:pointer; font-family:inherit;
  font-size:12px; font-weight:600; color:#fe655d; }
.trial-hint-cta:hover{ text-decoration:underline; }

/* NOTE: the sign-in / account dialog styles live in assets/auth.css, loaded by
   both this app and the marketing page (the dialog is shared, and the home page
   does not load this file). Do not re-add them here. */

/* Signed-in state of the EXISTING top-bar avatar. There is deliberately only one
   avatar: the mascot is the signed-out face and the user's photo replaces it, so
   a signed-in user never sees two different faces side by side. */
.avatar-top.avatar-signed-in{ overflow:hidden; }
.avatar-photo{ width:100%; height:100%; border-radius:999px; object-fit:cover; display:block; }
.avatar-initial{ display:flex; align-items:center; justify-content:center;
  background:var(--black); color:#fff; font-size:15px; font-weight:700; }
/* Which account you are signed in as, at the top of the avatar dropdown. */
.avatar-menu-who{ padding:10px 14px 8px; font-size:12px; color:var(--muted, #6b6b76);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:220px; }


.confirm-ok.danger{ background:#e11d48; }
.confirm-ok.danger:hover{ background:#be123c; }

.modal-script{ max-width:560px; text-align:left; }
.modal-script .modal-title{ text-align:left; }
.modal-script .modal-tagline{ text-align:left; }
.script-ta{ width:100%; min-height:200px; border-radius:12px; border:1px solid var(--border-2);
  padding:12px 16px; font-size:15px; line-height:1.6; font-family:inherit; color:var(--text);
  resize:vertical; outline:none; background:var(--surface); }
.script-ta:focus{ border-color:#d1d5db; }
.script-actions{ display:flex; gap:12px; margin-top:18px; }
.modal-btn.ghost{ background:var(--surface); color:var(--text); border:1px solid var(--border-2); }
.modal-btn.ghost:hover{ background:var(--hover); }

/* ---------- MOBILE / RESPONSIVE ---------- */
.sidebar-close{ display:none; }
.sidebar-backdrop{ display:none; }

@media (max-width:820px){
  /* Let the whole page scroll instead of the fixed 3-column desktop frame. */
  html,body{ height:auto; }
  body{ overflow-x:hidden; overflow-y:auto; -webkit-text-size-adjust:100%; }
  body.sidebar-drawer-open{ overflow:hidden; overscroll-behavior:none; }
  .layout{ flex-direction:column; height:auto; min-height:100vh; min-height:100dvh; overflow:visible; }
  .app{ height:auto; }

  /* Sidebar becomes a slide-in drawer. */
  .sidebar{ position:fixed; top:0; left:0; height:100vh; height:100dvh; width:300px; max-width:88vw; z-index:200;
    padding-top:env(safe-area-inset-top); padding-bottom:env(safe-area-inset-bottom);
    background:var(--surface); transform:translate3d(-100%,0,0); will-change:transform; backface-visibility:hidden;
    transition:transform .3s cubic-bezier(.22,1,.36,1); border-right:1px solid var(--border); }
  .sidebar-close{ display:flex; position:absolute; top:calc(8px + env(safe-area-inset-top)); right:10px; z-index:3;
    width:40px; height:40px; align-items:center; justify-content:center; border:none; border-radius:8px;
    background:transparent; color:var(--text-2); cursor:pointer; touch-action:manipulation;
    transition:background .15s ease, color .15s ease; }
  .sidebar-close:hover{ background:var(--hover); color:var(--text); }
  .sidebar .sb-scroll{ min-height:0; overscroll-behavior:contain; }
  .sidebar .sb-bottom{ max-height:58vh; overflow-y:auto; overscroll-behavior:contain; }
  .sidebar .sb-bottom{ max-height:58dvh; overflow-y:auto; overscroll-behavior:contain; }
  .sidebar.open{ transform:translate3d(0,0,0); box-shadow:var(--e-3); }
  /* Keep the hamburger reachable above the open drawer so the same button can
     close it. The topbar itself becomes a transparent, click-through layer;
     only the toggle receives pointer events. */
  body.sidebar-drawer-open .topbar{ z-index:210; background:transparent; border-bottom-color:transparent; pointer-events:none;
    transition:background .2s ease, border-color .2s ease; }
  body.sidebar-drawer-open .topbar-breadcrumb,
  body.sidebar-drawer-open .topbar-right{ opacity:0; pointer-events:none; }
  .topbar-breadcrumb,.topbar-right{ transition:opacity .2s ease; }
  body.sidebar-drawer-open #sidebarToggle{ pointer-events:auto; }
  body.sidebar-drawer-open #sidebarToggle .sb-ic-collapse,
  body.sidebar-drawer-open #sidebarToggle .sb-ic-expand{ display:none; }
  body.sidebar-drawer-open #sidebarToggle .sb-ic-close{ display:inline; }
  /* Leave room for the floating toggle over the drawer header. */
  .sidebar .site-brand{ padding-left:58px; }
  .sidebar.collapsed{ width:300px; }            /* ignore desktop rail-collapse on mobile */
  .sidebar.collapsed .s-item{ justify-content:flex-start; padding:6px 8px; gap:10px; }
  .sidebar.collapsed .workspace{ justify-content:flex-start; padding:8px 10px; }
  .sidebar.collapsed .ws-name,
  .sidebar.collapsed .ws-chev{ display:block; }
  .sidebar.collapsed .s-label{ display:block; flex:1 1 auto; width:100%; min-width:0; opacity:1; transform:none; pointer-events:auto; }
  .sidebar.collapsed .s-section,
  .sidebar.collapsed .sb-group{ display:block; max-height:42px; opacity:1; margin:0; padding:16px 12px 6px; pointer-events:auto; }
  .sidebar.collapsed .sb-group{ max-height:24px; margin:16px 0 10px; padding:0 12px; }
  .sidebar.collapsed .s-badge{ display:inline-flex; width:auto; max-width:none; padding:2px 7px; margin-left:auto; opacity:1; overflow:visible; pointer-events:auto; }
  .sidebar.collapsed .sb-foot{ display:block; }
  .sidebar.collapsed .site-brand{ justify-content:flex-start; padding:8px 14px; }
  /* …and the drawer is 300px wide, so the wordmark fits: undo the mark swap. */
  .sidebar.collapsed .site-brand .brand-logo{ content:url("/static/saydivoice-logo.svg"); width:auto; height:20px; }
  .sidebar-backdrop{ display:block; position:fixed; inset:0; background:rgba(0,0,0,.55); z-index:150; cursor:pointer; touch-action:none;
    opacity:0; pointer-events:none; transition:opacity .28s ease; }
  .sidebar-backdrop.show{ opacity:1; pointer-events:auto; }

  @media (prefers-reduced-motion:reduce){
    .sidebar,.sidebar-backdrop,.s-item,.s-label,.s-badge,.s-section,.sb-group{ transition:none; }
  }

  /* Stack editor + settings vertically; page handles scrolling. */
  .app > main{ padding:0; }
  .app > main > .main{ border:none; border-radius:0; box-shadow:none; overflow:visible; }
  .main{ display:flex; flex-direction:column; height:auto; min-height:0; }
  /* Content-first mobile shell: the editor is the first thing in the flow.
     Settings/history live in a bottom sheet opened from the sticky footer, so
     first paint is the text area + Generate, not the knobs. */
  .settings{
    position:fixed; left:0; right:0; bottom:0; top:auto; z-index:1500;
    width:100%; max-height:min(86vh, 760px);
    width:100%; max-height:min(86dvh, 760px);
    border-left:none; border-top:1px solid var(--border);
    border-radius:16px 16px 0 0;
    box-shadow:var(--e-3);
    padding:20px 16px calc(22px + env(safe-area-inset-bottom, 0px));
    background:var(--surface); overflow-y:auto; overscroll-behavior:contain;
    transform:translateY(102%); visibility:hidden; pointer-events:none;
    transition:transform .28s cubic-bezier(.22,1,.36,1), visibility .28s;
  }
  body.settings-sheet-open .settings{ transform:translateY(0); visibility:visible; pointer-events:auto; }
  body.settings-sheet-open{ overflow:hidden; }
  .st-sheet-close{
    display:grid; place-items:center; position:absolute; top:12px; right:12px; z-index:2;
    width:38px; height:38px; border:none; border-radius:999px; background:var(--hover); color:var(--text-2);
    cursor:pointer; touch-action:manipulation;
  }
  .st-sheet-close:hover{ background:var(--surface-hover); color:var(--text); }
  .st-tabs{ padding-right:48px; } /* keep the sheet close button clear of tabs */
  /* Finger-sized steppers. 24px is the WCAG 2.2 minimum but a phone tap that
     small misses often, and the ± pair sits right next to each other. */
  .brk-step > button{ width:38px; height:38px; font-size:16px; }
  .brk-level{ height:40px; }
  .brk-enable-row{ font-size:13px; }
  .settings-backdrop{
    display:block; position:fixed; inset:0; z-index:1400; background:rgba(15,23,42,.42);
    opacity:0; pointer-events:none; transition:opacity .24s ease;
  }
  body.settings-sheet-open .settings-backdrop{ opacity:1; pointer-events:auto; }
  .trust-strip{ display:flex; }               /* surface trust signals up top on phones */
  .editor{
    order:0; width:100%; overflow:visible;
    /* Fill the visible area below the sticky topbar. Without this, a short
       script leaves the sticky Generate footer floating mid-screen with a
       dead white band underneath it. */
    min-height:calc(100vh - 61px);
    min-height:calc(100dvh - 61px);
  }
  .editor-scroll{ flex:1 1 auto; }
  .editor-footer{
    order:1; position:sticky; bottom:0; z-index:80;
    background:var(--surface); border-top:1px solid var(--border);
  }
  /* One speaker + the workspace voice selector above: the extra per-speaker
     chip is redundant and, if its label is ever empty, just looks broken. */
  body.mode-tts.single-speaker .speaker-head{ display:none !important; }
  /* Make the empty middle a real writing surface instead of a dead gap:
     text area grows, toolbar/Add-speaker stay anchored near the footer. */
  body.mode-tts.single-speaker .editor-scroll{ display:flex; flex-direction:column; }
  body.mode-tts.single-speaker .editor-inner{ display:flex; flex-direction:column; flex:1 1 auto; min-height:0; min-width:0; width:100%; margin:0; }
  body.mode-tts.single-speaker #speakers{ display:flex; flex-direction:column; flex:1 0 auto; min-height:max-content; min-width:0; }
  body.mode-tts.single-speaker .speaker-block{ display:flex; flex-direction:column; flex:1 1 auto; min-height:0; min-width:0;
    padding-bottom:0; margin-bottom:0; border-bottom:none; }
  body.mode-tts.single-speaker .speaker-content{ display:flex; flex-direction:column; flex:1 1 auto; min-height:0; min-width:0; }
  body.mode-tts.single-speaker .speaker-textwrap{ flex:1 1 auto; margin-top:0; min-height:140px; display:flex; flex-direction:column; }
  body.mode-tts.single-speaker .speaker-text{ flex:1 1 auto; min-height:100%; }
  .settings-sheet-trigger{
    display:inline-flex; align-items:center; gap:7px; flex:none;
    min-height:36px; padding:0 12px; border-radius:8px;
    border:1px solid var(--border-2); background:var(--surface); color:var(--text);
    font-family:inherit; font-size:13px; font-weight:650; cursor:pointer; touch-action:manipulation;
  }
  .settings-sheet-trigger:hover{ background:var(--hover); }
  .settings-sheet-trigger svg{ flex:none; color:var(--text-2); }
  /* The topbar already carries "Sign in"; keep the footer trial counter quiet
     and single-line so it never competes with Generate. */
  .trial-hint{ min-width:0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  .trial-hint-cta{ display:none; }
  /* Sticky footer owns the bottom edge, so reserve its height inside the
     scroll area. Otherwise the last line / Add-speaker row can sit underneath
     the Generate row until the user scrolls. */
  .editor-scroll{ overflow:visible; padding:20px 16px calc(136px + env(safe-area-inset-bottom, 0px) + var(--gen-strip-h, 0px)); }
  /* On phones History lives inside the bottom sheet, so it must not hide the
     sticky Generate footer the way the desktop side-panel history does. */
  body.st-history .editor-footer{ display:flex; }
  /* Keep the character budget and CTA on one full-width row; the CTA stretches
     into the remaining space so it reads as the primary action. */
  /* Two explicit rows instead of "flex:1 1 100% on a wrapping row". That intent
     (this group gets its own row) does not survive the flex algorithm: measured at
     320px, .ef-right kept its 288px basis on the SAME line as .ef-left (252px), so
     the composer rendered 274px past the footer — off-screen — and margin:auto or
     a wrap toggle could not fix it. Column direction states the layout outright. */
  .editor-footer .el-top{ flex-direction:column; align-items:stretch; gap:8px; }
  .editor-footer .el-top .ef-right{ width:100%; flex:0 0 auto; min-width:0; justify-content:space-between;
    margin-inline-start:0; }
  .editor-footer .el-top .ef-right .char-count{ flex:none; }
  .editor-footer .el-top .ef-right .generate-btn{ width:auto; flex:1 1 auto; }
  /* Very small phones (320-360px) with the longest CTA labels: "Generate speech"
     plus "53 / 100,000 characters" needs ~316px against the 288px available, so the
     button was clipped 25px past the viewport edge (invisible, since .app clips its
     overflow) — the primary action, half cut off. Dropping the CTA to a full-width
     second row removes the overflow and gives it a bigger thumb target. */
  @media (max-width:360px){
    .editor-footer .el-top .ef-right{ flex-wrap:wrap; row-gap:8px; }
    .editor-footer .el-top .ef-right .char-count{ order:1; }
    .editor-footer .el-top .ef-right .generate-btn{ order:2; flex:1 1 100%; }
  }
  /* The playbar is the post-generation control surface; hide the Generate row
     underneath it and take the now-empty footer out of the sticky stack so the
     two fixed layers cannot overlap on phones. */
  body.has-playbar .editor-footer .el-top{ display:none; }
  body.has-playbar .editor-footer{ position:relative; z-index:auto; padding:0; border-top:none; background:transparent; }

  /* ----- Mobile WebView: keyboard-aware composer -----
     TTS sizes itself to visualViewport.height. When the keyboard opens the
     editor shrinks, the footer sits just above the keyboard, and the text
     scrolls inside .editor-scroll instead of the page panning under fixed bars. */
  body.mode-tts{ height:var(--app-vvh, 100dvh); overflow:hidden; overscroll-behavior:none; }
  body.mode-tts .layout{ position:fixed; top:0; left:0; right:0; height:var(--app-vvh, 100dvh); min-height:0; overflow:hidden; }
  body.mode-tts .app{ height:100%; min-height:0; overflow:hidden; }
  body.mode-tts .app > main{ flex:1 1 auto; min-height:0; overflow:hidden; }
  body.mode-tts .main{ height:100%; min-height:0; overflow:hidden; }
  body.mode-tts .editor{ flex:1 1 auto; min-height:0; overflow:hidden; }
  body.mode-tts .editor-scroll{ overflow-y:auto; overflow-x:hidden; overscroll-behavior:contain; -webkit-overflow-scrolling:touch;
    padding-bottom:calc(16px + env(safe-area-inset-bottom, 0px) + var(--gen-strip-h, 0px)); }
  body.mode-tts .editor-footer{ position:relative; flex:none; }
  body.mode-tts.has-playbar{ padding-bottom:0; }
  body.mode-tts.has-playbar .editor-scroll{ padding-bottom:calc(170px + env(safe-area-inset-bottom, 0px) + var(--gen-strip-h, 0px)); }

  /* Fixed chrome must not sit under the keyboard. The toast needs no rule here:
     the mobile block above already docks it under the topbar, which is above the
     keyboard by definition. */
  .ov-chat-bubble{ transition:opacity .18s ease, transform .16s ease, box-shadow .16s ease; }
  body.kb-open .ov-chat-bubble{ opacity:0; pointer-events:none; transform:translateY(10px) scale(.92); }

  /* Top bar tightening. */
  .topbar{ position:sticky; top:0; z-index:60; padding:10px 12px; background:var(--surface);
    border-bottom:1px solid var(--border); }
  .topbar-right{ gap:6px; }
  .topbar-title{ font-size:15px; }
  .notif-panel{ position:fixed; top:56px; right:8px; left:8px; width:auto; max-width:none; }
  .pill-btn{ padding:0 12px; }
  .topbar-left{ gap:4px; }
  .lang-picker-btn{ padding:0 4px 0 8px; }
  .lang-picker-btn span{ max-width:92px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

  /* Editor footer wraps so the button never gets clipped. */
  .editor-footer{ gap:10px; padding:12px 16px 16px; }
  .editor-footer .el-bottom{ width:100%; order:2; }
  .credits{ order:2; }
  .el-top{ order:1; width:100%; justify-content:space-between; gap:12px; }
  .generate-btn{ flex:1; justify-content:center; }

  /* Editor text a touch smaller; selectors comfortable for touch. */
  .speaker-text,.ph-overlay{ font-size:16px; }
  .selector,.dropdown{ height:56px; }

  /* Add speaker / Import script / Subtitles to speech: two compact rows
     instead of a clipped horizontal scroller. The third action spans full
     width so no label is hidden at the edge. */
  .add-speaker-row{ flex-wrap:wrap; gap:8px; margin:10px 0 16px; width:100%; min-width:0; max-width:100%; }
  .add-speaker-row .ln{ display:none; }
  .add-speaker{ padding:0 14px; font-size:13px; flex:1 1 0; min-width:0; justify-content:center; }
  #importSrt{ flex-basis:100%; }

  /* Playbar fixed to the bottom so controls are always visible after generating.
     Three clear rows: who/transport, progress bar, then a full-width Download. The
     ±10s skip buttons add clutter for short clips (the bar is drag-to-seek),
     so they're hidden on phones. `display:contents` lets .pb-right's children
     join the grid so Download can span full width. */
  .playbar{ position:fixed; left:0; right:0; bottom:0; z-index:70; padding:12px 14px 14px;
    column-gap:12px; row-gap:11px;
    grid-template-columns:auto 1fr auto;
    /* The effect trigger shares the Download row: the transport row keeps its
       `left center close` shape (the play button stays optically centred), and
       the picker sits at the left of the payoff row, which it no longer pushes
       down a band of its own. */
    grid-template-areas:"left center close" "prog prog prog" "fx dl dl";
    border-radius:16px 16px 0 0; border-top:1px solid var(--border);
    background:var(--glass-bg-strong); backdrop-filter:saturate(1.4) blur(12px); -webkit-backdrop-filter:saturate(1.4) blur(12px);
    box-shadow:var(--e-3); }
  .pb-center{ gap:14px; }
  .pb-center .skip-btn{ display:none; }         /* declutter: bar is drag-to-seek */
  .play-main{ width:48px; height:48px; }
  .pb-progress{ padding:0; }
  .pb-right{ display:contents; }
  #pbClose{ grid-area:close; }
  .pb-dl{ grid-area:dl; width:100%; height:50px; font-size:16px; }
  /* Taller to fit the extra band, or the buttons sit under the fixed bar. */
  body.has-playbar{ padding-bottom:244px; }     /* room so content isn't hidden behind the playbar */

  /* Live-chat bubble must clear the playbar (it was landing right on the
     controls). These are the same custom props Crisp's own offset uses. */
  body.has-playbar .crisp-client{ --crisp-customization-mobile-button-vertical:258px!important; }

  /* Modal full-width-ish. */
  .modal{ padding:28px 22px 22px; }
}

/* Tighter pass for narrow phones (Text to Speech screen). */
@media (max-width:560px){
  .trust-strip{ margin:12px 0 0; }
  .st-tab{ font-size:15px; }
  .selector,.dropdown{ height:48px; }
  .add-speaker-row{ gap:10px 10px; }
  .add-speaker{ height:36px; background:var(--surface); }
  .el-top{ flex-direction:column; align-items:center; gap:10px; }
  .generate-btn{ height:36px; min-height:36px; padding:0 18px; width:100%; justify-content:center; }
  .editor-footer .credits{ display:none; }
  .pb-dl{ height:36px; font-size:14px; }
  .play-main{ width:40px; height:40px; }
  .hist-item{ padding:8px; gap:10px; }
  /* Al Video Translator compact sizing at narrow widths. */
  #dubRunBtn,
  #dubResultsList .tr-btn,
  #dubRenderBtn{ height:36px; }
  #dubStatus{ height:auto; }
  .tr-seg-actions > button{ height:32px; }
  #dubResultsList .tr-dl{ height:32px; }
  .hist-clear{ padding:0 12px; }
  .hist-dl-all{ padding:0 12px; }
  .blk-tools{ opacity:1; transform:none; pointer-events:auto; margin-top:10px; }
  .char-count .cc-num{ font-size:15px; }
  .char-count .cc-label{ font-size:13px; }
  /* Bigger slider thumb on narrow phones (the 12px desktop thumb is fiddly to
     hit). The input box stays 44px tall from the base rule. */
  .slider::-webkit-slider-thumb{ width:18px; height:18px; margin-top:-8px; }
  .slider::-moz-range-thumb{ width:18px; height:18px; }
}

@media (max-width:420px){
  .speaker-text,.ph-overlay{ font-size:16px; }
  .pb-left .nm{ max-width:120px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .skip-btn{ width:34px; height:34px; }
}

/* ---------- HOMEPAGE MARKETING (scrollable content below the studio) ---------- */
/* NOTE: the home marketing block used to render below the studio here. It now
   lives on the lightweight home hub page (/{locale}/) — see
   frontend/assets/site.css + the buildHome* blocks in generate.mjs. The studio
   is a pure tool now, so those rules were removed from this stylesheet. */

/* ---------- VOICE LIBRARY (full-page browse view) ---------- */
/* min-height:0 lets this grid item bound to the viewport row so .lib-scroll can
   actually scroll (without it the row grows to content height and .layout's
   overflow:hidden clips it). grid-column spans the full width once the editor +
   settings panels are hidden in library mode. */
.library{ flex:1; min-width:0; min-height:0; display:none; flex-direction:column; grid-column:1 / -1; }
.lib-scroll{ flex:1; min-height:0; overflow-y:auto; padding:28px 40px 48px; }
.lib-inner{ max-width:1080px; margin:0 auto; }
.lib-head{ margin-bottom:22px; }
.lib-title{ font-size:24px; font-weight:800; letter-spacing:-.02em; }
.lib-sub{ font-size:14px; color:var(--text-2); margin-top:6px; max-width:640px; line-height:1.5; }

/* DEV (placeholder screen) */
.dev-view{ flex:1; min-width:0; min-height:0; display:none; flex-direction:column; grid-column:1 / -1; }
.devv-tabs{ flex:none; display:flex; align-items:stretch; gap:2px; padding:0 18px;
  background:var(--canvas); border-bottom:1px solid var(--border-2); overflow-x:auto; scrollbar-width:none; }
.devv-tabs::-webkit-scrollbar{ display:none; }
.devv-tab{ position:relative; display:inline-flex; align-items:center; gap:9px; padding:15px 14px 14px;
  border:none; background:none; font:inherit; font-size:14px; font-weight:600; color:#64748b;
  white-space:nowrap; cursor:pointer; transition:color .15s; }
.devv-tab svg{ flex:none; color:#94a3b8; transition:color .15s; }
.devv-tab:hover{ color:#0f172a; }
.devv-tab:hover svg{ color:#64748b; }
.devv-tab.active{ color:#0f172a; font-weight:700; }
.devv-tab.active svg{ color:#fe655d; }
.devv-tab.active::after{ content:""; position:absolute; left:0; right:0; bottom:-1px; height:3px;
  background:#fe655d; border-radius:8px 8px 0 0; }
.devv-tab:focus-visible{ outline:2px solid var(--focus); outline-offset:-3px; border-radius:8px; }
/* Block flow (not flex) so .devv-inner — which carries only height:fit-content
   — still spans the full scroll width instead of shrink-wrapping to its
   content; that is what makes the skeleton/console wrap div full width. */
.devv-scroll{ flex:1; min-height:0; overflow-y:auto; padding:28px 40px 48px; }
.devv-inner{ height:fit-content; }
.devv-pane{ display:flex; flex:1; align-items:center; justify-content:center; }
.devv-pane[hidden]{ display:none; }
/* The console content host (#devOverview) is a flex item of the centred pane;
   full width so the Overview content and its loading skeleton span the pane
   instead of shrink-wrapping to their intrinsic width. */
.devv-console{ width:100%; }
.devv-card{ width:100%; max-width:560px; text-align:center; padding:48px 36px;
  border:1px dashed var(--border-2); border-radius:16px; background:var(--surface); }
.devv-ico{ display:inline-flex; align-items:center; justify-content:center; width:54px; height:54px;
  border-radius:16px; background:var(--surface-hover); color:var(--text-2); margin-bottom:16px; }
.devv-title{ font-size:32px; font-weight:800; letter-spacing:-.02em; }
.devv-sub{ font-size:15px; color:var(--text-2); margin:10px auto 0; max-width:420px; line-height:1.55; }
@media (max-width:820px){
  .devv-tabs{ padding:0 8px; }
  .devv-tab{ padding:13px 10px 12px; }
  .devv-scroll{ padding:20px 16px 32px; }
}

/* HOME (launcher landing screen) */
.home-view{ flex:1; min-width:0; min-height:0; display:none; flex-direction:column; grid-column:1 / -1; }
.hview-scroll{ flex:1; min-height:0; overflow-y:auto; padding:28px 40px 48px; display:flex; }
.hview-inner{ max-width:1080px; margin:0 auto; width:min(1030px, calc(100% - 48px)); display:flex; flex-direction:column; height:fit-content; }

.hview-hero{ padding:18px 24px; border:1px solid var(--border-2); border-radius:16px;
  margin-bottom:16px; }
.hview-badge{ display:inline-flex; align-items:center; font-size:12px; font-weight:700;
  letter-spacing:.05em; text-transform:uppercase; color:#fe655d; background:var(--surface); border:1px solid #fadcd9;
  padding:4px 10px; border-radius:999px; margin-bottom:14px; }
.hview-title{ font-size:32px; font-weight:800; letter-spacing:-.02em; line-height:1.12; }
.hview-sub{ font-size:15px; color:var(--text-2); margin-top:10px; max-width:640px; line-height:1.55; }
.hview-hero-actions{ display:flex; align-items:center; gap:12px; flex-wrap:wrap; margin-top:22px; }
.hview-cta{ display:inline-flex; align-items:center; gap:9px; padding:12px 22px;
  border-radius:12px; background:#fe655d; color:#fff; font-size:15px; font-weight:600; text-decoration:none;
  transition:filter .15s, transform .15s; }
#hviewHero .hview-cta{ background:linear-gradient(135deg, #ff9e99, #fe655d 55%, #fc3e57); }
.hview-cta:hover{ filter:brightness(1.06); transform:translateY(-1px); }
.hview-cta-ghost{ display:inline-flex; align-items:center; gap:9px; padding:12px 22px;
  border-radius:12px; background:var(--surface); border:1px solid var(--border-2); color:var(--text-2);
  font-size:15px; font-weight:600; text-decoration:none; transition:box-shadow .15s, transform .15s, color .15s; }
.hview-cta-ghost:hover{ color:var(--text); box-shadow:var(--e-2); transform:translateY(-1px); }

.hview-section{ margin-bottom:30px; }
.hview-section-title{ font-size:20px; font-weight:700; letter-spacing:-.01em; }
.hview-section-sub{ font-size:14px; color:var(--text-2); margin-top:4px; }
.hview-section-head{ display:flex; align-items:flex-end; justify-content:space-between; gap:12px; }
.hview-workflows{ display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:16px; margin-top:18px; }
.hview-workflow{ display:flex; flex-direction:column; gap:10px; padding:22px 20px 18px; border:1px solid var(--border-2);
  border-radius:16px; background:var(--surface); transition:box-shadow .15s ease, transform .15s ease, border-color .15s ease; }
.hview-workflow:hover{ transform:translateY(-2px); box-shadow:var(--e-3); border-color:#e0dbef; }
.hw-ic{ display:grid; place-items:center; width:52px; height:52px; border-radius:16px; }
.hw-ic-text{ background:var(--warn-tint); color:#fe655d; }
.hw-ic-media{ background:var(--info-soft); color:#3b82f6; }
.hw-ic-voice{ background:var(--violet-soft); color:#8b5cf6; }
.hw-ic-subtitles{ background:var(--success-tint); color:#10b981; }
.hw-ic-library{ background:var(--warn-tint); color:#f59e0b; }
.hw-ic-myvoices{ background:var(--info-soft); color:#6366f1; }
.hw-title{ font-size:16px; font-weight:750; letter-spacing:-.01em; }
.hw-desc{ font-size:13px; line-height:1.55; color:var(--text-2); margin-bottom:auto; }
.hw-actions{ display:flex; align-items:center; gap:14px; margin-top:auto; }
.hview-workflow-cta{ display:inline-flex; align-items:center; justify-content:center; gap:7px; padding:10px 14px; border-radius:12px;
  background:#111114; color:#fff; font-size:13px; font-weight:700; text-decoration:none; white-space:nowrap;
  transition:background .15s ease, transform .15s ease; }
.hview-workflow-cta:hover{ background:var(--black); transform:translateY(-1px); }
.hview-workflow-cta svg{ transition:transform .15s ease; }
.hview-workflow-cta:hover svg{ transform:translateX(3px); }
.hw-link{ display:inline-flex; align-items:center; justify-content:center; padding:10px 14px; border-radius:12px; background:var(--warn-tint); color:#fe655d; font-size:13px; font-weight:700; text-decoration:none; white-space:nowrap; transition:background .15s ease, transform .15s ease; }
.hw-link:hover{ background:#ffded6; color:#fe655d; text-decoration:none; transform:translateY(-1px); }


.hview-recent-list{ display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:12px; margin-top:16px; }
.hview-recent-item{ display:flex; align-items:center; gap:13px; width:100%; padding:14px 16px; border:1px solid var(--border-2);
  border-radius:16px; background:var(--surface); color:var(--text); cursor:pointer; font-family:inherit; text-align:left;
  transition:box-shadow .15s, transform .15s, border-color .15s; }
.hview-recent-item:hover{ transform:translateY(-1px); box-shadow:var(--e-2); border-color:#e0dbef; }
.hr-ic{ flex:none; display:grid; place-items:center; width:44px; height:44px; border-radius:12px; }
.hr-ic-tts{ background:var(--warn-tint); color:#fe655d; }
.hr-ic-dub{ background:var(--info-soft); color:#3b82f6; }
.hr-body{ flex:1; min-width:0; display:flex; flex-direction:column; gap:2px; }
.hr-kicker{ font-size:12px; font-weight:700; letter-spacing:.05em; text-transform:uppercase; color:var(--text-3); }
.hr-title{ font-size:14px; font-weight:650; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.hr-meta{ font-size:12px; color:var(--text-2); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.hr-arrow{ flex:none; color:var(--text-3); transition:transform .15s, color .15s; }
.hview-recent-item:hover .hr-arrow{ color:var(--text); transform:translateX(3px); }
.hview-recent-empty{ grid-column:1/-1; display:flex; align-items:center; justify-content:space-between; gap:16px; padding:18px 20px;
  border:1px dashed var(--border-2); border-radius:16px; background:var(--surface); color:var(--text-2); font-size:13px; line-height:1.45; }
.hview-recent-start{ flex:none; padding:9px 16px; border:none; border-radius:12px; background:#111114; color:#fff;
  font-family:inherit; font-size:13px; font-weight:700; cursor:pointer; }
.hview-recent-start:hover{ background:var(--black); }
.hview-recent-skel{ display:flex; align-items:center; gap:13px; padding:14px 16px; border:1px solid var(--border-2);
  border-radius:16px; background:var(--surface); }
.hview-recent-skel .sks-ic{ flex:none; width:44px; height:44px; border-radius:12px; background:var(--hover); }
.hview-recent-skel .sks-lines{ flex:1; min-width:0; display:flex; flex-direction:column; gap:7px; }
.hview-recent-skel .sks-bar{ height:10px; border-radius:999px; background:var(--hover); }
.hview-recent-skel .sks-bar.short{ width:55%; }

.hview-grid{ display:grid; grid-template-columns:repeat(auto-fill,minmax(240px,1fr));
  gap:16px; margin-top:18px; }
.hview-cell{ display:flex; flex-direction:column; gap:8px; padding:22px 20px 18px 20px; border:1px solid var(--border-2);
  border-radius:16px; background:var(--surface); color:var(--text); text-decoration:none; cursor:pointer;
  transition:box-shadow .15s, transform .15s, border-color .15s; position:relative; }
.hview-cell:hover{ transform:translateY(-2px); box-shadow:var(--e-3); border-color:#e0dbef; }
.hview-cell:hover .hv-action{ color:var(--text); }
.hview-cell:hover .hv-action svg{ color:var(--text); transform:translateX(3px); }
/* Icon tile in the card's top-left. Each engine gets its own soft tint. */
.hv-ic{ flex:none; display:flex; align-items:center; justify-content:center; width:52px; height:52px;
  border-radius:12px; margin-bottom:12px; }
.hv-ic-tts{ background:var(--warn-tint); color:#fe655d; }
.hv-ic-video{ background:var(--info-soft); color:#3b82f6; }
.hv-ic-audio{ background:var(--violet-soft); color:#8b5cf6; }
.hv-ic-srt{ background:var(--success-tint); color:#14b8a6; }
.hv-t{ font-size:16px; font-weight:700; letter-spacing:-.01em; }
/* Clamp the description to exactly two lines with an ellipsis. */
.hv-d{ font-size:13px; color:var(--text-2); line-height:1.5;
  display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2; line-clamp:2;
  overflow:hidden; }
/* Footer action row, separated by a hairline divider. */
.hv-action{ display:flex; align-items:center; justify-content:space-between; margin-top:14px; padding-top:14px;
  border-top:1px solid var(--border-2); font-size:13px; font-weight:600; color:var(--text-3); transition:color .15s; }
.hv-action svg{ transition:transform .15s; }

.hview-voices-head{ display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:6px; }
.hview-voices-head .hview-section-title{ margin-bottom:0; }
.hview-browse{ font-size:13px; font-weight:600; color:#fe655d; text-decoration:none; white-space:nowrap; }
.hview-browse:hover{ text-decoration:underline; }
.hview-voices-sub{ font-size:13px; color:var(--text-2); margin:0 0 14px; }
.hview-voices-list{ display:grid; grid-template-columns:repeat(4,1fr); gap:14px; }
/* Error + retry styling for .hview-voices-error is shared with .lib-error further
   down, so the two catalogue error states cannot drift apart. */

/* Promo cards -> Clone / Design */
.lib-promos{ display:grid; grid-template-columns:1fr 1fr; gap:16px; margin-bottom:28px; }
.lib-promo{ display:flex; align-items:center; gap:15px; text-align:left; cursor:pointer; font-family:inherit;
  border:1px solid var(--border-2); border-radius:16px; padding:15px 18px; background:none; color:var(--text);
  transition:box-shadow .15s, transform .15s, border-color .15s; }
.lib-promo:hover{ transform:translateY(-2px); box-shadow:var(--e-3); }
.lib-promo.clone{ border-color:#e7e3fb; }
.lib-promo.design{ border-color:#dcefe0; }
.lib-promo.clone:hover{ border-color:#cfc7f5; }
.lib-promo.design:hover{ border-color:#bfe4c8; }
.lp-ico{ width:46px; height:46px; border-radius:12px; flex:none; display:grid; place-items:center; color:#fff; }
.lib-promo.clone .lp-ico{ background:linear-gradient(135deg,#9296e8,#b39ddb); }
.lib-promo.design .lp-ico{ background:linear-gradient(135deg,#72c978,#a5d6a7); }
/* Icon shadow appears only on promo hover. */
.lib-promo.clone:hover .lp-ico{ box-shadow:var(--e-2); }
.lib-promo.design:hover .lp-ico{ box-shadow:var(--e-2); }
.lp-txt{ flex:1; min-width:0; display:flex; flex-direction:column; gap:4px; }
.lp-t{ font-size:14px; font-weight:600; letter-spacing:-.01em; }
.lp-d{ font-size:12px; color:var(--text-2); line-height:1.45; }
.lp-arrow{ flex:none; color:var(--text-3); display:grid; place-items:center; transform:rotate(-45deg); }

/* Tabs */
.lib-tabs{ display:flex; gap:24px; border-bottom:1px solid var(--border); margin-bottom:16px; }
.lib-tab{ border:none; background:none; cursor:pointer; font-family:inherit; font-size:14px; font-weight:600;
  color:var(--text-3); padding:0 0 12px; margin-bottom:-1px; border-bottom:2px solid transparent; }
.lib-tab:hover{ color:var(--text); }
.lib-tab.is-active{ color:var(--text); border-bottom-color:#fc3e57; }

/* Search + filters */
.lib-controls{ display:flex; flex-wrap:wrap; gap:10px; align-items:center; margin-bottom:12px; }
.lib-search{ position:relative; display:flex; align-items:center; flex:1 1 260px; min-width:220px; }
.lib-search .search-ico{ position:absolute; left:16px; color:var(--text-3); pointer-events:none; display: flex; align-items: center;}
.lib-search input{ flex:1; height:46px; border-radius:12px; border:1px solid var(--border-2); padding:0 16px 0 46px;
  font-size:15px; font-family:inherit; color:var(--text); outline:none; background:var(--surface); }
.lib-search input::placeholder{ color:var(--text-3); }
.lib-search input:focus{ border-color:#d1d5db; }
.lib-tool-btn{ display:inline-flex; align-items:center; gap:7px; height:46px; padding:0 16px; flex:none; cursor:pointer;
  border:1px solid var(--border-2); border-radius:12px; background:var(--surface); color:var(--text); font-family:inherit;
  font-size:14px; font-weight:600; transition:border-color .15s, background .15s; }
.lib-tool-btn:hover{ background:var(--hover); }
.lib-tool-btn.is-open{ background:color-mix(in srgb, #fe655d 12%, transparent); border-color:#fe655d; color:#fe655d; }
.lib-sort{ position:relative; display:flex; align-items:center; flex:none; }
.lib-sort > svg{ position:absolute; left:12px; color:var(--text-3); pointer-events:none; }
.lib-sort select{ height:46px; padding:0 34px 0 34px; border-radius:12px; border:1px solid var(--border-2); background:var(--surface);
  color:var(--text); font-family:inherit; font-size:14px; font-weight:600; cursor:pointer; outline:none;
  appearance:none; -webkit-appearance:none; -moz-appearance:none;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%239ca3af' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right 10px center; background-size:16px; }
.lib-sort select:hover{ background-color:var(--hover); }
.lib-filter-panel{ display:flex; flex-wrap:wrap; gap:8px; align-items:stretch; margin:0 0 14px; padding:12px; border:1px solid var(--border-2);
  border-radius:12px; background:var(--surface); }
.lib-filter-panel .vp-filter{ flex:1 1 160px; }
.lib-filter-reset{ flex:none; margin-left:auto; padding:0 14px; border-radius:8px;
  border:1px solid var(--border-2); background:var(--surface); color:var(--text-2); font-size:13px;
  font-weight:600; cursor:pointer; font-family:inherit; transition:background .12s, color .12s; }
.lib-filter-reset:hover{ background:var(--hover); color:var(--text); }

.ov-field.ov-lib-field{ position:relative; display:flex; flex-direction:column; min-width:0; }
.lib-sort .ov-field.ov-lib-field{ flex:none; }
.lib-filter-panel .ov-field.ov-lib-field{ flex:1 1 160px; }
.ov-select.ov-lib-select{
  display:flex; align-items:center; justify-content:space-between; gap:8px;
  width:100%; height:46px; padding:0 12px 0 12px;
  font-family:inherit; font-size:14px; font-weight:600; color:var(--text);
  background:var(--surface); border:1px solid var(--border-2); border-radius:12px;
  cursor:pointer; text-align:left; outline:none;
  transition:border-color .15s, background .15s;
}
.lib-filter-panel .ov-select.ov-lib-select{ height:40px; border-radius:8px; font-size:14px; font-weight:500; }
.ov-select.ov-lib-select:hover{ background:var(--hover); }
.ov-select.ov-lib-select:focus-visible{ border-color:#fe655d; box-shadow:0 0 0 3px rgba(254,101,93,.13); }
.ov-select.ov-lib-select[aria-expanded="true"]{ border-color:#fe655d; }
.ov-select-value{ min-width:0; flex:1 1 auto; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.ov-select-caret{ display:inline-flex; flex:0 0 auto; color:var(--text-3); transition:transform .12s; }
.ov-select.ov-lib-select[aria-expanded="true"] .ov-select-caret{ transform:rotate(180deg); }
.ov-listbox.ov-lib-list{
  position:absolute; z-index:60; left:0; right:0; top:calc(100% + 4px);
  margin:0; padding:4px; list-style:none; max-height:264px; overflow-y:auto;
  background:var(--surface); border:1px solid var(--border-2); border-radius:12px;
  box-shadow:var(--e-3);
}
.ov-listbox[hidden]{ display:none; }
.ov-listbox.ov-lib-list [role="option"]{ padding:8px 10px; border-radius:8px; font-size:14px; cursor:pointer; }
.ov-listbox.ov-lib-list li + li{ margin-top:4px; }
.ov-listbox.ov-lib-list [role="option"]:hover,
.ov-listbox.ov-lib-list [role="option"].is-cursor{ background:var(--hover); }
.ov-listbox.ov-lib-list [role="option"][aria-selected="true"]{
  background:color-mix(in srgb, #fe655d 12%, transparent); color:#fe655d; font-weight:600;
}
@media (pointer: coarse){
  .ov-select.ov-lib-select,
  .ov-listbox.ov-lib-list [role="option"]{ min-height:44px; }
  .lib-filter-panel .ov-select.ov-lib-select{ min-height:44px; }
}
.lib-count{ font-size:13px; color:var(--text-3); margin:24px 2px 16px; }

/* "•••" dropdown menu */
.ov-menu{ position:fixed; z-index:3000; min-width:172px; background:var(--surface); border:1px solid var(--border-2);
  border-radius:12px; box-shadow:var(--e-3); padding:6px; animation:vppop .14s ease; }
.ov-menu-item{ display:flex; align-items:center; gap:9px; width:100%; text-align:left; border:none; background:none;
  cursor:pointer; font-family:inherit; font-size:14px; font-weight:600; color:var(--text); padding:9px 10px; border-radius:8px; }
.ov-menu-item:hover{ background:var(--hover); }
.ov-menu-item.danger{ color:#dc2626; }
.ov-menu-item.danger:hover{ background:var(--danger-soft); }
.ov-menu-item svg{ flex:none; width:16px; height:16px; }

.lib-grid{ display:block; }
.lib-empty{ text-align:center; color:var(--text-3); font-size:15px; padding:40px 12px;
  border:1px dashed var(--border-2); border-radius:16px; background:var(--surface); cursor:pointer; font-family:inherit; width:100%; }
.lib-empty:hover{ background:var(--hover); }
.lib-error, .hview-voices-error{ text-align:center; font-size:14px; font-weight:500; color:#e11d48; padding:24px 16px;
  background:rgba(254,101,93,0.08); border:1px solid rgba(254,101,93,0.3); border-radius:16px; }
.hview-voices-error{ grid-column:1 / -1; padding:20px 16px; border-radius:12px; }
/* Retry action inside the catalogue error states. A message that says "please
   try again" needs something to press — previously the only way out was a full
   page reload. */
.lib-error .vle-msg, .hview-voices-error .vle-msg{ margin:0 0 12px; }
.vle-retry{ display:inline-flex; align-items:center; justify-content:center; min-height:40px; padding:0 20px;
  border-radius:8px; border:1px solid rgba(225,29,72,.35); background:var(--surface); color:var(--danger);
  font-family:inherit; font-size:14px; font-weight:600; cursor:pointer;
  transition:background .15s, border-color .15s; }
.vle-retry:hover{ background:var(--danger-soft); border-color:#e11d48; }
.vle-retry:focus-visible{ outline:2px solid #e11d48; outline-offset:2px; }
.vle-retry[disabled]{ opacity:.6; cursor:default; }

/* Library pagination */
.lib-pagination{ padding:20px 0 8px; display:flex; justify-content:center; }

/* Library loading skeleton */
.lib-skel-count{ display:inline-block; width:100px; height:16px; border-radius:8px;
  background:linear-gradient(90deg,#f0f0f0 25%,#e8e8e8 50%,#f0f0f0 75%);
  background-size:200% 100%; animation:lib-shimmer 1.4s ease-in-out infinite; }
.lib-loading{ display:flex; flex-direction:column; gap:18px; }
.lib-skel-featured{ display:grid; grid-template-columns:repeat(4,1fr); gap:14px; }
.lib-skel-card{ aspect-ratio:4/3.15; border-radius:16px; background:linear-gradient(90deg,#f0f0f0 25%,#e8e8e8 50%,#f0f0f0 75%);
  background-size:200% 100%; animation:lib-shimmer 1.4s ease-in-out infinite; }
.lib-skel-bars{ display:grid; grid-template-columns:1fr 1fr; gap:12px; }
.lib-skel-row{ height:72px; border-radius:12px; background:linear-gradient(90deg,#f0f0f0 25%,#e8e8e8 50%,#f0f0f0 75%);
  background-size:200% 100%; animation:lib-shimmer 1.4s ease-in-out infinite; }
@keyframes lib-shimmer{ from{ background-position:200% 0; } to{ background-position:-200% 0; } }
@media(max-width:960px){ .lib-skel-featured{ grid-template-columns:1fr 1fr; } }
@media(max-width:520px){ .lib-skel-featured{ grid-template-columns:1fr; } .lib-skel-bars{ grid-template-columns:1fr; } }
.pagination{ display:flex; align-items:center; gap:4px; }
.pg-btn{ min-width:36px; height:36px; border:1px solid var(--border-2); border-radius:8px; background:var(--surface);
  color:var(--text); font-size:14px; font-weight:600; cursor:pointer; font-family:inherit;
  display:inline-flex; align-items:center; justify-content:center; transition:background .12s, border-color .12s; }
.pg-btn:hover:not(:disabled){ background:var(--hover); border-color:#ddd; color:var(--text); }
.pg-btn:disabled{ opacity:.35; cursor:default; }
.pg-btn.active{ background:var(--black); color:#fff; border-color:var(--black); }
.pg-prev,.pg-next{ font-size:20px; font-weight:400; }
.pg-dots{ width:28px; text-align:center; color:var(--text-3); font-size:16px; }
.pg-goto{ margin-left:auto; }
.pg-goto-select{ height:36px; border:1px solid var(--border-2); border-radius:8px; background:var(--surface); padding:0 10px;
  font-family:inherit; font-size:13px; font-weight:600; color:var(--text); cursor:pointer;
  -webkit-appearance:none; appearance:none; padding-right:24px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right 8px center; }
.pg-goto-select:focus{ outline:none; border-color:#fe655d; }

/* Featured cards: big portrait with overlaid name + tags (SAYVOICE-style) */
.lib-featured{ display:grid; grid-template-columns:repeat(4,1fr); gap:14px; margin-bottom:18px; }
.vfeat{ position:relative; aspect-ratio:4/3.15; border-radius:16px; overflow:hidden; cursor:pointer;
  background:var(--violet-soft) center/cover no-repeat; box-shadow:var(--e-2); }
.vfeat:focus-visible{ outline:2px solid var(--focus); outline-offset:2px; }
.vfeat.selected{ outline:2px solid #fc3e57; outline-offset:-2px; }
.vfeat.fav{ outline:2px solid #fc3e57; outline-offset:-2px; }
.vfeat.fav::before{ content:"\2605"; position:absolute; top:10px; left:10px; z-index:4;
  width:24px; height:24px; display:grid; place-items:center; border-radius:999px;
  background:rgba(252,62,87,.9); color:#fff; font-size:12px; line-height:1; }
.vfeat.playing{ overflow:visible; outline:none; box-shadow:none; }
.vfeat.playing::before{ content:""; position:absolute; inset:-3px; border-radius:16px; z-index:-1;
  background:conic-gradient(from var(--spin-angle,0deg), #fc3e57, transparent 40%, transparent 60%, #fc3e57);
  animation:poster-spin 1s linear infinite; }
.vf-video{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; z-index:0; opacity:0;
  transition:opacity .25s ease; pointer-events:none; }
.vfeat:hover .vf-video, .vfeat:focus-within .vf-video, .vfeat.playing .vf-video{ opacity:1; }
.vf-shade{ position:absolute; inset:0; z-index:1; border-radius:16px;
  background:linear-gradient(to top, rgba(10,8,25,.82) 0%, rgba(10,8,25,.30) 46%, rgba(10,8,25,0) 74%); }
.vfeat::after{ content:""; position:absolute; inset:0; z-index:1; border-radius:16px; background:rgba(10,8,25,.25);
  opacity:0; transition:opacity .2s ease; pointer-events:none; }
.vfeat:hover::after, .vfeat.playing::after{ opacity:1; }
.vf-top{ position:absolute; top:10px; right:10px; left:10px; z-index:3; display:flex; align-items:center;
  justify-content:flex-end; gap:8px; opacity:0; transform:translateY(-4px); transition:opacity .16s, transform .16s; }
.vfeat:hover .vf-top, .vfeat:focus-within .vf-top, .vfeat.playing .vf-top{ opacity:1; transform:none; }
.vf-use{ padding:8px 14px; border:none; border-radius:12px; cursor:pointer; font-family:inherit;
  font-size:12px; font-weight:500; line-height:1.4; color:#fff; background:linear-gradient(135deg,#fe655d,#fc3e57); }
.vf-use:hover{ filter:brightness(1.04); }
.vf-fav{ width:32px; height:32px; border:none; border-radius:8px; cursor:pointer; color:#fff; font-size:16px;
  background:rgba(255,255,255,.24); -webkit-backdrop-filter:blur(4px); backdrop-filter:blur(4px); display:grid; place-items:center; }
.vf-fav:hover{ background:rgba(255,255,255,.38); }
.vf-fav.on{ color:#f59e0b; }
.vf-copy{ width:32px; height:32px; border:none; border-radius:8px; cursor:pointer; color:#fff;
  background:rgba(255,255,255,.24); -webkit-backdrop-filter:blur(4px); backdrop-filter:blur(4px);
  display:grid; place-items:center; }
.vf-copy:hover{ background:rgba(255,255,255,.38); }
/* Star icons: show the line (outline) star by default, the filled star when
   favorited (.on). Both lucide SVGs live in the button; toggle by visibility. */
.vf-fav .vf-star-fill{ display:none; }
.vf-fav.on .vf-star-line{ display:none; }
.vf-fav.on .vf-star-fill{ display:block; fill:currentColor; }
.vf-menu{ width:32px; height:32px; border:none; border-radius:8px; cursor:pointer; color:#fff;
  background:rgba(255,255,255,.24); -webkit-backdrop-filter:blur(4px); backdrop-filter:blur(4px); display:grid; place-items:center; }
.vf-menu:hover{ background:rgba(255,255,255,.38); }
.vf-play{ position:absolute; top:44%; left:50%; z-index:3; transform:translate(-50%,-50%) scale(.85);
  width:36px; height:36px; border-radius:999px; background:var(--glass-bg-strong); color:#111; cursor:pointer;
  display:grid; place-items:center; opacity:0; transition:opacity .16s, transform .16s; box-shadow:var(--e-2); }
.vfeat:hover .vf-play, .vfeat.playing .vf-play{ opacity:1; transform:translate(-50%,-50%) scale(1); }
.vfeat.playing .vf-play{ background:var(--glass-bg-strong); color:#fc3e57; }
.vf-play svg{ width:16px; height:16px; }
/* History avatar play overlay: reuses .vf-play but must sit AFTER it to win the
   cascade (equal specificity), sizing to the 38px avatar and centering. */
.hist-av-play{ top:50%; border:none; width:calc(100% - 2px); height:calc(100% - 2px); padding:0; }
.hist-av-play svg{ width:16px; height:16px; }
.hist-item:hover .hist-av-play, .hist-item.playing .hist-av-play{ opacity:1; transform:translate(-50%,-50%) scale(1); }
.hist-item.playing .hist-av-play{ color:#fc3e57; }
.vf-info{ position:absolute; left:14px; right:14px; bottom:12px; z-index:2; color:#fff; }
.vf-name{ font-size:14px; font-weight:500; letter-spacing:-.01em; text-shadow:0 1px 6px rgba(0,0,0,.4); margin-bottom:6px; }
.vf-tags{ display:flex; flex-wrap:wrap; gap:4px; max-height:44px; overflow:hidden; }
.vf-tags .vt{ font-size:12px; font-weight:500; color:#fff; background:rgba(255,255,255,.22); border-radius:8px;
  padding:2px 7px; -webkit-backdrop-filter:blur(3px); backdrop-filter:blur(3px); }

/* Loading skeleton shaped like a real .vfeat card: shimmering poster with a
   faux name bar + tag chips pinned to the bottom-left. Uses the shared
   lib-shimmer animation. */
.vfeat-skel{ position:relative; aspect-ratio:4/3.15; border-radius:16px; overflow:hidden;
  background:linear-gradient(90deg,#f0f0f0 25%,#e8e8e8 50%,#f0f0f0 75%);
  background-size:200% 100%; animation:lib-shimmer 1.4s ease-in-out infinite; }
.vfeat-skel::after{ content:""; position:absolute; inset:0; border-radius:16px;
  background:linear-gradient(to top, rgba(10,8,25,.10) 0%, rgba(10,8,25,0) 55%); }
.vfeat-skel .vfs-info{ position:absolute; left:14px; right:14px; bottom:14px; z-index:1;
  display:flex; flex-direction:column; gap:8px; }
.vfeat-skel .vfs-bar{ height:14px; border-radius:8px; background:var(--glass-bg-strong); }
.vfeat-skel .vfs-name{ width:58%; }
.vfeat-skel .vfs-tags{ display:flex; gap:6px; }
.vfeat-skel .vfs-tag{ height:16px; width:44px; border-radius:8px; background:rgba(255,255,255,.5); }
.vfeat-skel .vfs-tag.w2{ width:60px; }
@media(max-width:520px){ .vfeat-skel{ aspect-ratio:16/8; } }

/* Compact bar rows: small poster + name + inline tags + Use */
.lib-bars{ display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:10px 14px; }
.lib-bars .mv-item{ min-width:0; }
#myVoicesPageList .mv-item,
#myVoicesList .mv-item{ min-width:0; }
.lib-group-head{ grid-column:1/-1; font-size:13px; font-weight:800; letter-spacing:.04em; text-transform:uppercase;
  color:var(--text-3); margin:14px 2px 2px; padding-bottom:8px; border-bottom:1px solid var(--border); }
.lib-group-head:first-child{ margin-top:0; }
.vbar{ display:flex; align-items:center; gap:12px; padding:10px; border:1px solid var(--border-2); border-radius:12px;
  cursor:pointer; background:var(--surface); transition:border-color .15s, box-shadow .15s; }
.vbar:hover{ border-color:#f3d4e0; box-shadow:var(--e-2); }
.vbar.selected{ border-color:#fc3e57; box-shadow:0 0 0 1px rgba(252,62,87,.3); }
.vbar.fav{ background:var(--coral-tint-2); border-color:#ffd7d3; }
.vb-poster-wrap{ position:relative; flex:none; padding:3px; border-radius:12px; }
.vbar.playing .vb-poster-wrap{
  background:conic-gradient(from var(--spin-angle,0deg), #fc3e57, transparent 40%, transparent 60%, #fc3e57);
  animation:poster-spin 1s linear infinite; }
@property --spin-angle{ syntax:"<angle>"; initial-value:0deg; inherits:false; }
@keyframes poster-spin{ to{ --spin-angle:360deg; } }
.vbar.playing .vb-poster{ border-radius:12px; }
.vb-fav-star{ position:absolute; top:-4px; right:-4px; width:18px; height:18px;
  display:grid; place-items:center; border-radius:999px; background:#fc3e57;
  color:#fff; font-size:12px; line-height:1; z-index:2; }
.vbar:focus-visible{ outline:2px solid var(--focus); outline-offset:2px; }
.vb-poster{ position:relative; width:52px; height:52px; border-radius:12px; flex:none; overflow:hidden; background:var(--violet-soft); }
.vb-play{ position:absolute; inset:0; display:grid; place-items:center; color:#fff; background:rgba(10,8,25,.30);
  opacity:1; transition:opacity .15s, background .15s; cursor:pointer; }
.vbar.playing .vb-play{ color:#fff; background:rgba(254,101,93,.85); }
.vbar.playing .vb-play svg{ filter:none; position:relative; z-index:2; }
.vb-play svg{ filter:drop-shadow(0 1px 2px rgba(0,0,0,.4)); }
.vbar:hover .vb-play{ background:rgba(10,8,25,.45); }
.vb-info{ flex:1; min-width:0; }
.vb-name{ font-size:14px; font-weight:700; letter-spacing:-.01em; white-space:nowrap; overflow:hidden;
  text-overflow:ellipsis; display:flex; align-items:center; gap:5px; }
.vb-tags{ display:flex; gap:4px; margin-top:5px; overflow:hidden; flex-wrap:nowrap;
  -webkit-mask-image:linear-gradient(90deg,#000 84%,transparent); mask-image:linear-gradient(90deg,#000 84%,transparent); }
.vb-tags .vt{ font-size:12px; font-weight:600; color:var(--text-2); background:var(--violet-soft); border-radius:8px;
  padding:2px 7px; white-space:nowrap; flex:none; }
.vb-tools{ display:flex; align-items:center; gap:6px; flex:none; }
.vb-menu{ width:30px; height:30px; border:none; background:none; border-radius:8px; cursor:pointer; color:var(--text-3);
  display:grid; place-items:center; }
.vb-menu:hover{ background:var(--hover); color:var(--text); }
/* Dark "Use" button (SAYVOICE sayvoice-dark-btn), revealed on hover/focus. */
.vb-use{ height:32px; padding:0 16px; border:none; background:#111114; border-radius:8px; cursor:pointer;
  font-family:inherit; font-size:13px; font-weight:700; color:#fff; opacity:0; transform:translateX(4px);
  transition:opacity .15s, transform .15s, background .15s; }
.vb-use:hover{ background:var(--black); }
.vbar:hover .vb-use, .vbar:focus-within .vb-use{ opacity:1; transform:none; }
@media (hover:none){ .vb-use{ opacity:1; transform:none; } }

@media (max-width:960px){
  .lib-featured{ grid-template-columns:1fr 1fr; }
  .hview-voices-list{ grid-template-columns:1fr 1fr; }
  .lib-bars{ grid-template-columns:1fr; }
}
@media (max-width:900px){
  .lib-scroll{ padding:22px 18px 50px; }
  .lib-promos{ grid-template-columns:1fr; }
  .lib-title{ font-size:24px; }
  .hview-scroll{ padding:22px 18px 50px; }
  .hview-title{ font-size:24px; }
  .hview-recent-list{ grid-template-columns:1fr; }
  .hview-workflows{ grid-template-columns:1fr; }
  .hview-hero{ padding:20px; }
  .hview-sub{ font-size:14px; }
  .hview-banner{ border-radius:12px; }
  .hview-banner-nav{ width:32px; height:32px; }
}
@media (max-width:560px){
  .lib-featured{ grid-template-columns:1fr; }
  .lib-promo{ padding:10px 12px; gap:10px; }
  .lp-ico{ width:36px; height:36px; }
  .lib-search input{ height:36px; }
  .lib-tool-btn{ height:36px; }
  .lib-sort select{ height:36px; }
  .vfeat{ aspect-ratio:16/8; }
  .hview-recent-empty{ flex-wrap:wrap; }
  .hview-voices-list{ grid-template-columns:1fr; }
  .lib-bars .mv-item,
  #myVoicesPageList .mv-item,
  #myVoicesList .mv-item{ flex-wrap:wrap; }
  .lib-bars .mv-item .mv-info,
  #myVoicesPageList .mv-item .mv-info,
  #myVoicesList .mv-item .mv-info{ min-width:0; }
  .lib-bars .mv-btn-group,
  #myVoicesPageList .mv-btn-group,
  #myVoicesList .mv-btn-group{ width:100%; justify-content:flex-end; }
  #myVoicesPageList .mv-btn.mv-edit,
  #myVoicesPageList .mv-btn.mv-del,
  #myVoicesPageList .mv-btn.mv-fav-toggle{ width:36px; height:36px; font-size:16px; }
  .pg-btn{ min-width:32px; height:32px; font-size:13px; }
  .pg-goto{ display:none; }
}
@media (max-width:380px){
  .lib-bars .mv-item,
  #myVoicesPageList .mv-item,
  #myVoicesList .mv-item{ gap:8px; padding:8px 10px; }
  .lib-bars .mv-btn,
  #myVoicesPageList .mv-btn,
  #myVoicesList .mv-btn{ width:30px; height:30px; }
  .lib-bars .mv-btn.mv-use,
  #myVoicesPageList .mv-btn.mv-use,
  #myVoicesList .mv-btn.mv-use{ width:auto; height:30px; }
  .lib-bars .mv-btn.mv-remove,
  #myVoicesPageList .mv-btn.mv-remove,
  #myVoicesList .mv-btn.mv-remove{ padding:0 10px; height:28px; font-size:12px; }
  .lib-bars .mv-btn-group,
  #myVoicesPageList .mv-btn-group,
  #myVoicesList .mv-btn-group{ gap:2px; }
  .pg-btn{ min-width:30px; height:30px; font-size:12px; border-radius:8px; }
  .pg-dots{ width:20px; font-size:14px; }
  .pg-goto-select{ height:30px; font-size:12px; padding:0 8px; padding-right:20px; }
}

/* ---------- VOICE CLONE: create-a-voice card ---------- */
.clone-hero{ display:flex; align-items:center; gap:14px; margin-bottom:18px; }
.clone-face{ width:48px; height:48px; border-radius:999px; flex:none;
  background:linear-gradient(135deg,#ff9e99,#fe655d 55%,#fc3e57);
  box-shadow:var(--e-2); }
.clone-title{ font-size:16px; font-weight:700; letter-spacing:-.01em; color:var(--text); }
.clone-sub{ font-size:13px; line-height:1.5; color:var(--text-2); margin-top:3px; }

.clone-tabs{ display:flex; gap:6px; background:var(--brand-coral-soft); border-radius:12px; padding:4px; margin-bottom:16px; }
.clone-tab{ flex:1; height:38px; border:none; border-radius:8px; background:none; cursor:pointer;
  font-family:inherit; font-size:14px; font-weight:600; color:var(--text-2); transition:background .15s,color .15s; }
.clone-tab:hover{ color:var(--text); }
.clone-tab.active{ background:var(--surface); color:var(--text); box-shadow:var(--e-1); }

.clone-pane{ margin-bottom:6px; }

/* Record pane */
.rec-guide{ border:1px dashed #e2d8f6; background:var(--brand-coral-soft); border-radius:12px; padding:14px 16px; margin-bottom:16px; }
.rec-guide-label{ font-size:12px; font-weight:700; text-transform:uppercase; letter-spacing:.04em; color:#9333ea; }
.rec-guide-text{ font-size:16px; line-height:1.5; color:var(--text); margin-top:6px; }
.rec-controls{ display:flex; align-items:center; gap:16px; }
.rec-btn{ display:inline-flex; align-items:center; gap:10px; height:40px; padding:0 18px; border-radius:8px;
  border:none; cursor:pointer; font-family:inherit; font-size:14px; font-weight:600; color:#fff;
  background:#fe655d; transition:background .15s; }
.rec-btn:hover{ background:#fc3e57; }
.rec-btn .rec-dot{ width:11px; height:11px; border-radius:999px; background:var(--surface); flex:none; }
.rec-btn.recording{ background:#e11d48; }
.rec-btn.recording:hover{ background:#be123c; }
.rec-btn.recording .rec-dot{ animation:rec-pulse 1s ease-in-out infinite; }
@keyframes rec-pulse{ 0%,100%{ opacity:1; transform:scale(1) } 50%{ opacity:.35; transform:scale(.8) } }
@media (prefers-reduced-motion:reduce){ .rec-btn.recording .rec-dot{ animation:none } }
.rec-timer{ font-size:16px; font-weight:700; font-variant-numeric:tabular-nums; color:var(--text-2); }
.rec-playback{ width:100%; margin-top:14px; }

/* Status / validation feedback (cute) */
.clone-status{ display:flex; align-items:center; gap:10px; border-radius:12px; padding:13px 15px;
  font-size:14px; line-height:1.5; height:66px; margin-top: 20px; }
.clone-status .cs-ico{ flex:none; font-size:16px; line-height:1.4; }
.clone-status.info{ background:var(--brand-coral-soft); color:var(--violet-ink); }
.clone-status.ok{ background:var(--success-tint); color:#0f7a52; }
.clone-status.warn{ background:var(--warn-tint); color:var(--warn-ink); }
.clone-status.err{ background:var(--danger-soft); color:var(--danger); }
.clone-status.busy{ background:var(--brand-coral-soft); color:var(--violet-ink); }
.clone-status .cs-spin{ width:17px; height:17px; border-radius:999px; flex:none; margin-top:1px;
  border:2px solid #d9ccf3; border-top-color:#9333ea; animation:spin 1s linear infinite; }

.clone-save-btn{ width:100%; height:50px; margin-top:16px; border:none; border-radius:12px; cursor:pointer;
  font-family:inherit; font-size:15px; font-weight:700; color:#fff;
  background:linear-gradient(135deg,#ff9e99,#fe655d 55%,#fc3e57);
  box-shadow:var(--e-2); transition:transform .15s, box-shadow .15s, opacity .15s; }
.clone-save-btn:hover{ transform:translateY(-1px); box-shadow:var(--e-3); }
.clone-save-btn:disabled{ opacity:.55; cursor:default; transform:none; box-shadow:none; }
.clone-hint{ font-size:12px; line-height:1.5; color:var(--text-3); text-align:center; margin-top:12px; }

/* Clone/Design own the full editor width when Settings is hidden. */
.vc, .vd{ }
.myvoices{ }

/* ============ VOICE CLONE (Add Voice → Preview → Save) ==================
   One job per screen. Step 1 is a drop zone that becomes the loaded clip and
   then the transcript check; step 2 is the listen preview; step 3 is the form.
   Surfaces stay neutral (white card, #f7f7f8 insets) so the brand coral is
   only ever marking the one thing to do next: the active step, the primary
   button, the "best" band on the clip-length scale, and focus rings. */
.vc{ border:1px solid var(--border-2); border-radius:16px; background:var(--surface); padding:22px 24px 24px; margin-top:6px; }

/* --- Header --- */
.vc-head{ margin-bottom:18px; }
.vc-title{ font-size:20px; font-weight:800; letter-spacing:-.02em; color:var(--text); }
.vc-subtitle{ font-size:13px; line-height:1.55; color:var(--text-2); margin-top:6px; max-width:620px; }

/* --- Stepper: icon discs on a connector rail --- */
.vc-steps{ display:flex; align-items:flex-start; list-style:none; margin:6px 0 26px; padding:0; }
.vc-step{ display:flex; flex-direction:column; align-items:center; gap:8px; flex:none; width:96px; }
.vc-step-ico{ position:relative; width:42px; height:42px; border-radius:999px; flex:none;
  display:flex; align-items:center; justify-content:center; color:var(--text-3);
  background:var(--surface-hover); border:1px solid var(--border-2); transition:background .2s, color .2s, border-color .2s, box-shadow .2s; }
.vc-step-ico svg{ position:absolute; transition:opacity .18s, transform .18s; }
.vc-ico-check{ opacity:0; transform:scale(.5); }
.vc-step-title{ font-size:13px; font-weight:600; color:var(--text-3); transition:color .2s; white-space:nowrap; }
.vc-step.is-active .vc-step-ico{ background:var(--black); border-color:#111; color:#fff;
  box-shadow:0 0 0 4px rgba(254,101,93,.16); }
.vc-step.is-active .vc-step-title{ color:var(--text); font-weight:700; }
.vc-step.is-done .vc-step-ico{ background:var(--warn-tint); border-color:#ffd0ca; color:var(--accent-text); }
.vc-step.is-done .vc-ico-step{ opacity:0; transform:scale(.5); }
.vc-step.is-done .vc-ico-check{ opacity:1; transform:scale(1); }
.vc-step.is-done .vc-step-title{ color:var(--text-2); }
.vc-step-line{ flex:1; height:2px; background:var(--border-2); border-radius:8px; margin-top:21px; min-width:20px; }
.vc-step-line.is-done{ background:#ffd0ca; }

.vc-pane-title{ font-size:16px; font-weight:700; letter-spacing:-.01em; color:var(--text); margin:0 0 6px; text-align:center; }
.vc-hint-line{ font-size:13px; line-height:1.55; color:var(--text-3); margin:0 0 16px; }
.vc-pane-title + .vc-hint-line{ text-align:center; }

/* --- Step 1a: drop zone --- */
.vc-pick{ display:block; }
.vc-drop{ display:flex; flex-direction:column; align-items:center; gap:2px; text-align:center;
  border:1.5px dashed #d6d3dd; border-radius:16px; background:var(--canvas); padding:34px 24px 28px; cursor:pointer;
  transition:border-color .15s, background .15s; }
.vc-drop:hover{ border-color:#fe655d; background:var(--coral-tint-2); }
.vc-drop:focus-visible{ outline:2px solid var(--focus); outline-offset:3px; }
.vc-drop.drag{ border-color:#fe655d; background:var(--brand-coral-soft); }
/* The two drop-zone cards are the icon artwork itself (see the sprite): a white
   rounded square with the glyph inside, so the shadow has to come from the
   filter here rather than from a CSS background on a wrapper. */
.vc-drop-art{ position:relative; display:block; width:94px; height:56px; margin-bottom:16px; }
.vc-drop-card{ position:absolute; filter:drop-shadow(0 3px 9px rgba(20,16,50,.10)); }
.vc-drop-card-file{ left:0; top:0; }
.vc-drop-card-mic{ right:0; top:9px; }
.vc-drop-title{ font-size:16px; font-weight:700; letter-spacing:-.01em; color:var(--text); }
.vc-drop-hint{ font-size:13px; color:var(--text-3); margin-top:3px; }
.vc-formats{ display:flex; flex-wrap:wrap; align-items:center; justify-content:center; gap:6px; margin-top:14px; }
.vc-fmt{ font-size:12px; font-weight:700; letter-spacing:.02em; color:var(--text-2); background:var(--surface);
  border:1px solid var(--border-2); border-radius:8px; padding:2px 7px; }
.vc-fmt-max{ font-size:12px; font-weight:600; color:var(--text-3); margin-left:2px; }
.vc-drop-actions{ display:flex; align-items:center; gap:10px; margin-top:18px; flex-wrap:wrap; justify-content:center; }

/* --- Step 1a: clip-length scale --- */
.vc-scale{ margin:26px 4px 0; }
/* Thin rail with a tick at each stop, like a ruler: the reference draws the
   whole rail as a rainbow, which carries no meaning, so here only the
   recommended 3s→7s band is coloured — the rest of the rail stays neutral to
   say "accepted, but not the sweet spot". */
.vc-scale-track{ position:relative; height:3px; border-radius:8px; background:var(--surface-hover); }
.vc-scale-zone{ position:absolute; left:0; top:0; bottom:0; width:45%; border-radius:8px;
  background:linear-gradient(90deg,#ffc0bb,#fe655d); }
.vc-scale-marks{ position:relative; height:40px; margin-top:12px; }
.vc-mark{ position:absolute; top:0; display:flex; flex-direction:column; align-items:center; gap:1px;
  transform:translateX(-50%); text-align:center; }
/* Ticks are children of the rail, not of the labels, so they straddle the line
   by construction: -4px puts the top 4px above the rail, the rail occupies the
   middle 3px, and the 11px height leaves 4px below. */
.vc-tick{ position:absolute; top:-4px; width:2px; height:11px; margin-left:-1px; border-radius:8px;
  background:#52525b; }
.vc-tick-best{ background:#e0463c; }
.vc-mark b{ font-size:12px; font-weight:700; color:var(--text); font-variant-numeric:tabular-nums; white-space:nowrap; }
.vc-mark i{ font-size:12px; font-style:normal; font-weight:600; color:var(--text-3); white-space:nowrap; }
.vc-mark:first-child{ left:0; transform:none; align-items:flex-start; }
.vc-mark-best{ left:45%; }
.vc-mark-best b{ color:var(--accent-text); }
.vc-mark-best i{ color:var(--accent-text); }
.vc-mark-max{ left:100%; transform:translateX(-100%); align-items:flex-end; }
.vc-data-note{ font-size:12px; line-height:1.55; color:var(--text-3); margin:20px 0 0; text-align:center; }
.vc-data-note a{ color:var(--accent-text); text-decoration:underline; white-space:nowrap; }
.vc-dot{ margin:0 6px; }

/* --- Step 1a': record panel --- */
.vc-record{ border:1px solid var(--border-2); border-radius:16px; background:var(--canvas); padding:22px 20px; text-align:center; }
.vc-rec-tip{ font-size:13px; color:var(--text-3); margin:0 0 10px; }
.vc-rec-text{ font-size:16px; line-height:1.55; font-weight:600; color:var(--text); margin:0 auto 16px; max-width:520px; }
.vc-rec-meter{ display:flex; align-items:center; justify-content:center; gap:14px; margin-bottom:18px;
  padding:14px 20px; border-radius:12px; background:var(--surface); border:1px solid var(--border-2); }
.vc-rec-timer{ font-size:14px; font-weight:700; font-variant-numeric:tabular-nums; color:var(--text);
  background:var(--surface); border:1px solid var(--border-2); border-radius:8px; padding:6px 12px; min-width:56px; text-align:center; }
.waveform-live{ height:52px; }
.waveform-live .wbar{ background:#fda4af; transition:height .15s ease; }
.waveform-live .wbar.on{ background:#fe655d; }
.vc-rec-controls{ display:flex; align-items:center; justify-content:center; gap:12px; flex-wrap:wrap; }
.vc-rec-change{ display:inline-flex; align-items:center; gap:7px; height:40px; padding:0 18px; border-radius:8px;
  border:1px solid var(--border-2); background:var(--surface); cursor:pointer; font-family:inherit; font-size:14px;
  font-weight:600; color:var(--text); transition:background .15s; }
.vc-rec-change:hover{ background:var(--hover); }
.vc-rec-rule,.vc-rule{ font-size:12px; color:var(--text-3); margin:16px 0 0; }
.vc-rec-subtitle{ font-size:13px; font-weight:700; color:var(--text); margin:18px 0 10px; }
.vc-tips{ list-style:none; display:flex; flex-wrap:wrap; justify-content:center; gap:10px 18px; margin:0; padding:0; }
.vc-tip{ font-size:13px; color:var(--text-2); }

/* --- Step 1b: the loaded clip --- */
.vc-ready{ border:1px solid var(--border-2); border-radius:16px; background:var(--canvas); padding:22px 20px 18px; }
.vc-ready-title{ font-size:16px; font-weight:700; letter-spacing:-.01em; color:var(--text); margin:0 0 4px; }
.vc-file{ display:flex; align-items:center; gap:10px; margin:14px 0 12px; padding:10px 14px;
  background:var(--surface); border:1px solid var(--border-2); border-radius:12px; }
.vc-file-ico{ display:flex; align-items:center; justify-content:center; width:34px; height:34px; border-radius:8px;
  flex:none; color:var(--accent-text); background:var(--brand-coral-soft); }
.vc-file-text{ display:flex; flex-direction:column; min-width:0; }
.vc-file-name{ font-size:13px; font-weight:700; color:var(--text); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.vc-file-sub{ font-size:12px; font-style:normal; font-weight:600; color:var(--text-3); margin-top:1px; }
.vc-ready-actions{ display:flex; align-items:center; justify-content:flex-end; gap:10px; margin-top:14px; flex-wrap:wrap; }

/* --- Step 1c: transcript review --- */
.vc-review{ border:1px solid var(--border-2); border-radius:16px; background:var(--canvas); padding:20px; }
.vc-review-title{ font-size:16px; font-weight:700; letter-spacing:-.01em; color:var(--text); margin:0 0 4px; }
.vc-review-card{ background:var(--surface); border:1px solid var(--border-2); border-radius:12px; padding:14px; }
.vc-transcript{ width:100%; min-height:82px; border:none; padding:0; resize:vertical;
  font-size:15px; font-family:inherit; line-height:1.55; color:var(--text); outline:none; background:transparent; }
.vc-transcript::placeholder{ color:var(--placeholder); }
.vc-review-foot{ display:flex; align-items:center; justify-content:space-between; gap:12px;
  border-top:1px solid var(--border); margin-top:10px; padding-top:10px; }
.vc-review-note{ font-size:12px; font-weight:600; color:var(--text-3); }
/* An empty transcript is a state to act on, not an error: amber. A filled one
   is the good case: green. Both are quieter than the status strip. */
.vc-review-note.is-missing{ color:var(--warn-ink); }
.vc-review-note.is-ok{ color:#0f7a52; }
.vc-review-foot + .vc-player{ margin-top:12px; }
.vc-linkbtn{ border:none; background:none; padding:2px 0; cursor:pointer; font-family:inherit; font-size:12px;
  font-weight:600; color:var(--text-3); text-decoration:underline; }
.vc-linkbtn:hover{ color:var(--accent-text); }

/* --- Step 1 footer: readiness + what happens next --- */
.vc-foot{ display:flex; align-items:center; justify-content:space-between; gap:14px; margin-top:20px; flex-wrap:wrap; }
.vc-ready-tag{ display:inline-flex; align-items:center; gap:7px; font-size:13px; font-weight:600; color:var(--text-2); }
.vc-ready-dot{ width:8px; height:8px; border-radius:999px; background:#22c55e; flex:none;
  box-shadow:0 0 0 3px rgba(34,197,94,.16); }
/* margin-inline-start, not margin-left: the studio renders Arabic with
   dir="rtl" and the buttons still belong on the trailing edge there. */
.vc-foot-btns{ display:flex; align-items:center; gap:10px; margin-inline-start:auto; }

/* --- Mini audio player (shared by the clip players) --- */
.vc-player{ display:flex; align-items:center; gap:12px; border:1px solid var(--border-2); border-radius:12px;
  background:var(--surface); padding:10px 14px; }
.vc-play-btn{ display:flex; align-items:center; justify-content:center; width:38px; height:38px; border-radius:999px;
  flex:none; border:none; cursor:pointer; color:#fff; background:linear-gradient(135deg,#fe655d,#fc3e57);
  box-shadow:var(--e-2); transition:filter .15s, transform .15s; }
.vc-play-btn:hover{ filter:brightness(1.05); transform:scale(1.03); }
.vc-time{ font-size:12px; font-weight:600; font-variant-numeric:tabular-nums; color:var(--text-2); min-width:42px; }
.vc-time-dur{ color:var(--text-3); text-align:right; }
.vc-bar{ flex:1; height:6px; border-radius:8px; background:var(--surface-hover); cursor:pointer; position:relative; }
.vc-bar-fill{ position:absolute; left:0; top:0; bottom:0; width:0%; border-radius:8px;
  background:linear-gradient(90deg,#fe655d,#fc3e57); }

/* --- Step 2: listen preview --- */
.vc-listen-card{ border:1px solid var(--border-2); border-radius:16px; background:var(--canvas); padding:18px; }
.vc-listen-text{ width:100%; min-height:92px; border:1px solid var(--border-2); border-radius:12px; padding:13px 15px;
  font-size:15px; line-height:1.55; font-family:inherit; color:var(--text); background:var(--surface); resize:vertical; outline:none;
  transition:border-color .15s; }
.vc-listen-text:focus{ border-color:#fe655d; }
.vc-listen-actions{ display:flex; align-items:center; justify-content:flex-end; gap:10px; margin-top:12px; flex-wrap:wrap; }
.vc-listen-card .vc-player{ margin-top:14px; }

/* --- Step 3: save form --- */
.vc-save{ display:flex; gap:26px; align-items:flex-start; }
.vc-save-side{ flex:none; width:168px; display:flex; flex-direction:column; align-items:center; gap:10px; }
.vc-poster{ width:168px; height:168px; border-radius:16px; overflow:hidden; position:relative;
  background:linear-gradient(135deg,#fff1ef,#ffe4e0); display:flex; align-items:center; justify-content:center; padding:12px; }
.vc-poster-orb{ width:100%; height:100%; border-radius:999px; background:linear-gradient(135deg,#ff9e99,#fe655d 55%,#fc3e57);
  box-shadow:inset 0 0 0 3px rgba(255,255,255,.35), 0 10px 24px rgba(252,62,87,.28);
  background-size:cover; background-position:center; }
.vc-poster-name{ font-size:14px; font-weight:700; color:var(--text); text-align:center; word-break:break-word; }
.vc-poster-meta{ font-size:12px; font-weight:600; color:var(--text-3); text-align:center; }
.vc-form{ flex:1; min-width:0; display:flex; flex-direction:column; gap:18px; }
.vc-field{ display:flex; flex-direction:column; gap:8px; }
.vc-field-label{ font-size:13px; font-weight:700; color:var(--text); }
.vc-req{ color:#e11d48; }
.vc-input{ width:100%; height:46px; border-radius:12px; border:1px solid var(--border-2); padding:0 16px;
  font-size:15px; font-family:inherit; color:var(--text); background:var(--surface); outline:none; transition:border-color .15s; }
.vc-input:focus{ border-color:#fe655d; }
.vc-textarea{ height:auto; min-height:84px; padding:12px 16px; line-height:1.55; resize:vertical; }
.vc-selects{ display:flex; gap:10px; }
.vc-select{ flex:1; min-width:0; height:44px; border-radius:12px; border:1px solid var(--border-2); background-color:var(--surface);
  padding:0 34px 0 14px; font-size:14px; font-family:inherit; color:var(--text); cursor:pointer; outline:none;
  appearance:none; -webkit-appearance:none; transition:border-color .15s;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%239ca3af' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right 12px center; }
.vc-select:focus{ border-color:#fe655d; }

/* Single-select tag chips (use case). Same geometry as the Design panel's
   .vd-chip so the two screens read as one product. */
.vc-chips{ display:flex; flex-wrap:wrap; gap:8px; }
.vc-chip{ height:34px; padding:0 14px; border-radius:999px; border:1px solid var(--border-2); background:var(--surface);
  cursor:pointer; font-family:inherit; font-size:13px; font-weight:600; color:var(--text-2);
  transition:border-color .15s, background .15s, color .15s, box-shadow .15s; }
.vc-chip:hover{ border-color:#fe655d; color:var(--text); }
.vc-chip.is-on{ background:var(--brand-coral-soft); border-color:#fe655d; color:var(--warn-ink);
  box-shadow:0 0 0 3px rgba(254,101,93,.12); }
.vc-chip.is-on::before{ content:"✓"; margin-right:5px; font-weight:800; }

.vc-consent{ display:flex; align-items:flex-start; gap:10px; font-size:12px; line-height:1.55; color:var(--text-2); cursor:pointer; }
.vc-consent-text{ padding-top:1px; }
.vc-consent input{ margin-top:2px; flex:none; width:16px; height:16px; accent-color:#fe655d; cursor:pointer; }
.vc-consent-more{ font-size:12px; color:var(--text-3); margin-top:-6px; }
.vc-consent-more a{ color:var(--accent-text); text-decoration:underline; }

/* --- Buttons --- */
.vc-actions{ display:flex; gap:12px; margin-top:22px; }
.vc-btn{ display:inline-flex; align-items:center; justify-content:center; gap:7px; flex:1;
  height:46px; padding:0 18px; border-radius:12px; border:none; cursor:pointer; font-family:inherit;
  font-size:14px; font-weight:700; white-space:nowrap;
  transition:transform .15s, box-shadow .15s, filter .15s, opacity .15s, background .15s, border-color .15s; }
.vc-btn:disabled{ opacity:.5; cursor:default; transform:none; box-shadow:none; }
.vc-btn-inline{ flex:none; height:40px; font-size:13px; border-radius:8px; padding:0 16px; }
.vc-btn-ghost{ background:var(--surface); border:1px solid var(--border-2); color:var(--text); }
.vc-btn-ghost:hover:not(:disabled){ background:var(--hover); border-color:#d4d4d8; }
.vc-btn-light{ background:var(--surface); border:1px solid var(--border-2); color:var(--text); }
.vc-btn-light:hover:not(:disabled){ background:var(--hover); border-color:#d4d4d8; }
.vc-btn-primary{ color:#fff; background:linear-gradient(135deg,#ff9e99,#fe655d 55%,#fc3e57); }
.vc-btn-primary:hover:not(:disabled){ transform:translateY(-1px); filter:brightness(1.04); box-shadow:var(--e-2); }
/* Clone-slot line ("Giọng trên tài khoản: 1/1"). One component, three hosts —
   the Clone screen (before recording and above Save) and the Library's "My
   Voices" tab. Neutral while a slot is free; coral when the plan is full,
   because that state is the one that costs the user a voice they think they
   saved (see renderVoiceSlots in app.js). */
.vc-slots{ display:flex; align-items:center; gap:10px; flex-wrap:wrap;
  margin:0 0 14px; padding:9px 12px; border-radius:10px;
  background:var(--canvas); border:1px solid var(--border-2); color:var(--text-2);
  font-size:12.5px; line-height:1.5; }
.vc-slots.is-full{ background:var(--coral-tint-2); border-color:#f6c9c4; color:var(--danger); }
.vc-slots-text{ flex:1 1 220px; min-width:0; }
.vc-slots-cta{ flex:none; height:30px; padding:0 12px; border-radius:8px; cursor:pointer;
  border:1px solid #f0b3ad; background:var(--surface); color:#c0392f; font-weight:600; font-size:12.5px; }
.vc-slots-cta:hover{ background:var(--warn-tint); }
.vc-slots-cta:focus-visible{ outline:2px solid var(--focus); outline-offset:2px; }
@media (max-width:560px){ .vc-slots-cta{ width:100%; } }
/* The step-1/2/3 footers keep the compact 38px height the rest of the studio
   uses for its action rows. */
#cloneStep1 .vc-foot .vc-btn,
#cloneStep2 .vc-actions .vc-btn,
#cloneStep3 .vc-actions .vc-btn{ height:38px; font-size:14px; }

/* Busy states: a step that is mid-request must not accept another click. */
#cloneStep1.is-busy .vc-drop,
#cloneStep1.is-busy .vc-transcript,
#cloneStep1.is-busy .vc-player,
#cloneStep2.is-busy .vc-listen-text,
#cloneStep2.is-busy .vc-player,
#cloneStep3.is-busy .vc-form{ pointer-events:none; opacity:.55; }

@media (max-width:820px){
  .vc-save{ flex-direction:column; align-items:stretch; }
  .vc-save-side{ width:100%; flex-direction:row; align-items:center; gap:12px; }
  .vc-poster{ width:84px; height:84px; border-radius:16px; padding:7px; flex:none; }
  .vc-poster-name,.vc-poster-meta{ text-align:left; flex:1; min-width:0; }
}
@media (max-width:680px){
  .vc{ padding:18px 16px 20px; }
  .vc-steps{ margin-bottom:20px; }
  .vc-step{ width:auto; flex:1; min-width:0; }
  .vc-step-ico{ width:34px; height:34px; }
  .vc-step-title{ font-size:12px; }
  .vc-step-line{ margin-top:17px; }
  .vc-drop{ padding:26px 16px 22px; }
  .vc-selects{ flex-direction:column; }
  .vc-foot{ flex-direction:column; align-items:stretch; gap:12px; }
  .vc-foot-btns{ margin-left:0; }
  .vc-foot-btns .vc-btn,.vc-actions .vc-btn{ flex:1; }
  .vc-ready-actions{ justify-content:stretch; }
  .vc-ready-actions .vc-btn{ flex:1; }
  .vc-listen-actions .vc-btn{ flex:1; }
}

/* ================= VOICE DESIGN (describe-a-voice + gallery) ============== */
.vd-header{ display:flex; align-items:center; justify-content:space-between; gap:16px; margin-bottom:16px; }
.vd-header-left{ flex:1; min-width:0; }
.vd-category{ font-size:20px; font-weight:800; letter-spacing:-.02em; color:var(--text); }
.vd-subtitle{ font-size:13px; line-height:1.55; color:var(--text-2); margin-top:6px; max-width:640px; }
.vd-design-cta{ flex:none; white-space:nowrap; height:44px; padding:0 22px; border-radius:12px; border:none;
  cursor:pointer; font-family:inherit; font-size:14px; font-weight:700; color:#fff;
  background:linear-gradient(135deg,#ff9e99,#fe655d 55%,#fc3e57);
  transition:transform .15s, box-shadow .15s, filter .15s; }
.vd-design-cta:hover{ transform:translateY(-1px); filter:brightness(1.04); box-shadow:var(--e-2); }

/* Attribute builder (valid model tags only) */
.vd-builder{ display:flex; flex-direction:column; gap:14px; border:1px solid var(--border-2); border-radius:16px;
  background:var(--surface); padding:16px 18px; }
.vd-group{ display:flex; flex-wrap:wrap; align-items:baseline; gap:10px; }
.vd-group-label{ flex:0 0 84px; font-size:13px; font-weight:700; color:var(--text-2); }
.vd-chips{ display:flex; flex-wrap:wrap; gap:8px; flex:1; min-width:0; }
.vd-chip{ height:34px; padding:0 14px; border-radius:999px; border:1px solid var(--border-2); background:var(--surface);
  cursor:pointer; font-family:inherit; font-size:13px; font-weight:600; color:var(--text-2);
  transition:border-color .15s, background .15s, color .15s; }
.vd-chip:hover{ border-color:#fe655d; color:var(--text); }
.vd-chip.is-on{ background:linear-gradient(135deg,#fe655d,#fc3e57); border-color:transparent; color:#fff;
  box-shadow:var(--e-2); }

/* Composed "voice recipe" + on-demand preview */
.vd-recipe{ margin-top:16px; border:1px solid var(--border-2); border-radius:16px; background:var(--surface); padding:16px 18px; }
.vd-recipe-head{ display:flex; align-items:center; justify-content:space-between; margin-bottom:8px; }
.vd-recipe-label{ font-size:13px; font-weight:700; color:var(--text); }
.vd-recipe-clear{ display:inline-flex; align-items:center; gap:4px; border:none; background:none; cursor:pointer;
  font-family:inherit; font-size:12px; font-weight:600; color:var(--text-3); padding:4px 8px; border-radius:8px; }
.vd-recipe-clear:hover{ color:#e11d48; background:var(--danger-soft); }
.vd-recipe-val{ font-size:15px; font-weight:600; color:var(--accent-text); line-height:1.5; word-break:break-word; margin:0; }
.vd-recipe-val.is-empty{ color:var(--text-3); font-weight:500; }
.vd-listen-row{ display:flex; align-items:center; gap:12px; margin-top:12px; flex-wrap:wrap;
  padding-top:16px; border-top:1px solid var(--border); }
.vd-listen-player{ flex:1; min-width:220px; }
.vd-result-box{ margin-top:14px; border:2px dashed #d8d5e4; border-radius:16px; padding:20px 16px;
  background:var(--surface); display:flex; flex-direction:column; gap:0; }
.vd-player-wrap{ margin-top:12px; padding-top:12px; border-top:1px solid var(--border); display:none; }

/* Presets */
.vd-templates{ margin-top:22px; }
/* When templates lead the panel (easiest entry point), give them a little air. */
.vd-templates-top{ margin-top:18px; }
.vd-tpl-title{ font-size:15px; font-weight:700; color:var(--text); margin:0 0 12px; }
/* "Or build your own" divider between templates and the manual builder. */
.vd-or-build{ display:flex; align-items:center; gap:12px; margin:26px 0 14px; font-size:14px;
  font-weight:600; color:var(--text-2); }
.vd-or-build::before,.vd-or-build::after{ content:""; flex:1; height:1px; background:var(--border); }
/* Image gallery cards (local sayvoice art + model-generated preview clip) */
.vd-tpl-list{ list-style:none; margin:0; padding:0; display:grid; gap:12px;
  grid-template-columns:repeat(4, minmax(150px, 1fr)); }
.vd-tpl-pagination{ padding:16px 0 0; display:flex; justify-content:center; }
.vd-tpl{ position:relative; aspect-ratio:4/5; border-radius:16px; overflow:hidden; cursor:pointer;
  padding:3px; transition:transform .15s, box-shadow .15s; }
.vd-tpl-bg{ position:absolute; inset:3px; border-radius:12px; overflow:hidden;
  background:var(--surface-hover) center/cover no-repeat; box-shadow:inset 0 -60px 60px -20px rgba(0,0,0,.72); }
.vd-tpl:hover{ transform:translateY(-2px); box-shadow:var(--e-2); }
.vd-tpl.is-selected{ outline:3px solid #fe655d; outline-offset:2px; }
.vd-tpl.is-selected .vd-tpl-try{ background:linear-gradient(135deg,#fe655d,#fc3e57); color:#fff; }
.vd-tpl-bg::after{ content:""; position:absolute; inset:0; background:linear-gradient(180deg, rgba(0,0,0,0) 40%, rgba(0,0,0,.62) 100%); }
.vd-tpl-play{ position:absolute; top:10px; right:10px; z-index:2; width:34px; height:34px; border-radius:999px;
  border:none; cursor:pointer; display:flex; align-items:center; justify-content:center; color:#fff;
  background:rgba(0,0,0,.45); backdrop-filter:blur(4px); transition:background .15s, transform .15s; }
.vd-tpl-play:hover{ background:rgba(0,0,0,.7); transform:scale(1.06); }
.vd-tpl-play.is-playing{ background:linear-gradient(135deg,#fe655d,#fc3e57); }
.vd-tpl.is-playing{
  background:conic-gradient(from var(--spin-angle,0deg), #fc3e57, transparent 40%, transparent 60%, #fc3e57);
  animation:poster-spin 1s linear infinite; }
.vd-tpl-body{ position:absolute; left:0; right:0; bottom:0; z-index:2; padding:12px; display:flex;
  flex-direction:column; gap:8px; }
.vd-tpl-name{ font-size:14px; font-weight:700; letter-spacing:-.01em; color:#fff; line-height:1.25;
  text-shadow:0 1px 4px rgba(0,0,0,.5); }
.vd-tpl-try{ display:inline-flex; align-items:center; gap:4px; align-self:flex-start; height:30px; padding:0 12px;
  border-radius:999px; border:none; cursor:pointer; font-family:inherit; font-size:12px; font-weight:700;
  color:var(--text); background:var(--glass-bg-strong); opacity:0; transform:translateY(4px);
  transition:background .15s, opacity .15s, transform .15s; }
.vd-tpl-try:hover{ background:var(--surface); }
/* Save button only appears on hover/focus — keeps the gallery clean. */
.vd-tpl:hover .vd-tpl-try, .vd-tpl:focus-within .vd-tpl-try{ opacity:1; transform:translateY(0); }
@media (hover: none){ .vd-tpl-try{ opacity:1; transform:none; } }  /* touch: always visible */

@media (max-width:680px){
  #designPanel .vd-header{ flex-direction:column; align-items:flex-start; margin-bottom:24px; }
  #designPresets{ grid-template-columns:repeat(2, minmax(0,1fr)); }
  #instructPreview{ font-size:14px; }
  #designListenBtn,
  #designSaveBtn{ height:38px; padding:0 16px; font-size:13px; }
}

/* Saving spinner overlay on the card */
.vd-tpl-loading{ display:none; position:absolute; inset:0; z-index:5; background:rgba(0,0,0,.5); }
.vd-tpl-loading::after{ content:""; position:absolute; top:50%; left:50%; width:26px; height:26px;
  margin:-13px 0 0 -13px; border-radius:999px; border:3px solid rgba(255,255,255,.45); border-top-color:#fff;
  animation:spin 1s linear infinite; }
.vd-tpl.is-saving{ pointer-events:none; }
.vd-tpl.is-saving .vd-tpl-loading{ display:block; }

/* Save-modal avatar chooser */
.vd-save-avatar{ display:flex; align-items:center; gap:14px; margin:4px 0 16px; }
.vd-save-avatar .vc-poster-orb{ width:56px; height:56px; }

/* designSaveStatus overrides */
#designSaveStatus{ height:auto; margin-top:14px; }

/* Voice details plain text */
.vd-save-details{ font-size:14px; font-weight:600; color:var(--text); margin:0; padding:10px 14px;
  background:var(--hover); border-radius:8px; line-height:1.5; }

/* Invalid input highlight */
.vc-input.is-invalid{ border-color:#dc2626; box-shadow:0 0 0 3px rgba(220,38,38,.12); }
.vd-avatar-shuffle{ display:inline-flex; align-items:center; gap:7px; height:36px; padding:0 14px; border-radius:8px;
  border:1px solid var(--border-2); background:var(--surface); cursor:pointer; font-family:inherit; font-size:13px;
  font-weight:600; color:var(--text-2); transition:background .15s, color .15s; }
.vd-avatar-shuffle:hover{ background:var(--hover); color:var(--text); }
.vd-preset-list{ list-style:none; margin:0; padding:0; display:grid; gap:10px;
  grid-template-columns:repeat(auto-fill, minmax(200px, 1fr)); }
.vd-preset{ display:flex; align-items:center; gap:12px; border:1px solid var(--border-2); border-radius:12px;
  background:var(--surface); padding:12px 14px; cursor:pointer; transition:border-color .15s, box-shadow .15s, transform .15s; }
.vd-preset:hover{ border-color:#e7dbf7; box-shadow:var(--e-2); transform:translateY(-1px); }
.vd-preset-orb{ width:40px; height:40px; border-radius:999px; flex:none; background-size:cover; background-position:center;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.22), 0 1px 4px rgba(0,0,0,.16); }
.vd-preset-body{ min-width:0; }
.vd-preset-name{ font-size:14px; font-weight:700; color:var(--text); letter-spacing:-.01em;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.vd-preset-tags{ font-size:12px; color:var(--text-3); margin-top:2px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

/* Buttons */
.vd-btn{ display:inline-flex; align-items:center; justify-content:center; gap:7px; height:44px; padding:0 22px;
  border-radius:12px; border:none; cursor:pointer; font-family:inherit;
  font-size:14px; font-weight:700; transition:transform .15s, box-shadow .15s, filter .15s, opacity .15s, background .15s; }
.vd-btn:disabled{ opacity:.55; cursor:default; }
.vd-btn-dark{ background:var(--black); color:#fff; flex:none; }
.vd-btn-dark:hover:not(:disabled){ background:var(--black); }
.vd-btn-dark.is-loading{ opacity:.7; cursor:default; }

/* ---------- VOICE PICKER: "My Voices" group + items ---------- */
.vp-group{ font-size:12px; font-weight:700; text-transform:uppercase; letter-spacing:.05em;
  color:var(--text-3); padding:14px 12px 6px; }
.vp-group:first-child{ padding-top:4px; }
.voice-item .vi-badge.mine{ color:#9333ea; font-size:12px; font-weight:700; border:1px solid #ffd8d3;
  border-radius:8px; padding:1px 6px; margin-left:6px; vertical-align:middle; text-transform:none; }
.voice-item .vi-del{ width:32px; height:32px; border-radius:8px; border:none; background:none; flex:none;
  cursor:pointer; display:flex; align-items:center; justify-content:center; color:var(--text-3); }
.voice-item .vi-del:hover{ background:var(--coral-tint-2); color:#e11d48; }
.voice-item.local .vi-avatar .av{
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.22), 0 0 0 2px #fc3e57aa; }

/* ---------- VOICE CLONE: "My Voices" manager (list + rename/delete) ---------- */
.myvoices{ margin-top:22px; }
.myvoices-page{ }
.myvoices-page-header{ margin-bottom:16px; }
.myvoices-page-title{ font-size:20px; font-weight:800; letter-spacing:-.02em; color:var(--text); margin:0; }
.myvoices-tabs{ display:flex; gap:4px; margin-bottom:14px; border-bottom:1px solid var(--border); padding-bottom:0; }
.myvoices-tab{ position:relative; padding:10px 16px; border:none; background:none; cursor:pointer; font-family:inherit;
  font-size:14px; font-weight:600; color:var(--text-2); border-radius:8px 8px 0 0; transition:color .12s; }
.myvoices-tab:hover{ color:var(--text); }
.myvoices-tab.active{ color:var(--text); }
.myvoices-tab.active::after{ content:""; position:absolute; left:0; right:0; bottom:-1px; height:2px;
  background:#fe655d; border-radius:8px; }
.myvoices-head{ font-size:14px; font-weight:700; letter-spacing:-.01em; color:var(--text); margin:0 2px 10px; }
.myvoices-empty{ font-size:13px; color:var(--text-3); text-align:center; padding:18px 12px;
  border:1px dashed #e2d8f6; border-radius:12px; background:var(--brand-coral-soft); }
/* Loading skeleton under the tabs — reuses the library shimmer rows. */
.myvoices-loading{ display:flex; flex-direction:column; gap:10px; margin-bottom:14px; }
.myvoices-loading[hidden]{ display:none; }
.mv-item{ display:flex; align-items:center; gap:12px; padding:10px 12px; border:1px solid #f0eaf8;
  border-radius:12px; background:var(--surface); margin-bottom:8px; cursor:pointer; transition:border-color .15s, box-shadow .15s; }
.mv-item:hover{ border-color:#e7dbf7; box-shadow:var(--e-2); }
.mv-item.selected{ border-color:#fc3e57; background:var(--coral-tint-2); box-shadow:var(--e-2); }
.mv-item.playing{ border-color:#fc3e57; background:var(--coral-tint-2); box-shadow:var(--e-2); }
.mv-play.playing{ color:#fc3e57; padding:3px; border-radius:12px;
  background:conic-gradient(from var(--spin-angle,0deg), #fc3e57, transparent 40%, transparent 60%, #fc3e57);
  animation:poster-spin 1s linear infinite; }
.mv-play.playing::before{ content:""; position:absolute; inset:2px; border-radius:8px; background:var(--surface); }
.mv-play.playing svg{ position:relative; z-index:1; }
.mv-av-wrapper{ position:relative; flex:none; width:38px; height:38px; border-radius:999px;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.22), 0 0 0 2px #fc3e57aa; }
.mv-av{display: block; width:100%; height:100%; border-radius:999px; position:relative; overflow:hidden;
  background-size:cover; background-position:center; }
.mv-av-img{ width:100%; height:100%; object-fit:cover; border-radius:999px; display:block; }
.mv-av-star{ position:absolute; top:-2px; right:-2px; font-size:12px; line-height:1; color:#fc3e57;
  background:var(--surface); border-radius:999px; width:14px; height:14px; display:flex; align-items:center; justify-content:center;
  box-shadow:var(--e-1); pointer-events:none; }
.mv-av-star svg{ fill:#fc3e57; }
.mv-info{ flex:1; min-width:0; }
.mv-name{ font-size:14px; font-weight:500; letter-spacing:-.01em; color:var(--text);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.mv-meta{ font-size:12px; color:var(--text-3); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; margin-top:1px; }
.mv-btn{ width:34px; height:34px; border-radius:8px; border:none; background:none; flex:none; cursor:pointer;
  display:flex; align-items:center; justify-content:center; color:var(--text-2); position:relative; overflow:visible; }
.mv-btn:hover{ background:var(--hover); color:var(--text); }
.mv-btn.mv-del:hover{ background:var(--coral-tint-2); color:#e11d48; }
.mv-btn.mv-fav-toggle{ font-size:16px; color:var(--text-3); }
.mv-btn.mv-fav-toggle:hover{ color:#f59e0b; background:var(--hover); }
.mv-btn.mv-fav-toggle.on{ color:#f59e0b; }
.mv-btn.mv-fav-toggle.on svg{ fill:#f59e0b; }
.mv-btn.mv-copy{ color:var(--text-3); }
.mv-btn.mv-copy:hover{ color:var(--text); background:var(--hover); }
.mv-btn.mv-use{ width:auto; padding:0 12px; font-size:12px; font-weight:700; color:#fff; border-radius:8px;
  background:var(--accent,#fe655d); height:30px; }
.mv-btn.mv-use:hover{ filter:brightness(.94); background:var(--accent,#fe655d); }
.mv-btn.mv-remove{ width:auto; padding:0 12px; font-size:12px; font-weight:700; color:#e11d48; border-radius:8px;
  background:var(--danger-soft); border:1px solid #fecdd3; height:30px; }
.mv-btn.mv-remove:hover{ background:var(--coral-tint-2); }
.mv-btn-group{ display:flex; align-items:center; gap:4px; margin-left:auto; }

/* mv-item dropdown */
.mv-menu-wrap{ position:relative; flex:none; }
.mv-dropdown{ display:none; position:absolute; top:36px; right:0; z-index:20; min-width:220px;
  background:var(--surface); border:1px solid var(--border-2); border-radius:12px; box-shadow:var(--e-2); padding:6px; }
.mv-dropdown.show{ display:block; }
.mv-dd-item{ display:flex; align-items:center; gap:10px; width:100%; padding:9px 12px; border:none;
  background:none; border-radius:8px; font-size:14px; font-weight:500; color:var(--text);
  cursor:pointer; font-family:inherit; text-align:left; }
.mv-dd-item:hover{ background:var(--hover); }
.mv-dd-item.danger{ color:#dc2626; }
.mv-dd-item.danger:hover{ background:var(--danger-soft); }
.mv-dd-item svg{ width:16px; height:16px; flex:none; }

/* Voice picker: My Voices empty-state (jumps to Voice Clone) */
.vp-empty{ display:block; width:100%; text-align:left; border:1px dashed #e2d8f6; background:var(--brand-coral-soft);
  color:var(--text-2); border-radius:12px; padding:13px 15px; font-size:13px; line-height:1.5;
  cursor:pointer; font-family:inherit; }
.vp-empty:hover{ background:var(--warn-tint); color:var(--text); }

/* ============ ONE-CLICK TRANSLATION (Dubbing) ============ */
.tr{ border:1px solid var(--border-2); border-radius:16px; background:var(--surface); padding:22px 24px 24px; }
.tr-header{ margin-bottom:22px; text-align:center; }
.tr-category{ font-size:20px; font-weight:800; letter-spacing:-.02em; color:var(--text); margin:0 0 6px; }
/* Phase-1 hero: a calm, centered title/subtitle that sets up the upload, echoing
   the "start here" feel of a landing rather than a dense form header. */
.tr-header .tr-category{ font-size:24px; letter-spacing:-.03em; }
.tr-header .tr-subtitle{ max-width:460px; margin-left:auto; margin-right:auto; }
.tr-subtitle{ font-size:13px; line-height:1.55; color:var(--text-3); margin:0; }

.tr-step-label{ display:flex; align-items:center; gap:9px; font-size:14px; font-weight:700; color:var(--text);
  margin:18px 0 10px; }
.tr-step-num{ width:24px; height:24px; border-radius:999px; flex:none; display:flex; align-items:center; justify-content:center;
  font-size:13px; font-weight:700; color:#fff; background:linear-gradient(135deg,#fe655d,#fc3e57); }

/* Upload dropzone — airier and softer, so it reads as the one obvious place to
   start. Coral only comes in on hover/drag, keeping the resting state calm. */
.tr-drop{ display:flex; flex-direction:column; align-items:center; gap:8px; text-align:center; width:100%;
  border:2px dashed #dcdbe6; border-radius:16px; background:var(--surface); padding:38px 22px; cursor:pointer;
  font-family:inherit; transition:border-color .15s, background .15s, transform .15s, box-shadow .15s; margin-bottom:16px; }
.tr-drop:hover{ border-color:#fe655d; background:var(--brand-coral-soft); transform:translateY(-1px); box-shadow:var(--e-3); }
.tr-drop.drag{ border-color:#fe655d; background:var(--warn-tint); }
.tr-drop.error{ border-color:#dc2626; background:var(--danger-soft); animation:tr-drop-shake .4s ease; }
@keyframes tr-drop-shake{ 0%,100%{ transform:translateX(0); } 20%{ transform:translateX(-4px); } 40%{ transform:translateX(4px); } 60%{ transform:translateX(-3px); } 80%{ transform:translateX(2px); } }
.tr-drop-ico{ display:flex; align-items:center; justify-content:center; width:60px; height:60px; border-radius:999px;
  color:#fe655d; background:var(--brand-coral-soft); margin-bottom:6px; transition:transform .15s; }
.tr-drop:hover .tr-drop-ico{ transform:scale(1.05); }
.tr-drop-title{ font-size:15px; font-weight:700; color:var(--text); letter-spacing:-.01em; }
.tr-drop-desc{ font-size:12px; color:var(--text-3); }
/* "Longer than 10 minutes?" guidance under the dubbing/translate uploader. */
.tr-longfile-hint{ margin:16px 0 0; text-align:center; font-size:12px; line-height:1.55; color:var(--text-3); }

/* Chosen file chip */
.tr-file{ display:flex; align-items:center; gap:10px; border:1px solid var(--border-2); border-radius:12px;
  background:var(--brand-coral-soft); padding:11px 14px; margin-top:10px; }
.tr-file-ico{ display:flex; color:#fe655d; flex:none; }
.tr-file-name{ flex:1; font-size:13px; font-weight:600; color:var(--text); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.tr-file-x{ flex:none; border:none; background:transparent; color:var(--text-3); font-size:15px; cursor:pointer;
  width:26px; height:26px; border-radius:8px; }
.tr-file-x:hover{ background:var(--hover); color:var(--text); }

/* Language selectors */
.tr-langs{ display:flex; align-items:flex-start; gap:14px; }
.tr-lang-col{ display:flex; flex-direction:column; gap:7px; min-width:150px; }
.tr-lang-col-grow{ flex:1; }
.tr-lang-label{ font-size:12px; font-weight:700; letter-spacing:.02em; text-transform:uppercase; color:var(--text-3); }
.tr-lang-arrow{ font-size:20px; color:var(--text-3); align-self:center; margin-top:16px; }
.tr-select{ height:44px; border:1px solid var(--border-2); border-radius:12px; background:var(--surface); padding:0 12px;
  padding-right:28px; background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right 10px center; -webkit-appearance:none; appearance:none;
  font-family:inherit; font-size:14px; font-weight:600; color:var(--text); cursor:pointer; }
.tr-select:focus{ outline:none; border-color:#fe655d; }
.tr-chips{ display:flex; flex-wrap:wrap; gap:8px; }
.tr-chip{ border:1px solid var(--border-2); background:var(--surface); color:var(--text-2); border-radius:999px;
  padding:8px 14px; font-family:inherit; font-size:13px; font-weight:600; cursor:pointer;
  transition:border-color .15s, background .15s, color .15s, transform .1s; }
.tr-chip:hover{ border-color:#fe655d; background:var(--brand-coral-soft); }
.tr-chip.on{ color:#fff; border-color:transparent; background:linear-gradient(135deg,#fe655d,#fc3e57);
  box-shadow:var(--e-2); }

/* Options */
.tr-opts{ display:flex; flex-direction:column; gap:12px; margin:0 0 6px; }

/* Voice Changer: the mandatory AI disclosure. Deliberately quiet and always
   present next to the result — a label people learn to ignore still satisfies
   the duty to disclose, whereas one hidden behind a tooltip does not. */
.tr-ai-label{ display:flex; align-items:center; gap:7px; margin:0 0 10px;
  padding:8px 12px; border:1px solid var(--border-2); border-radius:8px;
  background:var(--bg); color:var(--text-2);
  font-size:12px; line-height:1.4; }
.tr-ai-label > b{ color:var(--text); font-weight:700; white-space:nowrap; }
.tr-ai-label svg{ flex:none; }
/* An un-ticked consent box is a hard stop, so flagging it has to be visible. */
#dubChangerConsent.error .tr-toggle{ border-color:#fc3e57; }
#dubChangerConsent.error .tr-mix-hint{ color:#fc3e57; }

/* ---------------------------------------------------------------------------
   Voice Changer: the same panel, stripped of the dubbing furniture.

   A voice change keeps the words, the language and the timing it was recorded
   with, and hands back audio. So everything that exists to produce a translated
   VIDEO — the timing/pace choice, the subtitle style/position controls, the
   background-music mixer, the terminology list feeding the translator, the SRT
   export — is noise on this screen, and each one invites a decision that cannot
   change the result. Hidden by BODY CLASS rather than once in JS on purpose:
   the phase-2 render and the mixer re-show their own nodes as a job progresses,
   and a hide-once would be undone by the next poll.
   --------------------------------------------------------------------------- */
body.mode-changer #dubPlan,              /* "you'll get a dubbed video" promise */
body.mode-changer #dubMixBlock,          /* background-music bed + levels */
body.mode-changer .tr-p2-output-card,    /* timing/pace + subtitle controls */
body.mode-changer .tr-voice-opts,        /* "keep the original voices" vs library */
body.mode-changer #dubGlossaryBlock,     /* names & terms, for the translator */
body.mode-changer #dubTimingBlock,       /* duration/pace choice */
body.mode-changer #dubDlSrt{ display:none !important; }
/* The working-version card is Voice + Timing + Music; a voice change has no
   music bed, so the third cell would always read "no music added". */
body.mode-changer #dubWorkingVersion .tr-working-meta > span:nth-child(3){ display:none; }
/* Settings-row toggles: label on the left, an iOS-style switch on the right, so
   a stack of options reads like a settings sheet rather than a checklist. The
   switch keeps SaydiVoice's coral when on. */
.tr-toggle{ display:flex; flex-direction:row-reverse; align-items:center; justify-content:space-between;
  gap:12px; font-size:13px; font-weight:600; color:var(--text-2); cursor:pointer; }
.tr-toggle > span{ flex:1; min-width:0; }
.tr-toggle input{ position:relative; flex:none; width:40px; height:23px; border-radius:999px;
  appearance:none; -webkit-appearance:none; border:none; background:#d7dae0; cursor:pointer;
  transition:background .18s ease; }
.tr-toggle input::before{ content:""; position:absolute; top:2px; left:2px; width:19px; height:19px;
  border-radius:999px; background:var(--surface); box-shadow:var(--e-1); transition:transform .18s ease; }
.tr-toggle input:checked{ background:linear-gradient(135deg,#fe655d,#fc3e57); }
.tr-toggle input:checked::before{ transform:translateX(17px); }
.tr-toggle input:focus-visible{ outline:2px solid rgba(254,101,93,.45); outline-offset:2px; }

/* Action */
.tr-actions{ margin-top:16px; }
.tr-btn{ height:50px; border-radius:12px; border:none; cursor:pointer; font-family:inherit; font-size:15px; font-weight:700;
  transition:transform .15s, box-shadow .15s, filter .15s, opacity .15s; width:100%;
  display:inline-flex; align-items:center; justify-content:center; gap:8px; }
.tr-btn:disabled{ opacity:.55; cursor:not-allowed; transform:none; box-shadow:none; }
.tr-btn-primary{ color:#fff; background:linear-gradient(135deg,#ff9e99,#fe655d 55%,#fc3e57); }
.tr-btn-primary:hover:not(:disabled){ transform:translateY(-1px); filter:brightness(1.04); box-shadow:var(--e-2); }
.tr-btn.is-loading{ opacity:.7; }

/* Phase 2 disabled state during generation */
/* Escape hatches stay clickable precisely WHILE work is in flight: Cancel,
   Run-in-background and the setup close X. They are excluded from the blanket
   disable below alongside the primary button. */
#dubPhase1.is-generating button:not(#dubRunBtn):not(#dubCancelBtn):not(#dubBackgroundBtn):not(#dubFileClear),
#dubPhase1.is-generating select,
#dubPhase1.is-generating input,
#dubPhase1.is-generating .tr-drop,
#dubPhase1.is-generating .tr-chip,
#dubPhase1.is-generating .tr-toggle,
#dubPhase1.is-generating .tr-file-x,
#dubPhase1.is-generating .tr-input-video,
#dubPhase1.is-generating .tr-file{
  pointer-events:none; opacity:.5; }
#dubPhase2.is-generating textarea,
#dubPhase2.is-generating select,
#dubPhase2.is-generating button:not(#dubRenderBtn),
#dubPhase2.is-generating input,
#dubPhase2.is-generating .tr-slider,
#dubPhase2.is-generating .tr-seg-ta{
  pointer-events:none; opacity:.5; }
#dubPhase2.is-generating .tr-adv-sum{ pointer-events:none; opacity:.5; }

/* Progress */
.tr-progress{ margin-top:18px; }
.tr-progress-head{ display:flex; align-items:center; justify-content:space-between; margin-bottom:8px; }
.tr-stage{ font-size:13px; font-weight:600; color:var(--text-2); }
.tr-pct{ font-size:13px; font-weight:700; color:#fe655d; }
.tr-bar{ height:10px; border-radius:999px; background:var(--canvas); overflow:hidden; }
/* display:block is load-bearing, not decoration: width/height/transform do not
   apply to a non-replaced inline element, so a `<span class="tr-bar-fill">`
   renders 0x0 at ANY percentage (and its indeterminate transform never moves).
   The editor footer shipped exactly that span and the bar stayed invisible
   while the %% label counted up. Keep the declaration so the fill cannot go
   blank again if the markup switches back to an inline tag. */
.tr-bar-fill{ display:block; height:100%; width:0%; border-radius:999px; background:linear-gradient(90deg,#fe655d,#fc3e57);
  transition:width .4s ease; }
/* Stage label + its optional spinner. Kept as a flex row so the spinner sits on
   the text baseline instead of shifting the label down by the line-height. */
.tr-stage-line{ display:inline-flex; align-items:center; min-width:0; }
/* Secondary line under the bar: bytes/rate/ETA during an upload, and the
   "slow connection" reassurance. Deliberately NOT .tr-detect, which is the
   red warning style — this text is normal, expected information.
   pre-line so a two-part note (e.g. "direct upload failed" + the retry's own
   byte progress) reads as two lines instead of one run-on sentence. */
.tr-progress-note{ margin:8px 0 0; font-size:12px; font-weight:500; line-height:1.5;
  color:var(--text-3); font-variant-numeric:tabular-nums; white-space:pre-line; }

/* TTS generation progress in the editor footer.
 *
 * It is a FLOATING strip pinned to the footer's top edge, not a row inside the
 * footer's flow. Measured before this change (desktop 1440x900), the footer went
 * 68px -> 144px -> 269px -> 193px in the space of one second: the progress row
 * pushed the composer down, then the playbar arrived while the progress was still
 * there (269px — both at once), then the progress collapsed. Every step resized
 * the text area, which is the "giật cục" users reported. A strip that occupies no
 * layout cannot move anything, and app.js only cross-fades it.
 *
 * Contents: a moving waveform (liveness — this app is audio, and the result
 * playbar uses the same visual language), a 3-step phase indicator, the phase
 * name, the elapsed/remaining meta, and — ONLY when a real fraction exists
 * (several speakers) — a percentage and a bar. A bar that fills on a guess is
 * what taught users to distrust this corner of the UI.
 *
 * body.is-generating adds the strip's height to .editor-scroll's bottom padding so
 * the floating panel never covers the last line of text. It is 0px when idle, so
 * every padding rule keeps its original value outside a generation — the padding
 * only has to change while the strip exists, and padding does not move content
 * that is already laid out. */
body{ --gen-strip-h:0px; }
body.is-generating{ --gen-strip-h:96px; }
.gen-progress{
  position:absolute; left:0; right:0; bottom:100%;
  margin:0; width:auto; padding:9px 16px 10px;
  background:var(--surface); border-top:1px solid var(--border);
  box-shadow:var(--e-2);
  /* Closed: out of the way and untouchable. visibility is delayed so the
     fade-out is visible, then the element stops being hit-testable. */
  opacity:0; transform:translateY(6px); visibility:hidden; pointer-events:none;
  transition:opacity .18s ease, transform .18s ease, visibility 0s linear .18s;
}
.gen-progress.is-open{ opacity:1; transform:none; visibility:visible; pointer-events:auto; transition-delay:0s,0s,0s; }
.gen-progress[hidden]{ display:none; }
/* An id/class rule that sets `display` beats the UA stylesheet's [hidden] rule, so
   every element toggled via the `hidden` property needs this or it stays visible —
   the same failure that once made the progress fill render 0x0. These two are
   display:flex, so they are the ones that need it spelled out. */
.gen-wave[hidden], .gen-queue[hidden]{ display:none; }
.gen-line{ display:flex; align-items:center; gap:11px; min-width:0; flex-wrap:wrap; row-gap:6px; }
/* min-width:0 + normal wrapping, never nowrap: a long phase name on a 320px screen
   must wrap, not push the strip's content past the viewport edge (where it is
   clipped by .app's overflow:hidden and simply becomes unreadable). */
.gen-stage{ font-size:13px; font-weight:600; color:#374151; min-width:0; }
.gen-pct{ flex:none; }
/* min-width:0 + wrapping at every width, not only on phones: the strip can land in
   a narrow column (measured 218px wide on the RTL landscape layout, 186px of content
   box) where a nowrap meta 213px wide overflows the panel even though it stays
   inside the viewport. On a roomy line this changes nothing. */
/* margin-inline-start, not margin-left: the studio renders Arabic with dir="rtl",
   where margin-left pins the meta to the START of its line (measured: it sat under
   the waveform instead of at the opposite edge). Logical properties mirror it for
   free and are identical in LTR. */
.gen-meta{ margin-inline-start:auto; min-width:0; font-size:12px; line-height:1.4; color:var(--text-3);
  font-variant-numeric:tabular-nums; white-space:normal; overflow-wrap:break-word; }

/* Liveness. CSS-only so a repaint never costs a layout: 12 bars on a staggered
   peak loop. Decorative, so the whole group is aria-hidden. */
.gen-wave{ display:flex; align-items:center; gap:3px; height:20px; flex:none; }
.gen-wave i{ display:block; width:3px; height:6px; border-radius:8px; flex:none;
  background:linear-gradient(180deg,#fe655d,#fc3e57); animation:gen-wave 1.05s ease-in-out infinite; }
@keyframes gen-wave{ 0%,100%{ height:5px; opacity:.5 } 50%{ height:19px; opacity:1 } }
.gen-wave i:nth-child(1){ animation-delay:0s }    .gen-wave i:nth-child(2){ animation-delay:.09s }
.gen-wave i:nth-child(3){ animation-delay:.18s }  .gen-wave i:nth-child(4){ animation-delay:.27s }
.gen-wave i:nth-child(5){ animation-delay:.36s }  .gen-wave i:nth-child(6){ animation-delay:.45s }
.gen-wave i:nth-child(7){ animation-delay:.54s }  .gen-wave i:nth-child(8){ animation-delay:.63s }
.gen-wave i:nth-child(9){ animation-delay:.72s }  .gen-wave i:nth-child(10){ animation-delay:.81s }
.gen-wave i:nth-child(11){ animation-delay:.9s }  .gen-wave i:nth-child(12){ animation-delay:.99s }

/* Queue state: one block per request ahead of this one, plus your own block. It
   says "you are in a line", which a waveform cannot — and it is the state users
   used to read as "the app is broken". */
.gen-queue{ display:flex; align-items:center; gap:4px; flex:none; }
.gen-queue i{ display:block; width:13px; height:13px; border-radius:8px; flex:none; background:var(--canvas); }
.gen-queue i.ahead{ background:#f6c9cf; }
.gen-queue i.you{ background:linear-gradient(135deg,#fe655d,#fc3e57);
  animation:gen-pulse 1.5s ease-in-out infinite; }
@keyframes gen-pulse{ 0%,100%{ opacity:1 } 50%{ opacity:.5 } }

/* Phase indicator: ready -> audio -> assemble. Waiting for a slot and generating
   are the same step told two ways (label + waveform vs queue), so the dots never
   jump around. */
.gen-steps{ display:flex; align-items:center; gap:5px; flex:none; }
.gen-steps i{ display:block; width:7px; height:7px; border-radius:999px; background:#dfe2e7; flex:none; }
.gen-steps i.done{ background:#f2a1ad; }
.gen-steps i.now{ background:linear-gradient(135deg,#fe655d,#fc3e57); box-shadow:0 0 0 3px rgba(252,62,87,.16); }

.gen-bar{ margin-top:8px; }
.gen-leave{ margin-top:5px; font-size:12px; line-height:1.4; color:var(--text-3); }

/* Narrow screens: the phase name keeps its first line, the meta drops below it. */
@media (max-width:560px){
  /* Full row of its own, and allowed to wrap: nowrap here pushed the meta past the
     viewport on a 320px screen with the longer English wording ("1m00s elapsed ·
     longer than expected · about 5m50s left" = 336px), where .app's overflow:hidden
     then clipped it — text lost rather than wrapped. */
  .gen-meta{ margin-inline-start:0; flex-basis:100%; order:9; }
  .gen-stage{ font-size:13px; }
  .gen-wave{ height:16px; }
}
/* Very narrow (320-400px): the waveform yields room to the phase name and the
   segment counter rather than forcing them onto extra rows. */
@media (max-width:400px){
  .gen-line{ gap:8px; }
  .gen-wave{ gap:2px; }
  .gen-wave i:nth-child(n+9){ display:none; }
  .gen-steps{ gap:4px; }
}
@media (prefers-reduced-motion:reduce){
  .gen-progress{ transition:none; }
  .playbar.show{ animation:none; }
  .gen-wave i{ animation:none; height:12px; }
  .gen-queue i.you{ animation:none; }
  .gen-progress .tr-bar-fill{ transition:none; }
}

/* Results */
.tr-results{ margin-top:22px; }
.tr-results-head{ font-size:15px; font-weight:700; color:var(--text); margin-bottom:12px; }
.tr-result{ border:1px solid var(--border-2); border-radius:12px; background:var(--surface); padding:14px 16px; margin-bottom:12px; margin-top:12px;
  min-width:0; }
.tr-result-head{ display:flex; align-items:center; justify-content:space-between; margin-bottom:10px; }
/* A flex item defaults to min-width:auto and so refuses to shrink below its own
   content: a spelled-out language name or a long version label then widened the
   row past the card it lives in. Let the label shrink and wrap; keep the pill and
   the toggle at their natural size, since neither has anything to give. */
.tr-result-lang{ font-size:14px; font-weight:700; color:var(--text);
  min-width:0; flex:1 1 auto; overflow-wrap:anywhere; }
.tr-version-meta,
.tr-result-err{ min-width:0; overflow-wrap:anywhere; }
.tr-audio{ width:100%; height:40px; }
.tr-result-actions{ display:flex; flex-wrap:wrap; gap:10px; margin-top:10px; }
.tr-dl{ font-size:12px; font-weight:700; color:var(--accent-text); background:var(--brand-coral-soft); border:1px solid #ffd8d3;
  border-radius:8px; padding:7px 13px; text-decoration:none; }
.tr-dl:hover{ background:#ffe1dd; }
.tr-dl.is-stale{ background:var(--warn-tint); border-color:#f4c76b; color:var(--warn-ink); }
.tr-dl.is-loading{ opacity:.68; pointer-events:none; }
.tr-dl.is-loading::before{ content:""; display:inline-block; width:10px; height:10px; margin-right:6px;
  border:2px solid rgba(224,70,60,.25); border-top-color:#e0463c; border-radius:999px;
  animation:tr-working-spin .8s linear infinite; vertical-align:-1px; }
.tr-dl.is-stale::before{ content:"↻ "; font-weight:900; }
.tr-export-global{ position:fixed; right:18px; bottom:18px; z-index:100000; display:flex; align-items:center; gap:10px;
  max-width:min(420px, calc(100vw - 24px)); padding:10px 13px; border-radius:12px; background:var(--black); color:#fff;
  font-size:13px; font-weight:650; box-shadow:var(--e-3); }
.tr-export-global.is-error{ background:#9b2c2c; }
.tr-export-global-spin{ flex:none; width:14px; height:14px; border:2px solid rgba(255,255,255,.28);
  border-top-color:#fff; border-radius:999px; animation:tr-working-spin .8s linear infinite; }
.tr-export-global-time{ flex:none; font-variant-numeric:tabular-nums; opacity:.78; }
.tr-result-err{ font-size:13px; color:var(--danger); margin:0; }

@media (max-width:640px){
  .tr-langs{ flex-direction:column; }
  .tr-lang-arrow{ display:none; }
  .tr-lang-col{ min-width:0; }
}

/* Translate: voice choice */
.tr-voice-opts{ display:flex; gap:12px; }
.tr-voice-opt{ flex:1; display:flex; align-items:flex-start; gap:11px; text-align:left; cursor:pointer;
  border:1.5px solid var(--border-2); border-radius:12px; background:var(--surface); padding:14px 15px; font-family:inherit;
  transition:border-color .15s, background .15s, transform .12s, box-shadow .15s; }
.tr-voice-opt:hover{ border-color:#fe655d; background:var(--brand-coral-soft); transform:translateY(-1px); }
.tr-voice-opt.on{ border-color:#fe655d; background:var(--coral-tint); box-shadow:var(--e-2); }
.tr-voice-ico{ display:flex; align-items:center; justify-content:center; width:38px; height:38px; flex:none;
  border-radius:8px; color:#fe655d; background:var(--brand-coral-soft); }
.tr-voice-opt.on .tr-voice-ico{ color:#fff; background:linear-gradient(135deg,#fe655d,#fc3e57); }
.tr-voice-opt-body{ display:flex; flex-direction:column; gap:3px; }
.tr-voice-opt-title{ font-size:14px; font-weight:700; color:var(--text); letter-spacing:-.01em; }
.tr-voice-opt-desc{ font-size:12px; color:var(--text-3); line-height:1.4; }
.tr-voice-pick{ margin-top:12px; display:flex; flex-direction:column; gap:7px; }
.tr-select-block{ width:100%; }
/* Per-speaker voice assignment (multi-speaker "Choose voices") */
.tr-onevoice{ margin:2px 0; }
.tr-voice-hint{ font-size:12px; color:var(--text-2); line-height:1.5; margin:2px 0 6px; }
.tr-voice-speakers{ display:flex; flex-direction:column; gap:8px; }
.tr-spk-row{ display:flex; align-items:center; gap:8px; }
.tr-spk-label{ flex:none; min-width:88px; font-size:13px; font-weight:600; color:var(--text-2); }
.tr-spk-select{ flex:1; min-width:0; }
.tr-spk-prev{ flex:none; width:40px; height:40px; }
@media (max-width:640px){ .tr-voice-opts{ flex-direction:column; } .tr-spk-label{ min-width:64px; font-size:12px; } }

/* Translate phase 2: review & edit */
.tr-phase2-head{ display:flex; align-items:flex-start; justify-content:space-between; gap:14px; margin-bottom:14px; }
.tr-phase2-titles{ flex:1; }
.tr-restart{ flex:none; border:1px solid var(--border-2); background:var(--surface); color:var(--text-2); border-radius:8px;
  padding:8px 14px; font-family:inherit; font-size:13px; font-weight:600; cursor:pointer; transition:background .15s, border-color .15s; }
.tr-restart:hover{ background:var(--hover); border-color:#fe655d; color:var(--text); }

.tr-lang-tabs{ display:flex; flex-wrap:wrap; gap:8px; margin-bottom:14px; }
.tr-lang-tab{ border:1px solid var(--border-2); background:var(--surface); color:var(--text-2); border-radius:999px;
  padding:7px 15px; font-family:inherit; font-size:13px; font-weight:600; cursor:pointer; transition:all .15s; }
.tr-lang-tab:hover{ border-color:#fe655d; background:var(--brand-coral-soft); }
.tr-lang-tab.on{ color:#fff; border-color:transparent; background:linear-gradient(135deg,#fe655d,#fc3e57); box-shadow:var(--e-2); }
/* A language whose "translation" is really the untouched source text. Marked on
   the tab itself, because that is where the user switches into it. */
.tr-lang-tab.is-untranslated{ border-color:#f8c3d0; color:var(--danger); background:var(--danger-soft); }
.tr-lang-tab.is-untranslated.on{ color:#fff; background:linear-gradient(135deg,#f87171,#b4123f); }

.tr-seglist{ display:flex; flex-direction:column; gap:10px; margin-bottom:6px; }
.tr-seg{ border:1px solid var(--border-2); border-radius:12px; background:var(--surface); padding:11px 13px; }
.tr-seg-head{ display:flex; align-items:center; justify-content:space-between; margin-bottom:6px; }
.tr-seg-spk{ font-size:12px; font-weight:700; letter-spacing:.02em; text-transform:uppercase; color:var(--text-3); }
.tr-seg-src{ font-size:12px; color:var(--text-3); margin:0 0 8px; line-height:1.45; font-style:italic; }
.tr-seg-ta{ width:100%; border:1px solid var(--border-2); border-radius:8px; background:var(--surface); padding:9px 11px;
  font-family:inherit; font-size:14px; line-height:1.5; color:var(--text); resize:vertical; box-sizing:border-box; }
.tr-seg-ta:focus{ outline:none; border-color:#fe655d; box-shadow:0 0 0 3px rgba(254,101,93,.15); }

/* ============ Translate: collapsed review + one-button finish ============ */
/* The count rides on the closed disclosure, so a collapsed panel never hides the
   fact that some lines will be spoken quickly. */
.tr-review-sum{ margin-left:auto; font-size:12px; font-weight:600; color:var(--text-3);
  white-space:nowrap; }
.tr-review-sum.has-warn{ color:var(--warn-ink); }
.tr-review .tr-adv-sum{ flex-wrap:wrap; }
.tr-review-body{ padding:14px 14px 4px; }
.tr-review-body .tr-review-head{ margin-top:0; }
.tr-only-bad{ display:flex; align-items:center; gap:7px; font-size:12px; font-weight:600;
  color:var(--text-2); cursor:pointer; }
.tr-only-bad input{ width:16px; height:16px; accent-color:#fe655d; cursor:pointer; }
/* Filter by hiding, never by re-rendering: the rows have to stay in the DOM or
   dubSaveCurrentEdits loses every edit it cannot see. */
.tr-seglist.only-bad .tr-seg:not(.is-crowded){ display:none; }

/* What the button will produce. Sits directly above it, inside the sticky area's
   reach, so it is the last thing read before pressing. */
.tr-plan{ display:flex; align-items:flex-start; gap:9px; margin:16px 0 0; padding:11px 13px;
  border:1px solid #bff0d8; border-radius:12px; background:var(--success-tint); }
.tr-plan-ico{ display:flex; flex:none; color:#0f7a52; margin-top:1px; }
.tr-plan-text{ font-size:13px; line-height:1.5; font-weight:600; color:#0f5c3e; }

/* ============ Translate: review video + per-line redo ============ */
/* The picture stays with the lines. Sticky rather than fixed, so it scrolls away
   with the card instead of hovering over the whole app. */
.tr-p2-video{ position:sticky; top:0; z-index:4; margin:0 0 14px; padding-bottom:8px;
  background:var(--surface); }
.tr-p2-stage{ position:relative; display:block; }
.tr-p2-player{ width:100%; max-height:34vh; border-radius:12px; background:var(--black); display:block; }
/* Subtitle preview, drawn over the picture the way libass will draw it: bottom
   centre, white with a dark outline, at a font size derived from the video's
   rendered height (see DUB_SUB_SIZE_RATIO, which mirrors the backend default).
   pointer-events:none so it never steals a click from the video controls, and it
   stops short of them so it cannot sit under the scrub bar. */
.tr-p2-cap{ position:absolute; left:0; right:0; bottom:11%; display:none;
  flex-direction:column; align-items:center; gap:.12em; padding:0 6%;
  pointer-events:auto; touch-action:none; cursor:move; text-align:center;
  user-select:none; -webkit-user-select:none; }
.tr-p2-cap:active{ cursor:grabbing; }
.tr-cap-line{ display:inline-block; max-width:100%;
  color:var(--dub-cap-color,#fff);
  font-weight:var(--dub-cap-font-weight,700);
  line-height:1.28; letter-spacing:.01em;
  background:var(--dub-cap-bg,transparent);
  padding:var(--dub-cap-pad-y,0) var(--dub-cap-pad-x,0);
  border-radius:var(--dub-cap-radius,0);
  text-shadow:
    calc(-1 * var(--dub-cap-outline,0px)) 0 0 var(--dub-cap-outline-color,#000),
    var(--dub-cap-outline,0px) 0 0 var(--dub-cap-outline-color,#000),
    0 calc(-1 * var(--dub-cap-outline,0px)) 0 var(--dub-cap-outline-color,#000),
    0 var(--dub-cap-outline,0px) 0 var(--dub-cap-outline-color,#000),
    0 var(--dub-cap-shadow,0px) var(--dub-cap-shadow,0px) var(--dub-cap-shadow-color,#000a); }
.tr-p2-cap.is-boxed .tr-cap-line{ border-radius:.22em; }
/* Re-exporting is a secondary action once a video exists. */
.tr-export-again{ height:42px; border:1px solid var(--border-2); background:var(--surface);
  color:var(--text-2); font-size:13px; box-shadow:none; }
.tr-export-again:hover:not(:disabled){ border-color:#fe655d; color:var(--accent-text);
  background:var(--brand-coral-soft); box-shadow:none; transform:none; }
.tr-p2-vhint{ font-size:12px; color:var(--text-3); margin:7px 0 0; }
@media (max-width:640px){ .tr-p2-player{ max-height:28vh; } }

/* Phase-2 two-column: controls on the left, the video preview parked on the
   right and pinned while the controls scroll — the reference "Dub your video"
   split. The dubbing screen hides the right-hand Settings panel, so the editor
   owns the full width and can afford it. It only kicks in when there actually is
   a video on screen (video jobs); audio jobs and phones stay single column, with
   the picture leading. */
.tr-p2-main{ min-width:0; }
.tr-p2-side{ min-width:0; }
@media (min-width:900px){
  .tr-p2-grid:has(#dubReviewVideo[style*="block"]){
    display:grid; grid-template-columns:minmax(0,1fr) 340px; gap:26px; }
  .tr-p2-grid:has(#dubReviewVideo[style*="block"]) .tr-p2-main{ grid-column:1; grid-row:1; }
  .tr-p2-grid:has(#dubReviewVideo[style*="block"]) .tr-p2-side{ grid-column:2; grid-row:1; }
  .tr-p2-grid:has(#dubReviewVideo[style*="block"]) .tr-p2-video{ position:sticky; top:12px; margin-bottom:0; }
  .tr-p2-grid:has(#dubReviewVideo[style*="block"]) .tr-p2-player{ max-height:72vh; }
}

/* Names & terms */
.tr-gloss{ width:100%; border:1px solid var(--border-2); border-radius:8px; background:var(--surface);
  padding:9px 11px; font-family:inherit; font-size:13px; line-height:1.6; color:var(--text);
  resize:vertical; box-sizing:border-box; }
.tr-gloss:focus{ outline:none; border-color:#fe655d; box-shadow:0 0 0 3px rgba(254,101,93,.15); }

/* Cancel: present but quiet — it sits under the primary action and must never
   compete with it for the eye. */
.tr-cancel{ display:block; width:100%; margin-top:8px; border:1px solid var(--border-2);
  background:var(--surface); color:var(--text-3); border-radius:12px; padding:9px 14px;
  font-family:inherit; font-size:13px; font-weight:600; cursor:pointer; transition:.15s; }
.tr-cancel:hover{ border-color:var(--danger); color:var(--danger); background:var(--danger-soft); }
.tr-cancel.armed{ border-color:var(--danger); color:var(--danger); background:var(--danger-soft); font-weight:700; }

/* Clickable timecode — the handle for "show me this moment". */
.tr-seg-time{ border:1px solid transparent; background:transparent; color:var(--text-3);
  border-radius:8px; padding:1px 6px; font-family:inherit; font-size:12px; font-weight:700;
  letter-spacing:.02em; font-variant-numeric:tabular-nums; cursor:pointer; transition:.15s; }
.tr-seg-time:hover{ border-color:#fe655d; color:var(--accent-text); background:var(--brand-coral-soft); }
.tr-seg-head{ gap:8px; }
.tr-seg-head .tr-seg-spk:empty{ display:none; }
.tr-seg-head .tr-seg-fit{ margin-left:auto; }

/* The line under the playhead. A left bar rather than a fill, so the row does not
   change colour while it is also the row being edited. */
.tr-seg.is-playing{ background:var(--brand-coral-soft); box-shadow:inset 3px 0 0 #fe655d; }

/* "Shorter" only shouts on the lines the fit badge is already complaining about. */
.tr-seg-btn-attention{ border-color:#f8c3d0; color:var(--danger); background:var(--brand-coral-soft); }
.tr-seg-undo{ color:#0f7a52; border-color:#bff0d8; background:var(--success-tint); }
.tr-seg-undo:hover{ background:#d8f3e6; border-color:#0f7a52; color:#0f7a52; }
.tr-seg-actions{ flex-wrap:wrap; }

/* Translate: informed waiting + saved hint */
.tr-detect{ margin:10px 0 0; font-size:12px; font-weight:600; color:var(--accent-text); }
.tr-saved{ display:none; align-items:center; gap:4px; margin-left:10px; font-size:12px; font-weight:700;
  color:#0f7a52; background:var(--success-tint); border-radius:999px; padding:2px 9px; vertical-align:middle; }

/* Translate: per-segment fit indicator + line actions */
.tr-seg-fit{ font-size:12px; font-weight:700; letter-spacing:.01em; padding:2px 8px; border-radius:999px; }
.tr-seg-fit.ok{ color:#0f7a52; background:var(--success-tint); }
.tr-seg-fit.tight{ color:var(--warn-ink); background:var(--warn-tint); }
.tr-seg-fit.over{ color:var(--danger); background:var(--danger-soft); }
.tr-seg-actions{ display:flex; gap:8px; margin-top:8px; }
.tr-seg-btn{ display:inline-flex; align-items:center; gap:6px; border:1px solid var(--border-2); background:var(--surface);
  color:var(--text-2); border-radius:8px; padding:6px 11px; font-family:inherit; font-size:12px; font-weight:600;
  cursor:pointer; transition:background .15s, border-color .15s, opacity .15s; }
.tr-seg-btn:hover{ background:var(--hover); border-color:#fe655d; color:var(--text); }
.tr-seg-btn-accent{ color:var(--accent-text); }
.tr-seg-btn.is-loading{ opacity:.55; pointer-events:none; }

/* Translate: voice preview + empty state */
.tr-voice-pick-row{ display:flex; gap:8px; align-items:center; }
.tr-voice-preview{ flex:none; width:42px; height:44px; border:1px solid var(--border-2); background:var(--surface); color:var(--accent-text);
  border-radius:12px; cursor:pointer; display:flex; align-items:center; justify-content:center; transition:background .15s, border-color .15s; }
.tr-voice-preview:hover{ background:var(--brand-coral-soft); border-color:#fe655d; }
.tr-voice-preview.is-loading{ opacity:.55; pointer-events:none; }
.tr-voice-preview.playing{ background:var(--brand-coral-soft); border-color:#fe655d; position:relative; }
.tr-voice-preview.playing::after{ content:""; position:absolute; inset:-3px; border-radius:12px;
  border:2.5px solid transparent; border-top-color:#fe655d; animation:vi-spin .8s linear infinite; }
.tr-voice-empty{ margin:8px 0 0; font-size:12px; color:var(--text-3); }
.tr-link{ color:var(--accent-text); font-weight:700; text-decoration:none; }
.tr-link:hover{ text-decoration:underline; }

/* Translate: start-over armed state + original A/B row */
.tr-restart.armed{ border-color:var(--danger); color:var(--danger); background:var(--danger-soft); }
.tr-result-orig{ background:var(--coral-tint); border-color:#ffd8d3; margin-top:14px; }

/* Translate: find & replace toolbar */
.tr-find{ display:flex; align-items:center; gap:8px; margin:0 0 12px; flex-wrap:wrap; }
.tr-find-in{ flex:1; min-width:120px; height:38px; border:1px solid var(--border-2); border-radius:8px;
  padding:0 11px; font-family:inherit; font-size:13px; color:var(--text); background:var(--surface); box-sizing:border-box; }
.tr-find-in:focus{ outline:none; border-color:#fe655d; }
.tr-find-arrow{ color:var(--text-3); font-size:15px; }
.tr-find-btn{ flex:none; height:38px; border:1px solid var(--border-2); background:var(--surface); color:var(--accent-text);
  border-radius:8px; padding:0 14px; font-family:inherit; font-size:13px; font-weight:700; cursor:pointer; transition:background .15s, border-color .15s; }
.tr-find-btn:hover{ background:var(--brand-coral-soft); border-color:#fe655d; }

/* Translate: mix level sliders */
.tr-mix{ display:flex; flex-direction:column; gap:12px; margin:2px 0 6px; }
.tr-mix-row{ display:flex; align-items:center; gap:12px; }
.tr-mix-label{ flex:none; width:96px; font-size:13px; font-weight:600; color:var(--text-2); }
.tr-slider{ flex:1; height:6px; cursor:pointer; accent-color:#fe655d;
  border-radius:999px; outline:none; border:none; margin:0; }
.tr-slider::-webkit-slider-thumb{ border:none; box-shadow:none; }
.tr-slider::-moz-range-thumb{ border:none; box-shadow:none; }
.tr-mix-val{ flex:none; width:96px; text-align:right; font-size:12px; font-weight:700; color:var(--accent-text);
  font-variant-numeric:tabular-nums; }

/* ============ Translate: background music + mixer ============ */
/* Music gets its own accent (indigo) so it never reads as part of the speech
   pipeline, which owns the coral used everywhere else in this panel. */
.tr-mixer .tr-adv-sum{ justify-content:flex-start; flex-wrap:wrap; }
.tr-mix-sum{ margin-left:auto; font-size:12px; font-weight:600; color:var(--text-3);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; max-width:52%; }
.tr-mix-body{ padding:14px; display:flex; flex-direction:column; gap:20px; }
.tr-mix-sec{ display:flex; flex-direction:column; gap:9px; }
.tr-mix-sec .tr-blk-label{ margin-bottom:0; }
.tr-mix-hint{ font-size:12px; line-height:1.5; color:var(--text-3); margin:0; }
.tr-mix-warn{ font-size:12px; line-height:1.5; color:var(--warn-ink); background:var(--warn-tint);
  border-radius:8px; padding:8px 11px; margin:2px 0 0; }

/* Two ways to get a bed: pick one we ship, or bring your own. */
.tr-src-tabs{ display:inline-flex; border:1px solid var(--border-2); border-radius:8px;
  overflow:hidden; background:var(--surface); align-self:flex-start; }
.tr-src-tab{ border:none; background:transparent; color:var(--text-2); font-family:inherit;
  font-size:12px; font-weight:600; padding:7px 13px; cursor:pointer; transition:.15s; }
.tr-src-tab + .tr-src-tab{ border-left:1px solid var(--border-2); }
.tr-src-tab.on{ color:#fff; background:linear-gradient(135deg,#818cf8,#6366f1); }

/* Library list. Rows, not cards: this is dense pickable data, and every row
   auditions in place so you hear a track before committing it to the mix. */
.tr-lib{ border:1px solid var(--border-2); border-radius:12px; background:var(--surface); overflow:hidden; }
.tr-lib-list{ display:flex; flex-direction:column; max-height:236px; overflow-y:auto; }
.tr-lib-row{ display:flex; align-items:center; gap:10px; padding:9px 11px;
  border-bottom:1px solid var(--border-2); transition:background .15s; }
.tr-lib-row:last-child{ border-bottom:none; }
.tr-lib-row:hover{ background:var(--info-soft); }
.tr-lib-row.is-playing{ background:var(--info-soft); }
.tr-lib-play{ flex:none; width:30px; height:30px; border-radius:999px; cursor:pointer; border:none;
  color:#fff; background:linear-gradient(135deg,#818cf8,#6366f1);
  display:inline-flex; align-items:center; justify-content:center; }
.tr-lib-play:hover{ filter:brightness(1.06); }
.tr-lib-name{ flex:1; min-width:0; font-size:13px; font-weight:600; color:var(--text);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.tr-lib-dur{ flex:none; font-size:12px; font-weight:600; color:var(--text-3);
  font-variant-numeric:tabular-nums; }
.tr-lib-use{ flex:none; border:1px solid var(--border-2); background:var(--surface); color:#4f46e5;
  border-radius:8px; padding:6px 12px; font-family:inherit; font-size:12px; font-weight:700;
  cursor:pointer; transition:.15s; }
.tr-lib-use:hover{ background:var(--info-soft); border-color:#6366f1; }
.tr-lib-empty{ margin:0; padding:14px; font-size:12px; color:var(--text-3); }

/* Compact dropzone for the music track (the media dropzone stays the big one) */
.tr-drop-sm{ padding:16px 14px; gap:3px; margin-bottom:0; }
.tr-drop-sm .tr-drop-ico{ width:40px; height:40px; margin-bottom:2px; color:#6366f1; background:var(--info-soft); }
.tr-drop-sm:hover{ border-color:#6366f1; background:var(--info-soft); }
.tr-drop-sm.drag{ border-color:#6366f1; background:var(--info-soft); }
.tr-drop-sm .tr-drop-title{ font-size:14px; }

/* Per-channel mute. A muted row stays visible but recedes: hiding it would lose
   the reason the sound went away. */
.tr-ch-mute{ flex:none; width:28px; height:28px; border-radius:8px; cursor:pointer;
  border:1px solid var(--border-2); background:var(--surface); color:var(--text-2);
  display:inline-flex; align-items:center; justify-content:center; transition:.15s; }
.tr-ch-mute:hover{ border-color:#fe655d; color:var(--accent-text); }
.tr-ch-mute.is-muted{ background:var(--danger-soft); border-color:#f8c3d0; color:var(--danger); }
.tr-mix-row.is-off .tr-mix-label,
.tr-mix-row.is-off .tr-slider{ opacity:.4; }
.tr-mix-row.is-off .tr-mix-val{ color:var(--text-3); }
#dubChBgmRow .tr-slider{ accent-color:#6366f1; }
#dubChBgmRow .tr-mix-val{ color:#4f46e5; }
/* Switches inside the mixer follow the music accent (indigo), matching the
   sliders and library there so the mix section stays visually its own place. */
.tr-mix-body .tr-toggle input:checked{ background:linear-gradient(135deg,#818cf8,#6366f1); }

/* Numeric fields for fades / offset */
.tr-mix-opts{ display:flex; flex-direction:column; gap:11px; }
.tr-num-row{ display:flex; flex-wrap:wrap; gap:10px; }
.tr-num{ display:inline-flex; align-items:center; gap:7px; font-size:12px; font-weight:600;
  color:var(--text-2); border:1px solid var(--border-2); border-radius:8px; padding:6px 10px; background:var(--surface); }
.tr-num input{ width:56px; height:24px; border:none; background:transparent; font-family:inherit;
  font-size:13px; font-weight:700; color:var(--text); text-align:right; }
.tr-num input:focus{ outline:none; }
.tr-num-u{ color:var(--text-3); font-weight:600; }

/* Timeline. The speech lane comes from timings we already have, which is what
   makes the music decisions obvious instead of guesswork. */
.tr-tl{ position:relative; border:1px solid var(--border-2); border-radius:12px; background:var(--surface);
  padding:4px 0 0; overflow:hidden; }
.tr-tl-canvas{ display:block; width:100%; cursor:pointer; }
.tr-tl-playhead{ position:absolute; top:0; bottom:18px; width:2px; margin-left:-1px;
  background:var(--black); pointer-events:none; opacity:.75; }
.tr-tl-legend{ display:flex; align-items:center; gap:6px; flex-wrap:wrap; margin:0; padding:6px 10px 8px;
  font-size:12px; font-weight:600; color:var(--text-3); border-top:1px solid var(--border-2); }
.tr-tl-legend span:not(.tr-tl-key){ margin-right:8px; }
.tr-tl-key{ width:10px; height:10px; border-radius:8px; flex:none; }
.tr-tl-key-speech{ background:#fe655d; }
.tr-tl-key-bgm{ background:#6366f1; }
.tr-tl-key-duck{ background:#f59e0b; }

/* Clip lane: the song's own waveform, with the used slice highlighted. Unused
   parts stay visible (dimmed) rather than being cropped — you cannot drag to the
   chorus if the chorus is not on screen. */
.tr-clip{ position:relative; border:1px solid var(--border-2); border-radius:12px; background:var(--surface);
  overflow:hidden; }
.tr-clip-canvas{ display:block; width:100%; cursor:pointer; }
.tr-clip-bar{ display:flex; align-items:center; gap:10px; }
.tr-clip-play{ width:34px; height:34px; }
.tr-clip-read{ flex:1; font-size:12px; font-weight:700; color:#4f46e5;
  font-variant-numeric:tabular-nums; }

.tr-place-chips .tr-chip, .tr-jump-chips .tr-chip{ padding:6px 12px; font-size:12px; }
.tr-place-chips .tr-chip.on{ background:linear-gradient(135deg,#818cf8,#6366f1); border-color:transparent;
  color:#fff; box-shadow:var(--e-2); }
.tr-preset-chips .tr-chip{ padding:7px 13px; font-size:12px; }

/* Preview transport. This is the control people use most, so it is the one thing
   in the panel that looks like a player rather than a form. */
.tr-preview{ border:1px solid var(--border-2); border-radius:12px; background:var(--surface); padding:12px 13px; gap:11px; }
.tr-pv-bar{ display:flex; align-items:center; gap:11px; }
.tr-pv-play{ flex:none; width:38px; height:38px; border-radius:999px; border:none; cursor:pointer;
  color:#fff; background:linear-gradient(135deg,#818cf8,#6366f1); display:inline-flex;
  align-items:center; justify-content:center; box-shadow:var(--e-2); transition:.15s; }
.tr-pv-play:hover{ transform:translateY(-1px); }
.tr-pv-play.is-loading{ opacity:.6; pointer-events:none; }
.tr-pv-time{ flex:none; font-size:12px; font-weight:700; color:var(--text-2);
  font-variant-numeric:tabular-nums; min-width:34px; }
.tr-pv-track{ flex:1; height:8px; border-radius:999px; background:var(--canvas); cursor:pointer; position:relative; }
.tr-pv-fill{ height:100%; width:0%; border-radius:999px; background:linear-gradient(90deg,#818cf8,#6366f1); }
.tr-pv-tools{ display:flex; align-items:center; justify-content:space-between; gap:10px; flex-wrap:wrap; }
.tr-pv-toggles{ display:inline-flex; gap:8px; }
.tr-pv-btn{ border:1px solid var(--border-2); background:var(--surface); color:var(--text-2); border-radius:8px;
  padding:6px 11px; font-family:inherit; font-size:12px; font-weight:600; cursor:pointer; transition:.15s;
  user-select:none; -webkit-user-select:none; }
.tr-pv-btn:hover{ border-color:#6366f1; color:#4f46e5; background:var(--info-soft); }
.tr-pv-btn.on{ border-color:transparent; color:#fff; background:linear-gradient(135deg,#818cf8,#6366f1); }

/* Commit row */
.tr-mix-apply-sec{ gap:10px; }
.tr-btn-mix{ height:44px; color:#fff; background:linear-gradient(135deg,#818cf8,#6366f1 60%,#4f46e5);
  box-shadow:var(--e-2); }
.tr-btn-mix:hover:not(:disabled){ transform:translateY(-1px); box-shadow:var(--e-3); }
.tr-btn-mix:disabled{ background:var(--surface-hover); color:var(--text-3); box-shadow:none; }
.tr-mix-reset{ align-self:center; border:none; background:transparent; color:var(--text-3);
  font-family:inherit; font-size:12px; font-weight:600; cursor:pointer; text-decoration:underline; }
.tr-mix-reset:hover{ color:var(--text); }

@media (max-width:640px){
  .tr-mix-label{ width:74px; font-size:12px; }
  .tr-mix-val{ width:78px; font-size:12px; }
  .tr-mix-sum{ max-width:100%; margin-left:0; flex-basis:100%; }
  .tr-pv-tools{ flex-direction:column; align-items:stretch; }
}

/* Translate: video export */
.tr-dl-video{ border:none; cursor:pointer; color:#fff; background:linear-gradient(135deg,#fe655d,#fc3e57); font-family:inherit; }
.tr-dl-video:hover{ filter:brightness(1.05); }
.tr-dl-video.is-loading{ opacity:.6; pointer-events:none; }
.tr-video-wrap{ margin-top:12px; display:flex; flex-direction:column; gap:8px; }
.tr-video{ width:100%; max-height:360px; border-radius:12px; background:var(--black); }

/* Translate: uploaded-video preview (phase 1) */
.tr-input-video{ width:100%; max-height:320px; border-radius:12px; background:var(--black); margin-top:10px; margin-bottom:16px; }

/* ============ Translate: guided redesign (stepper + disclosure) ============ */
/* Journey stepper */
.tr-steps{ display:flex; align-items:center; justify-content:center; gap:0; list-style:none; padding:0; margin:0 0 20px;
  width:100%; }
.tr-stepx{ display:flex; align-items:center; gap:7px; flex:none; width:auto; min-width:0; color:var(--text-3); font-size:12px; font-weight:600;
  padding:0 12px; }
.tr-stepx-n{ flex:none; width:24px; height:24px; border-radius:999px; display:flex; align-items:center; justify-content:center;
  font-size:12px; font-weight:700; background:var(--canvas); color:var(--text-3); border:1.5px solid transparent; transition:all .2s; }
.tr-stepx-t{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.tr-stepx:not(:last-child)::after{ content:""; width:60px; height:2px; background:var(--canvas); border-radius:8px; margin-left: 17px; }
.tr-stepx.on{ color:var(--accent-text); }
.tr-stepx.on .tr-stepx-n{ background:linear-gradient(135deg,#fe655d,#fc3e57); color:#fff; box-shadow:var(--e-2); }
.tr-stepx.done{ color:#0f7a52; }
.tr-stepx.done .tr-stepx-n{ background:var(--success-tint); color:#0f7a52; border-color:#bff0d8; }
.tr-stepx.done:not(:last-child)::after{ background:#bff0d8; }
@media (max-width:640px){ .tr-stepx-t{ display:none; } .tr-stepx{ padding:0 2px; } .tr-stepx:not(:last-child)::after{ width:24px; margin-left:6px; } }

/* Generic block label */
.tr-block{ margin:16px 0; }
.tr-blk-label{ display:block; font-size:12px; font-weight:700; letter-spacing:.02em; text-transform:uppercase;
  color:var(--text-3); margin-bottom:8px; }

/* Collapsible advanced sections (native <details>) */
.tr-adv{ border:1px solid var(--border-2); border-radius:12px; margin:24px 0 16px; background:var(--surface); overflow:hidden; }
.tr-adv-sum{ cursor:pointer; list-style:none; padding:12px 14px; font-size:13px; font-weight:700; color:var(--text-2);
  display:flex; align-items:center; gap:8px; user-select:none; }
.tr-adv-sum::-webkit-details-marker{ display:none; }
.tr-adv-sum::before{ content:""; display:inline-block; width:16px; height:16px; flex:none;
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%239ca3af' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='9 6 15 12 9 18'/%3E%3C/svg%3E") center/contain no-repeat;
  transition:transform .15s; }
.tr-adv[open] .tr-adv-sum::before{ transform:rotate(90deg); }
.tr-adv[open] .tr-adv-sum{ border-bottom:1px solid var(--border-2); }
.tr-adv-body{ padding:14px; display:flex; flex-direction:column; gap:14px; }
.tr-adv .tr-find, .tr-adv .tr-mix{ padding:14px; margin:0; }

/* Sticky primary action bar */
.tr-actions.tr-sticky{ position:sticky; bottom:0; z-index:5; margin:16px -4px 0; padding:14px 4px 6px;
  background:linear-gradient(to top, #fff 62%, rgba(255,255,255,0)); }

/* Voice summary (collapsed) */
.tr-voice-summary{ display:flex; align-items:center; gap:10px; border:1px solid var(--border-2); border-radius:12px;
  background:var(--brand-coral-soft); padding:12px 14px; margin:0 0 16px; }
.tr-vs-ico{ display:flex; color:#fe655d; flex:none; }
.tr-vs-text{ flex:1; font-size:13px; color:var(--text-2); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.tr-vs-val{ color:var(--text); }
.tr-vs-change{ flex:none; border:1px solid var(--border-2); background:var(--surface); color:var(--accent-text); border-radius:8px;
  padding:6px 13px; font-family:inherit; font-size:12px; font-weight:700; cursor:pointer; }
.tr-vs-change:hover{ background:var(--brand-coral-soft); border-color:#fe655d; }
.tr-voice-block{ margin:0 0 16px; }

/* Transcript review: lighter, "tap to edit" feel */
.tr-review-head{ display:flex; align-items:center; justify-content:space-between; gap:8px 10px; flex-wrap:wrap; margin:4px 0 10px; }
.tr-review-title{ font-size:13px; font-weight:700; color:var(--text); }
.tr-review-actions{ display:inline-flex; align-items:center; gap:8px; flex-wrap:wrap; }
/* Download subtitles/text straight from the review step (no audio needed). */
.tr-dl-mini{ display:inline-flex; align-items:center; gap:5px; font-family:inherit; font-size:12px; font-weight:700;
  color:var(--accent-text); background:var(--brand-coral-soft); border:1px solid #ffd8d3; border-radius:8px; padding:5px 10px; cursor:pointer;
  transition:background .15s; }
.tr-dl-mini:hover{ background:#ffe1dd; }
.tr-dl-mini svg{ flex:none; }
.tr-seg{ border:1px solid transparent; border-bottom:1px solid var(--border-2); border-radius:0; background:transparent; padding:10px 4px; }
.tr-seg:hover{ background:var(--surface); }
.tr-seg-src{ font-size:12px; color:var(--text-3); font-style:italic; margin:0 0 4px; opacity:.75; }
.tr-seg-ta{ border:1px solid transparent; background:transparent; padding:6px 8px; border-radius:8px; transition:border-color .15s, background .15s; }
.tr-seg-ta:hover{ background:var(--surface); border-color:var(--border-2); }
.tr-seg-ta:focus{ background:var(--surface); border-color:#fe655d; box-shadow:0 0 0 3px rgba(254,101,93,.15); }

/* Results: video-first */
/* Subtitle choice for the video export. Sits directly above the export button
   because it changes what that button produces (and how long it takes). */
.tr-sub-opts{ display:flex; flex-wrap:wrap; align-items:center; gap:8px; margin:4px 0 10px; }
.tr-sub-label{ font-size:12px; font-weight:700; color:var(--text-3); margin-right:2px; }
.tr-sub-opts .tr-chip{ padding:6px 12px; font-size:12px; }
.tr-export-hero{ width:100%; margin:4px 0 6px; margin-bottom:32px; box-shadow:none; }
.tr-export-hero:hover:not(:disabled){ box-shadow:var(--e-2); }
.tr-export-hint{ font-size:12px; color:var(--text-3); margin:0 0 6px; }
/* Share sits BESIDE the video's own download link in the preview column: same
   deliverable, secondary weight, so it cannot be mistaken for the primary action
   — and it is in the one place that always shows the picture being looked at. */
.tr-share-video{ flex:0 0 auto; width:auto; height:42px; padding:0 16px; border:1px solid var(--border-2);
  background:var(--surface); color:var(--text); font-size:13px; }
.tr-preview-actions .tr-share-video{ display:inline-flex; align-items:center; justify-content:center; }
.tr-share-video:hover:not(:disabled){ border-color:#fe655d; color:var(--accent-text); }
.tr-share-video:disabled{ opacity:.6; cursor:default; }
.tr-audio-secondary{ opacity:.9; }

/* Translate: timing sync segmented toggle */
.tr-sync{ display:flex; align-items:center; gap:12px; margin:2px 0 4px; }
.tr-sync-label{ font-size:12px; font-weight:700; letter-spacing:.02em; text-transform:uppercase; color:var(--text-3); }
.tr-sync-opts{ display:inline-flex; border:1px solid var(--border-2); border-radius:8px; overflow:hidden; background:var(--surface); }
/* Enough style presets now that the segmented control has to wrap. */
#dubSubStyleOpts{ display:flex; flex-wrap:wrap; gap:6px; border:0; border-radius:0; overflow:visible; background:transparent; }
#dubSubStyleOpts .tr-sync-opt{ border:1px solid var(--border-2); border-radius:8px; padding:7px 11px; font-size:12px; }
#dubSubStyleOpts .tr-sync-opt + .tr-sync-opt{ border-left:1px solid var(--border-2); }
#dubSubStyleOpts .tr-sync-opt.on{ border-color:transparent; }
#dubSubStyleOpts .tr-sync-opt::before{ content:""; width:10px; height:10px; margin-right:6px;
  display:inline-block; vertical-align:-1px; border-radius:8px; border:1.5px solid #000; background:var(--surface); }
#dubSubStyleOpts .tr-sync-opt[data-substyle="classic"]::before{ background:var(--surface); border-color:#000; }
#dubSubStyleOpts .tr-sync-opt[data-substyle="tiktok"]::before{ background:#FFE94A; border-color:#000; }
#dubSubStyleOpts .tr-sync-opt[data-substyle="boxed"]::before{ background:var(--black); border-color:#fff; }
#dubSubStyleOpts .tr-sync-opt[data-substyle="highlight"]::before{ background:#FFE94A; border-color:#111; }
#dubSubStyleOpts .tr-sync-opt[data-substyle="clean"]::before{ background:var(--surface); border-color:#aaa; }
#dubSubStyleOpts .tr-sync-opt[data-substyle="neon"]::before{ background:var(--surface); border-color:#00E5FF; }
.tr-sync-opt{ border:none; background:transparent; color:var(--text-2); font-family:inherit; font-size:13px; font-weight:600;
  padding:8px 14px; cursor:pointer; transition:background .15s, color .15s; }
.tr-sync-opt + .tr-sync-opt{ border-left:1px solid var(--border-2); }
.tr-sync-opt.on{ color:#fff; background:linear-gradient(135deg,#fe655d,#fc3e57); }
.tr-sync-hint{ font-size:12px; color:var(--text-3); margin:0 0 14px; }
.tr-sub-adjust{ display:flex; flex-wrap:wrap; align-items:center; gap:10px; }
.tr-sub-scale{ display:inline-flex; align-items:center; gap:7px; font-size:12px; color:var(--text-2); }
.tr-sub-scale input[type=range]{ width:110px; accent-color:#fe655d; }
.tr-sub-scale b{ min-width:38px; font-size:12px; color:var(--text); font-variant-numeric:tabular-nums; }
#dubSubDefaultBtn{ border:1px solid var(--border-2); border-radius:8px; background:var(--surface); color:var(--text-2); padding:7px 11px; font-size:12px; font-weight:700; }
#dubSubDefaultBtn:hover{ border-color:#fe655d; color:var(--accent-text); background:var(--brand-coral-soft); }
.tr-sub-nudge{ display:inline-flex; gap:4px; }
.tr-sub-nudge button{ width:28px; height:28px; border:1px solid var(--border-2); border-radius:8px; background:var(--surface);
  color:var(--text-2); font-size:14px; line-height:1; cursor:pointer; }
.tr-sub-nudge button:hover{ border-color:#fe655d; color:var(--accent-text); background:var(--brand-coral-soft); }
.tr-keyboard-hint{ margin:0 0 8px; font-size:12px; color:var(--text-3); }

/* Translate: "what's next?" actions after a result */
.tr-next{ margin-top:16px; padding:16px; border:1px dashed var(--border-2); border-radius:12px; background:var(--coral-tint-2); }
.tr-next-title{ margin:0 0 12px; font-size:13px; font-weight:700; color:var(--text); }
.tr-next-btns{ display:flex; flex-wrap:wrap; gap:10px; }
.tr-next-btn{ display:inline-flex; align-items:center; gap:8px; border:1px solid var(--border-2); background:var(--surface);
  color:var(--text); border-radius:12px; padding:10px 16px; font-family:inherit; font-size:13px; font-weight:700;
  cursor:pointer; transition:background .15s, border-color .15s, transform .12s, box-shadow .15s; }
.tr-next-btn:hover{ background:var(--hover); border-color:#fe655d; transform:translateY(-1px); }
.tr-next-btn-primary{ color:#fff; border-color:transparent; background:linear-gradient(135deg,#fe655d,#fc3e57);
  box-shadow:var(--e-2); }
.tr-next-btn-primary:hover{ background:linear-gradient(135deg,#fe655d,#fc3e57); filter:brightness(1.05); }

/* ============ MOBILE POLISH for the studio feature panels ============ */
@media (max-width:560px){
  /* Tighter cards so content isn't squeezed by big paddings on phones */
  .vc, .vd, .tr{ padding:16px 14px 18px; border-radius:12px; margin-top:0; }
  .editor-scroll{ padding:16px 12px 10px; }
  /* These two screens bleed to the editor's padding edge (see the -16px rules
     in the max-width:899px block, which appear LATER in this file). This
     breakpoint drops that padding to 12px, so the bleed has to follow or the
     panel ends up 4px wider than the phone: a real horizontal scrollbar on
     /studio/subtitles, /translate/* and /changer at <=560px. The extra
     .editor-inner step is what lets this win — equal specificity would lose to
     the later block. */
  .editor-inner > #translatePanel, .editor-inner > #srtPanel{
    margin-left:-12px; margin-right:-12px; padding-left:12px; padding-right:12px;
  }
  .tr-category, .vd-category, .vc-category{ font-size:16px; }
  .tr-subtitle, .vd-subtitle, .clone-sub{ font-size:13px; }
  .tr-header{ margin-bottom:14px; }

  /* Journey stepper: keep it compact (labels already hide < 640) */
  .tr-steps{ margin-bottom:16px; }

  /* Find & replace stacks full-width instead of cramming on one line */
  .tr-find-in{ flex:1 1 100%; height:42px; }
  .tr-find-arrow{ display:none; }
  .tr-find-btn{ flex:1 1 100%; height:42px; }

  /* Phase-2 header + timing toggle wrap cleanly */
  .tr-phase2-head{ flex-wrap:wrap; }
  .tr-sync{ flex-wrap:wrap; gap:8px; }
  .tr-sync-opts{ width:100%; }
  .tr-keyboard-hint{ display:none; }
  .tr-sync-opt{ flex:1; text-align:center; }

  /* Mix row labels a bit narrower to leave room for the slider */
  .tr-mix-label{ width:72px; font-size:12px; }

  /* "What's next?" buttons stack full-width */
  .tr-next{ padding:14px; }
  .tr-next-btn{ flex:1 1 100%; justify-content:center; }

  /* Voice-picker & result actions: larger touch targets */
  .voice-item{ padding:13px 12px; }
  .tr-result-actions{ gap:8px; }

  /* Voice preview pick row stays on one line */
  .tr-voice-preview{ width:46px; }
}

/* Prevent iOS zoom-on-focus: text-entry surfaces at >=16px on touch phones */
@media (max-width:560px){
  #translatePanel textarea, #translatePanel input[type="text"], #translatePanel input[type="search"], #translatePanel select,
  #clonePanel textarea, #clonePanel input[type="text"], #designPanel textarea, #designPanel input[type="text"],
  .script-ta{ font-size:16px; }
}

@media (max-width:380px){
  .vc, .vd, .tr{ padding:14px 11px 16px; }
  .tr-voice-opt{ padding:12px; }
  .tr-drop{ padding:20px 12px; }
  .tr-seg{ padding:10px 0; }
}

/* Mobile topbar: keep it on one row (title truncates, controls stay compact) */
@media (max-width:560px){
  .topbar{ gap:8px; }
  .topbar-title{ font-size:15px; }
  #installBtn{ display:none !important; }   /* PWA install still available via the browser menu */
  .lang-picker-btn{ padding:0 6px; gap:4px; }
  .lang-picker-btn span#langPickerLabel{ display:none; }
  .lang-picker-flag{ display:inline; }
  .icon-pill{ width:38px; }
}

/* ---------------------------------------------------------------------------
   Subtitles → speech modal (Text-to-Speech screen)
   Reuses .tr-drop / .tr-file / .modal-script; adds the mode picker + progress.
   --------------------------------------------------------------------------- */
.modal-srt{ max-width:560px; }
.srt-drop{ margin-top:16px; text-align:center; }
/* "What's a .srt file?" helper under the uploader. */
.srt-help{ margin:16px 0 0; text-align:center; font-size:12px; line-height:1.55; color:var(--text-3); }

/* --- review panel --------------------------------------------------------- */
.srt-panel{ padding:2px 0 20px; }
.srt-head{ display:flex; align-items:flex-start; gap:14px; margin-bottom:6px; }
.srt-head-titles{ flex:1; }
.srt-category{ margin:0; font-size:20px; font-weight:800; letter-spacing:-.02em; color:var(--text); }
.srt-subtitle{ margin:4px 0 0; font-size:13px; line-height:1.5; color:var(--text-3); }
.srt-restart{ flex:none; height:36px; padding:0 14px; border-radius:8px; border:1px solid var(--border-2);
  background:var(--surface); color:var(--text-2); font-family:inherit; font-size:13px; font-weight:600; cursor:pointer; }
.srt-restart:hover{ background:var(--hover); color:var(--text); }
.srt-meta{ margin:4px 0 0; font-size:12px; font-weight:600; color:var(--text-3); }

/* What we did to a fragmented subtitle file (quality explainer) */
.srt-note{ display:block; margin:14px 0 0; padding:11px 13px; border-radius:12px;
  background:var(--success-tint); border:1px solid #bbf7d0; color:#166534;
  font-size:12px; font-weight:600; line-height:1.5; }
/* AI split explainer: purple accent so "AI touched this" reads at a glance */
.srt-ai-note{ background:var(--violet-soft); border-color:var(--violet-border); color:var(--violet-ink); }
/* Small AI pill on the toggle label */
.srt-ai-badge{ display:inline-block; margin-left:6px; padding:1px 7px; border-radius:999px;
  background:linear-gradient(90deg,#a855f7,#ec4899); color:#fff; font-size:12px;
  font-weight:800; letter-spacing:.04em; vertical-align:middle; }

/* Two visible steps — everything else is opt-in below */
.srt-step{ display:flex; gap:12px; margin-top:18px; }
.srt-step-n{ flex:none; display:flex; align-items:center; justify-content:center;
  width:26px; height:26px; border-radius:999px; font-size:12px; font-weight:800; color:#fff;
  background:linear-gradient(135deg,#fe655d,#fc3e57); }
.srt-step-body{ flex:1; min-width:0; }
.srt-step-t{ margin:3px 0 9px; font-size:11px; font-weight:700; letter-spacing:.09em;
  text-transform:uppercase; color:var(--text-3); }

/* Opt-in sections (review / advanced) */
.srt-adv{ margin-top:16px; border:1px solid var(--border-2); border-radius:12px; background:var(--surface); }
.srt-adv[open]{ background:var(--surface); }
.srt-adv-sum{ list-style:none; cursor:pointer; padding:13px 15px; font-size:13px; font-weight:700;
  color:var(--text-2); display:flex; align-items:center; gap:8px; }
.srt-adv-sum::-webkit-details-marker{ display:none; }
.srt-adv-sum::before{ content:""; width:7px; height:7px; flex:none; border-right:2px solid var(--text-3);
  border-bottom:2px solid var(--text-3); transform:rotate(-45deg); transition:transform .15s; }
.srt-adv[open] > .srt-adv-sum::before{ transform:rotate(45deg); }
.srt-adv-sum:hover{ color:var(--text); }
.srt-adv-body{ padding:0 15px 15px; display:flex; flex-direction:column; gap:12px; }
.srt-adv-hint{ margin:0; font-size:12px; line-height:1.5; color:var(--text-3); }
.srt-progress-sub{ margin:6px 0 0; font-size:12px; line-height:1.5; color:var(--text-3);
  font-variant-numeric:tabular-nums; }

.srt-aud-wrap{ margin-top:11px; }
.srt-aud-lead{ display:block; margin-bottom:7px; font-size:12px; font-weight:600; color:var(--text-3); }
.srt-try{ display:inline-flex; align-items:center; gap:6px; }
.srt-try.is-loading{ opacity:.6; pointer-events:none; }

.srt-tool{ display:flex; flex-direction:column; gap:7px; }
.srt-tool-label{ font-size:12px; font-weight:700; letter-spacing:.02em; text-transform:uppercase; color:var(--text-3); }
.srt-tool-row{ display:flex; align-items:center; gap:8px; flex-wrap:wrap; }

.srt-voice-chip{ display:inline-flex; align-items:center; gap:8px; height:40px; padding:0 10px 0 8px;
  border:1px solid var(--border-2); border-radius:12px; background:var(--surface); cursor:pointer; font-family:inherit;
  max-width:230px; }
.srt-voice-chip:hover{ border-color:#fe655d; background:var(--brand-coral-soft); }
.srt-voice-chip.own{ border-color:#fe655d; background:var(--brand-coral-soft); }
.srt-vc-av{ width:24px; height:24px; border-radius:999px; flex:none; }
.srt-vc-name{ font-size:13px; font-weight:600; color:var(--text); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.srt-vc-chev{ display:flex; color:var(--text-3); flex:none; }
.srt-voice-chip-sm{ height:32px; padding:0 8px 0 6px; border-radius:8px; max-width:170px; }
.srt-voice-chip-sm .srt-vc-av{ width:19px; height:19px; }
.srt-voice-chip-sm .srt-vc-name{ font-size:12px; }

.srt-mini-btn{ height:36px; padding:0 13px; border-radius:8px; border:1px solid var(--border-2);
  background:var(--surface); color:var(--text-2); font-family:inherit; font-size:12px; font-weight:600; cursor:pointer; }
.srt-mini-btn:hover{ border-color:#fe655d; background:var(--brand-coral-soft); color:var(--text); }

.srt-switch{ display:inline-flex; padding:3px; gap:3px; border:1px solid var(--border-2);
  border-radius:12px; background:var(--surface); }
/* nowrap + flex:none: the row has a fixed 32px height, so a wrapped label would
   spill out of the pill rather than just look cramped. Fits today at phone
   widths; this keeps it safe if a translation gets longer. */
.srt-switch button{ height:32px; padding:0 12px; border:none; border-radius:8px; background:transparent;
  font-family:inherit; font-size:12px; font-weight:600; color:var(--text-3); cursor:pointer;
  flex:none; white-space:nowrap; }
.srt-switch button:hover{ color:var(--text); }
.srt-switch button.on{ color:#fff; background:linear-gradient(135deg,#fe655d,#fc3e57); }

/* Quick voice audition chips */
.srt-aud-chips{ display:flex; flex-wrap:wrap; gap:7px; }
.srt-aud-chip{ display:inline-flex; align-items:center; gap:7px; height:34px; padding:0 11px 0 7px;
  border:1px solid var(--border-2); border-radius:999px; background:var(--surface); cursor:pointer; font-family:inherit; }
.srt-aud-chip:hover{ border-color:#fe655d; background:var(--brand-coral-soft); }
.srt-aud-chip.sel{ border-color:#fe655d; box-shadow:inset 0 0 0 1px #fe655d; background:var(--brand-coral-soft); }
.srt-aud-chip.is-loading{ opacity:.6; pointer-events:none; }
.srt-aud-av{ width:20px; height:20px; border-radius:999px; flex:none; }
.srt-aud-nm{ font-size:12px; font-weight:600; color:var(--text); max-width:130px;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

/* Line list */
.srt-list{ display:flex; flex-direction:column; gap:10px; max-height:460px; overflow-y:auto; }
.srt-seg.is-ready{ border-color:#bbf7d0; background:var(--success-tint); }
.srt-seg-actions{ flex-wrap:wrap; align-items:center; }
.srt-seg-status{ font-size:12px; font-weight:700; }
.srt-seg-status.ok{ color:#15803d; }
.srt-seg-status.err{ color:#dc2626; }

.srt-actions{ display:flex; align-items:center; gap:10px; margin-top:18px; }
.srt-actions .tr-btn{ flex:1; }
.srt-dl-all{ height:50px; padding:0 18px; }
.srt-progress{ margin-top:14px; }
.srt-progress-head{ display:flex; justify-content:space-between; font-size:12px; font-weight:600;
  color:var(--text-2); margin-bottom:6px; }
.srt-bar{ height:8px; border-radius:999px; background:var(--border-2); overflow:hidden; }
.srt-bar-fill{ height:100%; width:0%; border-radius:999px;
  background:linear-gradient(90deg,#ff9e99,#fe655d,#fc3e57); transition:width .2s ease; }

/* "Play the full audio" inside the result card. The playbar (in the editor
   footer) is where seeking/pause lives; this is the one-click way back to it,
   because closing the playbar must never strand the audio behind a download. */
.srt-play-full{ display:inline-flex; align-items:center; gap:6px; margin-left:auto;
  height:32px; padding:0 12px 0 9px; border-radius:999px; border:1px solid #86efac;
  background:var(--surface); color:#065f46; font-family:inherit; font-size:12px; font-weight:700;
  cursor:pointer; flex:none; }
.srt-play-full:hover{ background:var(--success-tint); border-color:#4ade80; }
.srt-play-full svg{ flex:none; }

/* Line status (the "stale audio" model from dubbing studios: every line says
   whether its clip is current, missing, stale or failed). */
.srt-row-dot{ width:8px; height:8px; border-radius:999px; flex:none; background:#d1d5db; margin-right:7px; }
.srt-row-dot.is-ready{ background:#10b981; }
.srt-row-dot.is-stale{ background:#f59e0b; }
.srt-row-dot.is-err{ background:#ef4444; }
.srt-row-dot.is-busy{ background:#6366f1; animation:srt-dot-pulse 1s ease-in-out infinite; }
@keyframes srt-dot-pulse{ 50%{ opacity:.35; } }
.srt-seg-status.warn{ color:var(--warn-ink); }

/* Chips: secondary facts (joined lines, detected language, AI split) as one
   quiet row instead of stacked full-width boxes. Details live in the tooltip. */
.srt-chips{ display:flex; flex-wrap:wrap; align-items:center; gap:6px; margin:12px 0 0; }
.srt-chip{ display:inline-flex; align-items:center; gap:6px; max-width:100%;
  padding:3px 9px; border-radius:999px; background:transparent; border:1px solid var(--border-2);
  font-size:11.5px; font-weight:600; color:var(--text-3); }
.srt-chip[hidden]{ display:none; }
.srt-chip-lang{ background:var(--info-soft); border-color:var(--info-border); color:var(--info-ink); }
.srt-chip-ai{ background:var(--violet-soft); border-color:var(--violet-border); color:var(--violet-ink); }
.srt-lang-cta{ flex:none; height:22px; padding:0 8px; border-radius:999px; border:1px solid var(--info-border);
  background:var(--info-soft); color:var(--info-ink); font-family:inherit; font-size:11px; font-weight:700; cursor:pointer; }

/* One quiet info line under the primary action: estimate · cost · trial · lock. */
.srt-info-line{ display:flex; flex-wrap:wrap; align-items:center; margin:9px 0 0;
  font-size:11.5px; line-height:1.5; color:var(--text-3); }
.srt-info-line[hidden]{ display:none; }
.srt-info-line > span{ white-space:normal; }
.srt-info-line > span[hidden]{ display:none; }
.srt-info-line > span:not([hidden]) + span:not([hidden]){ padding-left:10px; margin-left:10px;
  border-left:1px solid var(--border-2); }
.srt-trial-line.is-empty{ color:var(--warn-ink); font-weight:600; }

/* Export links live on the meta row (the script, not the audio). */
.srt-meta-links{ display:inline-flex; align-items:center; gap:4px; margin-left:8px; vertical-align:middle; }
.srt-meta-links-ico{ font-size:11px; color:var(--text-3); }
.srt-meta-sep{ color:var(--text-3); }
.srt-meta-links[hidden]{ display:none; }
.srt-inline-link{ border:0; background:none; padding:0; font:inherit; font-size:12px; font-weight:700;
  color:#e0524b; cursor:pointer; text-decoration:underline; text-underline-offset:2px; }
.srt-inline-link:hover{ color:#c23a34; }

/* Collapsed .srt explainer on the entry card. */
.srt-help-details{ margin:14px 0 0; text-align:center; font-size:12px; color:var(--text-3); }
.srt-help-details summary{ cursor:pointer; display:inline-block; color:var(--text-2); font-weight:600; }
.srt-help-details summary::-webkit-details-marker{ display:none; }
.srt-help-details summary::after{ content:"▾"; margin-left:4px; font-size:10px; }
.srt-help-details[open] summary::after{ content:"▴"; }
.srt-help-details .srt-help{ margin:8px auto 0; max-width:520px; }

.srt-aud-more{ color:var(--text-2); border-style:dashed; }
/* Icon-only secondary action (e.g. "hear this voice"): the label lives in the
   aria-label/tooltip, so the row stays quiet. */
.srt-icon-btn{ width:36px; padding:0; justify-content:center; }
/* ▶ on an audition chip: tapping plays the selected line in that voice. */
.srt-aud-play{ font-size:8px; line-height:1; color:var(--text-3); margin-left:1px; }

/* Entry alternatives: try a sample, or create subtitles from a video. */
.srt-alt-row{ display:flex; align-items:center; justify-content:center; gap:8px; flex-wrap:wrap;
  margin:12px 0 0; font-size:12px; }
.srt-link-btn{ border:0; background:none; padding:0; font:inherit; font-size:12px; font-weight:600;
  color:#e0524b; cursor:pointer; text-decoration:underline; text-underline-offset:2px; }
.srt-link-btn:hover{ color:#c23a34; }
.srt-alt-sep{ color:var(--text-3); }

/* Subtitles→Speech reuses the editor footer as the home of the playbar (that is
   where #playbar lives in the DOM), but the char counter + Generate row in it are
   Text-to-Speech furniture and would be wrong on this screen. Keep the footer as
   a bare player shell: mode-subtitles is set by setMode(). */
body.mode-subtitles .editor-footer .el-top{ display:none; }

/* The review panel is brought into view with scrollIntoView(block:"start"), and the
   topbar is sticky — without this the panel title lands UNDER it (visible on a
   phone: the heading is half cut off). */
#srtPanel{ scroll-margin-top: 76px; }

/* Free-trial counter and download-gate hint, in the flow where the decision is
   made (they used to live only in a footer this screen hides, or in the modal
   that opens AFTER the click). */
.srt-trial-line{ margin:10px 0 0; font-size:12px; font-weight:600; color:var(--text-2); }
.srt-trial-line.is-empty{ color:var(--warn-ink); }
.srt-gate-hint{ margin:8px 0 0; font-size:12px; line-height:1.5; color:var(--text-3); }
/* The join-lines toggle lives in the main flow now: it decides whether each
   subtitle sentence keeps its own timing. */
.srt-merge-row{ margin-top:14px; }
.srt-merge-hint{ margin-top:6px; }
.srt-lang-note{ display:flex; flex-wrap:wrap; align-items:center; gap:8px;
  background:var(--info-soft); border-color:var(--info-border); color:var(--info-ink); }
.srt-lang-cta{ flex:none; height:28px; padding:0 12px; border-radius:999px;
  border:1px solid var(--info-border); background:var(--surface); color:var(--info-ink); font-family:inherit;
  font-size:12px; font-weight:700; cursor:pointer; }
.srt-restore-note{ background:var(--warn-soft); border-color:var(--warn-border); color:var(--warn-ink); }
/* A finished render with missing lines is NOT the same as a clean one. */
/* Base card colours (the JS used to inline light-only hex values). Tokens keep
   light and the concurrent dark theme readable. */
.srt-result{ background:var(--success-tint);
  border:1px solid color-mix(in srgb, var(--success) 38%, transparent); color:var(--text); }
.srt-result.is-partial{ background:rgba(245,158,11,.10); border-color:rgba(245,158,11,.45); color:var(--warn-ink); }
/* Download asks for one free sign-in. The lock is part of the button LABEL
   (set in JS) rather than a ::after, because the brand pill already owns ::after
   for its hover/disabled overlay. */
@media (max-width:560px){ .srt-merge-hint{ display:none; } }
/* The support nudge is fixed bottom-right; keep it clear of the docked player. */
body.has-playbar .tip-nudge{ bottom:calc(150px + env(safe-area-inset-bottom, 0px)); }
@media (max-width:900px){
  body.has-playbar .tip-nudge{ bottom:calc(196px + env(safe-area-inset-bottom, 0px)); }
}

/* Dubbing screen: the voice chips reuse .srt-voice-chip but must fill their row
   (declared last so they win over the narrower .srt-voice-chip defaults). */
.tr-voice-chip{ flex:1; min-width:0; max-width:none; height:44px; }
.tr-spk-chip{ flex:1; min-width:0; max-width:none; height:40px; }

/* ---------------------------------------------------------------------------
   Video export / download status (dubbing results)
   The mux has no measurable progress, so it uses an animated bar + elapsed
   time; the download reports a real percentage.
   --------------------------------------------------------------------------- */
.tr-export-progress{ margin:10px 0 14px; }
.tr-export-progress .tr-stage{ display:inline-flex; align-items:center; }
.tr-stage-spin{ display:inline-block; width:13px; height:13px; margin-right:7px; flex:none;
  border-radius:999px; border:2px solid rgba(252,62,87,.25); border-top-color:#fc3e57;
  animation:spin .8s linear infinite; }
.tr-export-progress .tr-pct{ font-variant-numeric:tabular-nums; }
.tr-export-note{ margin:8px 0 0; font-size:12px; line-height:1.5; color:var(--text-3); }

/* Indeterminate: a sweeping gradient says "working" without faking a number.
   Shared by the export block and the prepare/upload block — the upload hand-off
   (browser done, server still accepting) is real work with no measurable
   percentage, and a frozen 0% there is what users read as "hung". */
.tr-export-progress.is-indeterminate .tr-bar-fill,
.tr-progress.is-indeterminate .tr-bar-fill{
  width:100%; transition:none;
  background:linear-gradient(90deg,#ffe3e0 0%,#fe655d 35%,#fc3e57 55%,#ffe3e0 100%);
  background-size:220% 100%;
  animation:tr-indet 1.3s linear infinite;
}
@keyframes tr-indet{ 0%{ background-position:120% 0; } 100%{ background-position:-120% 0; } }

/* Failed step: stop animating, turn red, and keep the message on screen. */
.tr-export-progress.is-error .tr-stage{ color:#dc2626; }
.tr-export-progress.is-error .tr-stage-spin{ display:none; }
.tr-export-progress.is-error .tr-bar-fill{ animation:none; background:#fca5a5; }
.tr-retry{ margin-top:4px; }

/* Render progress pinned inside the sticky footer of the dubbing review step.
   The editable line list can be hundreds of rows tall, so a bar placed in the
   normal flow scrolled out of view and left a spinning button as the only
   feedback. */
.tr-progress-sticky{ margin:0 0 10px; }
.tr-progress-sticky .tr-progress-head{ margin-bottom:6px; }
.tr-progress-sticky .tr-stage{ font-size:13px; }
.tr-progress-sticky .tr-pct{ font-variant-numeric:tabular-nums; }
.tr-progress-sticky .tr-bar{ height:8px; }
.tr-render-note{ margin:8px 0 0; font-size:12px; line-height:1.5; color:var(--text-3); }
/* "You changed the voice — regenerate to hear it": shown next to the footer
   button when the on-screen result no longer matches the current settings. */
.tr-stale-hint{ margin:0 0 8px; font-size:12px; line-height:1.5; color:var(--warn-ink); }
.tr-btn-attention{ box-shadow:0 0 0 3px rgba(254,101,93,.28); }

/* ========================================================================== */
/* Video dubbing workspace redesign — landing upload -> focused modal workflow */
/* ========================================================================== */
#translatePanel .tr{
  --tr-accent:#fe655d;
  --tr-accent-strong:#fc3e57;
  --tr-accent-soft:#fff1ef;
  --tr-surface:#ffffff;
  --tr-surface-muted:#f5f6f8;
  --tr-scrim:rgba(23,31,38,.52);
  border:0; border-radius:0; background:transparent; padding:0;
  min-height:calc(100vh - 124px);
  min-height:calc(100dvh - 124px);
}
#translatePanel .tr-header{ text-align:center; padding:38px 20px 0; margin:0 auto 22px; }
#translatePanel .tr-header .tr-category{ font-size:32px; line-height:1.15; letter-spacing:-.045em; }
#translatePanel .tr-header .tr-subtitle{ max-width:560px; font-size:14px; }
#translatePanel .tr-steps{ display:none; }

/* Empty state: intentionally sparse, matching a focused import canvas. */
#dubPhase1:not(.has-file){ width:min(100%,520px); margin:0 auto; }
#dubPhase1:not(.has-file) .tr-setup-grid,
#dubPhase1:not(.has-file) .tr-setup-actions{ display:none; }
.tr-upload-stage{ width:100%; }
#dubPhase1:not(.has-file) .tr-drop{
  width:min(100%,440px); min-height:230px; margin:0 auto; padding:44px 28px;
  border:1.5px dashed #d6d8df; border-radius:12px; background:var(--tr-surface-muted);
  box-shadow:none;
}
#dubPhase1:not(.has-file) .tr-drop:hover{
  border-color:var(--tr-accent); background:var(--tr-accent-soft);
  box-shadow:var(--e-3);
}
#dubPhase1:not(.has-file) .tr-drop-ico{ width:46px; height:46px; background:var(--tr-surface); }
#dubPhase1:not(.has-file) .tr-drop-title{ font-size:14px; }
#dubPhase1:not(.has-file) .tr-drop-desc{ font-size:12px; }
#dubPhase1:not(.has-file) .vc-tags{ display:none; }
#dubPhase1:not(.has-file) #dubStatus{ max-width:440px; margin:12px auto 0; }

/* Scrim belongs to the flow state, not the page, so all surrounding navigation
   remains visible but clearly inactive. */
body.dub-setup-open,
body.dub-review-open{ overflow:hidden; }
body.dub-setup-open::before,
body.dub-review-open::before{
  content:""; position:fixed; inset:0; z-index:80; background:var(--tr-scrim,rgba(23,31,38,.52));
  backdrop-filter:blur(1.5px); animation:tr-scrim-in .18s ease-out both;
}
@keyframes tr-scrim-in{ from{ opacity:0; } to{ opacity:1; } }
body.dub-setup-open #translatePanel .tr-header{ visibility:hidden; }

/* File-selected setup modal. */
#dubPhase1.has-file{
  position:fixed; z-index:90; inset:50% auto auto 50%; transform:translate(-50%,-50%);
  width:min(1100px,calc(100vw - 64px));
  /* vh FIRST, dvh second. A browser without dynamic-viewport units drops the
     dvh line entirely, and a dialog with NO height grows to its content (~1000px)
     inside a ~700px viewport — `overflow:hidden` then cuts off the footer with
     the primary button in it, and nothing scrolls it back into reach. That is
     exactly the "can't scroll to continue" dead end on the setup screen. vh is
     supported everywhere and is the correct value on desktop anyway.
     max-height + overflow-y:auto is the belt to that braces: even if a height
     rule is lost, the dialog is clamped to the viewport and can be scrolled, and
     .tr-setup-actions is sticky at its bottom so "continue" stays on screen. */
  height:min(700px,calc(100vh - 64px));
  height:min(700px,calc(100dvh - 64px));
  max-height:calc(100vh - 64px);
  display:flex; flex-direction:column; margin:0; overflow-x:hidden; overflow-y:auto;
  border:1px solid rgba(255,255,255,.72); border-radius:16px; background:var(--tr-surface);
  box-shadow:var(--e-3);
  animation:tr-dialog-in .22s cubic-bezier(.2,.8,.2,1) both;
}
@keyframes tr-dialog-in{
  from{ opacity:0; transform:translate(-50%,calc(-50% + 12px)) scale(.985); }
  to{ opacity:1; transform:translate(-50%,-50%) scale(1); }
}
#dubPhase1.has-file::before,
body.dub-review-open #dubPhase2::before{
  content:""; position:absolute; inset:0 0 auto; height:180px; pointer-events:none;
  background:radial-gradient(ellipse at 20% 0%,rgba(199,247,252,.42),transparent 72%);
}
#dubPhase1.has-file .tr-upload-stage{ display:none; }
#dubPhase1.has-file > #dubStatus{ position:absolute; z-index:3; left:56px; right:56px; top:18px; }
.tr-setup-grid{
  position:relative; z-index:1; display:grid; grid-template-columns:minmax(0,1fr) minmax(340px,440px);
  gap:56px; flex:1; min-height:0; padding:78px 56px 32px; overflow:auto;
}
.tr-setup-form{ min-width:0; align-self:start; }
.tr-setup-copy{ margin-bottom:24px; }
.tr-setup-kicker{ margin:0 0 7px; color:var(--tr-accent-strong); font-size:12px; font-weight:800;
  letter-spacing:.09em; text-transform:uppercase; }
.tr-setup-title{ margin:0 0 6px; color:var(--text); font-size:24px; line-height:1.2; letter-spacing:-.035em; }
.tr-setup-subtitle{ max-width:460px; margin:0; color:var(--text-3); font-size:12px; line-height:1.55; }
.tr-setup-form > .tr-block{ margin:0 0 18px; }
.tr-setup-form .tr-blk-label{ color:var(--text); font-size:12px; text-transform:none; letter-spacing:0; }
.tr-setup-form #dubTargets{
  max-height:128px; overflow:auto; gap:6px; padding:8px;
  border:1px solid var(--border-2); border-radius:8px; background:var(--tr-surface-muted);
}
.tr-setup-form #dubTargets .tr-chip{ padding:6px 10px; border-radius:8px; background:var(--tr-surface); font-size:12px; }
.tr-setup-form #dubTargets .tr-chip.on{
  color:#bd302b; border-color:#ffd1cc; background:var(--tr-accent-soft); box-shadow:none;
}
.tr-setup-form > .tr-adv{ margin:0; border:0; background:transparent; overflow:visible; }
.tr-setup-form > .tr-adv > .tr-adv-sum{ padding:9px 0; font-size:12px; }
.tr-setup-form > .tr-adv[open] > .tr-adv-sum{ border-bottom:0; }
.tr-setup-form > .tr-adv > .tr-adv-body{
  max-height:250px; margin-top:6px; padding:14px; overflow:auto;
  border:1px solid var(--border-2); border-radius:8px; background:var(--tr-surface-muted);
}
.tr-setup-preview{ position:relative; align-self:start; min-width:0; }
.tr-setup-close{
  position:fixed; z-index:4; top:calc(50% - min(350px,calc(50dvh - 32px)) + 16px);
  right:calc(50% - min(550px,calc(50vw - 32px)) + 16px);
  width:40px; height:40px; display:inline-flex; align-items:center; justify-content:center;
  border:0; border-radius:8px; background:transparent; color:var(--text-2); cursor:pointer;
}
.tr-setup-close:hover{ background:var(--hover); color:var(--text); }
.tr-setup-close:focus-visible{ outline:2px solid var(--focus); outline-offset:2px; }
.tr-setup-preview-frame{
  position:relative; aspect-ratio:16/9; overflow:hidden; padding:8px;
  border:1px solid var(--border-2); border-radius:16px; background:var(--tr-surface);
  box-shadow:var(--e-3);
}
.tr-setup-preview-frame .tr-input-video{
  width:100%; height:100%; max-height:none; margin:0; border-radius:8px; object-fit:contain; background:var(--black);
}
.tr-setup-audio-placeholder{
  position:absolute; inset:8px; display:flex; align-items:center; justify-content:center;
  border-radius:8px; color:var(--tr-accent); background:linear-gradient(145deg,var(--tr-accent-soft),#f8f9fb);
}
.tr-setup-preview-frame:has(.tr-input-video[style*="block"]) .tr-setup-audio-placeholder{ display:none; }
.tr-setup-preview .tr-file{ margin:10px 0 0; padding:8px 4px; border:0; background:transparent; }
.tr-setup-preview .tr-file-ico{ color:var(--tr-accent); }
.tr-setup-preview .tr-file-name{ font-size:12px; }
.tr-setup-actions{
  position:relative !important; z-index:2; flex:none; display:flex; justify-content:flex-end; align-items:center;
  min-height:68px; margin:0 !important; padding:12px 18px !important;
  border-top:1px solid var(--border-2); background:var(--glass-bg-strong) !important;
}
.tr-setup-actions .tr-btn{ width:auto; min-width:130px; height:42px; border-radius:8px; font-size:13px; }
.tr-setup-actions .tr-cancel{ width:auto; margin:0 10px 0 0; order:-1; }

/* Review/render phase uses the same centered workspace instead of returning to
   the old inline card. */
body.dub-review-open #translatePanel .tr-header{ visibility:hidden; }
body.dub-review-open #dubPhase2{
  position:fixed; z-index:90; inset:50% auto auto 50%; transform:translate(-50%,-50%);
  width:min(1160px,calc(100vw - 64px));
  height:min(760px,calc(100vh - 64px));      /* fallback: see the note above */
  height:min(760px,calc(100dvh - 64px));
  max-height:calc(100vh - 64px);
  /* No top padding: the sticky header supplies it. With padding here, `top:0` on
     the header sticks BELOW that padding and scrolled lines show in the gap above
     it. */
  overflow:auto; padding:0 40px 0; border:1px solid rgba(255,255,255,.72);
  border-radius:16px; background:var(--tr-surface); box-shadow:var(--e-3);
  animation:tr-dialog-in .22s cubic-bezier(.2,.8,.2,1) both;
}
body.dub-review-open #dubPhase2 > *{ position:relative; z-index:1; }
/* A real dialog header: it stays while the body scrolls. Bled out to the dialog
   edges and opaque, so scrolled content passes behind it rather than through it.
   Overrides the position:relative that `#dubPhase2 > *` sets, hence the same
   specificity plus the child selector.
   padding-right leaves room for the button cluster: close (40) + gap + Start over. */
body.dub-review-open #dubPhase2 > .tr-phase2-head{
  position:sticky; top:0; z-index:8; align-items:center; min-height:64px;
  margin:0 -40px 18px; padding:10px 216px 10px 40px;
  background:var(--tr-surface); box-shadow:0 1px 0 var(--border-2);
}
.tr-phase2-lede{ margin:0 0 18px; }
body.dub-review-open .tr-phase2-head .tr-category{ font-size:20px; margin:0; }
/* Close sits in the corner where a dialog's close always sits; "Start over" moves
   inboard of it, because the destructive action must not be the one under the
   thumb that is reaching for X. */
body.dub-review-open .tr-restart{ position:absolute; top:18px; right:78px; min-height:40px; }
/* Close sits in the corner where a dialog's close always sits; "Start over" steps
   inboard of it, because the destructive action must not be the one under the thumb
   that is reaching for X. Both ride the sticky header, so both stay reachable. */
.tr-review-close{ display:none; }
body.dub-review-open .tr-review-close{
  position:absolute; top:16px; right:24px; z-index:2;
  display:inline-flex; align-items:center; justify-content:center;
  width:32px; height:32px; padding:0; border:0; border-radius:8px;
  color:var(--text-2); background:transparent; cursor:pointer;
  transition:background .15s, color .15s;
}
body.dub-review-open .tr-review-close:hover{ background:var(--hover); color:var(--text); }
body.dub-review-open .tr-review-close:focus-visible{ outline:2px solid var(--focus); outline-offset:2px; }
body.dub-review-open #dubPhase2 .tr-actions.tr-sticky{
  bottom:0; margin:20px -40px 0; padding:14px 40px 16px;
  border-top:1px solid var(--border-2); background:var(--glass-bg-strong);
  backdrop-filter:blur(8px);
}
body.dub-review-open #dubPhase2 .tr-actions.tr-sticky .tr-btn{ width:auto; min-width:150px; margin-left:auto; }

@media (max-width:899px){
  #translatePanel .tr{ min-height:calc(100vh - 100px); }
  #translatePanel .tr{ min-height:calc(100dvh - 100px); }
  #translatePanel .tr-header{ padding-top:22px; }
  #translatePanel .tr-header .tr-category{ font-size:24px; }
  #dubPhase1.has-file,
  body.dub-review-open #dubPhase2{
    inset:0; width:100%; height:100vh; height:100dvh; transform:none; border:0; border-radius:0;
    animation:tr-sheet-in .2s ease-out both;
  }
  @keyframes tr-sheet-in{ from{ opacity:0; transform:translateY(14px); } to{ opacity:1; transform:none; } }
  .tr-setup-grid{ grid-template-columns:1fr; gap:24px; padding:70px 20px 28px; }
  .tr-setup-preview{ grid-row:1; }
  .tr-setup-form{ grid-row:2; }
  .tr-setup-close{ position:fixed; top:12px; right:12px; }
  .tr-setup-preview-frame{ max-width:560px; margin:0 auto; }
  body.dub-review-open #dubPhase2{ padding:0 18px 0; }
  /* Full-bleed sheet: the sticky header bleeds to the sheet edges and the two
     controls ride it, so the way out is on screen at every scroll position. */
  body.dub-review-open #dubPhase2 > .tr-phase2-head{
    min-height:48px; gap:8px; margin:0 -18px 14px; padding:8px 18px;
  }
  body.dub-review-open .tr-phase2-head .tr-category{ font-size:16px; margin:0; }
  /* In flow here: 390px cannot spare a corner for absolute controls, and a title
     squeezed to a hundred pixels wraps into a wall. */
  body.dub-review-open .tr-restart{ position:static; flex:none; padding:8px 10px; font-size:12px; }
  body.dub-review-open .tr-review-close{ position:static; flex:none; width:36px; height:36px; }
  body.dub-review-open #dubPhase2 .tr-actions.tr-sticky{ margin:20px -18px 0; padding:12px 18px; }
}
@media (max-width:560px){
  #translatePanel .tr-header .tr-category{ font-size:24px; }
  #dubPhase1:not(.has-file) .tr-drop{ min-height:210px; }
  .tr-setup-grid{ padding-left:16px; padding-right:16px; }
  .tr-setup-title{ font-size:20px; }
  .tr-setup-form #dubTargets{ max-height:156px; }
  .tr-setup-actions{ padding:10px 16px !important; }
  .tr-setup-actions .tr-btn{ width:100%; }
}
@media (prefers-reduced-motion:reduce){
  body.dub-setup-open::before,
  body.dub-review-open::before,
  #dubPhase1.has-file,
  body.dub-review-open #dubPhase2{ animation:none; }
}

/* Compact native disclosure for target languages: closed it reads like the
   single select field in the reference; open it reveals the multi-select chips. */
.tr-target-picker{ position:relative; border:1px solid var(--border-2); border-radius:8px; background:var(--tr-surface-muted); }
.tr-target-picker > summary{
  display:flex; align-items:center; justify-content:space-between; gap:10px; min-height:42px;
  padding:0 12px; list-style:none; color:var(--text); font-size:12px; font-weight:600; cursor:pointer;
}
.tr-target-picker > summary::-webkit-details-marker{ display:none; }
.tr-target-picker > summary svg{ flex:none; color:var(--text-3); transition:transform .18s ease; }
.tr-target-picker[open] > summary svg{ transform:rotate(180deg); }
.tr-target-picker[open] > summary{ border-bottom:1px solid var(--border-2); }
.tr-target-selected{ min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.tr-target-selected.is-placeholder{ color:var(--text-3); font-weight:500; }
.tr-setup-form .tr-target-picker #dubTargets{
  max-height:150px; margin:0; padding:8px; border:0; border-radius:0 0 8px 8px; background:var(--tr-surface);
}

/* Landing fidelity pass: proportions and hierarchy based on the supplied
   Synthesia markup/reference, expressed with SaydiVoice's coral accent. */
#translatePanel{
  position:relative; min-height:calc(100vh - 58px); margin:-34px -40px -60px; padding:34px 40px 60px;
  position:relative; min-height:calc(100dvh - 58px); margin:-34px -40px -60px; padding:34px 40px 60px;
  background:
    radial-gradient(ellipse 72% 310px at 50% 0,var(--hero-glow),rgba(255,255,255,0) 76%),
    radial-gradient(circle,var(--hero-dots) .75px,transparent .9px);
  background-size:auto,16px 16px;
  background-position:0 0,0 0;
}
#translatePanel[data-kind="audio"] .tr-youtube-field{ display:none; }
#translatePanel[data-kind="audio"] .tr-upload-divider{ display:none; }
#translatePanel .tr-header{ padding-top:42px; margin-bottom:24px; }
#translatePanel .tr-header .tr-category{ font-size:32px; font-weight:750; letter-spacing:-.04em; }
#translatePanel .tr-header .tr-subtitle{ margin-top:8px; color:var(--text-3); font-size:13px; }
#dubPhase1:not(.has-file){ width:100%; max-width:none; }
#dubPhase1:not(.has-file) .tr-upload-stage{ width:100%; }
#dubPhase1:not(.has-file) .tr-drop{
  width:min(100%,460px); min-height:220px; flex-direction:row; justify-content:center; align-items:center;
  gap:12px; padding:28px; border-width:1px; border-color:#d7d9e0; border-radius:16px;
  background:var(--canvas);
}
#dubPhase1:not(.has-file) .tr-drop:hover{ transform:none; box-shadow:none; background:var(--canvas); }
#dubPhase1:not(.has-file) .tr-drop-ico,
#dubPhase1:not(.has-file) .vc-tags{ display:none; }
#dubPhase1:not(.has-file) .tr-drop-title{
  display:inline-flex; align-items:center; justify-content:center; min-height:40px; padding:0 16px;
  border:1px solid #e4e5e9; border-radius:8px; background:var(--surface); color:var(--text);
  font-size:12px; font-weight:700; box-shadow:var(--e-1);
}
#dubPhase1:not(.has-file) .tr-drop-desc{ max-width:210px; color:#6f7480; font-size:12px; text-align:left; }

.tr-recent{ width:min(100%,1080px); margin:52px auto 0; text-align:left; }
.tr-recent-title{ margin:0 0 16px; color:var(--text); font-size:14px; font-weight:750; letter-spacing:-.01em; }
.tr-recent-head,.tr-recent-row{
  display:grid; grid-template-columns:minmax(0,1fr) 180px 120px; align-items:center; column-gap:20px;
}
.tr-recent-head{ min-height:28px; padding:0 8px; border-bottom:1px solid #e8eaee; color:#8b8f99; font-size:12px; }
.tr-recent-row{ min-height:56px; padding:6px 8px; border-bottom:1px solid #eef0f2; color:#3f4350; font-size:12px; }
.tr-recent-name{ display:flex; align-items:center; gap:10px; min-width:0; font-weight:600; }
.tr-recent-name > span:last-child{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.tr-recent-thumb{ display:inline-flex; align-items:center; justify-content:center; flex:none; width:38px; height:28px; border-radius:8px; color:#767b87; background:var(--surface-hover); }
.tr-recent-langs{ display:flex; align-items:center; gap:6px; color:#8b8f99; }
.tr-recent-langs b{ min-width:27px; padding:3px 6px; border:1px solid #e4e6ea; border-radius:8px; color:#555a67; background:var(--surface); font-size:12px; text-align:center; }
.tr-recent-row time{ color:#767b87; }
.tr-recent-empty{ margin:0; padding:24px 8px; border-bottom:1px solid #eef0f2; color:#8b8f99; font-size:12px; }
.tr-recent-start{ margin-left:10px; border:1px solid #ffd0ca; border-radius:8px; background:var(--brand-coral-soft); color:var(--accent-text); padding:6px 10px; font:inherit; font-size:12px; font-weight:750; cursor:pointer; }
.tr-recent-start:hover{ background:#ffe1dd; }
.tr-recent-skel-row{ display:grid; grid-template-columns:minmax(0,1fr) 180px 120px; gap:20px; align-items:center;
  min-height:56px; padding:6px 8px; border-bottom:1px solid #eef0f2; }
.tr-recent-skel-row span{ height:11px; border-radius:8px; background:linear-gradient(90deg,#eef0f2 25%,#f7f8f9 37%,#eef0f2 63%);
  background-size:400% 100%; animation:tr-skel 1.2s ease-in-out infinite; }
@keyframes tr-skel{ 0%{ background-position:100% 0; } 100%{ background-position:0 0; } }

/* Reopenable rows. The delete button is absolutely positioned rather than given
   its own grid column, so the three-column layout (and its two responsive
   overrides) stays exactly as it was. */
.tr-recent-row.is-openable{ position:relative; cursor:pointer; border-radius:8px; transition:background .12s ease; }
.tr-recent-row.is-openable:hover{ background:var(--surface); }
.tr-recent-row.is-openable:focus-visible{ outline:2px solid #2a7de1; outline-offset:-2px; }
.tr-recent-row.is-opening{ opacity:.55; pointer-events:none; }
.tr-recent-row.is-opening::after{ content:""; position:absolute; right:38px; top:50%;
  width:13px; height:13px; margin-top:-6.5px; border:2px solid rgba(254,101,93,.25);
  border-top-color:#fe655d; border-radius:999px; animation:tr-working-spin .8s linear infinite; }
.tr-recent-state{
  flex:none; margin-left:4px; padding:2px 7px; border-radius:16px;
  font-size:12px; font-weight:700; letter-spacing:.02em; text-transform:uppercase;
}
.tr-recent-state.is-done{ color:#1c7a4a; background:var(--success-tint); }
.tr-recent-state.is-busy{ color:#8a5a00; background:#fdf1d8; }
.tr-recent-state.is-error{ color:#9b2c2c; background:var(--coral-tint-2); }
.tr-recent-del{
  position:absolute; top:50%; right:8px; transform:translateY(-50%);
  display:inline-flex; align-items:center; justify-content:center;
  width:28px; height:28px; padding:0; border:0; border-radius:8px;
  color:#8b8f99; background:transparent; cursor:pointer;
  opacity:0; transition:opacity .12s ease, color .12s ease, background .12s ease;
}
.tr-recent-row:hover .tr-recent-del,
.tr-recent-row:focus-within .tr-recent-del{ opacity:1; }
.tr-recent-del:hover{ color:#c53030; background:var(--coral-tint-2); }
/* Always visible where hover does not exist, or the action would be unreachable. */
@media (hover:none){ .tr-recent-del{ opacity:1; } }
.tr-recent-del:focus-visible{ opacity:1; outline:2px solid #2a7de1; outline-offset:1px; }
.tr-recent-del-txt{ display:none; font-size:12px; font-weight:700; }
@media (hover:none){ .tr-recent-del{ width:auto; padding:0 8px; gap:5px; } .tr-recent-del-txt{ display:inline; } }

/* Setup dialog refinements matching the supplied split workspace. */
#dubPhase1.has-file{ width:min(1020px,calc(100vw - 72px));
  height:min(650px,calc(100vh - 72px)); height:min(650px,calc(100dvh - 72px)); }
#dubPhase1.has-file::before{ height:240px; background:linear-gradient(150deg,rgba(208,250,252,.66),rgba(255,255,255,0) 70%); }
.tr-setup-grid{ grid-template-columns:minmax(0,1fr) minmax(340px,420px); gap:64px; padding:118px 64px 32px; }
.tr-setup-kicker{ display:none; }
.tr-setup-title{ font-size:24px; }
.tr-setup-subtitle{ max-width:390px; font-size:12px; }
.tr-setup-form .tr-blk-label{ margin-bottom:7px; font-size:12px; }
.tr-target-picker > summary{ min-height:40px; }
.tr-setup-preview-frame{ padding:10px; border-radius:16px; }
.tr-setup-preview .tr-file{ padding-left:10px; }
.tr-setup-actions{ min-height:58px; }
.tr-setup-actions .tr-btn{ min-width:110px; height:38px; }

@media (max-width:1180px){
  #translatePanel{ margin:-26px -24px -20px; padding:26px 24px 40px; }
}
@media (max-width:899px){
  #translatePanel{ margin:-20px -16px -8px; padding:20px 16px 32px; }
  #translatePanel .tr-header{ padding-top:24px; }
  .tr-recent{ margin-top:40px; }
  .tr-recent-head,.tr-recent-row{ grid-template-columns:minmax(0,1fr) 120px 84px; column-gap:10px; }
  .tr-setup-grid{ padding-top:70px; }
}
@media (max-width:560px){
  #translatePanel .tr-header .tr-category{ font-size:24px; }
  #dubPhase1:not(.has-file) .tr-drop{ min-height:190px; flex-direction:column; }
  #dubPhase1:not(.has-file) .tr-drop-desc{ text-align:center; }
  .tr-recent-head{ display:none; }
  .tr-recent-row{ grid-template-columns:1fr auto; row-gap:6px; padding:10px 4px; }
  .tr-recent-langs{ grid-column:1; padding-left:48px; }
  .tr-recent-row time{ grid-column:2; grid-row:1; }
}

/* Scale & balance pass — the translate canvas should use the editor, not look
   like a small widget inside the generic 1080px content column. */
.editor:has(#translatePanel[style*="block"]) .editor-inner{ max-width:none; width:100%; }
#translatePanel .tr-header{ padding-top:64px; margin-bottom:34px; }
#translatePanel .tr-header .tr-category{ font-size:32px; line-height:1.12; font-weight:780; }
#translatePanel .tr-header .tr-subtitle{ max-width:650px; margin-top:11px; font-size:15px; line-height:1.5; }
#dubPhase1:not(.has-file) .tr-drop{
  width:min(100%,560px); min-height:264px; gap:15px; padding:36px;
}
#dubPhase1:not(.has-file) .tr-drop-title{
  min-height:46px; padding:0 20px; border-radius:8px; font-size:14px;
}
#dubPhase1:not(.has-file) .tr-drop-desc{ max-width:255px; font-size:13px; line-height:1.45; }
.tr-recent{ width:calc(100% - 72px); max-width:1280px; margin-top:76px; }
.tr-recent-title{ margin-bottom:20px; font-size:16px; }
.tr-recent-head{ min-height:34px; padding:0 10px; font-size:12px; }
.tr-recent-row{ min-height:66px; padding:8px 10px; font-size:13px; }
.tr-recent-head,.tr-recent-row{ grid-template-columns:minmax(0,1fr) 220px 145px; column-gap:24px; }
.tr-recent-thumb{ width:44px; height:32px; }
.tr-recent-langs b{ min-width:32px; padding:4px 7px; font-size:12px; }
.tr-recent-empty{ padding:30px 10px; font-size:13px; }

/* The setup/review workspace follows the same larger visual scale. */
#dubPhase1.has-file{ width:min(1180px,calc(100vw - 64px));
  height:min(720px,calc(100vh - 64px)); height:min(720px,calc(100dvh - 64px)); }
.tr-setup-grid{ grid-template-columns:minmax(0,1fr) minmax(400px,480px); gap:72px; padding:124px 72px 38px; }
.tr-setup-title{ margin-bottom:8px; font-size:32px; }
.tr-setup-subtitle{ max-width:470px; font-size:13px; line-height:1.55; }
.tr-setup-copy{ margin-bottom:30px; }
.tr-setup-form .tr-blk-label{ margin-bottom:9px; font-size:13px; }
.tr-target-picker > summary{ min-height:46px; padding:0 14px; font-size:13px; }
.tr-setup-form #dubTargets .tr-chip{ padding:8px 12px; font-size:12px; }
.tr-setup-form > .tr-adv > .tr-adv-sum{ min-height:44px; font-size:13px; }
.tr-setup-preview .tr-file-name{ font-size:13px; }
.tr-setup-actions{ min-height:68px; padding-left:24px !important; padding-right:24px !important; }
.tr-setup-actions .tr-btn{ min-width:128px; height:44px; font-size:14px; }
/* Scoped to wide screens. Unscoped, this size refinement landed AFTER the
   max-width:899px block that turns the dialog into a full-bleed sheet, so on a
   phone it clamped the sheet back to 100vw - 64px: a 390px screen got a 326px
   panel sitting at left:0, i.e. a sheet with a 64px bite out of its right edge. */
@media (min-width:900px){
  body.dub-review-open #dubPhase2{ width:min(1220px,calc(100vw - 64px));
    height:min(780px,calc(100vh - 64px)); height:min(780px,calc(100dvh - 64px)); }
  body.dub-review-open .tr-phase2-head .tr-category{ font-size:20px; margin:0; }
  body.dub-review-open .tr-phase2-lede{ max-width:680px; font-size:14px; }
}

@media (max-width:1180px){
  #translatePanel .tr-header{ padding-top:48px; }
  #translatePanel .tr-header .tr-category{ font-size:32px; }
  #dubPhase1:not(.has-file) .tr-drop{ width:min(100%,520px); min-height:244px; }
  .tr-recent{ width:calc(100% - 40px); margin-top:60px; }
  .tr-recent-head,.tr-recent-row{ grid-template-columns:minmax(0,1fr) 170px 110px; }
}

/* Upload entry as one dashed card (reference: icon tile, big title, subtitle,
   red file CTA, fact pills, link divider + link row). Scoped under the new
   .tr-upload-group wrapper and its .srt-upload-group mirror (subtitles) so the
   older row-style overrides above and the BGM dropzone are untouched.
   Specificity beats the #dubPhase1:not(.has-file) row rules and the #srtPanel
   row rules without !important. */
.tr-upload-group, .srt-upload-group{ display:flex; flex-direction:column; width:min(100%,620px); margin:0 auto;
  border:1.5px dashed #dbe0e8; border-radius:16px; background:var(--surface); padding:42px 30px 34px; }
/* Hovering either upload card tints its dashed border coral — the same affordance
   the dropzone had before the redesign. */
.tr-upload-group, .srt-upload-group{ transition:border-color .15s; }
.tr-upload-group:hover, .srt-upload-group:hover{ border-color:#fe655d; }
#dubPhase1:not(.has-file) .tr-upload-group .tr-drop,
#srtPanel #srtUpload .srt-upload-group .srt-drop{
  width:100%; min-height:0; margin:0 0 28px; padding:0; gap:0;
  border:0; border-radius:0; background:none; box-shadow:none;
  flex-direction:column; align-items:center;
}
#dubPhase1:not(.has-file) .tr-upload-group .tr-drop{ margin:0 0 28px; }
#srtPanel #srtUpload .srt-upload-group .srt-drop{ margin:16px 0 0; }
#dubPhase1:not(.has-file) .tr-upload-group .tr-drop:hover,
#srtPanel #srtUpload .srt-upload-group .srt-drop:hover{
  transform:none; box-shadow:none; background:none; border-color:transparent;
}
/* Drag + error feedback still needs a wash now that the resting card is bare. */
#dubPhase1:not(.has-file) .tr-upload-group .tr-drop.drag,
#srtPanel #srtUpload .srt-upload-group .srt-drop.drag{ background:var(--warn-tint); border-radius:16px; }
#dubPhase1:not(.has-file) .tr-upload-group .tr-drop.error,
#srtPanel #srtUpload .srt-upload-group .srt-drop.error{ background:var(--danger-soft); border-radius:16px; }
#dubPhase1:not(.has-file) .tr-upload-group .tr-drop-ico,
#srtPanel #srtUpload .srt-upload-group .srt-drop .tr-drop-ico{
  display:flex; width:68px; height:68px; border-radius:16px; margin:0 0 20px;
  background:var(--coral-tint-2); color:#f04438;
}
#dubPhase1:not(.has-file) .tr-upload-group .tr-drop-title,
#srtPanel #srtUpload .srt-upload-group .srt-drop .tr-drop-title{
  display:block; min-height:0; padding:0; border:0; border-radius:0; background:none; box-shadow:none;
  font-size:20px; font-weight:800; letter-spacing:-.02em; color:var(--text); white-space:normal;
}
#dubPhase1:not(.has-file) .tr-upload-group .tr-drop-desc,
#srtPanel #srtUpload .srt-upload-group .srt-drop .tr-drop-desc{
  max-width:none; margin:8px 0 0; font-size:14px; line-height:1.5; color:var(--text-3); text-align:center;
}
/* The red CTA is a visual label inside the card button (the whole card opens
   the picker), so it is aria-hidden and never a nested <button>. */
.tr-upload-group .tr-pick-cta, .srt-upload-group .tr-pick-cta{ display:inline-flex; align-items:center; gap:8px; margin:22px 0 0;
  padding:13px 30px; border-radius:12px; background:linear-gradient(135deg,#ff9e99,#fe655d 55%,#fc3e57); color:#fff;
  font-size:15px; font-weight:600;
  transition:background .15s; }
.tr-upload-group .tr-drop:hover .tr-pick-cta, .srt-upload-group .srt-drop:hover .tr-pick-cta{ filter:brightness(1.04);
  box-shadow:var(--e-2); }
.tr-upload-group .tr-facts, .srt-upload-group .tr-facts{ display:flex; flex-wrap:wrap; justify-content:center; gap:8px; margin:20px 0 0; }
.tr-upload-group .tr-fact, .srt-upload-group .tr-fact{ display:inline-flex; align-items:center; gap:7px; padding:7px 12px;
  border:1px solid #e6e9ee; border-radius:8px; background:var(--surface); color:var(--text-3);
  font-size:12px; font-weight:500; line-height:1.3; }
.tr-upload-group .tr-fact > svg, .srt-upload-group .tr-fact > svg{ flex:none; color:#9aa1ac; }
.tr-upload-group .tr-upload-divider{ width:100%; margin:0 0 20px; font-size:12px; font-weight:700;
  letter-spacing:.12em; text-transform:uppercase; color:#9aa1ac; }
.tr-upload-group .tr-upload-divider::before,.tr-upload-group .tr-upload-divider::after{ background:var(--info-soft); }
.tr-upload-group .tr-upload-divider span{ padding:0 12px; }
/* Link row: the field becomes a borderless row — boxed input on the left, the
   Soon badge restyled as the (disabled) fetch button on the right. display is
   deliberately untouched so the audio-route hide rule still applies. */
.tr-upload-group .tr-youtube-field{ display:flex; align-items:center; width:100%; margin:0; padding:0; gap:10px; min-height:0;
  background:none; border:0; border-radius:0; box-shadow:none; }
.tr-upload-group .tr-link-box{ flex:1; min-width:0; display:flex; align-items:center; gap:10px;
  min-height:48px; padding:0 14px; border:1px solid #e2e5ec; border-radius:12px;
  background:var(--surface); color:#8b8f99; }
.tr-upload-group .tr-youtube-field input{ flex:1; min-width:0; border:0; outline:0; background:transparent; color:var(--text); font:inherit; font-size:14px; }
.tr-upload-group .tr-youtube-field input:disabled{ opacity:1; -webkit-text-fill-color:#8b8f99; cursor:not-allowed; }
.tr-upload-group .tr-coming-soon{ flex:none; padding:13px 20px; border-radius:8px; background:var(--info-soft);
  color:var(--text-2); font-size:13px; font-weight:600; text-transform:none; letter-spacing:0; cursor:not-allowed; }
@media (max-width:560px){
  .tr-upload-group, .srt-upload-group{ padding:32px 20px 28px; }
  #dubPhase1:not(.has-file) .tr-upload-group .tr-drop-title,
  #srtPanel #srtUpload .srt-upload-group .srt-drop .tr-drop-title{ font-size:16px; }
  #dubPhase1:not(.has-file) .tr-upload-group .tr-drop-ico,
  #srtPanel #srtUpload .srt-upload-group .srt-drop .tr-drop-ico{ width:60px; height:60px; }
}
@media (max-width:899px){
  #translatePanel .tr-header{ padding-top:34px; margin-bottom:28px; }
  #translatePanel .tr-header .tr-category{ font-size:32px; }
  #translatePanel .tr-header .tr-subtitle{ font-size:14px; }
  .tr-recent{ width:100%; margin-top:48px; }
  .tr-setup-grid{ padding:74px 24px 32px; gap:28px; }
  .tr-setup-title{ font-size:24px; }
}
@media (max-width:560px){
  #translatePanel .tr-header .tr-category{ font-size:24px; }
  #dubPhase1:not(.has-file) .tr-drop{ min-height:220px; padding:28px 20px; }
  .tr-recent-title{ font-size:16px; }
  .tr-recent-row{ font-size:13px; }
}

/* Modal fidelity pass — form, preview metadata, and action footer based on the
   supplied setup-dialog reference while retaining SaydiVoice capabilities. */
.tr-setup-subtitle span{ color:#555b68; text-decoration:underline; text-decoration-color:#aeb3bc; text-underline-offset:2px; }
.tr-setup-input{
  width:100%; height:44px; padding:0 13px; border:1px solid transparent; border-radius:8px;
  outline:0; background:var(--canvas); color:var(--text); font:inherit; font-size:13px;
  transition:border-color .15s,background .15s,box-shadow .15s;
}
.tr-setup-input:hover{ background:var(--canvas); }
.tr-setup-input:focus{ border-color:var(--tr-accent); background:var(--surface); box-shadow:0 0 0 3px rgba(254,101,93,.13); }
.tr-setup-form > .tr-adv[open] > .tr-adv-body{
  max-height:340px; margin-top:3px; padding:6px 0 4px; border:0; border-radius:0; background:transparent;
}
.tr-setup-form > .tr-adv > .tr-adv-sum{ width:max-content; gap:5px; }
.tr-setup-form > .tr-adv > .tr-adv-sum::before{ order:2; width:14px; height:14px; }
.tr-setup-form .tr-adv-body > .tr-block{ margin:0; }
.tr-setup-form .tr-select{ height:44px; border-color:transparent; border-radius:8px; background-color:var(--canvas); font-size:13px; }
.tr-setup-form .tr-select:hover{ background-color:var(--canvas); }
.tr-setup-form .tr-select:focus{ border-color:var(--tr-accent); background-color:var(--surface); box-shadow:0 0 0 3px rgba(254,101,93,.13); }
.tr-setup-form .tr-opts{ gap:0; margin:2px 0; }
.tr-setup-form .tr-toggle{ min-height:42px; border-bottom:1px solid #eef0f2; font-size:13px; }
.tr-duration-setting{ display:flex; align-items:center; justify-content:space-between; gap:20px; min-height:48px; }
.tr-duration-setting .tr-blk-label{ margin:0; }
.tr-duration-setting .tr-select{ width:132px; flex:none; }

.tr-setup-preview-frame{ padding:10px; background:var(--surface); }
.tr-setup-preview-frame .tr-input-video{ object-fit:contain; }
.tr-preview-badge{
  position:absolute; z-index:2; display:inline-flex; align-items:center; gap:5px; min-height:25px;
  padding:4px 8px; border-radius:8px; color:#fff; background:rgba(24,29,38,.76);
  font-size:12px; font-weight:700; line-height:1; backdrop-filter:blur(5px);
}
.tr-preview-duration{ top:18px; right:18px; font-variant-numeric:tabular-nums; }
.tr-preview-language{ left:18px; bottom:18px; }
.tr-setup-preview .tr-file{ margin-top:4px; padding:8px 10px 0; }
.tr-setup-preview .tr-file-ico{ display:none; }
.tr-setup-preview .tr-file-name{ font-size:15px; font-weight:750; }
#dubPhase1.has-file .tr-setup-close{
  top:calc(50% - min(360px,calc(50dvh - 32px)) + 16px);
  right:calc(50% - min(590px,calc(50vw - 32px)) + 16px);
}

.tr-setup-actions{ justify-content:space-between; flex-wrap:wrap; gap:12px 16px; }
.tr-setup-usage{ display:flex; align-items:center; gap:8px; flex:1 1 260px; min-width:0; color:#737985; font-size:12px; line-height:1.4; }
.tr-credit-dot{ display:inline-flex; flex:none; color:var(--tr-accent); }
.tr-setup-cta{ display:flex; align-items:center; justify-content:flex-end; flex:1 1 340px; flex-wrap:wrap; gap:9px; min-width:0; margin-left:0; }
.tr-setup-cta .tr-btn{ flex:1 1 auto; min-width:0; white-space:normal; text-align:center; line-height:1.15; }
.tr-setup-cta-hint{ flex:1 1 100%; width:100%; margin:4px 0 0; font-size:12px; line-height:1.45; color:var(--text-3); text-align:right; }
.tr-onboard{ display:flex; align-items:center; justify-content:space-between; gap:12px;
  margin:10px 0 16px; padding:11px 13px; border:1px solid #ffd8d3; border-radius:12px;
  background:var(--brand-coral-soft); color:var(--text-2); font-size:12px; line-height:1.5; }
.tr-onboard b{ color:var(--text); margin-right:5px; }
.tr-onboard-close{ flex:none; border:1px solid #f4b5ad; background:var(--surface); color:var(--accent-text);
  border-radius:8px; padding:6px 11px; font-family:inherit; font-size:12px; font-weight:700; cursor:pointer; }
.tr-onboard-close:hover{ background:#ffe1dd; }
.tr-setup-actions .tr-btn{ width:auto; }
.tr-btn-review{
  min-width:0 !important; color:#252936; border:1px solid #e3e5e9; background:var(--surface); box-shadow:none;
}
.tr-btn-review:hover:not(:disabled){ background:var(--canvas); transform:none; box-shadow:none; }
#dubSetupDubBtn{ min-width:0; }
#dubSetupDubBtn:disabled{ opacity:.45; background:linear-gradient(135deg,#ff9e99,#fe655d 55%,#fc3e57); color:#fff; }
.tr-setup-actions .tr-cancel{ flex:none; order:0; min-width:90px; }

@media (max-width:899px){
  #dubPhase1.has-file{
    width:100%;
    height:100vh;      /* fallback for engines without dvh — see the setup-modal note */
    height:100vh;
    height:100dvh;
  }
  .tr-setup-grid{ grid-template-columns:minmax(0,1fr); }
  #dubPhase1.has-file .tr-setup-close{ top:12px; right:12px; }
  .tr-setup-actions{ flex-wrap:wrap; }
  .tr-setup-usage{ flex:1 1 100%; order:2; font-size:12px; }
  .tr-setup-cta{ flex:1; }
  .tr-setup-cta .tr-btn{ flex:1; }
}
@media (max-width:560px){
  .tr-setup-cta{ width:100%; }
  .tr-setup-usage{ display:none; }
  .tr-duration-setting .tr-select{ width:120px; }
}

.tr-target-picker .tr-target-selected:not(.is-placeholder){
  width:max-content; max-width:calc(100% - 28px); padding:5px 8px; border-radius:8px;
  color:#3d4556; background:var(--info-soft); font-size:12px;
}

/* Working draft vs saved versions — iterating on music/voice must never make it
   unclear which output the controls describe. */
.tr-working-version{ margin:0 0 16px; padding:13px 15px; border:1px solid var(--border-2);
  border-radius:12px; background:var(--surface); }
.tr-working-version.is-dirty{ border-color:#fcd9a6; background:var(--warn-tint); }
.tr-working-version.is-synced{ border-color:#bff0d8; background:var(--success-tint); }
.tr-working-version.is-working{ border-color:#bfd4ff; background:var(--info-soft); }
.tr-working-version.is-working .tr-working-badge{ color:var(--info-ink); background:var(--info-soft); white-space:nowrap; }
.tr-working-version.is-working .tr-working-badge::before{ content:""; display:inline-block; width:8px; height:8px;
  margin-right:6px; border:2px solid rgba(29,78,216,.25); border-top-color:var(--info-ink); border-radius:999px;
  animation:tr-working-spin .8s linear infinite; vertical-align:-1px; }
@keyframes tr-working-spin{ to{ transform:rotate(360deg); } }
.tr-working-head{ display:flex; align-items:flex-start; justify-content:space-between; gap:12px; }
.tr-working-title{ margin:0; font-size:13px; font-weight:750; color:var(--text); }
.tr-working-status{ margin:3px 0 0; font-size:12px; line-height:1.5; color:var(--text-2); }
.tr-working-badge{ flex:none; padding:4px 9px; border-radius:999px; font-size:12px; font-weight:700;
  color:var(--text-2); background:var(--canvas); }
.tr-working-version.is-dirty .tr-working-badge{ color:var(--warn-ink); background:var(--warn-tint); }
.tr-working-version.is-synced .tr-working-badge{ color:#0f7a52; background:var(--success-tint); }
.tr-working-meta{ display:flex; flex-wrap:wrap; gap:6px 18px; margin-top:10px; font-size:12px; }
.tr-working-meta span{ display:inline-flex; gap:6px; min-width:0; }
.tr-working-meta b{ color:var(--text-3); font-weight:700; }
.tr-working-meta em{ font-style:normal; font-weight:600; color:var(--text);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; max-width:190px; }

.tr-result.is-current{ border-color:#ffd1cc; background:var(--coral-tint-2); }
.tr-result.is-previous{ background:var(--canvas); }
.tr-version-state{ font-size:12px; font-weight:700; padding:3px 8px; border-radius:999px;
  color:var(--text-2); background:var(--canvas); flex:none; }
.tr-result.is-current .tr-version-state{ color:#bd302b; background:var(--tr-accent-soft,#fff1ef); }
.tr-version-toggle{ flex:none; border:1px solid var(--border-2); background:var(--surface); color:var(--text-2);
  border-radius:8px; padding:5px 11px; font-family:inherit; font-size:12px; font-weight:700; cursor:pointer; }
.tr-version-toggle:hover{ border-color:#fe655d; color:var(--text); }
.tr-version-meta{ margin:6px 0 0; font-size:12px; line-height:1.5; color:var(--text-3); }
.tr-result.is-collapsed > *:not(.tr-result-head):not(.tr-version-meta){ display:none; }
.tr-result-head{ flex-wrap:wrap; gap:8px; }

@media (max-width:560px){
  .tr-working-meta em{ max-width:140px; }
  .tr-version-toggle{ width:100%; }
}

/* The review footer belongs to the editing column only. Bleeding it across the
   whole dialog put an opaque bar over the video preview and its controls. */
body.dub-review-open #dubPhase2 .tr-p2-main{ position:relative; min-width:0; }
body.dub-review-open #dubPhase2 .tr-p2-main .tr-actions.tr-sticky{
  margin:20px 0 0 !important; padding:14px 0 16px !important;
  /* OPAQUE, deliberately. This used to fade to rgba(255,255,255,.92) at its top
     edge, so saved-version text scrolling underneath showed faintly through the
     bar — which reads as text spilling out of the panel rather than as content
     tucked behind a toolbar. A progress bar and a primary button need a floor
     under them, not a veil. */
  background:#fff !important;
  box-shadow:0 -1px 0 var(--border-2);
}
/* Anchored scrolls (and the "keep the footer in view" jump when a render starts)
   must not park content underneath that bar. */
body.dub-review-open #dubPhase2{ scroll-padding-bottom:96px; }
body.dub-review-open #dubPhase2 .tr-p2-side{ align-self:start; }
@media (min-width:900px){
  body.dub-review-open #dubPhase2 .tr-p2-side{ position:sticky; top:8px; }
}
@media (max-width:899px){
  body.dub-review-open #dubPhase2 .tr-p2-main .tr-actions.tr-sticky{
    padding-bottom:20px !important;
  }
}

/* One deliverable player per version. The source comparison is opt-in, and no
   player is allowed to grow tall enough to dominate the column. */
.tr-result-orig{ background:var(--canvas); }
.tr-result-orig > summary{ cursor:pointer; list-style:none; margin:0; }
.tr-result-orig > summary::-webkit-details-marker{ display:none; }
.tr-result-orig > summary .tr-result-lang{ color:var(--text-2); font-size:13px; }
.tr-result-orig[open] > summary{ margin-bottom:10px; }
.tr-result-orig .tr-video,
.tr-result-orig .tr-audio{ margin-top:0; }
.tr-result .tr-video,
.tr-result-orig .tr-video{ max-height:260px; object-fit:contain; }
.tr-result.is-collapsed .tr-video{ display:none; }
body.dub-review-open #dubPhase2 .tr-p2-video .tr-p2-player{ max-height:46vh; }
body.dub-review-open #dubPhase2 .tr-p2-video .tr-p2-player{ max-height:46dvh; }
@media (max-width:899px){
  .tr-result .tr-video,
  .tr-result-orig .tr-video{ max-height:220px; }
  body.dub-review-open #dubPhase2 .tr-p2-video .tr-p2-player{ max-height:34vh; }
  body.dub-review-open #dubPhase2 .tr-p2-video .tr-p2-player{ max-height:34dvh; }
}

/* Single preview player with a source switch: one place to look, one question
   answered ("does the dub work here?"). */
.tr-pv-switch{ display:inline-flex; margin:10px 0 0; border:1px solid var(--border-2);
  border-radius:8px; overflow:hidden; background:var(--surface); }
.tr-pv-switch-btn{ border:0; background:transparent; color:var(--text-2); cursor:pointer;
  font-family:inherit; font-size:12px; font-weight:700; padding:7px 14px; transition:.15s; }
.tr-pv-switch-btn + .tr-pv-switch-btn{ border-left:1px solid var(--border-2); }
.tr-pv-switch-btn.on{ color:#fff; background:linear-gradient(135deg,#fe655d,#fc3e57); }
.tr-pv-switch-btn:focus-visible{ outline:2px solid var(--focus); outline-offset:-3px; }
.tr-video-wrap:empty{ display:none; }

/* The preview is the thing being judged, so give it real size: a wider column
   and a taller player, without letting it push the editor into a narrow strip. */
@media (min-width:900px){
  body.dub-review-open #dubPhase2 .tr-p2-grid{
    display:grid; grid-template-columns:minmax(0,1fr) minmax(420px,46%); gap:28px; align-items:start;
  }
  body.dub-review-open #dubPhase2 .tr-p2-side{ order:2; }
  body.dub-review-open #dubPhase2 .tr-p2-main{ order:1; }
  body.dub-review-open #dubPhase2 .tr-p2-video .tr-p2-player{ max-height:62vh; }
  body.dub-review-open #dubPhase2 .tr-p2-video .tr-p2-player{ max-height:62dvh; }
}
@media (min-width:1280px){
  body.dub-review-open #dubPhase2 .tr-p2-grid{ grid-template-columns:minmax(0,1fr) minmax(480px,48%); }
}
body.dub-review-open #dubPhase2 .tr-p2-video{ width:100%; }
body.dub-review-open #dubPhase2 .tr-p2-stage{ width:100%; }
body.dub-review-open #dubPhase2 .tr-p2-player{ width:100%; background:var(--black); border-radius:12px; }

/* Aspect-aware preview: fill the column for 16:9, fill the height for 9:16.
   The stage keeps the real aspect ratio so there are no letterbox bars, and the
   player never overflows the dialog in either orientation. */
body.dub-review-open #dubPhase2 .tr-p2-stage{
  --tr-aspect:1.7778;
  position:relative; margin:0 auto; overflow:hidden; border-radius:12px; background:var(--black);
  aspect-ratio:var(--tr-aspect);
}
body.dub-review-open #dubPhase2 .tr-p2-stage.is-landscape{ width:100%; max-height:64vh; }
body.dub-review-open #dubPhase2 .tr-p2-stage.is-landscape{ width:100%; max-height:64dvh; }
body.dub-review-open #dubPhase2 .tr-p2-stage.is-portrait{
  width:auto; height:min(72vh, calc((100vw - 64px) * 0.30));
  width:auto; height:min(72dvh, calc((100vw - 64px) * 0.30));
  max-width:100%;
}
body.dub-review-open #dubPhase2 .tr-p2-stage .tr-p2-player{
  width:100%; height:100%; max-height:none; display:block; object-fit:contain; background:var(--black);
}
@media (max-width:899px){
  body.dub-review-open #dubPhase2 .tr-p2-stage.is-landscape{ max-height:38vh; }
  body.dub-review-open #dubPhase2 .tr-p2-stage.is-landscape{ max-height:38dvh; }
  body.dub-review-open #dubPhase2 .tr-p2-stage.is-portrait{ height:min(52vh,72vw); }
  body.dub-review-open #dubPhase2 .tr-p2-stage.is-portrait{ height:min(52dvh,72vw); }
}

/* Long labels must not spill out of their control. Vietnamese runs ~30% longer
   than English, so fixed heights + nowrap were the wrong default: let buttons
   wrap and grow instead of clipping or overflowing their container. */
#translatePanel .tr-btn,
#translatePanel .tr-next-btn,
#translatePanel .tr-version-toggle,
#translatePanel .tr-vs-change,
#translatePanel .tr-restart,
#translatePanel .tr-src-tab,
#translatePanel .tr-sync-opt,
#translatePanel .tr-lib-use,
#translatePanel .tr-dl,
#translatePanel .tr-dl-mini{
  height:auto; min-height:42px; max-width:100%;
  white-space:normal; overflow-wrap:anywhere; text-align:center;
  line-height:1.3; padding-block:9px;
}
#translatePanel .tr-btn-mix,
#translatePanel #dubRenderBtn{ min-height:46px; }
/* Footers and action rows must be allowed to reflow rather than push a button
   past the dialog edge. */
.tr-setup-actions,
.tr-setup-cta,
#dubPhase2 .tr-actions.tr-sticky,
.tr-next-btns,
.tr-result-actions,
.tr-review-actions{ flex-wrap:wrap; min-width:0; }
.tr-setup-cta .tr-btn{ flex:1 1 auto; }
/* Single-line chrome keeps ellipsis instead of growing unpredictably. */
#translatePanel .tr-mix-sum,
#translatePanel .tr-target-selected,
#translatePanel .tr-vs-text{ min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
@media (max-width:560px){
  #translatePanel .tr-btn,
  #translatePanel .tr-next-btn{ width:100%; }
}

/* Maximise the preview: portrait clips fill the column width and are only
   capped by dialog height, so a 9:16 video no longer sits in a narrow strip
   surrounded by empty space. */
body.dub-review-open #dubPhase2 .tr-p2-stage.is-portrait{
  width:100%; height:auto;
  max-width:min(100%, calc(76dvh * var(--tr-aspect)));
  max-height:76vh;
  max-height:76dvh;
}
body.dub-review-open #dubPhase2 .tr-p2-stage.is-landscape{ width:100%; height:auto; max-height:66vh; }
body.dub-review-open #dubPhase2 .tr-p2-stage.is-landscape{ width:100%; height:auto; max-height:66dvh; }
body.dub-review-open #dubPhase2 .tr-p2-side{ display:flex; flex-direction:column; }
body.dub-review-open #dubPhase2 .tr-p2-vhint{ margin-top:8px; }

/* Video deliverable lives with the video. */
.tr-preview-actions{ display:flex; flex-wrap:wrap; gap:8px; margin-top:10px; }
.tr-preview-actions:empty{ display:none; }
.tr-dl-video-main{ flex:1 1 auto; min-height:42px; display:inline-flex; align-items:center;
  justify-content:center; font-size:13px; }
@media (max-width:899px){
  body.dub-review-open #dubPhase2 .tr-p2-stage.is-portrait{ max-height:56dvh; max-width:min(100%,calc(56dvh * var(--tr-aspect))); }
  body.dub-review-open #dubPhase2 .tr-p2-stage.is-landscape{ max-height:40vh; }
  body.dub-review-open #dubPhase2 .tr-p2-stage.is-landscape{ max-height:40dvh; }
}

/* ---------------------------------------------------------------------------
   Blocked-account notice
   A persistent card, not a full-width error bar: the account is intentionally
   suspended, and the user needs a calm explanation + support path.
   --------------------------------------------------------------------------- */
.ov-blocked-wrap{
  position:fixed; top:14px; left:50%; transform:translateX(-50%);
  z-index:2147483647; width:min(620px, calc(100vw - 28px));
  pointer-events:none; font-family:inherit;
}
.ov-blocked-card{
  pointer-events:auto; display:flex; gap:14px; align-items:flex-start;
  padding:16px 16px 15px 16px; border-radius:16px;
  background:var(--surface); color:#0f172a;
  border:1px solid rgba(15,23,42,.10);
  box-shadow:var(--e-3);
  animation:ov-blocked-in .35s cubic-bezier(.2,.8,.2,1);
}
@keyframes ov-blocked-in{
  from{ opacity:0; transform:translateY(-12px) scale(.98); }
  to{ opacity:1; transform:translateY(0) scale(1); }
}
.ov-blocked-ico{
  flex:none; width:40px; height:40px; border-radius:12px;
  display:flex; align-items:center; justify-content:center;
  background:var(--coral-tint-2); color:#dc2626;
}
.ov-blocked-main{ flex:1; min-width:0; }
.ov-blocked-title{ font-size:15px; font-weight:750; line-height:1.3; letter-spacing:.1px; }
.ov-blocked-body{ margin-top:4px; font-size:13px; line-height:1.55; color:#475569; }
.ov-blocked-actions{ display:flex; align-items:center; gap:10px; margin-top:13px; }
.ov-blocked-cta{
  display:inline-flex; align-items:center; gap:7px;
  padding:8px 13px; border-radius:8px; text-decoration:none;
  background:linear-gradient(135deg,#f43f5e,#e11d48); color:#fff;
  font-size:12px; font-weight:700; box-shadow:var(--e-2);
  transition:transform .15s ease, box-shadow .15s ease;
}
.ov-blocked-cta:hover{ transform:translateY(-1px); box-shadow:var(--e-2); }
button.ov-blocked-cta{ border:0; cursor:pointer; font-family:inherit; }
.ov-blocked-close{
  margin-left:auto; flex:none; width:30px; height:30px; border-radius:8px;
  border:1px solid rgba(15,23,42,.10); background:var(--canvas); color:#64748b;
  font-size:16px; line-height:1; cursor:pointer;
}
.ov-blocked-close:hover{ background:var(--info-soft); color:#0f172a; }
@media (max-width:520px){
  .ov-blocked-card{ border-radius:16px; padding:14px; gap:11px; }
  .ov-blocked-ico{ width:36px; height:36px; border-radius:8px; }
  .ov-blocked-title{ font-size:14px; }
  .ov-blocked-body{ font-size:12px; }
}

/* Fair-use / soft-limit notice. Same calm card shape as the hard-block banner,
   but with usage details and donor options instead of a support-only dead end. */
.ov-soft-card{ border-color:rgba(245,158,11,.32); }
.ov-soft-ico{ background:#fef3c7; color:var(--warn-ink); }
.ov-soft-usage{
  margin-top:9px; font-size:12px; font-weight:650; color:var(--warn-ink);
  background:var(--warn-soft); border:1px solid var(--warn-border); border-radius:8px;
  padding:7px 9px; line-height:1.4;
}
.ov-soft-wait{ margin-top:8px; font-size:12px; line-height:1.5; color:#475569; }
.ov-soft-countdown{ display:block; color:var(--warn-ink); font-weight:750; }
.ov-soft-wait-note{ display:block; margin-top:2px; }
.ov-soft-plans{ display:flex; flex-wrap:wrap; gap:6px; margin-top:10px; }
.ov-soft-plans-title{ width:100%; font-size:12px; font-weight:750; color:#64748b; text-transform:uppercase; letter-spacing:.03em; }
.ov-soft-plan{
  display:inline-flex; align-items:center; gap:4px; padding:5px 9px;
  border:1px solid var(--border); border-radius:999px; background:var(--canvas);
  font-size:12px; font-weight:700; color:#334155;
}
.ov-soft-plan.recommended{ border-color:#fbbf24; background:var(--warn-soft); color:var(--warn-ink); }
.ov-soft-plan em{ font-style:normal; color:var(--warn-ink); }
.ov-soft-wait-btn{ width:auto; padding:0 12px; font-size:12px; font-weight:700; color:#475569; background:var(--info-soft); }

/* ---------------------------------------------------------------------------
   Fair-use limit modal (v2)
   A centered, calm conversion surface.  It replaces the old top-bar notice:
   clearer hierarchy, an honest live countdown, and a single high-intent CTA.
   The design follows the app's coral/amber language; behaviourally it uses a
   visible deadline (present bias), a recommended middle plan (compromise /
   anchoring), and low-friction copy (no blame, no fake urgency).
   --------------------------------------------------------------------------- */
.ov-soft-overlay{
  position:fixed; inset:0; z-index:7000; display:flex; align-items:center;
  justify-content:center; padding:18px; background:rgba(15,23,42,.58);
  -webkit-backdrop-filter:blur(7px); backdrop-filter:blur(7px);
  animation:ov-soft-fade .22s ease both;
}
.ov-soft-modal{
  position:relative; width:min(600px,100%); max-height:calc(100vh - 36px);
  overflow:auto; background:var(--surface); border-radius:16px;
  box-shadow:var(--e-3);
  animation:ov-soft-pop .28s cubic-bezier(.2,.8,.2,1) both;
  font-family:inherit; color:#0f172a;
}
.ov-soft-modal::-webkit-scrollbar{ width:8px; }
.ov-soft-modal::-webkit-scrollbar-thumb{ background:var(--info-soft); border-radius:999px; }
.ov-soft-close{
  position:absolute; top:14px; right:14px; z-index:2; width:34px; height:34px;
  border-radius:8px; border:1px solid rgba(15,23,42,.08);
  background:var(--glass-bg-strong); color:#64748b; font-size:20px; line-height:1;
  cursor:pointer; display:grid; place-items:center;
  transition:background .15s, color .15s, transform .15s;
}
.ov-soft-close:hover{ background:var(--surface); color:#0f172a; transform:scale(1.03); }
.ov-soft-hero{
  padding:26px 28px 18px;
  background:linear-gradient(135deg,#fff7ed 0%,#fff1f2 52%,#eef2ff 100%);
  border-bottom:1px solid #f1f5f9;
}
.ov-soft-pill{
  display:inline-flex; align-items:center; gap:6px; padding:4px 9px;
  border-radius:999px; background:rgba(254,101,93,.12); color:#e11d48;
  font-size:12px; font-weight:800; letter-spacing:.08em; text-transform:uppercase;
}
.ov-soft-pill svg{ width:13px; height:13px; }
.ov-soft-title{
  font-size:20px; line-height:1.25; font-weight:800; letter-spacing:-.025em;
  margin:12px 0 7px; max-width:480px;
}
.ov-soft-sub{ font-size:13px; line-height:1.55; color:#64748b; max-width:500px; }
.ov-soft-body{ padding:20px 24px 24px; display:flex; flex-direction:column; gap:14px; }
.ov-soft-usage-card{ border:1px solid var(--border); background:var(--canvas); border-radius:16px; padding:13px 14px; }
.ov-soft-usage-top{
  display:flex; justify-content:space-between; align-items:center; gap:10px;
  font-size:12px; font-weight:700; color:#475569;
}
.ov-soft-usage-value{
  font-size:12px; font-weight:750; color:#0f172a; text-align:right; max-width:68%;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.ov-soft-meter{ height:7px; border-radius:999px; background:var(--info-soft); overflow:hidden; margin:10px 0 7px; }
.ov-soft-meter > span{
  display:block; height:100%; width:100%; border-radius:999px;
  background:linear-gradient(90deg,#f59e0b,#fe655d);
}
.ov-soft-usage-note{ font-size:12px; color:var(--warn-ink); font-weight:650; }
.ov-soft-countdown-card{
  display:flex; gap:13px; align-items:center; border:1px solid var(--warn-border);
  background:linear-gradient(135deg,var(--warn-soft),#fff7ed); border-radius:16px; padding:13px 14px;
}
.ov-soft-countdown-ico{
  width:42px; height:42px; border-radius:12px; background:var(--surface); border:1px solid var(--warn-border);
  color:#d97706; display:grid; place-items:center; flex:none;
}
.ov-soft-countdown-main{ flex:1; min-width:0; }
.ov-soft-countdown-label{
  font-size:12px; font-weight:750; color:var(--warn-ink);
  text-transform:uppercase; letter-spacing:.06em;
}
.ov-soft-countdown-time{
  font-size:32px; line-height:1.05; font-weight:800; color:#78350f;
  font-variant-numeric:tabular-nums; letter-spacing:-.03em;
}
.ov-soft-countdown-note{ font-size:12px; line-height:1.45; color:#a16207; margin-top:2px; }
.ov-soft-timebar{
  height:4px; border-radius:999px; background:rgba(217,119,6,.20);
  overflow:hidden; margin-top:7px;
}
.ov-soft-timebar > span{
  display:block; height:100%; width:100%; border-radius:999px;
  background:linear-gradient(90deg,#f59e0b,#fe655d); transition:width 1s linear;
}
.ov-soft-offer{ border:1px solid var(--border); border-radius:16px; padding:15px; background:var(--surface); }
.ov-soft-offer-head{ display:flex; justify-content:space-between; gap:10px; align-items:flex-start; margin-bottom:12px; }
.ov-soft-offer-title{ font-size:14px; font-weight:800; letter-spacing:-.01em; }
.ov-soft-offer-sub{ font-size:12px; color:#64748b; line-height:1.45; margin-top:2px; }
.ov-soft-plans{ display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:9px; }
.ov-soft-plan-card{
  position:relative; text-align:left; border:1.5px solid #e2e8f0; background:var(--surface);
  border-radius:12px; padding:11px 11px 10px; cursor:pointer; font-family:inherit;
  transition:border-color .15s, box-shadow .15s, transform .15s; color:#0f172a;
}
.ov-soft-plan-card:hover{ border-color:#cbd5e1; transform:translateY(-1px); }
.ov-soft-plan-card.selected{
  border-color:#fe655d; box-shadow:0 0 0 3px rgba(254,101,93,.12); background:var(--coral-tint-2);
}
.ov-soft-plan-tag{
  position:absolute; top:-8px; right:8px; font-size:12px; font-weight:800;
  color:#fff; background:linear-gradient(135deg,#f59e0b,#fe655d);
  border-radius:999px; padding:2px 7px; letter-spacing:.03em;
}
.ov-soft-plan-label{ font-size:12px; font-weight:750; line-height:1.25; padding-right:20px; }
.ov-soft-plan-price{ font-size:16px; font-weight:800; letter-spacing:-.02em; margin-top:5px; }
.ov-soft-plan-benefit{ font-size:12px; line-height:1.35; color:#64748b; margin-top:3px; }
.ov-soft-cta{
  width:100%; margin-top:12px; border:0; border-radius:12px; padding:13px 16px;
  font-family:inherit; font-size:14px; font-weight:800; color:#fff;
  background:linear-gradient(135deg,#fe655d,#e11d48);
  box-shadow:var(--e-2); cursor:pointer;
  transition:transform .15s, box-shadow .15s;
}
.ov-soft-cta:hover{ transform:translateY(-1px); box-shadow:var(--e-3); }
.ov-soft-disclaimer{ font-size:12px; line-height:1.45; color:#94a3b8; text-align:center; margin-top:9px; }
.ov-soft-footer{ padding:0 24px 24px; display:flex; justify-content:center; }
.ov-soft-wait-btn{
  width:100%; border:1px solid var(--border); background:var(--surface); color:#475569;
  border-radius:12px; padding:12px 16px; font-family:inherit; font-size:13px;
  font-weight:750; cursor:pointer; transition:background .15s,border-color .15s;
}
.ov-soft-wait-btn:hover{ background:var(--canvas); border-color:#cbd5e1; }
.ov-soft-wait-btn.ready{ color:#047857; border-color:#a7f3d0; background:var(--success-tint); }
@media (max-width:560px){
  .ov-soft-plans{ grid-template-columns:1fr; }
  .ov-soft-hero{ padding:22px 18px 16px; }
  .ov-soft-body{ padding:16px 16px 20px; }
  .ov-soft-footer{ padding:0 16px 18px; }
  .ov-soft-title{ font-size:20px; }
  .ov-soft-countdown-time{ font-size:24px; }
}
@keyframes ov-soft-fade{ from{ opacity:0; } to{ opacity:1; } }
@keyframes ov-soft-pop{ from{ opacity:0; transform:translateY(12px) scale(.97); } to{ opacity:1; transform:none; } }

/* ---------------------------------------------------------------------------
   Donate modal — content-side only for now. The QR is a real Ko-fi QR; the
   "I already supported" state is stored locally and gives the user a thank-you
   without pretending the backend has verified a payment.
   --------------------------------------------------------------------------- */
.donate-modal{ max-width:440px; padding:30px 30px 22px; }
.donate-emoji{ font-size:32px; line-height:1; margin-bottom:4px; }
.donate-benefits{
  display:flex; flex-direction:column; gap:8px; text-align:left;
  background:var(--canvas); border:1px solid var(--border); border-radius:12px;
  padding:12px 14px; margin:4px 0 16px;
}
.donate-benefit{ display:flex; align-items:center; gap:9px; font-size:13px; color:#334155; }
.donate-benefit-ico{
  width:22px; height:22px; border-radius:999px; flex:none;
  display:grid; place-items:center; font-size:12px; font-weight:800;
  background:var(--coral-tint-2); color:#e11d48;
}
.donate-qr-card{
  background:var(--surface); border:1px solid var(--border); border-radius:16px;
  padding:14px 14px 12px; display:flex; flex-direction:column; align-items:center;
  gap:3px; margin-bottom:16px;
}
.donate-qr{ width:190px; height:190px; display:block; image-rendering:pixelated; }
.donate-qr[hidden]{ display:none; }
.donate-qr-hint{ font-size:14px; font-weight:750; color:#0f172a; margin-top:4px; }
.donate-amount{ font-size:12px; color:#64748b; }
.donate-kofi{
  display:flex; align-items:center; justify-content:center; text-decoration:none;
  background:linear-gradient(135deg,#f43f5e,#e11d48); margin:16px 0 6px; font-size:14px;
}
.donate-kofi:hover{ background:linear-gradient(135deg,#e11d48,#be123c); }
.donate-later{ height:42px; font-size:14px; }
.donate-already{
  width:100%; border:0; background:none; cursor:pointer; padding:2px 0;
  color:#64748b; font-size:12px; font-weight:600; font-family:inherit;
}
.donate-already:hover{ color:#0f172a; }
.donate-thanks{
  display:flex; flex-direction:column; gap:3px; margin-top:10px;
  background:var(--success-tint); border:1px solid #a7f3d0; border-radius:12px; padding:11px 12px;
  color:#065f46; font-size:13px; line-height:1.45; text-align:left;
}
.donate-thanks[hidden]{ display:none; }
.donate-thanks strong{ font-size:14px; }
.donate-disclaimer{ font-size:12px; color:#94a3b8; margin-top:12px; line-height:1.4; }

.donate-support{ font-size:12px; color:#64748b; margin-top:12px; }
.donate-support a{ color:#e11d48; font-weight:700; text-decoration:none; }
.donate-support a:hover{ text-decoration:underline; }

/* Richer donate modal: hero, personalised nudge, rewards, tiers. */
.donate-modal{
  max-width:460px; height:min(88vh,780px); overflow-y:auto;
  padding:26px 26px 20px; scrollbar-width:thin;
}
.donate-hero{
  position:relative; overflow:hidden; margin:-4px -4px 14px; padding:14px 10px 4px;
  border-radius:16px;
  background:linear-gradient(160deg,#fff1f2 0%,#fff7ed 55%,#ffffff 100%);
  border:1px solid #ffe4e6;
}
.donate-hero-glow{
  position:absolute; width:150px; height:150px; border-radius:999px;
  right:-50px; top:-70px; background:radial-gradient(circle,#fb718566,transparent 70%);
  pointer-events:none;
}
.donate-emoji{ font-size:32px; line-height:1; margin-bottom:2px; }
.donate-personal{
  display:flex; align-items:center; gap:8px; text-align:left;
  background:var(--warn-soft); border:1px solid var(--warn-border); color:var(--warn-ink);
  border-radius:12px; padding:9px 11px; font-size:13px; line-height:1.4;
  margin-bottom:12px;
}
.donate-personal[hidden]{ display:none; }
.donate-personal-ico{ color:#f59e0b; flex:none; }
.donate-rewards{ display:flex; flex-direction:column; gap:7px; margin-bottom:14px; text-align:left; }
.donate-reward{
  display:flex; align-items:center; gap:9px; font-size:13px; color:#334155;
  background:var(--canvas); border:1px solid var(--border); border-radius:12px; padding:8px 10px;
}
.donate-reward-ico{ width:24px; text-align:center; flex:none; font-size:15px; }
.donate-tiers{ display:flex; gap:6px; flex-wrap:wrap; justify-content:center; margin-top:7px; }
.donate-tier{
  display:flex; flex-direction:column; align-items:center; gap:1px;
  min-width:76px; padding:6px 8px; border-radius:8px;
  background:var(--canvas); border:1px solid var(--border); color:#334155;
}
.donate-tier b{ font-size:13px; color:#0f172a; }
.donate-tier small{ font-size:12px; color:#64748b; }
.donate-tier{ font-family:inherit; cursor:pointer; transition:background .15s,border-color .15s,box-shadow .15s,transform .15s; }
.donate-tier:hover{ border-color:#cbd5e1; background:var(--surface); }
.donate-tier.selected{
  background:var(--danger-soft); border-color:#fb7185; color:#9f1239;
  box-shadow:var(--e-2);
}
.donate-tier.selected b{ color:#be123c; }
.donate-tier:focus-visible{ outline:2px solid #e11d48; outline-offset:2px; }
.donate-qr-card.is-loading .donate-qr{ opacity:.45; filter:grayscale(.7); transition:opacity .15s,filter .15s; }
.donate-intent-status{
  margin:7px 0 0; font-size:12px; line-height:1.4; text-align:center;
  color:#0f766e; background:var(--success-tint); border:1px solid #99f6e4;
  border-radius:8px; padding:6px 9px;
}
.donate-intent-status[hidden]{ display:none; }
.donate-email.invalid{ border-color:#e11d48; box-shadow:0 0 0 3px rgba(225,29,72,.10); }
.donate-email-error{
  margin:5px 0 0; font-size:12px; line-height:1.4; color:#be123c;
}
.donate-email-error[hidden]{ display:none; }
.donate-modal .modal-tagline{ margin-bottom:12px; }

/* Donate modal: badge email + transfer memo helper. */
.donate-badge-box{
  text-align:left; background:var(--canvas); border:1px solid var(--border);
  border-radius:12px; padding:11px 12px; margin:12px 0 4px;
}
.donate-badge-label{ display:block; font-size:12px; font-weight:700; color:#334155; margin-bottom:6px; }
.donate-email{
  width:100%; height:38px; border:1px solid #cbd5e1; border-radius:8px;
  padding:0 11px; font-size:13px; font-family:inherit; color:#0f172a;
  background:var(--surface); outline:none; box-sizing:border-box;
}
.donate-email:focus{ border-color:#e11d48; box-shadow:0 0 0 3px rgba(225,29,72,.10); }
.donate-memo-row{ display:flex; align-items:center; gap:7px; margin-top:8px; }
.donate-memo{
  flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  background:var(--surface); border:1px dashed #cbd5e1; border-radius:8px;
  padding:7px 9px; font-size:12px; color:#334155;
}
.donate-copy-memo{
  flex:none; height:34px; padding:0 10px; border-radius:8px; border:1px solid #cbd5e1;
  background:var(--surface); color:#334155; font-size:12px; font-weight:700; cursor:pointer;
  font-family:inherit;
}
.donate-copy-memo:hover{ border-color:#e11d48; color:#e11d48; }
.donate-badge-hint{ font-size:12px; color:#94a3b8; line-height:1.45; margin-top:7px; }
.donate-intl{ font-size:12px; color:#64748b; margin:-2px 0 10px; line-height:1.45; }

/* Donate modal: VN bank / international Ko-fi tabs. */
.donate-qr-tabs{
  display:flex; gap:4px; padding:3px; background:var(--info-soft); border:1px solid var(--border);
  border-radius:12px; margin-bottom:12px; width:100%; box-sizing:border-box;
  position:relative;
}
.donate-qr-tab{
  flex:1; border:0; background:transparent; color:#64748b; cursor:pointer;
  font-family:inherit; font-size:12px; font-weight:700; padding:7px 6px;
  border-radius:8px; transition:color .15s; position:relative; z-index:1;
}
/* Sliding active pill — same technique as the pricing toggle. app.js sets
   --dq-x/--dq-w to the active tab's offsetLeft/offsetWidth; this ::before glides
   to match. Re-measured when step 2 becomes visible (tabs have no layout while
   the modal/step is hidden). */
.donate-qr-tabs::before{
  content:""; position:absolute; top:3px; bottom:3px; left:0;
  width:var(--dq-w,0); transform:translateX(var(--dq-x,0));
  background:var(--surface); border-radius:8px; box-shadow:var(--e-1);
  opacity:var(--dq-ind,0); pointer-events:none; z-index:0;
  transition:transform .18s cubic-bezier(.4,0,.2,1), width .18s cubic-bezier(.4,0,.2,1), opacity .12s ease;
}
.donate-qr-tab.active{
  color:#0f172a;
}
.donate-qr-pane{ display:flex; flex-direction:column; align-items:center; gap:3px; }
.donate-qr-pane[hidden]{ display:none; }
[data-qr-extra][hidden]{ display:none; }

/* Subtle two-step donate modal: explain first, reveal QR only when asked. */
.donate-subtle{ max-width:420px; padding:24px 24px 18px; }
.donate-subtle .donate-mini-ico{
  width:44px; height:44px; border-radius:12px; margin:0 auto 10px;
  display:grid; place-items:center; font-size:20px;
  background:var(--danger-soft); color:#e11d48;
}
.donate-step[hidden]{ display:none; }
.donate-subtle .modal-title{ font-size:20px; }
.donate-subtle .modal-tagline{ font-size:14px; margin-bottom:14px; }
.donate-reveal{ background:var(--black); margin-top:2px; font-size:14px; }
.donate-reveal:hover{ background:#262626; }
#donateLater{ margin-top:6px; }
.donate-back{
  display:inline-flex; align-items:center; gap:2px; border:0; background:none;
  color:#64748b; font-size:12px; font-weight:700; cursor:pointer; padding:2px 0 8px;
  font-family:inherit;
}
.donate-back:hover{ color:#0f172a; }
.donate-subtle .donate-qr-card{ margin-top:4px; }
.donate-subtle .donate-rewards{ margin-bottom:14px; }


/* Noiz-style login modal: legal copy sits quietly under the social buttons. */
.ov-auth-backdrop .ovl-legal{ font-size:12px; line-height:1.45; color:var(--text-3,#9ca3af);
  margin:8px 2px 14px; }

/* Account entry: keep both the topbar pill and the sidebar card available so
   testers and users can pick whichever is closer. The sidebar row is hidden on
   mobile (see the max-width rule below); the topbar pill covers that case. */

/* Mobile: the topbar pill is the single account entry; the drawer can stay
   focused on navigation. */
@media (max-width: 1180px){
  .sb-signin-btn{ display:none; }
}

/* Custom assistant launcher (opens Crisp; Crisp's own launcher stays hidden).
   The shared generate.mjs crispBlock carries the same rules for marketing
   pages; keep this copy aligned for studio builds that fall back to app.js. */
.ov-chat-bubble{
  position:fixed; right:24px; bottom:calc(24px + env(safe-area-inset-bottom,0px)); z-index:75;
  width:56px; height:56px; padding:0; border:0; border-radius:999px;
  background:transparent; cursor:pointer;
  box-shadow:var(--e-2);
  transition:transform .16s ease, box-shadow .16s ease;
}
.ov-chat-bubble img{ width:100%; height:100%; display:block; border-radius:999px; }
.ov-chat-bubble::after{
  content:""; position:absolute; right:3px; bottom:3px; width:12px; height:12px;
  border-radius:999px; background:#22c55e; border:2px solid #fff;
}
.ov-chat-bubble:hover{ transform:translateY(-2px) scale(1.04); box-shadow:var(--e-3); }
/* Keep the launcher clear of the bottom player/playbar while it is visible. */
body.has-playbar .ov-chat-bubble{ bottom:calc(140px + env(safe-area-inset-bottom,0px)); }
@media (max-width:640px){
  .ov-chat-bubble{ right:16px; bottom:calc(16px + env(safe-area-inset-bottom,0px)); width:52px; height:52px; }
  body.has-playbar .ov-chat-bubble{ bottom:calc(224px + env(safe-area-inset-bottom,0px)); }
}

/* Supporter state (granted by admin, returned through /auth/me). The menu chip
   that used to carry the tier colour was removed 2026-11-05; the gold ring on the
   avatar is now the only visual, and the tier itself still drives the "you
   received a badge" toast. */
.avatar-top.avatar-supporter{
  box-shadow:var(--e-2);
}


/* ==========================================================================
   DUBBING — full-screen subtitle editor (dub-editor.js)
   The browser gets a professional NLE surface: subtitle list on the left, the
   real preview player in the centre, and a draggable timeline deck along the
   bottom. The existing phase-2 <video> and settings panel are re-used and
   positioned here, so no state/event listeners are duplicated.
   ========================================================================== */
:root{
  --de-side-w: clamp(300px, 24vw, 360px);
  --de-top-h: 58px;
  --de-tl-h: 184px;
  --de-bg: #f6f7fb;
  --de-panel: #ffffff;
  --de-line: #8b91a1;
  --de-ink: #111827;
  --de-muted: #5f6675;
  --de-accent: #6d5efc;
  --de-accent-2: #8b7cff;
  --de-danger: #e5484d;
}
#dubEditor{ position:fixed; inset:0; z-index:90; pointer-events:none; }
/* The legacy review screen is a centred modal with its own transform. A
   transformed ancestor becomes the containing block for position:fixed, which
   would pin the editor surfaces to the modal instead of the viewport. In editor
   mode we flatten that modal and its grid: the children below are intentionally
   lifted to viewport-level fixed layers. */
body.dub-editor-open #dubPhase2{
  position:static !important; inset:auto !important; width:auto !important; height:auto !important;
  transform:none !important; overflow:visible !important; padding:0 !important; border:0 !important;
  border-radius:0 !important; background:transparent !important; box-shadow:none !important;
  animation:none !important;
}
body.dub-editor-open #dubPhase2 > .tr-p2-grid{
  position:static !important; z-index:auto !important; display:block !important;
  width:auto !important; height:auto !important; overflow:visible !important;
}

#dubEditor *{ box-sizing:border-box; }
body.dub-editor-open{ overflow:hidden; }

/* Top bar ---------------------------------------------------------------- */
.de-topbar{
  position:fixed; top:0; left:0; right:0; height:var(--de-top-h); z-index:160;
  display:flex; align-items:center; gap:16px; padding:0 16px 0 18px;
  background:var(--glass-bg-strong); border-bottom:1px solid var(--de-line);
  box-shadow:0 1px 0 rgba(17,24,39,.02); backdrop-filter:blur(12px);
  pointer-events:auto;
}
.de-brand{ display:flex; align-items:center; gap:10px; min-width:0; flex:0 0 var(--de-side-w); }
.de-brand-ico{ display:flex; align-items:center; justify-content:center; width:32px; height:32px; flex:none;
  border-radius:8px; color:#fff; background:linear-gradient(135deg,var(--de-accent),#7e22ce); box-shadow:var(--e-2); }
.de-brand-text{ display:flex; flex-direction:column; line-height:1.1; min-width:0; }
.de-brand-text strong{ font-size:14px; letter-spacing:-.01em; }
.de-brand-text span{ margin-top:2px; font-size:12px; color:var(--de-muted); }
.de-top-center{ display:flex; align-items:center; justify-content:center; gap:8px; flex:1; min-width:0; }
.de-lang{ display:inline-flex; align-items:center; gap:7px; height:34px; padding:0 10px 0 11px;
  border:1px solid var(--de-line); border-radius:8px; background:var(--surface); color:var(--de-muted); }
.de-lang select{ border:0; background:transparent; outline:none; color:var(--de-ink); font:700 12.5px/1.2 inherit; cursor:pointer; max-width:150px; }
.de-top-actions{ display:flex; align-items:center; gap:8px; flex:none; }
.de-tool{
  display:inline-flex; align-items:center; justify-content:center; gap:6px; height:34px; padding:0 12px;
  border:1px solid var(--de-line); border-radius:8px; background:var(--surface); color:#3f4451;
  font:700 12.5px/1 inherit; cursor:pointer; transition:.14s ease;
}
.de-tool:hover{ border-color:#cfcaff; color:var(--de-accent); background:var(--violet-soft); }
.de-tool.on{ border-color:var(--de-accent); color:var(--de-accent); background:var(--violet-soft); }
.de-tool.de-danger{ color:var(--de-danger); border-color:#f6c9cb; }
.de-tool.de-danger:hover{ background:var(--danger-soft); border-color:var(--de-danger); color:var(--de-danger); }
.de-icon-btn{
  display:inline-flex; align-items:center; justify-content:center; width:34px; height:34px; flex:none;
  border:1px solid transparent; border-radius:8px; background:transparent; color:#5a6070;
  font:800 15px/1 inherit; cursor:pointer; transition:.14s ease;
}
.de-icon-btn:hover:not(:disabled){ background:var(--canvas); color:var(--de-ink); }
.de-icon-btn:disabled{ opacity:.34; cursor:not-allowed; }
.de-icon-btn.de-close{ font-size:20px; color:#7b8190; }
.de-icon-btn.de-zoom{ width:28px; height:28px; border-color:var(--de-line); background:var(--surface); }
.de-pill{
  height:34px; padding:0 13px; border:1px solid var(--de-line); border-radius:999px; background:var(--surface);
  color:#3f4451; font:700 12.5px/1 inherit; cursor:pointer; transition:.14s ease;
}
.de-pill:hover{ border-color:#cfcaff; background:var(--violet-soft); color:var(--de-accent); }
.de-primary{
  height:34px; padding:0 16px; border:0; border-radius:8px; color:#fff;
  background:linear-gradient(135deg,var(--de-accent),#6d28d9 60%,#7e22ce);
  box-shadow:var(--e-2); font:800 12.5px/1 inherit; cursor:pointer; transition:.14s ease;
}
.de-primary:hover:not(:disabled){ transform:translateY(-1px); box-shadow:var(--e-2); }
.de-primary:disabled{ opacity:.48; cursor:not-allowed; box-shadow:none; transform:none; }
.de-primary.is-busy::before{ content:""; width:11px; height:11px; margin-right:6px; display:inline-block;
  border:2px solid rgba(255,255,255,.5); border-top-color:#fff; border-radius:999px; animation:de-spin .7s linear infinite; }
@keyframes de-spin{ to{ transform:rotate(360deg); } }

/* Left: subtitle list ---------------------------------------------------- */
.de-side{
  position:fixed; top:var(--de-top-h); left:0; bottom:var(--de-tl-h); width:var(--de-side-w); z-index:130;
  display:flex; flex-direction:column; background:var(--de-panel); border-right:1px solid var(--de-line);
  pointer-events:auto; box-shadow:var(--e-3);
}
.de-side-head{
  height:54px; flex:none; display:flex; align-items:center; justify-content:space-between;
  padding:0 12px 0 16px; border-bottom:1px solid #f0f1f5;
  font:800 13px/1 inherit; color:var(--de-ink); letter-spacing:-.01em;
}
.de-side-head .de-add{
  width:30px; height:30px; border:1px solid var(--de-line); border-radius:8px; background:var(--surface);
  color:var(--de-accent); font:700 19px/1 inherit; cursor:pointer; transition:.14s ease;
}
.de-side-head .de-add:hover{ border-color:#cfcaff; background:var(--violet-soft); }
.de-list{ flex:1; overflow:auto; padding:10px 10px 72px; scrollbar-width:thin; }
.de-list::-webkit-scrollbar{ width:8px; }
.de-list::-webkit-scrollbar-thumb{ background:#dfe1ea; border-radius:999px; }
.de-list-empty{ padding:28px 18px; color:var(--de-muted); font-size:12px; text-align:center; line-height:1.55; }
.de-item{
  position:relative; padding:10px 12px 10px 12px; margin:0 0 6px; border:1px solid transparent;
  border-radius:12px; cursor:pointer; transition:background .12s ease, border-color .12s ease;
}
.de-item:hover{ background:var(--surface); }
.de-item.is-active{ background:var(--violet-soft); border-color:#ddd8ff; box-shadow:inset 3px 0 0 var(--de-accent); }
.de-item.is-active .de-item-text{ background:var(--surface); }
.de-item-head{ display:flex; align-items:center; gap:7px; margin-bottom:7px; }
.de-item-no{ flex:none; width:22px; font:800 10.5px/22px ui-monospace,SFMono-Regular,Menlo,monospace;
  text-align:center; color:#6f7686; background:var(--canvas); border-radius:8px; }
.de-item.is-active .de-item-no{ color:#fff; background:linear-gradient(135deg,var(--de-accent),#7e22ce); }
.de-item-time{ font:700 11px/1.2 ui-monospace,SFMono-Regular,Menlo,monospace; color:#737a8a; }
.de-item-head .de-fit{ margin-left:auto; }
.de-fit{ font-size:12px; font-weight:800; padding:2px 7px; border-radius:999px; color:transparent; background:transparent; }
.de-fit.ok{ color:#0f7a52; background:var(--success-tint); }
.de-fit.tight{ color:var(--warn-ink); background:var(--warn-tint); }
.de-fit.over{ color:var(--danger); background:var(--danger-soft); }
.de-item-text{
  width:100%; min-height:28px; max-height:170px; resize:none; border:1px solid transparent;
  border-radius:8px; background:transparent; padding:7px 9px; color:#202533;
  font:600 13.5px/1.5 inherit; outline:none; transition:.13s ease; overflow-x:hidden; overflow-y:auto;
  overflow-wrap:anywhere; word-break:break-word; white-space:pre-wrap;
}
.de-item-text:hover{ border-color:#e4e5ee; background:var(--surface); }
.de-item-text:focus{ border-color:#6d5efc; background:var(--surface); box-shadow:0 0 0 3px rgba(109,94,252,.18);
  max-height:none; overflow-y:hidden; }
.de-item.is-empty .de-item-text{ color:#a4a9b6; font-weight:500; }

/* Centre: preview -------------------------------------------------------- */
.de-stage{
  position:fixed; top:var(--de-top-h); left:var(--de-side-w); right:0; bottom:var(--de-tl-h);
  z-index:100; display:flex; align-items:center; justify-content:center; pointer-events:none;
  background:transparent; overflow:hidden;
}
.de-stage-empty{
  display:none; padding:18px 22px; border-radius:12px; color:#cbd2e0; background:rgba(15,17,23,.72);
  border:1px solid rgba(255,255,255,.08); font:600 13px/1.5 inherit; text-align:center; max-width:420px;
}
body.dub-editor-open.audio-job .de-stage-empty{ display:block; }

/* The original phase-2 video is owned by app.js, so we only re-position it. */
body.dub-editor-open #dubPhase2 .tr-p2-side{
  position:fixed; top:var(--de-top-h); left:var(--de-side-w); right:0; bottom:auto;
  height:calc(100vh - var(--de-top-h) - var(--de-tl-h));
  height:calc(100dvh - var(--de-top-h) - var(--de-tl-h));
  align-self:auto !important;
  z-index:80; display:flex; flex-direction:column; align-items:center; justify-content:center;
  padding:18px 24px; background:radial-gradient(circle at 50% 42%, #1b2330 0, #0b0f16 72%);
  pointer-events:auto; overflow:hidden;
}
body.dub-editor-open #dubPhase2 .tr-p2-video{
  position:static; width:100%; max-width:1180px; margin:0; padding:0; background:transparent;
}
body.dub-editor-open #dubPhase2 .tr-p2-stage,
body.dub-editor-open #dubPhase2 .tr-p2-stage.is-landscape,
body.dub-editor-open #dubPhase2 .tr-p2-stage.is-portrait{
  position:relative; display:flex; align-items:center; justify-content:center;
  width:100%; height:auto; max-width:none; max-height:calc(100vh - var(--de-top-h) - var(--de-tl-h) - 30px);
  margin:0 auto; border-radius:12px; overflow:hidden; background:var(--black);
  box-shadow:var(--e-3);
}
body.dub-editor-open #dubPhase2 .tr-p2-stage .tr-p2-player,
body.dub-editor-open #dubPhase2 .tr-p2-player{
  width:100%; height:auto; max-height:calc(100vh - var(--de-top-h) - var(--de-tl-h) - 30px);
  border-radius:12px; object-fit:contain; background:var(--black);
}
body.dub-editor-open #dubPhase2 .tr-p2-cap{ bottom:auto; z-index:3; }
body.dub-editor-open #dubPhase2 #dubPreviewActions,
body.dub-editor-open #dubPhase2 #dubPreviewSwitch,
body.dub-editor-open #dubPhase2 .tr-p2-vhint,
body.dub-editor-open #dubPhase2 .tr-p2-video > .tr-export-hint{ display:none !important; }

/* Bottom: timeline ------------------------------------------------------- */
.de-timeline{
  position:fixed; left:0; right:0; bottom:0; height:var(--de-tl-h); z-index:130;
  display:flex; flex-direction:column; background:var(--de-panel); border-top:1px solid var(--de-line);
  pointer-events:auto; box-shadow:var(--e-3);
}
.de-tl-toolbar{
  height:48px; flex:none; display:flex; align-items:center; gap:12px; padding:0 12px 0 14px;
  border-bottom:1px solid #f0f1f5;
}
.de-tl-left,.de-tl-center,.de-tl-right{ display:flex; align-items:center; }
.de-tl-left{ gap:7px; }
.de-tl-center{ flex:1; justify-content:center; gap:6px; }
.de-tl-right{ gap:6px; }
.de-transport{
  width:30px; height:30px; border:1px solid transparent; border-radius:8px; background:transparent;
  color:#4a5060; font:800 13px/1 inherit; cursor:pointer; transition:.14s ease;
}
.de-transport:hover{ background:var(--canvas); color:var(--de-ink); }
.de-transport.de-play{ width:36px; height:36px; color:#fff; background:var(--de-ink); box-shadow:var(--e-2); }
.de-transport.de-play:hover{ background:#2b3040; }
.de-time{ min-width:120px; text-align:center; font:700 11px/1 ui-monospace,SFMono-Regular,Menlo,monospace; color:#6f7686; }
.de-tl-right input[type="range"]{ width:104px; accent-color:var(--de-accent); }
.de-tl-scroll{ flex:1; overflow-x:auto; overflow-y:hidden; position:relative; scrollbar-width:thin; }
.de-tl-scroll::-webkit-scrollbar{ height:8px; }
.de-tl-scroll::-webkit-scrollbar-thumb{ background:#dfe1ea; border-radius:999px; }
.de-tl-content{ position:relative; height:100%; min-width:100%; background:var(--surface); overflow:hidden; }
.de-ruler{ position:absolute; left:0; right:0; top:0; height:22px; border-bottom:1px solid #ececf3; z-index:4; }
.de-tick{ position:absolute; top:0; bottom:0; width:1px; background:var(--info-soft); }
.de-tick span{ position:absolute; left:4px; top:5px; font:600 10px/1 ui-monospace,SFMono-Regular,Menlo,monospace;
  color:#9aa0ae; white-space:nowrap; }
.de-track{ position:absolute; left:0; right:0; }
.de-video-track{ top:22px; height:34px; background:var(--info-soft); border-bottom:1px solid #ececf3; }
.de-video-track .de-filmstrip{ position:absolute; inset:0; opacity:.92;
  background:
    repeating-linear-gradient(90deg, rgba(255,255,255,.28) 0 3px, transparent 3px 9px),
    linear-gradient(180deg,#d8dae8,#c7cadb);
}
.de-video-track .de-filmstrip::after{ content:""; position:absolute; inset:0;
  background:repeating-linear-gradient(90deg, transparent 0 46px, rgba(255,255,255,.45) 46px 62px); }
.de-sub-track{ top:56px; height:48px; }
.de-audio-track{ top:105px; height:34px; background:var(--canvas); border-top:1px solid #f0f1f5; }
.de-audio-track .de-wave{ position:absolute; left:0; right:0; top:8px; bottom:8px; opacity:.55;
  background:
    repeating-linear-gradient(90deg, #b9bdd8 0 2px, transparent 2px 5px),
    repeating-linear-gradient(90deg, transparent 0 5px, #b9bdd8 5px 6px);
  mask-image:linear-gradient(180deg,transparent 0,#000 30%,#000 70%,transparent 100%);
  -webkit-mask-image:linear-gradient(180deg,transparent 0,#000 30%,#000 70%,transparent 100%);
}
.de-track-label{ position:absolute; left:8px; top:50%; transform:translateY(-50%); z-index:2;
  padding:2px 6px; border-radius:8px; color:#7b8190; background:var(--glass-bg-strong);
  font:800 9px/1.2 inherit; letter-spacing:.08em; text-transform:uppercase; pointer-events:none; }
.de-clip{
  position:absolute; top:5px; height:38px; z-index:6; overflow:hidden; border-radius:8px; cursor:grab;
  background:linear-gradient(180deg,#7c6dfc,#6657ef); border:1px solid rgba(255,255,255,.32);
  box-shadow:var(--e-2); color:#fff; user-select:none;
}
.de-clip:hover{ background:linear-gradient(180deg,#8a7cff,#6f5ff5); }
.de-clip.is-selected{ box-shadow:var(--e-2); }
.de-clip.is-empty{ background:linear-gradient(180deg,#a9aec4,#8d93ac); }
.de-clip-text{ display:block; padding:5px 9px; overflow:hidden; white-space:nowrap; text-overflow:ellipsis;
  font:700 11px/1.25 inherit; pointer-events:none; }
.de-handle{ position:absolute; top:50%; width:10px; height:10px; margin-top:-5px; border-radius:999px; background:var(--surface);
  border:1px solid #cfcaff; opacity:0; transition:opacity .12s ease; box-shadow:var(--e-1); }
.de-clip:hover .de-handle,.de-clip.is-selected .de-handle{ opacity:1; }
.de-handle-l{ left:2px; cursor:w-resize; }
.de-handle-r{ right:2px; cursor:e-resize; }
body:not(.de-edit-mode) .de-handle{ display:none; }
.de-playhead{ position:absolute; top:0; bottom:0; left:0; width:2px; background:var(--black); z-index:20; pointer-events:none;
  box-shadow:0 0 0 1px rgba(255,255,255,.55); }
.de-playhead::before{ content:""; position:absolute; top:0; left:50%; width:8px; height:8px; margin-left:-4px;
  background:var(--black); border-radius:0 0 8px 8px; }

/* Popovers --------------------------------------------------------------- */
.de-pop{
  position:fixed; z-index:180; width:min(360px,calc(100vw - 24px)); padding:14px;
  background:var(--surface); border:1px solid var(--de-line); border-radius:12px;
  box-shadow:var(--e-3); pointer-events:auto;
}
.de-style-pop{ top:calc(var(--de-top-h) + 9px); left:50%; transform:translateX(-50%); }
.de-edit-pop{ bottom:calc(var(--de-tl-h) + 10px); left:50%; transform:translateX(-50%); width:min(430px,calc(100vw - 24px)); }
.de-pop-title{ margin:0 0 11px; font:800 13px/1.2 inherit; color:var(--de-ink); }
.de-pop-title em{ color:var(--de-accent); font-style:normal; }
.de-pop-row{ display:flex; align-items:center; gap:10px; margin:9px 0; }
.de-pop-row > span:first-child,
.de-pop-row > label:first-child{ flex:0 0 78px; font:700 11.5px/1.3 inherit; color:var(--de-muted); }
.de-pop-row input[type="range"]{ flex:1; accent-color:var(--de-accent); }
.de-seg{ display:flex; gap:5px; flex-wrap:wrap; }
.de-seg button{ height:29px; padding:0 11px; border:1px solid var(--de-line); border-radius:8px; background:var(--surface);
  color:#525968; font:700 11.5px/1 inherit; cursor:pointer; transition:.13s ease; }
.de-seg button:hover{ border-color:#cfcaff; color:var(--de-accent); }
.de-seg button.on{ border-color:var(--de-accent); color:#fff; background:linear-gradient(135deg,var(--de-accent),#8b5cf6); }
.de-pop-hint{ margin:11px 0 0; font-size:12px; line-height:1.5; color:var(--de-muted); }
.de-val{ flex:0 0 44px; text-align:right; font:800 11.5px/1 ui-monospace,SFMono-Regular,Menlo,monospace; color:var(--de-accent); }
#deStylePop .de-pop-actions{ margin-top:10px; }
.de-edit-grid{ display:grid; grid-template-columns:1fr 1fr; gap:9px; }
.de-edit-grid label{ display:flex; flex-direction:column; gap:4px; font:700 11px/1.2 inherit; color:var(--de-muted); }
.de-edit-grid label.de-full{ grid-column:1 / -1; }
.de-edit-grid input{ height:34px; border:1px solid var(--de-line); border-radius:8px; padding:0 10px;
  color:var(--de-ink); font:700 12px/1 ui-monospace,SFMono-Regular,Menlo,monospace; outline:none; }
.de-edit-grid input:focus{ border-color:#c9c3ff; box-shadow:0 0 0 3px rgba(109,94,252,.11); }
.de-pop-actions{ display:flex; flex-wrap:wrap; gap:7px; margin-top:12px; }

/* Settings drawer: the old phase-2 controls, reused verbatim -------------- */
.de-settings-scrim{ position:fixed; inset:0; z-index:150; background:rgba(17,24,39,.22); pointer-events:auto; }
body.dub-editor-open #dubPhase2 .tr-p2-main{
  position:fixed; top:0; right:0; bottom:0; width:min(430px,94vw); z-index:170;
  display:block; margin:0; padding:22px 20px 80px; overflow:auto; background:var(--surface);
  border-left:1px solid var(--de-line); box-shadow:var(--e-3);
  transform:translateX(104%); transition:transform .22s cubic-bezier(.2,.7,.25,1);
  pointer-events:auto;
}
body.dub-editor-open.de-settings-open #dubPhase2 .tr-p2-main{ transform:translateX(0); }
body.dub-editor-open #dubPhase2 .tr-p2-main #dubLangTabs{ display:none !important; }
/* Keep the review disclosure for Find & replace + .SRT/.TXT downloads, but the
   full-screen list on the left is the single place lines are edited. */
body.dub-editor-open #dubPhase2 .tr-p2-main #dubReviewBlock{ display:block !important; }
body.dub-editor-open #dubPhase2 .tr-p2-main #dubReviewBlock .tr-seglist,
body.dub-editor-open #dubPhase2 .tr-p2-main #dubReviewBlock #dubOnlyBadRow{ display:none !important; }
body.dub-editor-open #dubPhase2 .tr-p2-main .tr-actions.tr-sticky{ position:static; margin:18px 0 0; padding:14px 0 0; background:transparent; }
body.dub-editor-open #dubPhase2 .tr-p2-main .tr-voice-opt{ padding:11px 12px; }
body.dub-editor-open #dubPhase2 .tr-p2-main .tr-mix-body{ padding-top:2px; }
body.dub-editor-open #dubPhase2 > .tr-phase2-head,
body.dub-editor-open #dubPhase2 > .tr-subtitle{ display:none !important; }
body.dub-editor-open #dubPhase2 .tr-p2-grid{ display:block; }
body.dub-editor-open #dubPhase2 .tr-p2-side{ order:0; }
body.dub-editor-open #dubPhase2 .tr-p2-main .tr-working-version{ margin-top:0; }
body.dub-editor-open #dubPhase2 .tr-p2-main .tr-phase2-head,
body.dub-editor-open #dubPhase2 .tr-p2-main .tr-subtitle{ display:none; }
body.dub-editor-open #dubPhase2 .tr-p2-main .tr-plan{ display:none !important; }

/* Small screens: the list becomes a drawer-like bottom sheet is overkill, so
   the timeline and player get the full width and the list hides. Users can
   still edit every line by reopening on a wider screen. */
@media (max-width:860px){
  :root{ --de-side-w:0px; --de-tl-h:174px; }
  body.dub-editor-open #dubPhase2 .tr-p2-side{ padding:10px 10px; }
  .de-side{ display:none; }
  .de-brand{ flex-basis:auto; }
  .de-brand-text span{ display:none; }
  .de-top-center .de-lang select{ max-width:96px; }
  .de-topbar{ gap:8px; padding:0 9px; }
  .de-top-actions .de-pill{ display:none; }
  .de-tl-center .de-time{ min-width:96px; }
  .de-tl-right input[type="range"]{ width:70px; }
}
@media (max-width:560px){
  .de-tl-left .de-tool span{ display:none; }
  .de-brand-text strong{ font-size:12px; }
  .de-top-center{ gap:5px; }
  .de-top-center .de-tool{ padding:0 8px; }
  .de-tl-right input[type="range"]{ display:none; }
}

/* Subtitle editor audit fixes: accessible interaction, contained-video preview,
   and mobile drawers without removing core editing/settings functions. */
.de-subtitles-trigger,.de-subtitles-close,.de-drawer-head{display:none}
.de-side-actions{display:flex;align-items:center;gap:6px}
.de-scale-number{width:58px;height:32px;border:1px solid var(--de-line);border-radius:8px;padding:0 6px;font:700 12px/1 inherit;color:var(--de-ink)}
.de-scale-presets{justify-content:flex-end;margin-top:-3px}
.de-tool:focus-visible,.de-icon-btn:focus-visible,.de-pill:focus-visible,.de-primary:focus-visible,.de-transport:focus-visible,.de-clip:focus-visible,.de-seg button:focus-visible,.de-item-text:focus-visible,.tr-p2-cap:focus-visible{outline:3px solid color-mix(in srgb,var(--de-accent) 38%,transparent);outline-offset:2px}
.de-clip:focus-visible{box-shadow:0 0 0 2px #fff,0 0 0 5px var(--de-accent)}
.de-handle{width:16px;height:28px;margin-top:-14px;border-radius:8px;background:transparent;border:0}
.de-handle::after{content:"";position:absolute;left:4px;top:9px;width:8px;height:10px;border-radius:8px;background:var(--surface);border:1px solid #aaa2ff;box-shadow:var(--e-1)}
.tr-p2-cap{font-family:var(--dub-cap-font,inherit)}
.tr-p2-cap .tr-cap-line{color:var(--dub-cap-color,#fff);text-shadow:calc(-1 * var(--dub-cap-outline,2px)) 0 var(--dub-cap-outline-color,#000),var(--dub-cap-outline,2px) 0 var(--dub-cap-outline-color,#000),0 calc(-1 * var(--dub-cap-outline,2px)) var(--dub-cap-outline-color,#000),0 var(--dub-cap-outline,2px) var(--dub-cap-outline-color,#000),var(--dub-cap-shadow,1px) var(--dub-cap-shadow,1px) calc(2 * var(--dub-cap-shadow,1px)) var(--dub-cap-shadow-color,#000)}
.tr-p2-cap::before{content:"⋮⋮";position:absolute;top:-24px;left:50%;transform:translateX(-50%);padding:2px 8px;border-radius:999px;background:rgba(15,23,42,.8);color:#fff;font:700 12px/1 sans-serif;opacity:0;transition:opacity .15s}
.tr-p2-cap:hover::before,.tr-p2-cap:focus-visible::before,.tr-p2-cap.is-dragging::before{opacity:1}
body.dub-editor-open #dubReviewStage::after{content:"";position:absolute;inset:5%;border:1px dashed rgba(255,255,255,.28);pointer-events:none;opacity:0;transition:opacity .15s}
body.dub-editor-open #dubReviewStage:has(.tr-p2-cap:hover)::after,body.dub-editor-open #dubReviewStage:has(.tr-p2-cap:focus-visible)::after,body.dub-editor-open #dubReviewStage:has(.tr-p2-cap.is-dragging)::after{opacity:1}
.de-drawer-head{position:sticky;top:-22px;z-index:4;align-items:center;justify-content:space-between;margin:-22px -20px 16px;padding:12px 14px;background:var(--surface);border-bottom:1px solid var(--de-line)}
body.de-settings-open .de-drawer-head{display:flex}
@media (pointer:coarse){
  .de-tool,.de-icon-btn,.de-pill,.de-primary,.de-transport,.de-add,.de-seg button{min-width:44px;min-height:44px;height:44px}
  .de-handle{width:24px;height:44px;margin-top:-22px}.de-handle::after{left:8px;top:15px}
}
@media (max-width:860px){
  :root{--de-side-w:0px;--de-top-h:104px;--de-tl-h:196px}
  #dubEditor{height:100vh;padding-top:env(safe-area-inset-top);padding-bottom:env(safe-area-inset-bottom)}
  #dubEditor{height:100dvh;padding-top:env(safe-area-inset-top);padding-bottom:env(safe-area-inset-bottom)}
  .de-topbar{height:var(--de-top-h);min-height:var(--de-top-h);padding:env(safe-area-inset-top) 9px 6px;display:grid;grid-template-columns:auto 1fr auto;grid-template-rows:48px 44px;gap:4px 8px}
  .de-brand{grid-column:1;grid-row:1}.de-top-center{grid-column:1 / -1;grid-row:2;justify-content:flex-start;overflow-x:auto}.de-top-actions{grid-column:2 / 4;grid-row:1;justify-content:flex-end;min-width:0}
  .de-subtitles-trigger{display:inline-flex}.de-top-actions .de-pill{display:inline-flex;font-size:0;width:44px;padding:0}.de-top-actions .de-pill::before{content:"Settings";font-size:12px}
  .de-side{display:flex;top:var(--de-top-h);bottom:0;width:min(390px,92vw);z-index:175;transform:translateX(-104%);transition:transform .2s ease;box-shadow:var(--e-3)}
  body.de-subtitles-open .de-side{transform:translateX(0)}.de-subtitles-close{display:inline-flex}
  .de-tl-toolbar{height:68px;align-items:flex-start;padding-top:5px;flex-wrap:wrap}.de-tl-left{order:1}.de-tl-center{order:2;justify-content:flex-end}.de-tl-right{order:3;width:100%;justify-content:flex-end}
  .de-list{padding-bottom:max(24px,env(safe-area-inset-bottom))}
  body.dub-editor-open #dubPhase2 .tr-p2-main{padding-bottom:max(80px,env(safe-area-inset-bottom))}
}
@media (max-width:560px){
  :root{--de-top-h:108px;--de-tl-h:204px}
  .de-brand-text strong{font-size:12px}.de-top-actions{gap:3px}.de-top-actions .de-primary{padding:0 10px}.de-top-actions .de-icon-btn:not(.de-close){display:none}
  .de-top-center .de-lang{max-width:132px}.de-top-center .de-tool{flex:none}.de-tl-toolbar{gap:4px}.de-tl-left .de-tool span{display:inline}
  .de-tl-center{flex:1}.de-tl-center .de-time{min-width:84px;font-size:12px}.de-tl-right input[type="range"]{display:block;flex:1;width:auto}.de-fit-zoom{padding:0 9px}
  .de-item-text{max-height:none;overflow-y:hidden;font-size:16px}.de-list{overscroll-behavior:contain}
}
@media (prefers-reduced-motion:reduce){
  .de-side,body.dub-editor-open #dubPhase2 .tr-p2-main,.tr-p2-cap::before,body.dub-editor-open #dubReviewStage::after{transition:none!important}
}

/* ==========================================================================
   DUB EDITOR — visual NLE pass
   Context-first hierarchy inspired by modern subtitle/video editors: the
   transcript is a permanent rail, the picture owns the canvas, formatting
   follows the selected caption, and the timeline reads as an editing deck.
   ========================================================================== */
body.dub-editor-open{
  --de-side-w:clamp(292px,22vw,324px);
  --de-top-h:64px;
  --de-tl-h:244px;
  --de-canvas:#f4f5f7;
  --de-surface:#ffffff;
  --de-border:#e2e5ea;
  --de-border-strong:#c8cdd6;
  --de-purple:#7448f5;
  --de-purple-soft:#f1edff;
  --de-cyan:#31bdf2;
  background:var(--de-canvas);
}
body.dub-editor-open .de-topbar{
  height:var(--de-top-h); gap:12px; padding:0 16px;
  background:var(--glass-bg-strong); border-color:var(--de-border);
  box-shadow:var(--e-2); backdrop-filter:blur(16px);
}
body.dub-editor-open .de-brand{flex-basis:calc(var(--de-side-w) - 16px);gap:9px}
body.dub-editor-open .de-brand-ico{width:34px;height:34px;border-radius:8px;box-shadow:none}
body.dub-editor-open .de-brand-text strong{font-size:15px;font-weight:800}
body.dub-editor-open .de-brand-text span{font-size:12px;color:#697180}
body.dub-editor-open .de-top-center{justify-content:flex-start}
body.dub-editor-open .de-lang,
body.dub-editor-open .de-tool,
body.dub-editor-open .de-pill{
  border-color:var(--de-border);border-radius:8px;background:var(--surface);box-shadow:var(--e-1)}
body.dub-editor-open .de-tool:hover,
body.dub-editor-open .de-pill:hover{border-color:#b9aaff;background:var(--violet-soft);color:#5b35d5}
body.dub-editor-open .de-primary{height:38px;padding-inline:18px;border-radius:8px;background:var(--black);box-shadow:none}
body.dub-editor-open .de-primary:hover:not(:disabled){background:#272d3a;transform:none;box-shadow:none}

/* Transcript rail: flat, dense rows like a subtitle bin — not stacked cards. */
body.dub-editor-open .de-side{
  top:var(--de-top-h);bottom:0;width:var(--de-side-w);border-color:var(--de-border);
  box-shadow:var(--e-2);z-index:142
}
body.dub-editor-open .de-side-head{
  height:58px;padding:0 14px 0 18px;border-color:var(--de-border);font-size:14px;background:var(--surface)
}
body.dub-editor-open .de-side-head .de-add{width:34px;height:34px;border-color:var(--de-border);color:var(--text)}
body.dub-editor-open .de-list{padding:0 0 90px;scrollbar-gutter:stable}
body.dub-editor-open .de-item{
  margin:0;padding:14px 18px 15px 24px;border:0;border-bottom:1px solid #f0f1f3;border-radius:0;background:var(--surface);
  transition:background .14s ease,box-shadow .14s ease
}
body.dub-editor-open .de-item::before{
  content:"";position:absolute;left:10px;top:18px;bottom:18px;width:2px;border-radius:999px;background:transparent
}
body.dub-editor-open .de-item:hover{background:var(--canvas)}
body.dub-editor-open .de-item.is-active{background:var(--surface);box-shadow:none}
body.dub-editor-open .de-item.is-active::before{background:var(--de-purple)}
body.dub-editor-open .de-item-head{margin-bottom:5px;gap:8px}
body.dub-editor-open .de-item-no{width:auto;min-width:18px;height:auto;background:transparent;color:#9299a6;font:700 10px/1.2 ui-monospace,monospace;text-align:left}
body.dub-editor-open .de-item.is-active .de-item-no{background:transparent;color:var(--de-purple)}
body.dub-editor-open .de-item-time{font-size:12px;color:#8a919e;font-weight:650}
body.dub-editor-open .de-fit{font-size:12px;padding:3px 6px}
body.dub-editor-open .de-item-text{
  min-height:32px;padding:5px 0;border:0;border-radius:0;background:transparent;color:#232832;font-size:13px;font-weight:520;line-height:1.55
}
body.dub-editor-open .de-item-text:hover{border:0;background:transparent}
body.dub-editor-open .de-item-text:focus{padding:8px 10px;margin:0 -10px;border:1px solid #a994ff;border-radius:8px;background:var(--surface);box-shadow:0 0 0 3px rgba(116,72,245,.11)}

/* Light editing canvas. The picture, rather than the chrome, is the focal point. */
body.dub-editor-open .de-stage{left:var(--de-side-w);bottom:var(--de-tl-h);background:var(--de-canvas)}
body.dub-editor-open #dubPhase2 .tr-p2-side{
  top:var(--de-top-h);left:var(--de-side-w);height:calc(100dvh - var(--de-top-h) - var(--de-tl-h));
  padding:18px clamp(22px,4vw,58px);background:var(--de-canvas)
}
body.dub-editor-open #dubPhase2 .tr-p2-video{width:min(100%,1040px);max-width:1040px}
body.dub-editor-open #dubPhase2 .tr-p2-stage,
body.dub-editor-open #dubPhase2 .tr-p2-stage.is-landscape,
body.dub-editor-open #dubPhase2 .tr-p2-stage.is-portrait{
  width:max-content;max-width:100%;max-height:calc(100dvh - var(--de-top-h) - var(--de-tl-h) - 36px);
  border-radius:8px;background:var(--black);box-shadow:var(--e-3)}
body.dub-editor-open #dubPhase2 .tr-p2-player{
  width:auto;max-width:100%;max-height:calc(100dvh - var(--de-top-h) - var(--de-tl-h) - 36px);border-radius:8px
}

/* Caption selection bounds and resize handles. */
body.dub-editor-open .tr-p2-cap{padding:4px 10px;gap:0;isolation:isolate}
body.dub-editor-open .tr-p2-cap.is-editable::after{
  content:"";position:absolute;inset:-8px -10px;z-index:-1;border:1.5px solid #fff;border-radius:8px;
  box-shadow:var(--e-2);
  background:
    radial-gradient(circle at 0 0,#fff 0 4px,#596273 4.5px 5.5px,transparent 6px),
    radial-gradient(circle at 50% 0,#fff 0 4px,#596273 4.5px 5.5px,transparent 6px),
    radial-gradient(circle at 100% 0,#fff 0 4px,#596273 4.5px 5.5px,transparent 6px),
    radial-gradient(circle at 0 50%,#fff 0 4px,#596273 4.5px 5.5px,transparent 6px),
    radial-gradient(circle at 100% 50%,#fff 0 4px,#596273 4.5px 5.5px,transparent 6px),
    radial-gradient(circle at 0 100%,#fff 0 4px,#596273 4.5px 5.5px,transparent 6px),
    radial-gradient(circle at 50% 100%,#fff 0 4px,#596273 4.5px 5.5px,transparent 6px),
    radial-gradient(circle at 100% 100%,#fff 0 4px,#596273 4.5px 5.5px,transparent 6px)
}
body.dub-editor-open .tr-p2-cap::before{content:"↕";top:-29px;padding:3px 7px;background:var(--surface);color:#242936;border:1px solid #cfd3da;box-shadow:var(--e-2)}
body.dub-editor-open .tr-p2-cap.is-editable::before{opacity:1}
body.dub-editor-open #dubReviewStage::after{inset:7%;border-color:rgba(255,255,255,.32)}

/* Contextual formatter follows the selected caption. */
.de-caption-toolbar[hidden]{display:none!important}
.de-caption-toolbar{
  position:absolute;z-index:12;transform:translateX(-50%);display:flex;align-items:center;gap:3px;height:44px;padding:5px 7px;
  border:1px solid #dfe2e7;border-radius:8px;background:var(--glass-bg-strong);box-shadow:var(--e-2);
  pointer-events:auto;color:#343a46;white-space:nowrap
}
.de-caption-toolbar-grip{padding:0 3px;color:#9aa1ad;font-size:15px}
.de-caption-divider{width:1px;height:22px;margin:0 3px;background:var(--surface-hover)}
.de-caption-tool,.de-caption-icon{
  height:32px;border:0;border-radius:8px;background:transparent;color:#343a46;font:700 11px/1 inherit;cursor:pointer
}
.de-caption-tool{display:inline-flex;align-items:center;gap:6px;padding:0 9px}
.de-caption-icon{width:32px;font-size:15px}
.de-caption-tool:hover,.de-caption-icon:hover{background:var(--canvas);color:var(--text)}
.de-caption-tool:focus-visible,.de-caption-icon:focus-visible{outline:2px solid var(--de-purple);outline-offset:1px}
.de-caption-size{min-width:38px;text-align:center;font:750 10px/1 ui-monospace,monospace;color:#5e6571}

/* Timeline deck: separated from transcript rail, taller tracks, clearer playhead. */
body.dub-editor-open .de-timeline{
  left:var(--de-side-w);height:var(--de-tl-h);border-color:var(--de-border);box-shadow:var(--e-2);z-index:145
}
body.dub-editor-open .de-tl-toolbar{height:58px;padding:0 16px;border-color:var(--de-border);background:var(--surface)}
body.dub-editor-open .de-tl-left{gap:4px}
body.dub-editor-open .de-tl-center{gap:5px}
body.dub-editor-open .de-tl-right{gap:8px}
body.dub-editor-open .de-transport{width:34px;height:34px}
body.dub-editor-open .de-transport.de-play{width:38px;height:38px;border-radius:999px;box-shadow:none}
body.dub-editor-open .de-time{min-width:132px;font-size:12px;color:#515866}
body.dub-editor-open .de-tl-content{background:var(--surface)}
body.dub-editor-open .de-ruler{height:28px;border-color:#eceef1;background:var(--canvas)}
body.dub-editor-open .de-tick{background:#dfe2e7}
body.dub-editor-open .de-tick span{top:8px;color:#747c89;font-size:12px}
body.dub-editor-open .de-video-track{top:28px;height:52px;background:var(--info-soft);border:0}
body.dub-editor-open .de-video-track .de-filmstrip{opacity:1;background:linear-gradient(180deg,#cbd1da,#b8c0cb)}
body.dub-editor-open .de-video-track .de-filmstrip::after{background:repeating-linear-gradient(90deg,rgba(255,255,255,.45) 0 3px,transparent 3px 54px,rgba(40,48,62,.10) 54px 55px)}
body.dub-editor-open .de-sub-track{top:82px;height:48px;background:linear-gradient(180deg,#fcfbff,#f7f5ff)}
body.dub-editor-open .de-audio-track{top:132px;height:48px;background:var(--info-soft);border:0}
body.dub-editor-open .de-audio-track .de-wave{top:7px;bottom:7px;opacity:.8;background:repeating-linear-gradient(90deg,var(--de-cyan) 0 2px,transparent 2px 5px)}
body.dub-editor-open .de-track-label{left:10px;padding:3px 7px;color:#596170;background:var(--glass-bg-strong);font-size:12px}
body.dub-editor-open .de-clip{
  top:5px;height:38px;border-radius:8px;background:#8758f6;border-color:#7242e8;box-shadow:none
}
body.dub-editor-open .de-clip:hover{background:#7949ed}
body.dub-editor-open .de-clip.is-selected{box-shadow:0 0 0 2px #fff,0 0 0 4px #7448f5}
body.dub-editor-open .de-playhead{width:1.5px;background:var(--black);box-shadow:none}
body.dub-editor-open .de-playhead::before{width:10px;height:10px;margin-left:-5px;background:var(--black);border-radius:0 0 8px 8px}

/* Tablet: retain the rail until the actual drawer breakpoint. */
@media (max-width:1100px) and (min-width:861px){
  body.dub-editor-open{--de-side-w:286px;--de-tl-h:232px}
  body.dub-editor-open .de-brand-text span{display:none}
  body.dub-editor-open .de-topbar{gap:8px;padding-inline:12px}
  body.dub-editor-open .de-tool{padding-inline:9px}
  body.dub-editor-open .de-pill{max-width:132px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  body.dub-editor-open #dubPhase2 .tr-p2-side{padding:14px 20px}
}
@media (max-width:860px){
  body.dub-editor-open{--de-side-w:0px;--de-top-h:104px;--de-tl-h:218px}
  body.dub-editor-open .de-timeline{left:0}
  body.dub-editor-open .de-side{top:0;bottom:0;width:min(400px,94vw);padding-top:env(safe-area-inset-top);z-index:190}
  body.dub-editor-open #dubPhase2 .tr-p2-side{left:0;padding:14px;background:var(--de-canvas)}
  body.dub-editor-open #dubPhase2 .tr-p2-stage{max-height:calc(100dvh - var(--de-top-h) - var(--de-tl-h) - 28px)}
  body.dub-editor-open #dubPhase2 .tr-p2-player{max-height:calc(100dvh - var(--de-top-h) - var(--de-tl-h) - 28px)}
  .de-caption-toolbar{height:42px;max-width:calc(100vw - 20px);overflow-x:auto}
}
@media (max-width:560px){
  body.dub-editor-open{--de-top-h:108px;--de-tl-h:226px}
  body.dub-editor-open .de-top-actions .de-primary{height:40px}
  body.dub-editor-open .de-tl-toolbar{height:82px;padding:6px 8px;align-content:center}
  body.dub-editor-open .de-tl-left .de-tool{height:36px;min-height:36px;font-size:12px}
  body.dub-editor-open .de-tl-right{padding-inline:2px}
  body.dub-editor-open #dubPhase2 .tr-p2-side{padding:8px}
  .de-caption-toolbar-grip,.de-caption-divider{display:none}
  .de-caption-toolbar{gap:1px;padding-inline:4px}
  .de-caption-tool{padding-inline:7px}
}

body.dub-editor-open .de-item-head .de-fit{margin-left:auto}
body.dub-editor-open .de-item-time{padding:2px 0;border:0;background:transparent;cursor:pointer;text-align:left}
body.dub-editor-open .de-item-time:hover{color:#4f2fbd;text-decoration:underline;text-underline-offset:2px}
body.dub-editor-open .de-item-more{width:28px;height:28px;margin:-5px -8px -5px 0;border:0;border-radius:8px;background:transparent;color:#727986;font-size:16px;line-height:1;cursor:pointer;opacity:.35}
body.dub-editor-open .de-item:hover .de-item-more,body.dub-editor-open .de-item.is-active .de-item-more,body.dub-editor-open .de-item-more:focus-visible{opacity:1}
body.dub-editor-open .de-item-more:hover{background:var(--canvas);color:var(--text)}
body.dub-editor-open .de-item-more:focus-visible{outline:2px solid var(--de-purple);outline-offset:1px}
@media (pointer:coarse){body.dub-editor-open .de-item-more{width:44px;height:44px;margin:-12px -12px -12px 0;opacity:1}}

/* ==========================================================================
   DUB REVIEW — professional phase-2 workspace
   --------------------------------------------------------------------------
   The full-screen dub editor is gone; this is the polished, single-surface
   review flow that remains. It keeps the existing IDs/hooks untouched and is
   written as a final override layer so the legacy rules can stay as fallbacks.
   ========================================================================== */
body.dub-review-open #dubPhase2{
  --p2-ink:#0f172a;
  --p2-text:#334155;
  --p2-muted:#64748b;
  --p2-faint:#94a3b8;
  --p2-line:#e2e8f0;
  --p2-line-soft:#f1f5f9;
  --p2-soft:#f8fafc;
  --p2-accent:#e11d48;
  --p2-accent-2:#fb7185;
  --p2-accent-soft:#fff1f2;
  --p2-shadow:0 1px 2px rgba(15,23,42,.04), 0 12px 32px rgba(15,23,42,.06);
  color:var(--p2-text);
}

/* The old decorative wash is replaced with a very quiet top highlight. */
body.dub-review-open #dubPhase2::before{
  height:132px !important;
  background:linear-gradient(180deg,rgba(248,250,252,.94),rgba(255,255,255,0)) !important;
}

@media (min-width:900px){
  body.dub-review-open #dubPhase2{
    width:min(1240px,calc(100vw - 48px)) !important;
    height:min(840px,calc(100vh - 48px)) !important;
    height:min(840px,calc(100dvh - 48px)) !important;
    padding:0 44px !important;
    border:1px solid rgba(226,232,240,.96) !important;
    border-radius:16px !important;
    background:var(--surface) !important;
    box-shadow:var(--e-3);
    scrollbar-gutter:stable;
  }
  body.dub-review-open #dubPhase2 > .tr-phase2-head{
    min-height:72px;
    margin:0 -44px 22px !important;
    padding:14px 224px 14px 44px !important;
    align-items:center;
    background:var(--glass-bg-strong) !important;
    border-bottom:1px solid var(--p2-line-soft);
    box-shadow:var(--e-2);
    backdrop-filter:blur(16px) saturate(1.25);
  }
  body.dub-review-open #dubPhase2 > .tr-phase2-head .tr-category{
    font-size:20px !important;
    margin:0;
    font-weight:760;
    letter-spacing:-.045em;
    color:var(--p2-ink);
  }
  body.dub-review-open #dubPhase2 .tr-p2-grid{
    grid-template-columns:minmax(0,1fr) minmax(430px,46%) !important;
    gap:30px !important;
  }
}

body.dub-review-open #dubPhase2 .tr-phase2-lede{
  max-width:700px;
  margin:0 0 24px;
  color:var(--p2-muted);
  font-size:14px;
  line-height:1.65;
}

body.dub-review-open #dubPhase2 .tr-restart{
  top:26px !important;
  right:84px !important;
  min-height:40px;
  padding:0 15px;
  border:1px solid var(--p2-line);
  border-radius:8px;
  background:var(--surface);
  color:var(--p2-muted);
  font-size:12px;
  font-weight:650;
  box-shadow:var(--e-1);
}
body.dub-review-open #dubPhase2 .tr-restart:hover{
  background:var(--p2-soft);
  border-color:#cbd5e1;
  color:var(--p2-ink);
}
body.dub-review-open #dubPhase2 .tr-review-close{
  top:20px !important;
  right:24px !important;
  width:32px;
  height:32px;
  border-radius:8px;
  color:var(--p2-muted);
}
body.dub-review-open #dubPhase2 .tr-review-close:hover{
  background:var(--p2-soft);
  color:var(--p2-ink);
}

/* Main column: one clean stack instead of a long list of unrelated rows. */
body.dub-review-open #dubPhase2 .tr-p2-main{
  display:flex;
  flex-direction:column;
  gap:16px;
  min-width:0;
  padding-bottom:6px;
}

/* Language tabs become a compact inset segmented control. */
body.dub-review-open #dubPhase2 .tr-lang-tabs{
  width:max-content;
  max-width:100%;
  margin:0;
  padding:4px;
  gap:4px;
  border:1px solid var(--p2-line);
  border-radius:12px;
  background:var(--p2-soft);
}
body.dub-review-open #dubPhase2 .tr-lang-tab{
  border:0;
  border-radius:8px;
  padding:8px 15px;
  background:transparent;
  color:var(--p2-muted);
  font-size:12px;
  font-weight:650;
  box-shadow:none;
}
body.dub-review-open #dubPhase2 .tr-lang-tab:hover{
  background:var(--surface);
  border-color:transparent;
  color:var(--p2-ink);
}
body.dub-review-open #dubPhase2 .tr-lang-tab.on{
  background:var(--surface);
  color:var(--p2-ink);
  box-shadow:var(--e-1);
}
body.dub-review-open #dubPhase2 .tr-lang-tab.is-untranslated{
  background:var(--danger-soft);
  color:#b91c1c;
}
body.dub-review-open #dubPhase2 .tr-lang-tab.is-untranslated.on{
  background:var(--surface);
  color:#b91c1c;
  box-shadow:var(--e-1);
}

/* Shared surface for the cards in the review column. */
body.dub-review-open #dubPhase2 .tr-working-version,
body.dub-review-open #dubPhase2 .tr-p2-voice-card,
body.dub-review-open #dubPhase2 .tr-p2-output-card,
body.dub-review-open #dubPhase2 .tr-adv,
body.dub-review-open #dubPhase2 .tr-results,
body.dub-review-open #dubPhase2 .tr-plan{
  border:1px solid var(--p2-line);
  border-radius:16px;
  background:var(--surface);
  box-shadow:var(--p2-shadow);
}

body.dub-review-open #dubPhase2 .tr-working-version{
  margin:0;
  padding:14px 16px;
  background:linear-gradient(135deg,#f8fafc,#fff 70%);
}
body.dub-review-open #dubPhase2 .tr-working-title{
  font-size:13px;
  font-weight:700;
  color:var(--p2-ink);
}
body.dub-review-open #dubPhase2 .tr-working-status{
  color:var(--p2-muted);
}
body.dub-review-open #dubPhase2 .tr-working-badge{
  padding:5px 9px;
  border-radius:999px;
  background:var(--info-soft);
  color:var(--p2-muted);
  font-size:12px;
  font-weight:750;
  letter-spacing:.06em;
  text-transform:uppercase;
}
body.dub-review-open #dubPhase2 .tr-working-meta{
  gap:6px 20px;
  color:var(--p2-muted);
}
body.dub-review-open #dubPhase2 .tr-working-meta b{
  color:var(--p2-faint);
  font-size:12px;
  letter-spacing:.04em;
  text-transform:uppercase;
}
body.dub-review-open #dubPhase2 .tr-working-meta em{
  color:var(--p2-ink);
}

/* Voice card: summary row + expansion read as one control. */
body.dub-review-open #dubPhase2 .tr-p2-voice-card{
  overflow:hidden;
}
body.dub-review-open #dubPhase2 .tr-p2-voice-card .tr-voice-summary{
  margin:0;
  border:0;
  border-radius:0;
  background:linear-gradient(135deg,#fff 0%,#fff7f8 100%);
  padding:15px 16px;
  box-shadow:none;
}
body.dub-review-open #dubPhase2 .tr-vs-ico{
  width:38px;
  height:38px;
  align-items:center;
  justify-content:center;
  border-radius:12px;
  color:var(--p2-accent);
  background:var(--p2-accent-soft);
}
body.dub-review-open #dubPhase2 .tr-vs-text{
  color:var(--p2-muted);
  font-size:13px;
}
body.dub-review-open #dubPhase2 .tr-vs-val{
  color:var(--p2-ink);
  font-weight:650;
}
body.dub-review-open #dubPhase2 .tr-vs-change{
  border:1px solid var(--p2-line);
  border-radius:8px;
  color:var(--p2-ink);
  background:var(--surface);
  font-weight:650;
}
body.dub-review-open #dubPhase2 .tr-vs-change:hover{
  background:var(--p2-soft);
  border-color:#cbd5e1;
  color:var(--p2-accent);
}
body.dub-review-open #dubPhase2 .tr-p2-voice-card .tr-voice-block{
  margin:0;
  padding:16px;
  border:0;
  border-top:1px solid var(--p2-line-soft);
  border-radius:0;
}
body.dub-review-open #dubPhase2 .tr-voice-opt{
  border-color:var(--p2-line);
  border-radius:12px;
  box-shadow:none;
}
body.dub-review-open #dubPhase2 .tr-voice-opt:hover{
  border-color:#fda4af;
  background:var(--coral-tint-2);
}
body.dub-review-open #dubPhase2 .tr-voice-opt.on{
  border-color:#fda4af;
  background:var(--p2-accent-soft);
  box-shadow:0 0 0 3px rgba(244,63,94,.08);
}
body.dub-review-open #dubPhase2 .tr-voice-opt.on .tr-voice-ico{
  background:linear-gradient(135deg,var(--p2-accent-2),var(--p2-accent));
}
body.dub-review-open #dubPhase2 .tr-voice-opt-title{
  color:var(--p2-ink);
}
body.dub-review-open #dubPhase2 .tr-voice-opt-desc,
body.dub-review-open #dubPhase2 .tr-voice-hint,
body.dub-review-open #dubPhase2 .tr-voice-empty{
  color:var(--p2-muted);
}

/* Output settings card: grouped rows with a quiet, modern segmented control. */
body.dub-review-open #dubPhase2 .tr-p2-output-card{
  overflow:hidden;
  padding:2px 0;
}
body.dub-review-open #dubPhase2 .tr-p2-output-card .tr-sync{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:16px;
  margin:0;
  padding:14px 16px 8px;
  border:0;
}
body.dub-review-open #dubPhase2 .tr-p2-output-card .tr-sync{
  border-top:1px solid var(--p2-line-soft);
}
body.dub-review-open #dubPhase2 .tr-p2-output-card .tr-sync:first-child{
  border-top:0;
}
body.dub-review-open #dubPhase2 .tr-p2-output-card .tr-sync-label{
  color:var(--p2-ink);
  font-size:12px;
  font-weight:700;
  letter-spacing:.01em;
  text-transform:none;
}
body.dub-review-open #dubPhase2 .tr-p2-output-card .tr-sync-hint{
  margin:0;
  padding:0 16px 14px;
  color:var(--p2-muted);
  font-size:12px;
  line-height:1.55;
}
body.dub-review-open #dubPhase2 .tr-p2-output-card .tr-sync-opts{
  display:inline-flex;
  gap:2px;
  padding:3px;
  border:0;
  border-radius:12px;
  background:var(--info-soft);
  overflow:visible;
}
body.dub-review-open #dubPhase2 .tr-p2-output-card .tr-sync-opt{
  border:0;
  border-radius:8px;
  padding:7px 13px;
  background:transparent;
  color:var(--p2-muted);
  font-size:12px;
  font-weight:650;
  white-space:nowrap;
  box-shadow:none;
}
body.dub-review-open #dubPhase2 .tr-p2-output-card .tr-sync-opt + .tr-sync-opt{
  border-left:0;
}
body.dub-review-open #dubPhase2 .tr-p2-output-card .tr-sync-opt:hover{
  background:var(--info-soft);
  color:var(--p2-ink);
}
body.dub-review-open #dubPhase2 .tr-p2-output-card .tr-sync-opt.on{
  background:var(--surface);
  color:var(--p2-ink);
  box-shadow:var(--e-1);
}

/* Collapsible review/mixer/details share the same quiet chrome. */
body.dub-review-open #dubPhase2 .tr-adv{
  margin:0;
  background:var(--surface);
  overflow:hidden;
}
body.dub-review-open #dubPhase2 .tr-adv-sum{
  padding:15px 16px;
  color:var(--p2-ink);
  font-size:13px;
  font-weight:700;
}
body.dub-review-open #dubPhase2 .tr-adv-sum::before{
  opacity:.7;
}
body.dub-review-open #dubPhase2 .tr-adv[open] .tr-adv-sum{
  border-bottom:1px solid var(--p2-line);
}
body.dub-review-open #dubPhase2 .tr-review-body{
  padding:16px;
}
body.dub-review-open #dubPhase2 .tr-review-title{
  color:var(--p2-ink);
}
body.dub-review-open #dubPhase2 .tr-dl-mini{
  border:1px solid #fecdd3;
  border-radius:8px;
  background:var(--p2-accent-soft);
  color:var(--p2-accent);
}
body.dub-review-open #dubPhase2 .tr-dl-mini:hover{
  background:var(--coral-tint-2);
}
body.dub-review-open #dubPhase2 .tr-find-in{
  border-color:var(--p2-line);
  border-radius:8px;
}
body.dub-review-open #dubPhase2 .tr-find-in:focus{
  border-color:var(--p2-accent-2);
  box-shadow:0 0 0 3px rgba(244,63,94,.10);
}
body.dub-review-open #dubPhase2 .tr-find-btn{
  border-radius:8px;
  color:var(--p2-accent);
  border-color:#fecdd3;
  background:var(--p2-accent-soft);
}
body.dub-review-open #dubPhase2 .tr-seg{
  border:1px solid var(--p2-line);
  border-radius:12px;
  background:var(--surface);
  padding:12px;
  margin-bottom:8px;
}
body.dub-review-open #dubPhase2 .tr-seg:last-child{
  margin-bottom:0;
}
body.dub-review-open #dubPhase2 .tr-seg:hover{
  background:var(--surface);
  border-color:#cbd5e1;
}
body.dub-review-open #dubPhase2 .tr-seg-src{
  color:var(--p2-muted);
  font-size:12px;
  opacity:1;
}
body.dub-review-open #dubPhase2 .tr-seg-ta{
  border:1px solid var(--p2-line);
  border-radius:8px;
  background:var(--p2-soft);
  padding:9px 11px;
  color:var(--p2-ink);
}
body.dub-review-open #dubPhase2 .tr-seg-ta:hover{
  background:var(--surface);
  border-color:#cbd5e1;
}
body.dub-review-open #dubPhase2 .tr-seg-ta:focus{
  background:var(--surface);
  border-color:var(--p2-accent-2);
  box-shadow:0 0 0 3px rgba(244,63,94,.12);
}
body.dub-review-open #dubPhase2 .tr-seg-fit.ok,
body.dub-review-open #dubPhase2 .tr-seg-undo{
  color:#047857;
  background:var(--success-tint);
}
body.dub-review-open #dubPhase2 .tr-seg-fit.tight{
  color:var(--warn-ink);
  background:var(--warn-soft);
}
body.dub-review-open #dubPhase2 .tr-seg-fit.over,
body.dub-review-open #dubPhase2 .tr-seg-btn-attention{
  color:#be123c;
  background:var(--danger-soft);
}

/* Results/version cards. */
body.dub-review-open #dubPhase2 .tr-results{
  margin:0;
  padding:16px;
}
body.dub-review-open #dubPhase2 .tr-results-head{
  color:var(--p2-ink);
  font-size:13px;
  font-weight:700;
}
body.dub-review-open #dubPhase2 .tr-result{
  border:1px solid var(--p2-line);
  border-radius:12px;
  background:var(--p2-soft);
}
body.dub-review-open #dubPhase2 .tr-result.is-current{
  border-color:#fecdd3;
  background:var(--coral-tint-2);
}

/* Outcome summary + sticky action bar. */
body.dub-review-open #dubPhase2 .tr-plan{
  margin:0;
  border-color:#bbf7d0;
  background:var(--success-tint);
  box-shadow:none;
}
body.dub-review-open #dubPhase2 .tr-plan-text{
  color:#166534;
  font-size:12px;
  line-height:1.55;
}
body.dub-review-open #dubPhase2 .tr-p2-main .tr-actions.tr-sticky{
  margin:6px 0 0 !important;
  padding:16px 0 10px !important;
  border-top:1px solid var(--p2-line);
  border-radius:0 0 16px 16px;
  background:var(--glass-bg-strong) !important;
  box-shadow:none;
  backdrop-filter:blur(12px);
}
body.dub-review-open #dubPhase2 .tr-p2-main .tr-actions.tr-sticky .tr-btn{
  width:100%;
  min-height:52px;
  margin-left:0;
  border-radius:12px;
  font-size:14px;
  font-weight:700;
}
body.dub-review-open #dubPhase2 #dubRenderBtn{
  min-height:52px !important;
  background:linear-gradient(135deg,#fb7185,#e11d48) !important;
  box-shadow:var(--e-2);
}
body.dub-review-open #dubPhase2 #dubRenderBtn:hover:not(:disabled){
  transform:translateY(-1px);
  box-shadow:var(--e-3);
}
body.dub-review-open #dubPhase2 .tr-stale-hint{
  color:var(--warn-ink);
}

/* Preview column: frame the video like a real monitor, not a raw element. */
body.dub-review-open #dubPhase2 .tr-p2-side{
  gap:12px;
}
body.dub-review-open #dubPhase2 .tr-p2-video{
  margin:0;
  padding:13px;
  border:1px solid var(--p2-line);
  border-radius:16px;
  background:var(--p2-soft);
  box-shadow:var(--p2-shadow);
}
body.dub-review-open #dubPhase2 .tr-p2-stage{
  border-radius:12px;
  overflow:hidden;
  background:#0b1120;
  box-shadow:var(--e-3);
}
body.dub-review-open #dubPhase2 .tr-p2-player{
  border-radius:12px;
}
body.dub-review-open #dubPhase2 .tr-preview-actions{
  gap:9px;
}
body.dub-review-open #dubPhase2 .tr-pv-switch{
  margin-top:12px;
  border-radius:8px;
  border-color:var(--p2-line);
}
body.dub-review-open #dubPhase2 .tr-pv-switch-btn.on{
  background:linear-gradient(135deg,#fb7185,#e11d48);
}
body.dub-review-open #dubPhase2 .tr-p2-vhint{
  margin-top:10px;
  color:var(--p2-muted);
  font-size:12px;
}

/* Mixer keeps its indigo accent, but adopts the same geometry. */
body.dub-review-open #dubPhase2 .tr-mixer .tr-adv-sum::before{
  opacity:.75;
}
body.dub-review-open #dubPhase2 .tr-mix-body .tr-blk-label{
  color:var(--p2-muted);
}
body.dub-review-open #dubPhase2 .tr-lib,
body.dub-review-open #dubPhase2 .tr-preview,
body.dub-review-open #dubPhase2 .tr-tl,
body.dub-review-open #dubPhase2 .tr-clip{
  border-radius:12px;
  border-color:var(--p2-line);
}
body.dub-review-open #dubPhase2 .tr-src-tabs{
  border-color:var(--p2-line);
  background:var(--p2-soft);
  padding:3px;
  gap:2px;
  border-radius:12px;
}
body.dub-review-open #dubPhase2 .tr-src-tab{
  border:0 !important;
  border-radius:8px;
  color:var(--p2-muted);
}
body.dub-review-open #dubPhase2 .tr-src-tab.on{
  color:#fff;
  background:linear-gradient(135deg,#818cf8,#6366f1);
}

/* Mobile: preserve the full-bleed sheet, just soften the internal grouping. */
@media (max-width:899px){
  body.dub-review-open #dubPhase2{
    border-radius:0 !important;
  }
  body.dub-review-open #dubPhase2 .tr-p2-main{
    gap:12px;
  }
  body.dub-review-open #dubPhase2 .tr-phase2-lede{
    font-size:13px;
    margin-bottom:18px;
  }
  body.dub-review-open #dubPhase2 .tr-restart{
    position:static !important;
    top:auto !important;
    right:auto !important;
  }
  body.dub-review-open #dubPhase2 .tr-review-close{
    position:static !important;
    top:auto !important;
    right:auto !important;
  }
  body.dub-review-open #dubPhase2 .tr-lang-tabs{
    width:100%;
  }
  body.dub-review-open #dubPhase2 .tr-lang-tab{
    flex:1 1 auto;
  }
  body.dub-review-open #dubPhase2 .tr-p2-output-card .tr-sync{
    display:flex;
    flex-direction:column;
    align-items:stretch;
    gap:10px;
    padding:14px 14px 6px;
  }
  body.dub-review-open #dubPhase2 .tr-p2-output-card .tr-sync-opts{
    width:100%;
  }
  body.dub-review-open #dubPhase2 .tr-p2-output-card .tr-sync-opt{
    flex:1 1 0;
    min-width:0;
    padding-inline:8px;
  }
  body.dub-review-open #dubPhase2 .tr-p2-output-card .tr-sync-hint{
    padding:0 14px 13px;
  }
  body.dub-review-open #dubPhase2 .tr-p2-video{
    padding:10px;
    border-radius:16px;
  }
  body.dub-review-open #dubPhase2 .tr-p2-main .tr-actions.tr-sticky{
    border-radius:0;
  }
}

/* ==========================================================================
   DUB REVIEW — full-screen workspace
   --------------------------------------------------------------------------
   Phase 2 is the working screen for a long transcript, a preview and several
   output controls. A centred modal forces it into a fixed 1240px box; this
   layer lets it use the whole viewport while keeping the same two-column
   layout, sticky header and sticky action footer.
   ========================================================================== */
body.dub-review-open::before{
  display:none !important;
}

body.dub-review-open #dubPhase2{
  position:fixed !important;
  inset:0 !important;
  transform:none !important;
  width:100% !important;
  height:100vh !important;
  height:100dvh !important;
  min-height:100vh !important;
  min-height:100dvh !important;
  max-width:none !important;
  max-height:none !important;
  margin:0 !important;
  /* The gutter is shared with the header, which bleeds into it, so one value
     keeps the title, the close button and the columns on one edge. */
  padding:0 clamp(16px,2.4vw,36px) !important;
  border:0 !important;
  border-radius:0 !important;
  background:var(--surface) !important;
  box-shadow:none !important;
  animation:none !important;
  overflow:auto !important;
  overscroll-behavior:contain;
}

/* A 72px header with a blur, a shadow and a 216px right pad for buttons that no
   longer sit there. This is a workspace toolbar: one line, one hairline. */
body.dub-review-open #dubPhase2 > .tr-phase2-head{
  position:sticky;
  top:0;
  z-index:8;
  min-height:56px;
  margin:0 calc(-1 * clamp(16px,2.4vw,36px)) 10px !important;
  padding:0 clamp(16px,2.4vw,36px) !important;
  border-radius:0 !important;
  background:var(--surface) !important;
  border-bottom:1px solid var(--border-2) !important;
  box-shadow:none !important;
  backdrop-filter:none;
}

body.dub-review-open #dubPhase2 .tr-phase2-lede{
  max-width:760px;
}

@media (min-width:900px){
  body.dub-review-open #dubPhase2 .tr-p2-grid{
    width:100%;
    max-width:unset;
    margin-left:auto;
    margin-right:auto;
    grid-template-columns:minmax(0, 1fr) minmax(320px, 38%) !important;
    gap:30px !important;
  }
  body.dub-review-open #dubPhase2 .tr-p2-main{
    max-width:900px;
  }
}

@media (max-width:899px){
  body.dub-review-open #dubPhase2{
    padding:0 18px !important;
  }
  body.dub-review-open #dubPhase2 > .tr-phase2-head{
    min-height:48px;
    margin:0 -18px 14px !important;
    padding:8px 18px !important;
  }
  body.dub-review-open #dubPhase2 .tr-phase2-lede{
    max-width:none;
    margin-bottom:18px;
  }
  body.dub-review-open #dubPhase2 .tr-p2-grid{
    max-width:none;
    margin-left:0;
    margin-right:0;
  }
}

/* ==========================================================================
   DUB REVIEW — smoothness pass
   --------------------------------------------------------------------------
   The full-screen surface is where long transcripts live. Keep scrolling and
   typing cheap: avoid live backdrop blur on sticky chrome, let off-screen rows
   skip layout/paint, and make touch targets respond without a tap delay.
   ========================================================================== */
body.dub-review-open #dubPhase2 > .tr-phase2-head,
body.dub-review-open #dubPhase2 .tr-p2-main .tr-actions.tr-sticky{
  backdrop-filter:none !important;
  -webkit-backdrop-filter:none !important;
  background:var(--surface) !important;
}

body.dub-review-open #dubPhase2 .tr-seg,
body.dub-review-open #dubPhase2 .tr-result{
  content-visibility:auto;
  contain-intrinsic-size:1px 128px;
}

body.dub-review-open #dubPhase2 button,
body.dub-review-open #dubPhase2 a,
body.dub-review-open #dubPhase2 label,
body.dub-review-open #dubPhase2 .tr-seg,
body.dub-review-open #dubPhase2 .tr-seg-ta,
body.dub-review-open #dubPhase2 .tr-sync-opt,
body.dub-review-open #dubPhase2 .tr-lang-tab,
body.dub-review-open #dubPhase2 .tr-src-tab{
  -webkit-tap-highlight-color:transparent;
}

body.dub-review-open #dubPhase2 button,
body.dub-review-open #dubPhase2 .tr-sync-opt,
body.dub-review-open #dubPhase2 .tr-lang-tab,
body.dub-review-open #dubPhase2 .tr-src-tab,
body.dub-review-open #dubPhase2 .tr-seg{
  touch-action:manipulation;
}


@media (prefers-reduced-motion:reduce){
  body.dub-review-open #dubPhase2 *,
  body.dub-review-open #dubPhase2 *::before,
  body.dub-review-open #dubPhase2 *::after{
    scroll-behavior:auto !important;
    transition-duration:.01ms !important;
    animation-duration:.01ms !important;
    animation-iteration-count:1 !important;
  }
}

/* ==========================================================================
   DUB REVIEW — virtual transcript list
   --------------------------------------------------------------------------
   A long transcript (hundreds of lines) is windowed inside its own scroll area.
   This keeps the DOM small while preserving the native scroll height with top /
   bottom spacer elements.
   ========================================================================== */
body.dub-review-open #dubPhase2 .tr-seglist.is-virtual{
  display:block;
  max-height:clamp(360px, 64vh, 760px);
  overflow-y:auto;
  overscroll-behavior:contain;
  -webkit-overflow-scrolling:touch;
  scrollbar-gutter:stable;
  padding:2px 6px 2px 2px;
  scroll-behavior:auto;
}
body.dub-review-open #dubPhase2 .tr-seglist.is-virtual .tr-seg{
  margin:0 0 10px;
  content-visibility:visible !important;
}
body.dub-review-open #dubPhase2 .tr-seg-spacer{
  height:0;
  margin:0;
  padding:0;
  pointer-events:none;
}
body.dub-review-open #dubPhase2 .tr-seglist.is-virtual::-webkit-scrollbar{
  width:10px;
}
body.dub-review-open #dubPhase2 .tr-seglist.is-virtual::-webkit-scrollbar-thumb{
  background:#d7dce5;
  border:3px solid #fff;
  border-radius:999px;
}
body.dub-review-open #dubPhase2 .tr-seglist.is-virtual::-webkit-scrollbar-thumb:hover{
  background:#c2c9d6;
}
@media (max-width:899px){
  body.dub-review-open #dubPhase2 .tr-seglist.is-virtual{
    max-height:58vh;
    padding-right:4px;
  }
}

/* ---------------------------------------------------------------------------
   Voice Library — mobile layout polish.
   The library toolbar was usable on a phone but cramped: search + filter +
   sort all competed for one row, and the filter panel could wrap into a narrow
   column. Give the toolbar a clear two-column grid on small screens so the
   search field owns its own row and the controls stay comfortably tappable.
   --------------------------------------------------------------------------- */
@media (max-width:560px){
  .lib-controls{ display:grid; grid-template-columns:1fr 1fr; gap:8px; }
  .lib-search{ grid-column:1 / -1; min-width:0; }
  .lib-tool-btn, .lib-sort{ width:100%; }
  .lib-sort select{ width:100%; }
  .lib-filter-panel{ display:grid; grid-template-columns:1fr; gap:8px; }
  .lib-filter-panel .vp-filter{ width:100%; }
  .lib-filter-reset{ width:100%; margin-left:0; }
  .lib-tabs{ overflow-x:auto; white-space:nowrap; -webkit-overflow-scrolling:touch; }
  .lib-tab{ flex:0 0 auto; }
}

/* ---------------------------------------------------------------------------
   Dubbing review workspace — keep navigation and show a real deliverable
   --------------------------------------------------------------------------
   The review overlay used to cover the whole viewport, including the sidebar,
   so the only way out was the close X. Keep a compact tool rail visible while
   the workspace is open, and shift the workspace into the remaining space.
   Audio-only jobs (and cold video jobs whose MP4 is gone) get an audio card in
   the preview column instead of a blank pane.
   --------------------------------------------------------------------------- */
body.dub-review-open #dubPhase2 .tr-p2-audio{
  margin:0;
  padding:14px 16px 16px;
  border:1px solid var(--p2-line, var(--border-2));
  border-radius:16px;
  background:var(--p2-soft, #f8fafc);
  box-shadow:var(--p2-shadow, 0 1px 2px rgba(15,23,42,.04), 0 12px 32px rgba(15,23,42,.06));
}
body.dub-review-open #dubPhase2 .tr-p2-audio-head{
  margin:0 0 10px;
  color:var(--p2-muted, var(--text-3));
  font-size:12px;
  font-weight:750;
  letter-spacing:.07em;
  text-transform:uppercase;
}
body.dub-review-open #dubPhase2 .tr-p2-audio-player{
  display:block;
  width:100%;
  height:40px;
}
/* A/B on the audio card: the switch belongs to the player it swaps, and its hint
   is one quiet line under it. */
body.dub-review-open #dubPhase2 .tr-audio-switch{ margin:0 0 10px; }
body.dub-review-open #dubPhase2 .tr-p2-audio-hint{
  margin:8px 0 0; color:var(--p2-muted, var(--text-3)); font-size:12px; line-height:1.45;
}
/* "Just created" — a finished render has to be findable in a list that grows with
   every take, and audible without hunting for the newest row. */
.tr-fresh-badge{
  flex:none; margin-left:8px; padding:2px 7px; border-radius:999px;
  background:linear-gradient(135deg,#fe655d,#fc3e57); color:#fff;
  font-size:12px; font-weight:800; letter-spacing:.04em; text-transform:uppercase;
}
@keyframes tr-fresh-pulse{
  0%{ box-shadow:0 0 0 0 rgba(254,101,93,.42); }
  100%{ box-shadow:0 0 0 14px rgba(254,101,93,0); }
}
body.dub-review-open #dubPhase2 .tr-p2-audio.is-fresh,
.tr-result.is-fresh{
  animation:tr-fresh-pulse 1.1s ease-out 2;
  border-color:rgba(254,101,93,.55);
}
/* Voice Changer: the pre-render voice picker sits in the setup form, above the
   buttons it gates. */
#dubChangerVoice .tr-voice-pick-row{ display:flex; align-items:center; gap:10px; }
#dubChangerVoice .tr-voice-chip{ flex:1; min-width:0; }
#dubChangerVoicePreview:disabled{ opacity:.4; cursor:not-allowed; }

/* SUPERSEDED 2026-11-08 — this block used to turn the review into a "resident"
   in-flow workspace on wide screens (static, centred at 1200px, its own scroll
   box capped at 100dvh-120px), while mobile stayed a full-bleed sheet. Two
   different shells for one screen meant the workspace changed shape at 900px
   and never used the height it was given. The workspace is now full-viewport at
   every breakpoint (see the DUB REVIEW block above and the monochrome workspace
   layer at the end of this file); the rules that did the capping are gone rather
   than overridden, so there is one shell and nothing left to fight. */

/* Slightly softer rail collapse/expand instead of a hard width snap. */
.sidebar{
  transition:width .24s cubic-bezier(.22,.8,.32,1);
}

/* The phase-2 shell sets `#dubPhase2 > * { position:relative }` for its normal
   content. The export status is the one child that must escape that flow and
   stay pinned to the viewport, otherwise it sits at the very bottom of a long
   transcript and can only be seen after scrolling all the way down. */
body.dub-review-open #dubPhase2 > #dubExportGlobal{
  position:fixed !important;
  left:auto !important;
  top:auto !important;
  right:18px !important;
  bottom:18px !important;
  z-index:100000 !important;
  margin:0 !important;
}

/* Prepare/queue escape hatch: a non-destructive way to leave the setup dialog
   while the job keeps running and shows up in Recent translations. */
.tr-background{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  border:1px solid var(--border-2);
  border-radius:8px;
  background:var(--surface);
  color:var(--text-2);
  padding:8px 14px;
  font-family:inherit;
  font-size:13px;
  font-weight:650;
  cursor:pointer;
  white-space:nowrap;
  transition:background .15s, border-color .15s, color .15s;
}
.tr-background:hover{
  background:var(--hover);
  border-color:#cbd5e1;
  color:var(--text);
}
.tr-background:focus-visible{
  outline:2px solid var(--focus);
  outline-offset:2px;
}
.tr-setup-actions .tr-background{
  width:auto;
  margin:0 10px 0 0;
  order:-2;
}
@media (max-width:560px){
  .tr-setup-actions .tr-background{ width:100%; margin:0 0 8px; }
}

/* ---------------------------------------------------------------------------
   Subtitles -> Speech: reuse the translation canvas so the top-level tools
   (upload -> review) read as one design system. app.js still drives the
   existing IDs/classes; this block only aligns the visual shell.
   --------------------------------------------------------------------------- */
#srtPanel{
  position:relative;
  min-height:calc(100vh - 58px);
  min-height:calc(100dvh - 58px);
  margin:-34px -40px -60px;
  padding:34px 40px 60px;
  background:
    radial-gradient(ellipse 72% 310px at 50% 0,var(--hero-glow),rgba(255,255,255,0) 76%),
    radial-gradient(circle,var(--hero-dots) .75px,transparent .9px);
  background-size:auto,16px 16px;
  background-position:0 0,0 0;
}
.editor:has(#srtPanel[style*="block"]) .editor-inner{ max-width:none; width:100%; }

/* Upload state: same centered hero + horizontal drop card as Translate. */
#srtPanel #srtUpload > .srt-head{
  display:block;
  margin:0 auto 34px;
  padding-top:64px;
  text-align:center;
}
#srtPanel #srtUpload > .srt-head .srt-category{
  margin:0;
  font-size:32px;
  line-height:1.12;
  font-weight:780;
  letter-spacing:-.045em;
  color:var(--text);
}
#srtPanel #srtUpload > .srt-head .srt-subtitle{
  max-width:650px;
  margin:11px auto 0;
  font-size:15px;
  line-height:1.5;
  color:var(--text-3);
}
#srtPanel .srt-drop{
  width:min(100%,560px);
  min-height:264px;
  margin:0 auto;
  gap:15px;
  padding:36px;
  flex-direction:row;
  justify-content:center;
  align-items:center;
  border:1px dashed #d7d9e0;
  border-radius:16px;
  background:var(--canvas);
}
#srtPanel .srt-drop:hover{ transform:none; box-shadow:none; background:var(--canvas); }
#srtPanel .srt-drop .tr-drop-ico,
#srtPanel .srt-drop .vc-tags{ display:none; }
#srtPanel .srt-drop .tr-drop-title{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  flex:0 0 auto;
  white-space:nowrap;
  min-height:46px;
  padding:0 20px;
  border:1px solid #e4e5e9;
  border-radius:8px;
  background:var(--surface);
  color:var(--text);
  font-size:14px;
  font-weight:700;
  box-shadow:var(--e-1);
}
#srtPanel .srt-drop .tr-drop-desc{
  flex:1 1 auto;
  min-width:0;
  max-width:255px;
  color:#6f7480;
  font-size:13px;
  line-height:1.45;
  text-align:left;
}
#srtPanel #srtProgress{
  width:min(100%,560px);
  margin:22px auto 0;
}
#srtPanel #srtProgress .srt-bar{ height:10px; background:var(--canvas); }
#srtPanel #srtProgress .srt-bar-fill{ background:linear-gradient(90deg,#fe655d,#fc3e57); }

/* Review state: a centered white workspace card, matching the translate
   dialogs' surface, radius and typography without changing the controls. */
#srtPanel .srt-panel{
  width:min(100%,1080px);
  margin:0 auto;
  padding:28px 32px 32px;
  border:1px solid var(--border-2);
  border-radius:16px;
  background:var(--surface);
  box-shadow:var(--e-3);
}
#srtPanel .srt-panel > .srt-head{ align-items:center; margin-bottom:18px; }
#srtPanel .srt-panel .srt-category{ font-size:24px; letter-spacing:-.035em; }
#srtPanel .srt-panel .srt-subtitle{ font-size:13px; }

@media (max-width:1180px){
  #srtPanel{ margin:-26px -24px -20px; padding:26px 24px 40px; }
  #srtPanel #srtUpload > .srt-head{ padding-top:48px; }
  #srtPanel #srtUpload > .srt-head .srt-category{ font-size:32px; }
  #srtPanel .srt-drop{ width:min(100%,520px); min-height:244px; }
}
@media (max-width:899px){
  #srtPanel{ margin:-20px -16px -8px; padding:20px 16px 32px; }
  #srtPanel #srtUpload > .srt-head{ padding-top:34px; margin-bottom:28px; }
  #srtPanel #srtUpload > .srt-head .srt-category{ font-size:32px; }
  #srtPanel .srt-panel{ padding:20px 18px 24px; }
}
@media (max-width:560px){
  #srtPanel #srtUpload > .srt-head .srt-category{ font-size:24px; }
  #srtPanel .srt-drop{
    min-height:220px;
    flex-direction:column;
    gap:10px;
    padding:28px 20px;
  }
  /* Very narrow phones: long translations may need to wrap rather than
     overflow the card. The shorter English/Vietnamese copy still fits one line. */
  #srtPanel .srt-drop .tr-drop-title{ white-space:normal; }
  #srtPanel .srt-drop .tr-drop-desc{ text-align:center; }
  #srtPanel .srt-panel{ padding:16px 12px 20px; }
}

/* ---------------------------------------------------------------------------
   Mobile WebView polish (native-app feel)
   - Stop iOS zoom-on-focus for text entry
   - Keep the voice picker inside the visual viewport when the keyboard opens
   - Give the editor more room while typing
   - Respect notch / home-indicator safe areas
   - Remove tap highlight and pull-to-refresh feel
   --------------------------------------------------------------------------- */
@media (max-width:820px){
  body{ -webkit-tap-highlight-color:transparent; overscroll-behavior-y:none; }
  button, [role="button"], a, .blk-tool, .settings-sheet-trigger, .wsb-voice, .wsb-preview{ touch-action:manipulation; }

  /* Keep the chat bubble visible on phones, but park it on the right edge at
     mid-height so it cannot cover Generate / Add speaker / the playbar. It is
     draggable, snaps to the nearest side, and remembers where the user put it. */
  .ov-chat-bubble{ left:auto; right:8px; bottom:auto; top:50%; transform:translateY(-50%);
    touch-action:none; user-select:none; -webkit-user-select:none; cursor:grab; }
  .ov-chat-bubble:hover{ transform:translateY(-50%) scale(1.04); }
  .ov-chat-bubble.dragging{ cursor:grabbing; transition:none; }
  body.has-playbar .ov-chat-bubble{ top:50%; bottom:auto; }
  body.kb-open .ov-chat-bubble{ opacity:0; pointer-events:none; }

  /* iOS Safari zooms any focused control below 16px and shoves the viewport
     sideways. Keep every mobile text-entry surface at 16px. */
  .vp-search input, .vp-filter, .prompt-input, .lib-sort select, .pg-goto-select,
  .script-ta, .clone-ta, .clone-name, .vc-textarea, .vc-select,
  #translatePanel textarea, #translatePanel input[type="text"], #translatePanel input[type="search"], #translatePanel select,
  #clonePanel textarea, #clonePanel input[type="text"], #designPanel textarea, #designPanel input[type="text"]{
    font-size:16px;
  }

  /* Voice picker on a phone: a full-height sheet whose header is ONE row of tabs
     and ONE row of filters. It used to be a floating card with four stacked rows
     (two-line tab labels + a bordered wrapper around each dropdown) that ate
     ~half the screen before the first voice. --app-vvh still shrinks the sheet
     when the search field opens the keyboard, so no row hides behind it. */
  #viewVoice.vp-open{ height:var(--app-vvh, 100dvh); padding:0; align-items:stretch; }
  .vp-dialog{ max-height:none; height:100%; width:100%; border:none; border-radius:0;
    padding:calc(10px + env(safe-area-inset-top, 0px)) 14px 0; }
  .vp-head{ margin-bottom:6px; }
  .vp-head h2{ font-size:17px; }
  .vp-tabs{ gap:4px; margin-bottom:8px; }
  .vp-tab{ flex:1 1 0; min-width:0; white-space:nowrap; text-align:center;
    font-size:13.5px; padding-bottom:8px; }
  .vp-search{ margin-bottom:8px; }
  .vp-search input{ height:42px; }
  /* Column, not the desktop wrap: each row then spans the sheet, so an unfolded
     filter gets half the width instead of sizing to its text. */
  .vp-filters{ display:flex; flex-direction:column; gap:8px; margin-bottom:8px; }
  .vp-count{ margin:0 2px 8px; font-size:12px; }
  /* Language + "Filters" in one row; gender / use case unfold under it on demand
     (the badge counts the folded-away filters that are active). */
  .vp-filter-main{ display:flex; gap:8px; }
  .vp-filter-main .ov-field.ov-lib-field{ flex:1 1 auto; min-width:0; }
  .vp-filter-more{ display:inline-flex; align-items:center; gap:6px; flex:none; height:40px;
    padding:0 12px; border:1px solid var(--border-2); border-radius:8px; background:var(--surface);
    color:var(--text); font:inherit; font-size:13.5px; font-weight:600; cursor:pointer; }
  .vp-filter-more.is-filtering{ border-color:var(--accent); color:var(--accent); }
  .vp-filter-count{ display:inline-grid; place-items:center; min-width:18px; height:18px; padding:0 5px;
    border-radius:999px; background:var(--accent); color:#fff; font-size:11px; font-weight:700; }
  .vp-filter-count[hidden]{ display:none; }
  .vp-filter-extra{ display:none; gap:8px; }
  .vp-filters.is-open .vp-filter-extra{ display:flex; }
  .vp-filter-extra .ov-field.ov-lib-field{ flex:1 1 0; min-width:0; }
  .vp-list-wrap{ padding-bottom:calc(8px + env(safe-area-inset-bottom, 0px)); }

  /* While the keyboard is up, hide the secondary footer controls (settings,
     trial hints) and keep only the character budget + Generate. */
  body.kb-open .editor-footer .ef-left{ display:none; }
  body.kb-open .editor-footer{ padding-top:8px; padding-bottom:calc(8px + env(safe-area-inset-bottom, 0px)); }
  body.kb-open .editor-footer .el-top{ gap:8px; }
  body.kb-open .add-speaker-row{ display:none !important; }

  /* Comfortable thumb targets for the editor toolbar. */
  .blk-tools{ height:38px; gap:2px; margin-top:6px; }
  .blk-tool{ width:38px; height:38px; border-radius:8px; }
  .blk-div{ height:18px; margin:0 6px; }

  /* PWA / standalone safe areas. */
  .topbar{ padding-top:calc(10px + env(safe-area-inset-top, 0px)); }
  .editor-footer{ padding-bottom:calc(16px + env(safe-area-inset-bottom, 0px)); }
  .playbar{ padding-bottom:calc(14px + env(safe-area-inset-bottom, 0px)); }
}

/* Policy links under the voice-clone consent checkbox. Deliberately plain text
   links: the consent itself is the checkbox; these are only the "read the detail"
   path, so they stay quiet and never compete with the Save button. Indented to
   line up with the label text next to the checkbox. */
.vc-consent-more{ display:flex; flex-wrap:wrap; align-items:center; gap:6px;
  margin:6px 0 0 26px; font-size:12px; line-height:1.5; color:var(--text-3); }
.vc-consent-more a{ color:var(--text-3); text-decoration:underline; text-underline-offset:2px; }
.vc-consent-more a:hover{ color:var(--accent-text); }
.vc-dot{ color:var(--text-3); opacity:.6; }
@media(max-width:640px){
  .vc-consent-more{ margin-left:0; font-size:12px; }
}

/* ==========================================================================
   Studio document heading
   The studio is one SPA shell that inlines several view fragments; two of them
   used to carry their own <h1>, so every /studio/ URL shipped two h1s (Ahrefs
   "Multiple H1 tags"). Those are now <h2> and this is the single document <h1>.
   Kept visually hidden so the layout is unchanged — it still gives assistive
   tech (and the document outline) one unambiguous top-level heading.
   ========================================================================== */
.ov-page-title{
  position:absolute;
  width:1px;
  height:1px;
  margin:-1px;
  padding:0;
  overflow:hidden;
  clip:rect(0 0 0 0);
  clip-path:inset(50%);
  white-space:nowrap;
  border:0;
}

/* ==========================================================================
   VOICE CHANGER — the voice rail
   --------------------------------------------------------------------------
   What this replaces, and why.

   The voice used to be chosen in a full-screen modal (#voice picker) opened by
   a chip that sat BELOW the fold of the setup dialog. Measured at 1440x900 with
   a file chosen: `.tr-setup-grid` was 365px tall holding 778px of form, so
   #dubChangerVoice started at y=709 and #dubChangerConsent at y=836 — past the
   dialog's own bottom edge (y=810). The two things that gate the primary button
   were both off-screen inside a nested scroller with no scroll indicator.

   The rail puts the choice in a column beside the canvas. It is built from
   markup that is always in the DOM (see translate-panel.html) so the dubbing
   screen is unaffected: every rule below is gated on `body.mode-changer`.
   ========================================================================== */

/* The stack wrapper exists so the rail can sit beside the WHOLE of phase 1
   without wrapping each block. On the dubbing screen it must not create a box,
   or the blocks that used to be siblings would lose the flexible layout they
   were written against — hence contents rather than block. */
.ch-stack { display: contents; }

.ch-rail { display: none; }

/* The panel is a viewport-sized two-column grid in changer mode: the header
   spans both columns and the canvas/rail share the remaining height. Without a
   definite height here the rail's own scroller has nothing to size against, and
   the page grows instead of the list scrolling. */
body.mode-changer #translatePanel .tr {
  display: grid;
  /* One column: the voice rail was removed (it read as a second, competing
     task beside the form). The stack keeps the only track. */
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr);
  height: calc(100vh - 124px);
  height: calc(100dvh - 124px);
  min-height: 0;
  overflow: hidden;
  column-gap: 0;
  transition: grid-template-columns .22s cubic-bezier(.22,1,.36,1);
}
@media (prefers-reduced-motion: reduce){ body.mode-changer #translatePanel .tr { transition: none; } }
body.mode-changer #translatePanel .tr > .tr-header { grid-column: 1 / -1; }
body.mode-changer #translatePanel .tr > .ch-stack { grid-column: 1; grid-row: 2; }
body.mode-changer .ch-stack { display: block; min-width: 0; min-height: 0; overflow: auto; }
/* The setup form used to be a SECOND scroller inside this one (.tr-setup-grid
   was 365px tall holding 778px of form, with no scroll indicator), which is how
   the voice block and the consent box came to start below the dialog's own
   bottom edge. In changer mode the dialog is a plain block in the stack's single
   scroll: one scroller, and the sticky actions bar rides the same one. */
body.mode-changer .tr-setup-grid {
  display: block; overflow: visible; flex: none; padding: 0 0 8px;
}
/* `has-file` pads the grid by 78px to clear the onboarding banner. Inline the
   banner is in normal flow above it, so that padding is just a hole. */
body.mode-changer #dubPhase1.has-file .tr-setup-grid { padding-top: 0; }
/* The upload stage keeps its normal behaviour (hidden once a file is chosen):
   the history block used to be inside it, which is why showing the history meant
   showing the stage — and with it an empty bordered box where the drop card had
   been, plus a second Record affordance. The history now lives at the bottom of
   the column, so the stage can hide itself again. */
body.mode-changer #dubPhase1.has-file .tr-upload-stage { padding-top: 0; }
/* The sticky actions bar sits over the last of the scroll, so the scroll needs
   room to end under it rather than at it. */
body.mode-changer .ch-stack { padding-bottom: 8px; }
body.mode-changer #dubPhase1.has-file {
  position: static; transform: none; width: auto; height: auto; max-height: none;
  inset: auto; border: 0; border-radius:0; background: transparent; box-shadow: none;
  animation: none; overflow: visible;
}
/* The close button was the way out of the modal; there is no modal to leave now,
   and a "remove file" affordance already sits on the file row. */
body.mode-changer #dubPhase1.has-file .tr-setup-close { display: none; }
/* The preview pane survives as a plain block on the left — it is where the file
   name and the real duration live. The progress bar is the one part that belongs
   to the dialog's layout, so it keeps its own row. */
body.mode-changer #dubPhase1.has-file .tr-setup-preview {
  position: static; width: auto; max-width: 520px; margin: 0 auto 16px;
}
body.mode-changer #dubPhase1.has-file .tr-detect { position: static; margin-top: 8px; }
body.mode-changer #translatePanel .tr-setup-actions {
  margin-top: 14px; padding: 14px 0 10px; background: var(--canvas);
  box-shadow: 0 -1px 0 var(--border), 0 -18px 22px -14px rgba(17,24,39,.14);
}

body.mode-changer .ch-rail {
  display: flex; flex-direction: column; min-height: 0; min-width: 0;
  border-left: 1px solid var(--border); background: var(--surface);
}

/* ---- tabs ---- */
.ch-rail-tabs { display: flex; align-items: center; gap: 18px; padding: 0 16px;
  border-bottom: 1px solid var(--border); flex: none; }
/* This stylesheet has NO global button reset: every button declares its own
   appearance/background/border. These three did not, so Chromium painted the
   native control over them — `2px outset` plus a #efefef fill, i.e. two grey
   bevelled rectangles sitting in a white panel. Measured before the fix:
   background rgb(239,239,239), border "2px outset rgb(0,0,0)", appearance auto. */
.ch-rail-tab { appearance: none; -webkit-appearance: none; background: none; border: 0;
  padding: 13px 2px 11px; margin-bottom: -1px; font: inherit; font-size:13px; font-weight: 700;
  color: var(--text-2); border-bottom: 2px solid transparent; cursor: pointer; }
.ch-rail-tab:hover { color: var(--text); }
.ch-rail-tab.on { color: var(--text); border-bottom-color: var(--tr-accent); }
.ch-rail-tab:focus-visible { outline:2px solid var(--focus); outline-offset: 2px; border-radius:8px; }

.ch-rail-panel { display: none; flex-direction: column; min-height: 0; flex: 1; }
.ch-rail-panel.on { display: flex; }

/* ---- tools ---- */
.ch-rail-tools { padding: 12px 14px 0; flex: none; }
.ch-rail-search { display: flex; align-items: center; gap: 8px; padding: 8px 11px;
  border: 1px solid var(--border); border-radius:8px; background: var(--tr-surface-muted); }
.ch-rail-search:focus-within { border-color: var(--tr-accent); background: var(--surface); }
.ch-rail-search input { border: 0; background: none; outline: none; font: inherit; font-size:12px; width: 100%; }
.ch-rail-search i, .ch-rail-search svg { color: var(--text-2); flex: none; }

.ch-rail-filters { display: grid; grid-template-columns: 1fr 1fr; gap: 7px; padding: 8px 14px 0; flex: none; }
.ch-rail-filters .tr-select { font-size:12px; padding: 7px 9px; width: 100%; min-width: 0; }
.ch-rail-count { padding: 9px 14px 7px; font-size:12px; color: var(--text-2); flex: none; }

.ch-rail-scroll { flex: 1; min-height: 0; overflow: auto; padding: 0 14px 16px; }

/* ---- the voice grid ---- */
.ch-voice-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
.ch-vcard { appearance: none; -webkit-appearance: none; font: inherit;
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 10px 5px 9px; border: 1.5px solid transparent; border-radius:12px; background: var(--surface);
  text-align: center; transition: background .13s ease, border-color .13s ease; }
.ch-vcard:hover { background: var(--tr-surface-muted); }
.ch-vcard.on { border-color: var(--tr-accent); background: var(--tr-accent-soft); }
.ch-vcard:focus-visible { outline:2px solid var(--focus); outline-offset: 1px; }
.ch-vcard-av { position: relative; width: 54px; height: 54px; border-radius:999px; overflow: hidden;
  display: grid; place-items: center; flex: none; }
.ch-vcard-av img { width: 100%; height: 100%; object-fit: cover; }
/* Curated pick — the reference screen's "pro" star. Sits on the portrait's
   top-left, opposite the play badge so the two never collide. */
.ch-vcard-star { position: absolute; left: -1px; top: -1px; width: 21px; height: 21px;
  border-radius:999px; background: var(--surface); border: 1px solid var(--border); display: grid;
  place-items: center; color: #d97706; box-shadow:var(--e-1); }
.ch-vcard.on .ch-vcard-star { background: var(--surface); border-color: #fcd34d; }
.ch-vcard-play { position: absolute; right: -1px; bottom: -1px; width: 21px; height: 21px; border-radius:999px;
  background: var(--surface); border: 1px solid var(--border); display: grid; place-items: center;
  color: #374151; box-shadow:var(--e-1); }
.ch-vcard.on .ch-vcard-play { background: var(--black); border-color: #111; color: #fff; }
.ch-vcard-play.is-loading { opacity: .55; }
/* Two lines for the name, one for the facts, both clamped: cards of equal height
   keep the grid from going ragged, which is what made the old picker list hard
   to scan once names wrapped. */
.ch-vcard-name { width: 100%; min-height: 2.5em; font-size:12px; font-weight: 700;
  line-height: 1.25; letter-spacing: -.01em; display: -webkit-box; -webkit-line-clamp: 2;
  -webkit-box-orient: vertical; overflow: hidden; }
.ch-vcard-meta { width: 100%; font-size:12px; color: var(--text-2); line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ch-rail-empty { padding: 26px 10px; text-align: center; font-size:12px; line-height: 1.5;
  color: var(--text-2); }
/* The same line is the retry affordance after a failed load. */
.ch-rail-empty.is-retry { color: #b42318; }
.ch-rail-empty.is-retry > span { display: block; margin-bottom: 10px; }
.ch-rail-retry { appearance: none; -webkit-appearance: none; font: inherit; cursor: pointer;
  padding: 8px 16px; border-radius:8px; border: 1px solid #fecdca;
  background: var(--surface); color: #b42318; font-size:12px; font-weight: 700; }
.ch-rail-retry:hover:not(:disabled) { background: var(--warn-tint); }
.ch-rail-retry:disabled { opacity: .6; cursor: default; }
.ch-rail-note { flex: none; padding: 10px 14px; border-top: 1px solid var(--border);
  font-size:12px; line-height: 1.5; color: var(--text-2); }

/* ---- history tab reuses the shared recent list ---- */
.ch-rail .tr-recent-list { padding: 4px 0 0; }
.ch-rail .tr-recent-row { border-radius:8px; }

/* ---- dock: who will speak, and a way to hear them ---- */
/* #dubSetupTitle gets .focus() on file pick so a keyboard user lands in the form.
   It is not an interactive control, so the focus ring is noise — a screen reader
   still announces it. */
/* The dialog carried its own title; inline it is a duplicate of the page
   heading. The page heading (the panel header) is the one a reader sees first,
   and it was hidden in has-file mode because the dialog used to replace it. */
body.mode-changer #translatePanel .tr-header { visibility: visible; margin-bottom: 4px; }
body.mode-changer #translatePanel .tr-header .tr-category { font-size:20px; }
body.mode-changer #translatePanel .tr-header .tr-subtitle { display: none; }
body.mode-changer #dubPhase1.has-file .tr-setup-copy > .tr-setup-kicker,
body.mode-changer #dubPhase1.has-file .tr-setup-title { display: none; }
body.mode-changer #dubPhase1.has-file .tr-setup-copy { margin-bottom: 14px; }

body.mode-changer #dubSetupTitle:focus { outline: none; }
body.mode-changer #dubSetupTitle:focus-visible { outline:2px solid var(--focus); outline-offset: 3px; border-radius:8px; }

.ch-rail-dock { flex: none; border-top: 1px solid var(--border); padding: 10px 14px; background: var(--surface); }
.ch-dock-head { display: flex; align-items: center; gap: 10px; }
/* Only rendered once the rail is folded; the full rail shows the grid instead. */
.ch-dock-change { display: none; appearance: none; -webkit-appearance: none; width: 100%;
  margin-top: 8px; padding: 6px 8px; border: 1px solid var(--border-2); border-radius:8px;
  background: var(--surface); cursor: pointer; font-family: inherit; font-size:12px; font-weight: 700;
  color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ch-dock-change:hover { background: var(--hover); border-color: #d4d4d8; }
.ch-dock-change:focus-visible { outline: 2px solid var(--tr-accent); outline-offset: 2px; }

/* ---- Folded rail ----
   The tab buttons stay (history must not become unreachable) but shrink to fit
   the narrow column; everything that exists to help you CHOOSE goes away,
   because the choice is made. What is left answers the only two questions that
   still matter: who will speak, and how do I change it. */
body.mode-changer .ch-rail.is-collapsed .ch-rail-tabs { gap: 10px; padding: 0 10px; }
body.mode-changer .ch-rail.is-collapsed .ch-rail-tab { font-size:12px; padding: 12px 0; }
body.mode-changer .ch-rail.is-collapsed .ch-rail-tools,
body.mode-changer .ch-rail.is-collapsed .ch-rail-filters,
body.mode-changer .ch-rail.is-collapsed .ch-rail-count,
body.mode-changer .ch-rail.is-collapsed .ch-rail-scroll { display: none; }
body.mode-changer .ch-rail.is-collapsed .ch-rail-dock { margin-top: auto; padding: 12px 10px; }
body.mode-changer .ch-rail.is-collapsed .ch-dock-head { flex-direction: column; gap: 7px; text-align: center; }
body.mode-changer .ch-rail.is-collapsed .ch-dock-head > span { min-width: 0; width: 100%; }
body.mode-changer .ch-rail.is-collapsed .ch-dock-name { white-space: normal; font-size:12px; line-height: 1.3;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
body.mode-changer .ch-rail.is-collapsed .ch-dock-label { font-size:12px; }
body.mode-changer .ch-rail.is-collapsed .ch-dock-meta { margin-left: 0; font-size:12px; white-space: normal; }
body.mode-changer .ch-rail.is-collapsed .ch-dock-change { display: block; }
.ch-dock-play { appearance: none; -webkit-appearance: none; border: 0; cursor: pointer;
  width: 34px; height: 34px; border-radius:999px; background: var(--black); color: #fff;
  display: grid; place-items: center; flex: none; }
.ch-dock-play:hover { background: var(--black); }
.ch-dock-play.is-loading { opacity: .6; }
.ch-dock-label { display: block; font-size:12px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--text-3); }
.ch-dock-name { display: block; font-size:13px; font-weight: 800; letter-spacing: -.015em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ch-dock-meta { margin-left: auto; flex: none; font-size:12px; font-weight: 700;
  color: var(--tr-accent-strong); white-space: nowrap; }

/* ---- what is kept / what is not ---- */
/* The scope note and the A/B player sit OUTSIDE .tr-setup-form, so they have to
   reproduce its measure and its left edge: centred here would put them under the
   middle of the column while every other block starts at the left rule. */
.ch-scope { margin: 0 auto 18px; max-width: 520px; border: 1px solid var(--border);
  border-radius:12px; background: var(--tr-surface); }
/* Inside .tr-setup-form they inherit its measure and left edge, so no alignment
   hack is needed — and it is where a reader expects them, between the promise and
   the controls. */
.ch-scope, .ch-video-note, .ch-ab { margin-left: 0; margin-right: 0; max-width: none; }
.ch-scope > summary { display: flex; align-items: center; gap: 8px; padding: 11px 13px;
  font-size:12px; font-weight: 700; color: var(--text-2); cursor: pointer; list-style: none; }
.ch-scope > summary::-webkit-details-marker { display: none; }
.ch-scope > summary::before { content: "›"; font-size:15px; font-weight: 800; color: var(--text-2);
  transition: transform .15s ease; }
.ch-scope[open] > summary::before { transform: rotate(90deg); }
.ch-scope > summary:focus-visible { outline:2px solid var(--focus); outline-offset: 2px; border-radius:8px; }
.ch-scope-body { padding: 0 13px 12px 30px; font-size:12px; line-height: 1.6; color: var(--text-2); }
.ch-scope-body p { margin: 0 0 8px; }
.ch-scope-body ul { margin: 0; padding: 0; list-style: none; }
.ch-scope-body li { position: relative; padding-left: 15px; margin: 4px 0; }
.ch-scope-body li::before { position: absolute; left: 0; font-weight: 800; }
.ch-scope-body li.keep::before { content: "✓"; color: #0f766e; }
.ch-scope-body li.lose::before { content: "–"; color: var(--warn-ink); }
/* These three only describe a VOICE CHANGE. Dubbing rewrites the words and
   accepts a video as the deliverable, so every one of them would be a lie on the
   dubbing screens — and `data-kind` is always set, so gating on it (as an earlier
   draft of this rule did) never hid anything. */
body:not(.mode-changer) .ch-scope,
body:not(.mode-changer) .ch-video-note,
body:not(.mode-changer) .ch-ab { display: none !important; }

/* ---- record instead of upload (Voice Changer only) ---- */
.ch-rec { appearance: none; -webkit-appearance: none; font: inherit; cursor: pointer;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  width: 100%; margin: 12px 0 0; padding: 11px 14px; border: 1px solid var(--border);
  border-radius:12px; background: var(--surface); font-size:13px; font-weight: 700;
  color: #374151; transition: background .15s ease, border-color .15s ease; }
.ch-rec:hover { background: var(--tr-surface-muted); }
.ch-rec:focus-visible { outline:2px solid var(--focus); outline-offset: 2px; }
.ch-rec.is-recording { border-color: #fecdca; background: var(--warn-tint); color: #b42318; }
body:not(.mode-changer) .ch-rec,
body:not(.mode-changer) .ch-rec-panel { display: none !important; }

.ch-rec-panel { display: none; align-items: center; gap: 10px; margin-top: 10px;
  padding: 10px 12px; border: 1px solid #fecdca; border-radius:12px; background: var(--warn-tint); }
.ch-rec-dot { width: 9px; height: 9px; border-radius:999px; background: #e11d48; flex: none;
  animation: chRecPulse 1.1s ease-in-out infinite; }
@keyframes chRecPulse { 0%,100% { opacity: 1; } 50% { opacity: .25; } }
.ch-rec-time { font-size:13px; font-weight: 800; font-variant-numeric: tabular-nums; color: #b42318; flex: none; }
.ch-rec-note { flex: 1; min-width: 0; font-size:12px; color: #b42318; }
/* `border: 0` here is load-bearing, not tidiness: setting `appearance:none`
   without it still left Chromium's default `outset` border on the Stop button
   (measured: border-top-style "outset"), which is the same bevel the tabs had. */
.ch-rec-stop, .ch-rec-cancel { appearance: none; -webkit-appearance: none; font: inherit;
  cursor: pointer; flex: none; border: 0; padding: 7px 13px; border-radius:8px;
  font-size:12px; font-weight: 700; }
.ch-rec-stop { background: var(--black); color: #fff; }
.ch-rec-stop:hover { background: var(--black); }
.ch-rec-cancel { border: 1px solid #fecdca; background: var(--surface); color: #b42318; }
.ch-rec-cancel:hover { background: var(--warn-tint); }
@media (prefers-reduced-motion: reduce) { .ch-rec-dot { animation: none; } }

.ch-video-note { max-width: 520px; margin: 0 auto 16px; padding: 10px 13px; border-radius:12px;
  font-size:12px; line-height: 1.5; color: var(--info-ink); background: var(--info-soft); border: 1px solid var(--info-border); }

/* Voice Changer has ONE action. "Review the words" and "Change voice now" were
   two halves of the same job sitting side by side, with a third line of text
   under them explaining the difference — and the one that actually ran was the
   one that started disabled, so the first thing a new user pressed did nothing
   but show a tooltip. The review step is still reachable: it is what the primary
   button does. */
body.mode-changer #dubRunBtn,
body.mode-changer .tr-setup-cta-hint { display: none !important; }
body.mode-changer .tr-setup-cta { width: 100%; }
body.mode-changer #dubSetupDubBtn { width: 100%; height: 48px; font-size:14px; border-radius:12px; }

/* ---- Voice Changer: leftovers removed from the screen (de-clutter pass) ----
   Each of these is a real duplicate, not a taste call: the same information is
   still on screen, once. */

/* The onboarding strip teaches the DUBBING flow — "upload a video, pick a
   language, press Dub now" — which is not this screen's flow, and its three
   steps restate the subtitle directly above it. The changer's own honesty
   blocks (the scope disclosure and the amber why-line) already cover the
   "what will happen / what is missing" ground. Kept for dubbing. */
body.mode-changer #dubOnboard { display: none !important; }

/* The in-column "recent" list is now the changer's ONLY history: it used to
   live in the rail's History tab, and the rail is gone. dubRenderRecent() writes
   #dubRecentList, which now lives here (see translate-panel.html). */

/* The filename block printed three lines for one fact: the label, the input,
   and a "File tải về: <name>" line that repeats the label's own words. The
   static label used to step aside to save that line — which left a bare box
   reading "vi_voice" and no way to know what it was. It is back, sharing a line
   with the derived name (see rule 4 in the comprehension pass below). */

/* The credits line shared a flex row with the primary button, so on the changer
   it collapsed to a ~120px column and wrapped to four lines beside a full-width
   CTA. Give it its own row instead. */
body.mode-changer .tr-setup-usage { flex: 1 1 100%; }

/* ---- Text audit: four more blocks the screen did not need to say ------------
   Measured on the rendered screen (visible text only; closed disclosures and the
   rail excluded): 129 words in the has-file state. These are the biggest blocks
   that either repeat something already on screen, or explain a DUBBING concept
   on a screen that is not dubbing. */

/* "Pick the voice that should read your recording out loud. Same words, same
   language, same length — the delivery is re-recorded." (21 words) — the scope
   disclosure one line below says exactly this, and expands to the detail. */
body.mode-changer .tr-setup-copy .tr-setup-subtitle { display: none !important; }

/* "Leave this alone to keep the language that was recorded. Pick one to
   translate the words first…" (24 words) — the select's own selected option IS
   that sentence ("Keep the recorded language (voice only)"), and the rest of the
   options are language names. */
body.mode-changer #dubChangerLangHint { display: none !important; }

/* "File longer than 10 minutes? Split it…" (19 words) — advice for a problem the
   user has not hit yet, on the screen where they are trying to start. The limit
   is already a pill on the card ("5 seconds–10 minutes"), and a real rejection
   still prints in #dubStatus, in the flow, where it is actually seen. */
body.mode-changer .tr-longfile-hint { display: none !important; }

/* "Auto-detect" badge on the preview poster — that belongs to the dubbing
   screen's target-language detection. A voice change detects nothing. */
body.mode-changer #dubPreviewLanguage { display: none !important; }

/* ---- Comprehension pass ----------------------------------------------------
   The two rounds above optimised WORDS. That was the wrong lever: measured on
   the rendered has-file screen, the primary button sat at y=1104 in a 900px
   viewport (invisible), the step indicator was hidden, and the two objects a
   first-timer has to identify carried no label. Fewer words, less to go on. */

/* 1. The journey map. `changer.stTranscribe / stReview / stExport` are written
   for this screen in all 32 locales and then hidden by
   `#translatePanel .tr-steps { display:none }` — orientation that was already
   paid for and never shown (this is finding 8 in nghien-cuu-ux-man-doi-giong.md,
   still open). One compact permanent row, no dismissal state. */
body.mode-changer #translatePanel .tr-steps { display: flex; margin: 0 0 14px; }

/* 2. The primary button must not be something you have to scroll to find. The
   base rule pins the bar with `position: relative !important`, so this one has
   to match that weight.
   No padding is needed to keep the content clear of it: the bar is the LAST
   child of the scroll container, so at full scroll its flow position and its
   sticky position coincide and everything above it is reachable. Padding was
   tried on the container (shrinks the content box, and a sticky `bottom: 0`
   resolves against that box, so the bar parked 191px up the column, on top of
   the consent block) and on the bar's parent (the bar then rides up with its
   containing block and stops being a footer at all). */
body.mode-changer #translatePanel .tr-setup-actions {
  position: sticky !important; bottom: 0; z-index: 3;
}

/* 3. The input poster was a 327px 16:9 box holding a music note and a duration,
   pushing the primary button off screen, while the file it depicts was named
   nowhere near the player that plays it. Drop the frame; keep the file row (it
   carries the name and the way to remove the file). */
body.mode-changer .tr-setup-preview-frame { display: none !important; }
body.mode-changer #dubPhase1.has-file .tr-setup-preview {
  display: flex; align-items: center; gap: 10px;
  position: static; width: auto; max-width: none; margin: 0 0 10px;
}
body.mode-changer .tr-setup-preview .tr-file {
  order: 1; flex: 1 1 auto; margin: 0; padding: 10px 14px;
  border: 1px solid var(--border-2); border-radius:12px; background: var(--brand-coral-soft);
}
/* 3b. BUG: this was hidden with the comment "a remove-file affordance already
   sits on the file row" — it does not. `#dubFileClear` IS this button, so the
   changer had no way to un-select a file at all. Put it back, in the row. */
body.mode-changer #dubPhase1.has-file .tr-setup-close {
  display: inline-flex !important; order: 2;
  position: static; top: auto; right: auto; width: 36px; height: 36px;
  border: 1px solid var(--border-2); border-radius:8px; background: var(--surface);
  color: var(--text-2); cursor: pointer;
}
body.mode-changer #dubPhase1.has-file .tr-setup-close:hover { background: var(--hover); color: var(--text); }

/* 4. Undo the over-cut from the word audit: the download-name field was left as
   a bare box reading "vi_voice" with no label at all. Label and derived name
   share one line, the input takes the next. */
body.mode-changer label[for="dubProjectName"] {
  position: static; width: auto; height: auto; margin: 0; overflow: visible;
  clip: auto; white-space: normal;
}
body.mode-changer .tr-block:has(#dubProjectName) { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; }
/* The DOM order is label → input → derived name. Flex-wrap alone put them on
   THREE rows: the input's 100% basis breaks the line after the label, and the
   derived name starts another. `order` lifts the derived name up beside the
   label, so it reads "Tên file tải về     vi_voice_en_v1.mp4" with the input
   below — two rows, and the name is never shown without its label. */
body.mode-changer .tr-block:has(#dubProjectName) > label { flex: none; order: 1; }
body.mode-changer #dubNamePreview { flex: 1 1 auto; order: 2; margin: 0; text-align: right; font-weight: 600; color: var(--text-3); }
body.mode-changer #dubProjectName { flex: 1 1 100%; order: 3; }

/* The consent sentence, moved behind its policy name. */
.ch-consent-more { margin: 6px 0 0; }
.ch-consent-more > summary { cursor: pointer; font-size:12px; font-weight: 600;
  color: var(--text-2); width: fit-content; }
.ch-consent-more > summary:hover { color: var(--tr-accent-strong); }
.ch-consent-more > summary:focus-visible { outline: 2px solid var(--tr-accent); outline-offset: 2px; border-radius:8px; }
.ch-consent-more[open] > summary { margin-bottom: 4px; }
.ch-consent-more .tr-mix-hint { margin: 0; }

/* The primary button's "why it is disabled" line. A disabled button with only a
   `title` explains nothing on a touch screen. */
.tr-setup-cta-why { flex: 1 1 100%; margin: 6px 0 0; font-size:12px; line-height: 1.45;
  color: var(--warn-ink); text-align: right; }
.tr-setup-cta-why[hidden] { display: none; }

/* Voice Changer only: the audition button. Secondary weight on purpose — it is
   an offer, not the job, and it must not compete with "Change the voice". */
body:not(.mode-changer) .tr-preview-btn,
body:not(.mode-changer) .ch-preview-note { display: none !important; }
.tr-preview-btn { flex: 1 1 auto; min-width: 0; height: 38px; border-radius:8px;
  border: 1px solid var(--border); background: var(--surface); font-size:12px; font-weight: 700;
  color: #374151; white-space: normal; line-height: 1.2; text-align: center; }
.tr-preview-btn:hover:not(:disabled) { background: var(--tr-surface-muted); }
.tr-preview-btn:disabled { opacity: .55; cursor: not-allowed; }
.ch-preview-note { flex: 1 1 100%; margin: 8px 0 0; padding: 9px 12px; border-radius:8px;
  font-size:12px; line-height: 1.45; color: #0f766e; background: var(--success-tint);
  border: 1px solid #99f6e4; text-align: left; }
.ch-preview-note[hidden] { display: none; }

/* ---- responsive: below 1100px the rail has nowhere to go ----
   Two columns of 380px + a usable canvas need ~1100px. Below that the rail is
   dropped and the voice chip in the form takes over again (it is always in the
   markup), so a phone keeps a working path instead of a squeezed grid. */
@media (max-width: 1099px) {
  body.mode-changer #translatePanel .tr {
    display: block; height: auto; min-height: calc(100vh - 124px);
    min-height: calc(100dvh - 124px); overflow: visible;
  }
  body.mode-changer .ch-stack { overflow: visible; }
}
/* The in-form voice chip is the changer's only picker at every width now that
   the rail is gone — it opens the same voice modal the editor uses. */
body.mode-changer #dubChangerVoice { display: block !important; }
/* The form's own chip is the substitute for the rail, so exactly one of them is
   on screen at a time: the chip appears when the rail is hidden (narrow screens,
   or a mode that has no rail), and is redundant while the rail is visible.
   The rule is keyed on the rail's OWN hidden attribute rather than a breakpoint
   so it stays true however the rail was turned off. */


/* ---- Voice Changer: the original / new-voice A/B player -------------------
   One player with a source switch, not two players: the two takes have the same
   length and the same layout, so switching keeps the playhead where it was and
   the comparison is instant. Two players would answer the same question more
   slowly. `New voice` stays disabled until a take exists, so the switch never
   promises audio that is not there. */
.ch-ab { max-width: 520px; margin: 0 auto 20px; padding: 14px; border: 1px solid var(--border);
  border-radius:12px; background: var(--tr-surface); }
.ch-ab .tr-audio-switch { margin-bottom: 11px; }
.ch-ab-row { display: flex; align-items: center; gap: 11px; }
.ch-ab-play { appearance: none; -webkit-appearance: none; border: 0; cursor: pointer;
  width: 34px; height: 34px; border-radius:999px; background: var(--black); color: #fff;
  display: grid; place-items: center; flex: none; }
.ch-ab-play:hover { background: var(--black); }
.ch-ab-play:focus-visible { outline: 3px solid rgba(254,101,93,.35); outline-offset: 2px; }
.ch-ab-seek { flex: 1; min-width: 0; height: 6px; margin: 0; cursor: pointer;
  accent-color: var(--tr-accent); appearance: auto; }
.ch-ab-time { flex: none; font-size:12px; font-variant-numeric: tabular-nums; color: var(--text-2); }
body:not(.mode-changer) .ch-ab { display: none !important; }

/* ---------------------------------------------------------------------------
   Voice card v2 (2026-10-26): media trên, info dưới.
   Lý do: ảnh bìa fish là ảnh user upload 249×249, chất lượng rất khác nhau; đặt
   chữ + 5 chip đè lên ảnh làm lưới giọng trông lộn xộn. Tách khối chữ ra nền
   trắng, giảm còn 3 nhãn, cố định tỉ lệ ảnh để hàng cột thẳng nhau.
--------------------------------------------------------------------------- */
.vfeat{
  aspect-ratio:auto; display:flex; flex-direction:column; background:var(--surface);
  border:1px solid #ece9f3; border-radius:12px; overflow:hidden;
  box-shadow:var(--e-2); transition:box-shadow .18s, transform .18s;
}
.vfeat:hover{ box-shadow:var(--e-2); transform:translateY(-2px); }
.vfeat::after{ display:none; }
.vf-media{
  position:relative; aspect-ratio:1/1; flex:none; overflow:hidden;
  background:var(--violet-soft) var(--vf-img, none) center 22%/cover no-repeat;
}
.vf-media .vf-video{ border-radius:0; }
.vf-shade{ background:linear-gradient(to top, rgba(10,8,25,.45) 0%, rgba(10,8,25,.06) 34%, rgba(10,8,25,0) 60%); }
.vf-play{ width:44px; height:44px; }
.vf-favmark{ position:absolute; top:10px; left:10px; z-index:3; width:24px; height:24px; display:grid;
  place-items:center; border-radius:999px; background:rgba(252,62,87,.94); color:#fff; font-size:12px; }
.vf-info{ display:flex; flex-direction:column; gap:6px; padding:11px 13px 12px; }
.vf-name{ font-size:14px; font-weight:600; line-height:1.3; color:#1c1a24;
  display:-webkit-box; -webkit-line-clamp:1; -webkit-box-orient:vertical; overflow:hidden; }
.vf-tags{ display:flex; align-items:center; gap:5px; flex-wrap:nowrap; overflow:hidden;
  font-size:12px; color:#79728c; line-height:1.3; min-height:15px; }
.vf-tag{ white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.vf-dot{ opacity:.55; }
.vf-actions{ display:flex; align-items:center; gap:6px; margin-top:2px; }
.vf-use{ padding:6px 12px; border-radius:8px; font-size:12px; font-weight:600;
  background:linear-gradient(135deg,#fe655d,#fc3e57); color:#fff; border:none; cursor:pointer; }
.vf-copy,.vf-fav{ width:28px; height:28px; border-radius:8px; border:1px solid #e7e3f0; background:var(--surface);
  color:#6f6785; display:grid; place-items:center; cursor:pointer; }
.vf-copy:hover,.vf-fav:hover{ background:var(--violet-soft); color:#4f46e5; }
.vf-fav.on{ color:#f59e0b; border-color:#f6dfae; background:var(--warn-tint); }
.vf-top{ display:none; }

/* Lưới 6 cột cho màn studio: 24 thẻ = 4 hàng gọn, ảnh nhỏ nên ít lộ độ phân giải */
@media (min-width:1180px){ .hview-voices-list{ grid-template-columns:repeat(6,1fr); gap:12px; } }
@media (min-width:1500px){ .hview-voices-list{ grid-template-columns:repeat(6,1fr); } }
@media (max-width:1179px){ .hview-voices-list{ grid-template-columns:repeat(4,1fr); } }
@media (max-width:760px){ .hview-voices-list{ grid-template-columns:1fr 1fr; } }

/* v2 overrides: bỏ luật cũ đặt tên/chip đè lên ảnh */
.vfeat .vf-info{ position:static; left:auto; right:auto; bottom:auto; z-index:auto; color:inherit; }
.vfeat .vf-name{ color:#1c1a24; text-shadow:none; margin-bottom:0; letter-spacing:-.01em; }
.vfeat .vf-tags{ flex-wrap:nowrap; max-height:none; }
.vfeat .vf-tags .vt{ color:#79728c; background:none; padding:0; }
.vfeat .vf-media .vf-shade{ border-radius:0; }
.vfeat .vf-play{ top:50%; left:50%; transform:translate(-50%,-50%) scale(.92); opacity:0; }
.vfeat:hover .vf-play, .vfeat:focus-within .vf-play{ opacity:1; transform:translate(-50%,-50%) scale(1); }
@media (max-width:560px){ .vfeat .vf-actions .vf-use{ flex:1; } }

/* Tên dài cho xuống 2 dòng (thẻ cùng chiều cao nhờ flex), thay vì cắt "Mark - …" */
.vfeat .vf-name{ display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical;
  overflow:hidden; min-height:2.6em; }
.vfeat .vf-tags{ min-height:15px; }

/* Cờ ngôn ngữ đi kèm tên ngôn ngữ (emoji) — nhỏ hơn chữ một chút cho cân */
.vf-tags{ font-size:12px; }
.vfeat .vf-tags .vf-tag:first-child{ letter-spacing:0; }
.mv-meta, .lib-bars .mv-meta{ line-height:1.45; }

/* ==========================================================================
   App-specific overrides (studio). Tokens and primitives now live in
   /static/ds.css, imported at the top of this file (spec 006, T003).
   ========================================================================== */

body{ color:var(--text); background:var(--canvas); }

h1,.topbar-title{ font-size:var(--fs-h1); line-height:var(--lh-h1); font-weight:650; letter-spacing:-.02em; }

h2{ font-size:var(--fs-h2); line-height:var(--lh-h2); }

h3{ font-size:var(--fs-h3); line-height:var(--lh-h3); }

::selection{ background:var(--accent-soft); color:var(--text); }

/* App chrome: calmer canvas, one solid primary */
.layout{ background:var(--canvas); }

.app > main{ padding:8px 16px 12px 8px; }

.app > main > .main{ background:var(--surface); border:1px solid var(--border); border-radius:var(--r-lg); box-shadow:var(--e-1); }

.topbar{ padding:10px 20px; background:var(--canvas); }

.topbar-title{ font-size:var(--fs-h1); margin:0; }

.sidebar{ width:248px; background:var(--canvas); }

.sb-scroll{ padding:8px 10px; }

.sb-group{ color:var(--text-3); font-size:12px; font-weight:700; letter-spacing:.07em; margin:18px 0 6px; padding:0 10px; }

.s-item{ min-height:40px; margin:2px 0; padding:8px 10px; border-radius:var(--r-sm); color:var(--text-2); font-size:14px; }

.s-item:hover{ background:var(--surface-hover); color:var(--text); }

.s-item.active{ background:var(--surface); border:1px solid var(--border); color:var(--text); box-shadow:var(--e-1); }

.s-item.active .s-ico{ color:var(--accent); }

.sb-bottom{ padding:10px; }

.sb-foot{ color:var(--text-3); }

/* TTS editor */
/* `scrollbar-gutter:stable` keeps the scrollbar's 15px reserved even when the
   content fits. Without it, the writing column got 15px wider the moment the
   script grew past the viewport and the bar appeared, so every extra line
   re-wrapped the whole script mid-typing (measured 1239px → 1224px at 1917). */
.editor-scroll{ padding:28px 32px 48px; scrollbar-gutter:stable; }

/* Flush LEFT and FULL WIDTH (owner reports 2026-11-08: first "chỗ nhập text lại
   lơ lửng ở giữa", then "mở trên màn hình to tôi thấy thừa 1 khoảng trắng ở giữa").
   Two separate faults in one rule:
     1. The base rule above is `margin:0 auto`, which parked the column in the
        middle of the editor pane — at a 1922px window the script began 204px
        from the pane's left edge while the voice row above it (full width, 16px
        padding) began at 16px and the footer at 32px: the text read as floating.
     2. `max-width:900px` then left the 900px column short of the settings rail,
        i.e. a 374px band of empty canvas between the script and the settings
        panel that belongs to nothing. The writing surface is the whole point of
        this screen, so it takes the whole pane: both edges now line up with the
        toolbar row above it and the generate row below it.
   Measured at 1922×1051: the column used to run 461→1361 inside a 257→1565
   pane; it now runs 289→1533 (= the pane minus its 32px scroll padding). */
body.mode-tts .editor-inner{ max-width:none; margin:0; }

/* The creation panels that do NOT own the whole pane — Voice Clone, Voice Design,
   My Voices — keep the base measure from `.editor-inner` above: max-width:1080px,
   CENTRED. A 900px column flush LEFT (the first cut of this rule, 2026-11-08) is
   what the owner saw on /vi/studio/clone/ as "bị chia đôi": at a 1908px window the
   editor pane is 1555px wide, so the card hugged its left edge and left a ~700px
   band of canvas on the right — the screen read as two columns, one of them empty.
   Two things rule out the alternatives, so don't "fix" it again without reading:
     1. FULL WIDTH (what TTS/translate/subtitles do) distorts this wizard. Measured
        at 1908: `#cloneName` and the step-3 Save button stretch to ~1300px, the
        three-step rail spreads over 1,5k, and the drop zone becomes a shallow
        1507×160 strip. Those screens are one long text surface; this one is a
        1090px form.
     2. The approved design is exactly this: frontend/mockups/clone-ux/
        after-1-pick.png (1440×900) draws the card edge to edge INSIDE a 1087px
        pane — i.e. the same 1080 measure, which a ≤1440 window still fills.
   So the card keeps the width it was drawn at and the extra width of a big screen
   is spent evenly on both sides instead of all on the right. */
.editor-inner{ max-width:1080px; margin-inline:auto; }

.workspace-bar.voice-row{ min-height:52px; padding:8px 16px; background:var(--surface); border-bottom:1px solid var(--border); }

/* ---------- TTS VOICE COMPOSER: voice row + script + tools = ONE card ----------
   Owner reports (2026-11-08), in order:
     "chia các khối tách riêng biệt, tên voice, ô nhập text, ô biểu cảm xoá …
      gộp 1 cái vào chung 1 khối để liền mạch"
     "user focus vào chỗ nhập text, chỗ đấy rộng hơn"
     "nhìn to bị thô quá, ô nhập text nên như này thôi"  ← reference: a compact
      one-row-header editor card, ~3 lines of script, plenty of white canvas BELOW
      the card rather than a giant empty field.
   So: one card, but SMALL. The voice row is the header (square bottom corners, no
   bottom border), the script is the body, the format toolbar is the footer. The
   scroll column is what makes that possible: the row sits inside it (see
   templates/studio/tts.html) so both halves share the same left/right edges and
   the outside corners can be rounded into one outline.

   Sizing is the part the reference changed: the body is a 3-4 line field
   (`clamp(76px, 9vh, 112px)`), not a 345px box. A big screen keeps a comfortable
   field and the empty canvas lives OUTSIDE the input, which is what makes the
   screen read as airy instead of a huge framed hole. Focusing grows it by roughly
   one line (`clamp(112px, 14vh, 176px)`) — enough to feel like the field made room
   for you, not enough to jump the page around. */
/* Header inset = the body's (16px + the 1px border), so the voice chip, the
   script's first character and the toolbar's right edge all sit on the same two
   vertical lines. At 10px the chip hung 6px left of the text and the preview
   button 6px right of the icons: small, but it is the kind of 6px that makes a
   card look assembled rather than designed. */
body.mode-tts.single-speaker{ --composer-bar-h:46px; }
body.mode-tts.single-speaker .workspace-bar.voice-row{
  position:static; min-height:0; height:var(--composer-bar-h); padding:6px 16px;
  border:1px solid var(--border); border-bottom:none;
  border-radius:16px 16px 0 0; box-shadow:none; z-index:auto;
  transition:border-color .14s ease;
}
body.mode-tts.single-speaker .workspace-bar.voice-row .wsb-voice{
  height:32px; min-height:32px; padding:0 10px 0 4px;
}
/* The preview control joins the card's icon language: a ghost 32px button, not a
   bordered 36px square. A boxed button in the header competed with the boxed
   Generate CTA and left the card with two different icon styles (boxed up top,
   borderless at the bottom). */
body.mode-tts.single-speaker .workspace-bar.voice-row .wsb-preview{
  width:32px; height:32px; border:0; background:transparent; color:var(--text-2);
  border-radius:10px;
}
body.mode-tts.single-speaker .workspace-bar.voice-row .wsb-preview:hover{
  background:var(--hover); color:var(--text);
}

body.mode-tts.single-speaker .editor-inner > .speaker-block:first-of-type{
  border:1px solid var(--border); border-top:none; border-radius:0 0 16px 16px;
  padding:0 16px 8px; margin:0 0 14px;
  transition:border-color .14s ease;
}

/* The script IS the card body: no second box inside the card, and its hit area is
   the whole body — `.speaker-textwrap` is a flex column and the contenteditable
   fills it, so a click anywhere in the card (not only on a line of text) puts the
   caret in and starts typing. */
body.mode-tts.single-speaker .speaker-textwrap{
  display:flex; flex-direction:column;
  margin:0; padding:8px 0 0; min-height:clamp(76px, 9vh, 112px);
  border:0; border-radius:0; background:transparent;
  transition:min-height .18s ease;
}
body.mode-tts.single-speaker .speaker-textwrap:hover,
body.mode-tts.single-speaker .speaker-content:focus-within .speaker-textwrap{
  background:transparent; border-color:transparent;
}
body.mode-tts.single-speaker .speaker-text{ flex:1 1 auto; }
/* Placeholder shares the script's own metrics; the 20px default belongs to the
   old empty-editor hero and reads as a different, louder paragraph here. */
body.mode-tts.single-speaker .speaker-textwrap .ph-overlay{
  top:8px; left:0; right:0; font-size:16px; line-height:1.75;
  /* A whole-editor-wide hint lands on one endless line; two comfortable lines
     fill the empty field instead of leaving it looking half-built. */
  max-width:64ch;
}
/* Toolbar: always visible, and on a desktop it moves UP into the card's header
   row, to the left of the preview button — one icon row per card, exactly like
   the reference. That is what turns the card from three visual rows
   (voice / script / toolbar) into two, and it saves the 26px the footer used to
   occupy. It is `position:absolute` rather than a DOM move because the tools are
   generated per speaker block by app.js while the header row belongs to the
   shared shell; pinning it is a layout concern, moving it would be a data one.
   The offset is derived from --composer-bar-h so the two halves cannot drift. */
body.mode-tts.single-speaker .blk-tools{
  justify-content:flex-end; gap:2px; height:26px; margin:2px 0 0;
  opacity:1; transform:none; pointer-events:auto;
}
body.mode-tts.single-speaker .blk-tool{ width:26px; height:26px; }
@media (min-width:821px){
  body.mode-tts.single-speaker .editor-inner > .speaker-block:first-of-type{ position:relative; }
  body.mode-tts.single-speaker .blk-tools{
    position:absolute; right:56px; margin:0; z-index:2;
    /* 10px = (bar height - icon row height) / 2, +1px for the card's border */
    top:calc(11px - var(--composer-bar-h));
  }
}
/* Phones keep the icons in the flow at the card's bottom edge: the live-chat
   bubble floats over the editor's bottom-right, and the header row there is
   already full (the voice chip stretches to fill it). */
@media (max-width:820px){
  body.mode-tts.single-speaker .blk-tools{ justify-content:flex-start; }
}

/* Focus: the whole card grows a line taller and its outline turns coral, so
   "where am I typing" is answerable at a glance. Hover uses the neutral strong
   border. */
body.mode-tts.single-speaker .editor-scroll:has(:is(.speaker-textwrap, .blk-tools):hover) > .workspace-bar.voice-row,
body.mode-tts.single-speaker .editor-scroll:has(:is(.speaker-textwrap, .blk-tools):hover) > .editor-inner > .speaker-block:first-of-type{
  border-color:var(--border-strong);
}
/* --brand-coral (#fe655d) is 2.9:1 on white, under the 3:1 that WCAG 1.4.11 asks
   of a focus indicator; the deep variant of the same hue is 3.5:1. */
body.mode-tts.single-speaker .editor-scroll:has(.speaker-text:focus) > .workspace-bar.voice-row,
body.mode-tts.single-speaker .editor-scroll:has(.speaker-text:focus) > .editor-inner > .speaker-block:first-of-type{
  border-color:var(--brand-coral-deep);
}
body.mode-tts.single-speaker .speaker-content:focus-within .speaker-textwrap{
  min-height:clamp(112px, 14vh, 176px);
}

/* Dark theme: on #15161a the 1px `--border` line is 1.3:1 — the card had no
   visible edge at all. The raised neutral fill does the defining instead, and the
   border stays as the hairline it is. */
:root[data-theme="dark"] body.mode-tts.single-speaker .workspace-bar.voice-row,
:root[data-theme="dark"] body.mode-tts.single-speaker .editor-inner > .speaker-block:first-of-type{
  background:var(--surface-hover);
}

/* Below the card: one clear action, two quiet ones. Three identical outlined
   boxes competed with the card above them; the row now reads
   primary / shortcut / menu. */
body.mode-tts .add-speaker-row #addSpeaker{
  background:var(--surface); border-color:var(--border); color:var(--text);
}
body.mode-tts .add-speaker-row #importScript,
body.mode-tts .add-speaker-row #wsMoreBtn{
  background:transparent; border-color:transparent; color:var(--text-2);
}
body.mode-tts .add-speaker-row #importScript:hover,
body.mode-tts .add-speaker-row #wsMoreBtn:hover{ background:var(--hover); color:var(--text); }

.voice-row .wsb-voice{ min-height:36px; border-radius:var(--r-full); font-size:13px; max-width:320px; }

.voice-row .wsb-voice-name{ flex:1 1 auto; }

.voice-row .wsb-preview{ display:inline-flex; }

.speaker-text{ font-size:16px; line-height:1.75; }

.add-speaker-row{ gap:8px; margin:16px 0 22px; }

.add-speaker{ background:var(--surface); border:1px solid var(--border); border-radius:var(--r-sm);
  min-height:38px; padding:8px 12px; font-size:13px; font-weight:600; color:var(--text-2); }

.add-speaker:hover{ background:var(--surface-hover); color:var(--text); }

.add-speaker-row .ln{ background:var(--border); }

.generate-btn{ background:var(--accent); border-radius:var(--r-sm); height:44px; font-weight:650; box-shadow:none; }

.generate-btn:hover{ background:var(--accent-hover); filter:none; box-shadow:none; }

.generate-btn:disabled{ background:var(--border); color:var(--text-3); opacity:1; }

.char-count .cc-num{ font-weight:650; }

.char-count.near-limit{ color:var(--warning); }

.char-count.near-limit .cc-num{ color:var(--warning); }

.char-count.over-limit{ color:var(--danger); }

.el-top.over-limit .generate-btn{ opacity:1; }

/* Characters past the plan's per-run allowance, painted in the text itself
   (2026-11-08). CSS Custom Highlight API: the range is styled without touching the
   DOM, so the caret, undo/redo, IME composition, the expression chips and
   serializeEditable() keep working. The editor no longer trims the text or blocks
   typing when it overflows, so this red is the only "where does it start" cue. */
::highlight(ov-over-limit){ background-color:rgba(220,38,38,.10); color:#b42318; }

/* Settings: 3 default controls + one Advanced disclosure + Recent */
.settings{ width:340px; padding:18px 16px; background:var(--surface); }

.st-head{ padding:2px 0 14px; }

.st-title{ font-size:var(--fs-h2); font-weight:650; }

.st-tabs{ display:none; }

.st-section{ padding:14px 0; border-bottom:1px solid var(--border); }

.st-section:last-of-type{ border-bottom:none; }

.st-label{ font-size:13px; font-weight:650; }

.st-hint{ font-size:12px; line-height:1.5; color:var(--text-3); }

.slider-val{ background:var(--surface-hover); border:1px solid var(--border); color:var(--text-2); font-size:12px; }

#formatSelect{ margin-top:6px; }

/* Settings → "Use in your AI agent". One quiet row: the five client marks as a
   fanned stack, one line of label, one arrow. It is a shortcut, not a banner —
   the card used to wrap onto four lines and pushed the panel's real controls
   down the page. */
.st-connect{ display:flex; align-items:center; gap:12px; min-height:60px; padding:0 12px 0 10px;
  border:1px solid var(--border); border-radius:var(--r-md); background:var(--surface-hover);
  color:var(--text); text-decoration:none;
  transition:background var(--t-fast), border-color var(--t-fast); }

.st-connect:hover{ background:var(--surface); border-color:var(--text-3); }

.st-connect:focus-visible{ outline:2px solid var(--focus); outline-offset:2px; }

/* Five marks fanned like a hand of cards: bolder than a row of separate chips,
   and it buys back the width the label needs. Each chip is placed from the
   cluster's centre (--x/--y) and rotated (--r); the glyphs keep their own colour
   on white chips, so overlapping never merges two logos. */
.st-connect-duo{ position:relative; flex:none; width:90px; height:50px; }

.st-connect .avatar-menu-ai-tile{ position:absolute; left:50%; top:50%;
  width:21px; height:21px; border-radius:7px; background:var(--surface);
  box-shadow:0 0 0 1px rgba(17,24,39,.10), 0 1px 2px rgba(17,24,39,.06);
  transform:translate(-50%,-50%) translate(var(--x,0),var(--y,0)) rotate(var(--r,0deg));
  transition:transform .3s cubic-bezier(.22,1,.36,1); }

.st-connect .avatar-menu-ai-tile img{ display:block; width:14px; height:14px; filter:none; }

.st-connect .avatar-menu-ai-tile--codex{ background:var(--surface-hover); }

/* The whole row is the target; hovering spreads the fan a little further. */
.st-connect:hover .st-connect-duo .avatar-menu-ai-tile{
  transform:translate(-50%,-50%) translate(var(--x),calc(var(--y) - 2px)) rotate(calc(var(--r) * 1.25)); }

.st-connect:focus-visible .st-connect-duo .avatar-menu-ai-tile{
  transform:translate(-50%,-50%) translate(var(--x),calc(var(--y) - 2px)) rotate(calc(var(--r) * 1.25)); }

@media (prefers-reduced-motion: reduce){
  .st-connect .avatar-menu-ai-tile{ transition:none; }
}

.st-connect-copy{ flex:1; min-width:0; }

.st-connect-title{ display:block; font-size:13px; line-height:18px; font-weight:600;
  letter-spacing:-.01em; }

.st-connect-ext{ display:inline-flex; flex:none; color:var(--text-3);
  transition:color var(--t-fast), transform var(--t-fast); }

.st-connect-ext svg{ width:13px; height:13px; }

.st-connect:hover .st-connect-ext{ color:var(--text); transform:translateX(1px); }

/* The row sits above the controls, so the first section after it keeps its
   normal rhythm instead of the 34px gap that separates sections from each other. */
.st-connect + .st-section{ margin-top:20px; }

.st-recent{ padding-top:14px; border-top:1px solid var(--border); }

.st-recent-head{ display:flex; align-items:center; justify-content:space-between; gap:8px; }

.st-recent-link{ font-size:12px; font-weight:650; color:var(--accent); text-decoration:none; }

.st-recent-link:hover{ text-decoration:underline; }

.st-recent-list{ display:flex; flex-direction:column; gap:6px; margin-top:8px; }

.st-recent-row{ display:flex; align-items:center; gap:10px; width:100%; padding:8px; border:1px solid transparent;
  border-radius:var(--r-sm); background:transparent; font:inherit; text-align:left; cursor:pointer; }

.st-recent-row:hover{ background:var(--surface-hover); border-color:var(--border); }

.st-recent-av{ width:28px; height:28px; border-radius:999px; flex:none; }

.st-recent-info{ display:flex; flex-direction:column; min-width:0; flex:1; }

.st-recent-info strong{ font-size:12px; font-weight:650; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* Label + "New" chip share one line so the chip follows the name instead of
   wrapping under it. */
.st-recent-top{ display:flex; align-items:center; gap:6px; min-width:0; }
.st-recent-top strong{ min-width:0; }
/* Deliberately quiet: a neutral chip, not a second accent colour next to the
   label. It says "this one is new" and then gets out of the way — the row is
   already the last thing the user looked at. */
.st-recent-new{ flex:none; padding:0 6px; border-radius:6px; font-size:10px; font-weight:600;
  letter-spacing:.01em; line-height:16px; font-style:normal; white-space:nowrap;
  color:var(--text-3); background:var(--surface-hover); }

.st-recent-info small{ font-size:12px; color:var(--text-3); }

.st-recent-play{ color:var(--text-3); font-size:12px; }

.st-recent-empty{ font-size:12px; color:var(--text-3); padding:8px 0; }

/* Account */
.account-view{ display:flex; min-height:100%; }

.acc-scroll{ flex:1; overflow:auto; padding:32px; }

.acc-inner{ max-width:940px; margin:0 auto; }

.acc-head h2{ font-size:var(--fs-display); line-height:var(--lh-display); }

.acc-head p{ color:var(--text-2); font-size:14px; margin-top:4px; }

.acc-grid{ display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:14px; margin-top:22px; }

.acc-card h3{ font-size:15px; margin-bottom:8px; }

.acc-card p{ font-size:13px; color:var(--text-2); line-height:1.55; }

.acc-plan{ font-size:20px; font-weight:650; color:var(--text) !important; }

.acc-usage{ margin:8px 0 16px; color:var(--text-2); font-size:13px; }

.acc-card .ov-btn{ margin-top:10px; }

@media (max-width:900px){ .acc-grid{ grid-template-columns:1fr; } .acc-scroll{ padding:20px 16px; } }

/* Library: content first, filters behind one control */
.lib-inner{ max-width:1180px; }

.lib-title{ font-size:var(--fs-display); line-height:var(--lh-display); }

.lib-sub{ color:var(--text-2); font-size:14px; }

.lib-controls{ gap:8px; }

.lib-search input{ height:42px; border-radius:var(--r-sm); }

.lib-tool-btn,.lib-sort select{ height:42px; border-radius:var(--r-sm); }

.lib-filter-panel{ background:var(--surface); border-radius:var(--r-sm); }

.lib-count{ margin:14px 2px 10px; }

.lib-featured{ gap:12px; }

.vfeat{ border-radius:var(--r-md); box-shadow:none; }

.lib-bars .mv-item{ border-radius:var(--r-sm); }

@media (max-width:820px){
  .lib-scroll{ padding:20px 14px 40px; }
  .lib-controls{ grid-template-columns:1fr auto; }
  .lib-search{ grid-column:1 / -1; min-width:0; }
  .lib-tool-btn{ width:auto; }
  .lib-sort{ width:auto; }
  .lib-filter-panel{ grid-template-columns:1fr 1fr; }
  .lib-filter-panel .vp-filter{ min-height:40px; }
  .lib-featured{ grid-template-columns:1fr; }
  .vfeat{ aspect-ratio:16/9; }
}

/* Responsive: compact rail at 1024, sheet instead of the settings column */
@media (max-width:1180px) and (min-width:1025px){
  .sidebar{ width:72px; }
  .sidebar .s-label,.sidebar .sb-group,.sidebar .sb-foot{ display:none; }
  .sidebar .s-item{ justify-content:center; padding:0; }
  .sidebar .sb-bottom{ display:none; }
  .settings{ width:320px; }
  .editor-scroll{ padding:24px 24px 40px; }
}

@media (max-width:1024px){
  .main{ display:flex; flex-direction:column; min-height:0; }
  .editor{ width:100%; }
  .settings{
    position:fixed; left:0; right:0; bottom:0; top:auto; z-index:1500;
    width:100%; max-height:min(86dvh,720px); border-left:none; border-top:1px solid var(--border);
    border-radius:16px 16px 0 0; box-shadow:var(--e-3);
    padding:18px 16px calc(20px + env(safe-area-inset-bottom,0px));
    transform:translateY(102%); visibility:hidden; pointer-events:none;
    transition:transform .26s cubic-bezier(.22,1,.36,1),visibility .26s;
  }
  body.settings-sheet-open .settings{ transform:translateY(0); visibility:visible; pointer-events:auto; }
  body.settings-sheet-open{ overflow:hidden; }
  .settings-sheet-trigger{ display:inline-flex; align-items:center; gap:6px; height:36px; padding:0 10px;
    border:1px solid var(--border); border-radius:var(--r-sm); background:var(--surface); color:var(--text-2);
    font:inherit; font-size:13px; font-weight:600; }
  .settings-backdrop{ display:block; position:fixed; inset:0; z-index:1400; background:rgba(15,23,42,.42);
    opacity:0; pointer-events:none; transition:opacity .2s; }
  body.settings-sheet-open .settings-backdrop{ opacity:1; pointer-events:auto; }
  .editor-scroll{ padding:22px 20px 44px; }
  .side-rail{ display:none; }
}

@media (max-width:820px){
  .editor-scroll{ padding:16px 14px 38px; }
  .topbar{ padding:8px 12px; }
  .topbar-title{ font-size:16px; }
  .app > main{ padding:0; }
  .app > main > .main{ border:none; border-radius:0; box-shadow:none; }
  .workspace-bar.voice-row{ padding:8px 12px; }
  .voice-row .wsb-voice{ max-width:none; flex:1; }
  .add-speaker-row{ display:grid; grid-template-columns:1fr 1fr; gap:8px; }
  .add-speaker-row .ln{ display:none; }
  .add-speaker{ justify-content:center; }
  .editor-footer .el-top{ gap:8px; }
  .editor-footer .el-top .ef-right{ gap:8px; }
  .generate-btn{ min-width:0; }
  .st-recent{ padding-bottom:20px; }
  .chat-bot-widget{ bottom:84px; }
  body.settings-sheet-open .chat-bot-widget{ display:none; }
}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{ animation-duration:.01ms !important; animation-iteration-count:1 !important;
    transition-duration:.01ms !important; scroll-behavior:auto !important; }
}

/* Pricing trust strip — same signals as the public page; currency label is
   hydrated at runtime from /billing/plans. */
.pricing-trust{ display:flex; flex-wrap:wrap; align-items:center; gap:8px 16px; margin:4px 0 16px;
  padding:10px 12px; border:1px solid var(--border); border-radius:var(--r-sm); background:var(--surface);
  color:var(--text-2); font-size:12px; font-weight:600; }

.pricing-trust span::before{ content:"✓"; color:var(--success); margin-right:5px; font-weight:800; }

@media (max-width:1024px){
  .st-sheet-close{ display:flex; position:absolute; top:10px; right:10px; z-index:2; width:38px; height:38px;
    align-items:center; justify-content:center; border:none; border-radius:8px; background:var(--surface-hover);
    color:var(--text-2); cursor:pointer; }
  .st-head{ padding-right:44px; }
}

/* Guest rail: keep the account summary one compact block so all 9 destinations
   fit at 1440×900. Signed-in users keep the meters. */
.sb-plan[data-state="guest"] .sb-plan-credit,
.sb-plan[data-state="guest"] .sb-plan-notice{ display:none; }

.sb-plan[data-state="guest"]{ padding-bottom:6px; }

.sb-plan[data-state="guest"] .sb-plan-head{ margin-bottom:2px; }

.sb-signin-btn{ margin-top:6px; }

/* Single-speaker TTS: the voice row above the editor is the one selector.
   Multi-speaker blocks keep their per-speaker chip. */
body.mode-tts .speaker-block:only-of-type .speaker-head .speaker-chip{ display:none; }

body.mode-tts.single-speaker .speaker-block .speaker-chip,
body.mode-tts.single-speaker .speaker-block .speaker-copy,
body.mode-tts.single-speaker .speaker-block .speaker-del{ display:none; }

/* The voice row is the only selector; the block keeps only its text. */
body.mode-tts.single-speaker .speaker-head{ display:none; }

/* Picker rows: select + preview are the two primary row actions; secondary
   management (copy/favourite/delete) stays on the Library page. */
.vp-list-wrap .mv-copy,
.vp-list-wrap .mv-fav-toggle,
.vp-list-wrap .mv-del{ display:none; }

.vp-see-all{ border:none; background:none; padding:0; font:inherit; font-weight:600; color:var(--text-2); cursor:pointer; text-decoration:underline; text-underline-offset:2px; }
.vp-see-all:hover{ color:var(--text); }

@media (max-width:1180px) and (min-width:821px){
  .sidebar{ width:72px; }
  .sidebar .s-label,.sidebar .sb-group,.sidebar .sb-foot{ display:none; }
  .sidebar .s-item{ justify-content:center; padding:0; }
  .sidebar .sb-bottom{ display:none; }
  .sidebar .site-brand{ justify-content:center; padding:18px 0 12px; }
  .sidebar .site-brand .brand-logo{ content:url("/static/saydivoice-mark.svg"); width:24px; height:24px; }
}

/* Minimum target size for small inline controls (24×24 WCAG 2.2 AA). */
.sb-foot a,.st-recent-link,.trial-hint-cta{ min-height:24px; display:inline-flex; align-items:center; }

.sb-plan-info{ min-width:24px; min-height:24px; padding:4px; }

input[type="checkbox"]{ min-width:24px; min-height:24px; accent-color:var(--accent); }

.trial-hint-cta{ color:var(--accent); }

.vf-play{ border:0; padding:0; font:inherit; cursor:pointer; }

#hpEmpty p, #hpEmpty .hp-empty p{ color:var(--text-2); }

#hpEmpty, #hpEmpty *{ color:var(--text); }

.hp-sr{ color:var(--text); }

.vc-data-note a{ color:var(--accent); }

body.mode-tts.single-speaker .speaker-block .speaker-del,
body.mode-tts.single-speaker .speaker-block .speaker-copy{ display:none; }

.skip-link{ position:fixed; top:8px; left:8px; z-index:5000; transform:translateY(-200%);
  background:var(--surface); color:var(--text); border:1px solid var(--border); border-radius:var(--r-sm);
  padding:10px 14px; font-weight:650; text-decoration:none; box-shadow:var(--e-2); }

.skip-link:focus{ transform:none; }

body.mode-tts.single-speaker .speaker-del,
body.mode-tts.single-speaker .speaker-copy{ display:none !important; }

/* TTS script toolbar: Add speaker + Import script + More. */
.ws-more{ position:relative; display:inline-flex; }

.ws-more-menu{ position:absolute; right:0; top:calc(100% + 6px); z-index:80; min-width:220px;
  background:var(--surface); border:1px solid var(--border); border-radius:var(--r-md);
  box-shadow:var(--e-2); padding:6px; }

.ws-more-menu[hidden]{ display:none; }

.ws-more-menu button{ display:flex; align-items:center; gap:9px; width:100%; padding:9px 10px;
  border:none; border-radius:var(--r-sm); background:transparent; color:var(--text); font:inherit;
  font-size:13px; font-weight:600; text-align:left; cursor:pointer; }

.ws-more-menu button:hover{ background:var(--surface-hover); }

@media (max-width:820px){
  .add-speaker-row{ grid-template-columns:1fr 1fr; }
  .ws-more{ width:100%; }
  .ws-more > .add-speaker{ width:100%; }
}

.devv-toolbar{ display:flex; justify-content:flex-end; padding:0 18px 6px; }

.devv-api-docs{ display:inline-flex; align-items:center; min-height:32px; padding:0 12px; border:1px solid var(--border);
  border-radius:var(--r-sm); background:var(--surface); color:var(--text-2); font-size:12px; font-weight:650;
  text-decoration:none; }

.devv-api-docs:hover{ background:var(--surface-hover); color:var(--text); }

@media (max-width:820px){
  .workspace-bar.voice-row{ position:sticky; top:0; z-index:20; box-shadow:0 1px 0 var(--border); }
}

/* Featured card actions: Use + More; copy/favourite live in the menu. */
.vf-actions{ position:relative; }

.vf-more{ display:inline-flex; align-items:center; justify-content:center; width:30px; height:30px;
  border:1px solid var(--border); border-radius:var(--r-sm); background:var(--surface);
  color:var(--text-2); font:inherit; font-size:16px; line-height:1; cursor:pointer; }

.vf-more:hover{ background:var(--surface-hover); color:var(--text); }

.vf-more-menu{ position:absolute; right:0; bottom:calc(100% + 6px); z-index:40; min-width:190px;
  padding:5px; border:1px solid var(--border); border-radius:var(--r-sm); background:var(--surface);
  box-shadow:var(--e-2); }

.vf-more-menu[hidden]{ display:none; }

.vf-more-menu button{ display:flex; align-items:center; gap:8px; width:100%; padding:7px 8px;
  border:none; border-radius:8px; background:transparent; color:var(--text); font:inherit;
  font-size:12px; font-weight:600; text-align:left; cursor:pointer; }

.vf-more-menu button:hover{ background:var(--surface-hover); }

#viewAccount .acc-card #usageBar{ width:100%; max-width:none; margin:0 0 14px; border:1px solid var(--border); border-radius:var(--r-sm); }

#viewAccount .acc-card #usageBar .sb-plan-head{ margin:0; }

/* ==========================================================================
   T003–T007 — token enforcement and shared primitives
   Type scale: 12/13/14/15/16/20/24/32. Radius: 8/12/16/999.
   Elevation: e-1/e-2/e-3. Every legacy control below shares the .ov-btn
   declaration through :where() (zero specificity, so component rules win).
   ========================================================================== */
:where(.generate-btn,.modal-btn,.vc-btn,.vd-btn,.pb-dl){
  display:inline-flex; align-items:center; justify-content:center; gap:8px;
  min-height:40px; padding:0 16px; border-radius:var(--r-sm);
  font:inherit; font-size:14px; font-weight:650; cursor:pointer;
}

/* Clone: the disabled Continue says why, and metadata folds away. */
.vc-foot-reason{ color:var(--text-3); font-size:13px; margin-right:auto; }

.vc-foot-reason[hidden]{ display:none; }

.vc-optional{ margin:0 0 var(--s-4); }

.vc-optional summary{ min-height:40px; }

/* Design: the Save action appears only after a real preview. */
.vd-btn[hidden]{ display:none; }

/* Changer: no journey map before there is something to change. */
body.mode-changer #dubPhase1:not(.has-file) #dubStepper{ display:none; }

/* T045: the assistant launcher yields to every overlay so it can never sit on
   top of a control the user is trying to reach. */
body.settings-sheet-open .ov-chat-bubble,
body:has(.modal.show) .ov-chat-bubble,
body:has(dialog[open]) .ov-chat-bubble,
body:has(.vp-overlay.show) .ov-chat-bubble,
body:has(.vp-panel.show) .ov-chat-bubble{
  opacity:0; pointer-events:none;
}

/* T021/T059: the shared voice row (TTS, Dubbing, Subtitles). */
.ov-voice-row{ display:flex; align-items:center; gap:8px; min-width:0; }

.ov-voice-row .wsb-voice{ min-height:36px; border-radius:var(--r-full); font-size:13px; max-width:320px; }

.ov-voice-row .wsb-preview{ display:inline-flex; }

.ov-voice-row .wsb-voice-name{ flex:1 1 auto; }

@media (max-width:820px){ .ov-voice-row .wsb-voice{ max-width:none; flex:1; } }

:is(.ov-btn--primary,.generate-btn,.mv-btn.mv-use,.vc-btn-primary,.tr-btn-primary,.vd-btn-primary,.modal-btn:not(.ghost),.first-audio-go){
  position:relative; isolation:isolate; overflow:hidden;
  border:0; border-radius:999px;
  color:#fff; background:#ff665e;
  font-weight:500; line-height:1; white-space:nowrap;
  box-shadow:none; filter:drop-shadow(0 4px 10px rgba(255,102,94,.28));
  text-shadow:0 1px 1.5px rgba(120,10,10,.28);
}
:is(.ov-btn--primary,.generate-btn,.mv-btn.mv-use,.vc-btn-primary,.tr-btn-primary,.vd-btn-primary,.modal-btn:not(.ghost),.first-audio-go)::before{
  content:""; position:absolute; inset:0; z-index:-2; border-radius:inherit; pointer-events:none;
  background-color:#ff665e; background-repeat:no-repeat;
  background-image:url(pill-lime.svg),url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20width='142'%20height='85'%20viewBox='-41%20-3%20142%2085'%20preserveAspectRatio='none'%20fill='none'%3e%3cdefs%3e%3cfilter%20id='pm-tex'%20x='-41'%20y='-3'%20width='142'%20height='85'%20filterUnits='userSpaceOnUse'%20color-interpolation-filters='sRGB'%3e%3cfeTurbulence%20type='fractalNoise'%20baseFrequency='0.999'%20numOctaves='3'%20seed='4033'/%3e%3cfeDisplacementMap%20in='SourceGraphic'%20scale='8'%20xChannelSelector='R'%20yChannelSelector='G'/%3e%3c/filter%3e%3cfilter%20id='pm-b'%20x='-66'%20y='-66'%20width='183.8755'%20height='162.9506'%20filterUnits='userSpaceOnUse'%20color-interpolation-filters='sRGB'%3e%3cfeGaussianBlur%20stdDeviation='8'/%3e%3c/filter%3e%3c/defs%3e%3cg%20filter='url(%23pm-tex)'%3e%3cg%20transform='matrix(0.953%200.3028%20-0.9233%200.384%2016.5773%2025.1191)'%3e%3cg%20filter='url(%23pm-b)'%3e%3cpath%20d='M%2051.8754%204.3500%20C%2051.8754%2010.8253%2037.6978%2030.9505%2024.2854%2030.9505%20C%2010.8729%2030.9505%200%2025.7013%200%2019.2260%20C%200%2012.7507%2011.8601%2011.0072%2025.2726%2011.0072%20C%2027.5346%20-2.4873%2051.8754%20-2.1251%2051.8754%204.3500%20Z'%20fill='%23FA41C9'%20fill-opacity='0.6'/%3e%3c/g%3e%3c/g%3e%3c/g%3e%3c/svg%3e"),url(pill-gold.svg),url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20width='267'%20height='122'%20viewBox='-119%20-39%20267%20122'%20preserveAspectRatio='none'%20fill='none'%3e%3cdefs%3e%3cfilter%20id='pr-tex'%20x='-119'%20y='-39'%20width='267'%20height='122'%20filterUnits='userSpaceOnUse'%20color-interpolation-filters='sRGB'%3e%3cfeTurbulence%20type='fractalNoise'%20baseFrequency='0.999'%20numOctaves='3'%20seed='4218'/%3e%3cfeDisplacementMap%20in='SourceGraphic'%20scale='8'%20xChannelSelector='R'%20yChannelSelector='G'/%3e%3c/filter%3e%3cfilter%20id='pr-b'%20x='-66'%20y='-66'%20width='281'%20height='179'%20filterUnits='userSpaceOnUse'%20color-interpolation-filters='sRGB'%3e%3cfeGaussianBlur%20stdDeviation='8'/%3e%3c/filter%3e%3c/defs%3e%3cg%20filter='url(%23pr-tex)'%3e%3cg%20transform='matrix(1%200%200%201%20-34%203)'%3e%3cg%20filter='url(%23pr-b)'%3e%3cpath%20d='M%20149%2029.9682%20C%20104.0814%2038.5563%20125.8917%2047.0000%2090.8689%2047.0000%20C%2055.8461%2047.0000%20-51.8578%2026.7157%2030.0540%2021.6638%20C%2050.8711%2026.2243%20110.3438%2016.3076%20116.2910%202.9153%20C%20121.3219%20-8.4132%20149%2015.9675%20149%2029.9682%20Z'%20fill='%23EA483F'%20fill-opacity='1'/%3e%3c/g%3e%3c/g%3e%3c/g%3e%3c/svg%3e");
  background-position:188.8889% 85.4167%,186.3636% 6.6667%,-42.623% 38.2979%,80.9524% 47.561%;
  background-size:130% 220%,118.3333% 212.5%,150.8333% 217.5%,222.5% 305%;
  background-blend-mode:normal,lighten,normal,normal;
}
:is(.ov-btn--primary,.generate-btn,.mv-btn.mv-use,.vc-btn-primary,.tr-btn-primary,.vd-btn-primary,.modal-btn:not(.ghost),.first-audio-go)::after{
  content:""; position:absolute; inset:0; z-index:-1; border-radius:inherit; pointer-events:none;
  background:transparent; transition:background .12s;
}
:is(.ov-btn--primary,.generate-btn,.mv-btn.mv-use,.vc-btn-primary,.tr-btn-primary,.vd-btn-primary,.modal-btn:not(.ghost),.first-audio-go):hover:not(:disabled)::after{ background:rgba(0,0,0,.08); }
:is(.ov-btn--primary,.generate-btn,.mv-btn.mv-use,.vc-btn-primary,.tr-btn-primary,.vd-btn-primary,.modal-btn:not(.ghost),.first-audio-go):disabled::after{ background:rgba(0,0,0,.5); z-index:1; }
:is(.ov-btn--primary,.generate-btn,.mv-btn.mv-use,.vc-btn-primary,.tr-btn-primary,.vd-btn-primary,.modal-btn:not(.ghost),.first-audio-go):disabled{ color:#fff; background:#ff665e; box-shadow:none; filter:none; opacity:1; cursor:not-allowed; }

/* ==========================================================================
   PRO EDITOR — regenerate one segment
   ==========================================================================
   A sub-view of the Text-to-Speech composer, not a new screen: it replaces the
   speaker blocks inside #speakers while an edit session is open (app.js owns
   the toggle). Its only job is "find the bad segment, re-render that segment".

   Deliberate restraint, because this list can hold 40+ rows:
     · a row is text first — number, text, and NOTHING else until you reach for it
     · state is carried by shape, not by a label: italic+amber for edited text,
       a coral ↻ for the one action the row is asking for, a spinner while it runs
     · the three row actions are always in the DOM (hidden by visibility, so the
       row never shifts) and always visible on touch, where hover does not exist

   Tokens only: this file must not redeclare ds.css ink/border/surface, and a
   literal white background is a hole in the dark theme (tests/test_theme_ratchet).
   -------------------------------------------------------------------------- */

/* Entry offer, above the playbar. One line, one link. */
.pe-entry{ flex:none; display:flex; align-items:center; gap:9px;
  padding:10px 18px; border-top:1px solid var(--border); background:var(--surface); }
.pe-entry[hidden]{ display:none !important; }
.pe-entry-ic{ display:flex; flex:none; color:var(--brand-coral); }
.pe-entry-t{ font-size:12.5px; font-weight:500; color:var(--text-2); letter-spacing:-.005em; }
.pe-entry-go{ margin-left:auto; display:inline-flex; align-items:center; gap:4px;
  border:none; background:none; border-radius:6px; padding:4px 6px; cursor:pointer;
  font-family:inherit; font-size:12.5px; font-weight:650; color:var(--accent-text); }
.pe-entry-go:hover{ background:var(--brand-coral-soft); }

/* Panel shell */
.pe-panel{ display:block; }
.pe-panel[hidden]{ display:none !important; }

.pe-top{ display:flex; align-items:center; gap:10px; padding:0 0 12px; }
.pe-title{ font-size:13.5px; font-weight:600; letter-spacing:-.012em; color:var(--text); }
.pe-meta{ margin-left:auto; font:500 10.5px/1 var(--mono-stack,ui-monospace,SFMono-Regular,Menlo,monospace);
  color:var(--text-3); letter-spacing:.02em; font-variant-numeric:tabular-nums; }

.pe-icon{ width:28px; height:28px; display:inline-flex; align-items:center; justify-content:center;
  border:none; border-radius:7px; background:transparent; color:var(--text-3);
  cursor:pointer; font-family:inherit; transition:background .12s, color .12s; }
.pe-icon:hover{ background:var(--hover); color:var(--text); }
.pe-icon:focus-visible{ outline:2px solid var(--focus); outline-offset:1px; }

.pe-btn{ display:inline-flex; align-items:center; justify-content:center; gap:6px;
  height:30px; padding:0 11px; border-radius:8px; cursor:pointer;
  border:1px solid var(--border-2); background:var(--surface); color:var(--text-2);
  font-family:inherit; font-size:12.5px; font-weight:500; letter-spacing:-.005em;
  transition:background .13s, border-color .13s, color .13s; }
.pe-btn:hover{ background:var(--hover); border-color:var(--border-strong); color:var(--text); }
.pe-btn-ghost{ border-color:transparent; background:transparent; }
.pe-btn-ghost:hover{ background:var(--hover); border-color:transparent; }
.pe-btn-solid{ border:none; color:#fff; font-weight:650;
  background:linear-gradient(135deg,#ff9e99,var(--brand-coral) 55%,var(--brand-coral-deep));
  box-shadow:0 1px 2px rgba(224,70,63,.28); }
.pe-btn-solid:hover{ filter:brightness(1.04); }
.pe-btn-solid:disabled{ opacity:.6; cursor:default; filter:none; }

/* List */
.pe-list{ list-style:none; margin:0; padding:0;
  border:1px solid var(--border-2); border-radius:14px; background:var(--surface); }
/* No overflow:hidden here on purpose: the take menu is anchored to its row and
   would be clipped by it. The rows carry the corner radius instead, so a hover or
   "now playing" tint still follows the card's shape. */
.pe-list > .pe-row:first-child, .pe-list > .pe-edit:first-child{ border-radius:13px 13px 0 0; }
.pe-list > .pe-row:last-child, .pe-list > .pe-edit:last-child{ border-radius:0 0 13px 13px; }
.pe-list > .pe-row:only-child, .pe-list > .pe-edit:only-child{ border-radius:13px; }

.pe-row{ position:relative; display:flex; align-items:center; gap:0;
  padding:10px 14px 10px 16px; transition:background .1s; }
.pe-row + .pe-row, .pe-row + .pe-edit, .pe-edit + .pe-row{ border-top:1px solid var(--border); }
.pe-row:hover{ background:var(--hover); }
.pe-row.is-on{ background:var(--brand-coral-soft); }
.pe-row.is-on::before, .pe-edit::before{ content:""; position:absolute; left:0; top:9px; bottom:9px;
  width:2px; border-radius:0 2px 2px 0; background:var(--brand-coral); }

.pe-n{ flex:none; width:22px; margin-right:16px; text-align:left;
  font:500 10px/1 var(--mono-stack,ui-monospace,SFMono-Regular,Menlo,monospace);
  color:var(--text-3); opacity:.72; letter-spacing:.02em; font-variant-numeric:tabular-nums; }

.pe-txt{ flex:1; min-width:0; font-size:13px; font-weight:450; line-height:1.5;
  color:var(--text-2); letter-spacing:-.005em; cursor:text;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.pe-row:hover .pe-txt, .pe-row.is-on .pe-txt{ color:var(--text); }
.pe-row.is-dirty .pe-txt{ font-style:italic; color:var(--warn-ink); }

/* Actions: present but invisible, so revealing them cannot move the row. */
.pe-acts{ flex:none; display:flex; align-items:center; gap:1px; margin-left:10px; }
.pe-acts > *{ visibility:hidden; }
.pe-row:hover .pe-acts > *, .pe-row:focus-within .pe-acts > *{ visibility:visible; }
.pe-row.is-on .pe-acts .pe-pp{ visibility:visible; }
.pe-row.is-dirty .pe-acts .pe-re{ visibility:visible; color:var(--accent-text); }
.pe-row.is-busy .pe-acts .pe-spin{ visibility:visible; }
.pe-pp{ color:var(--text-2); }
.pe-row.is-on .pe-pp{ color:var(--accent-text); }

.pe-spin{ width:12px; height:12px; margin:0 8px; border-radius:999px;
  border:1.5px solid rgba(224,70,63,.2); border-top-color:var(--brand-coral-deep);
  animation:pe-spin .75s linear infinite; }
@keyframes pe-spin{ to{ transform:rotate(360deg); } }

/* Inline text edit: the row itself becomes the editor. */
.pe-edit{ position:relative; display:flex; align-items:flex-start;
  padding:10px 14px 12px 16px; background:var(--canvas); }
.pe-edit .pe-n{ padding-top:9px; }
.pe-edit-b{ flex:1; min-width:0; }
.pe-edit textarea{ width:100%; display:block; resize:none; height:58px; overflow:auto;
  border:1px solid var(--border-2); border-radius:8px; background:var(--surface);
  padding:8px 10px; color:var(--text); font-family:inherit; font-size:13px;
  font-weight:450; line-height:1.55; outline:none;
  transition:border-color .13s, box-shadow .13s; }
.pe-edit textarea:focus{ border-color:var(--brand-coral); box-shadow:0 0 0 3px rgba(254,101,93,.12); }
.pe-edit-f{ display:flex; align-items:center; gap:2px; margin-top:7px; }
.pe-edit-save{ border:none; background:none; border-radius:6px; padding:5px 7px; cursor:pointer;
  font-family:inherit; font-size:12.5px; font-weight:650; color:var(--accent-text); }
.pe-edit-save:hover{ background:var(--brand-coral-soft); }
.pe-edit-save:disabled{ opacity:.55; cursor:default; }
.pe-edit-cancel{ border:none; background:none; border-radius:6px; padding:5px 7px; cursor:pointer;
  font-family:inherit; font-size:12.5px; font-weight:500; color:var(--text-3); }
.pe-edit-cancel:hover{ background:var(--hover); color:var(--text); }
.pe-edit-cost{ margin-left:auto; font:500 10.5px/1 var(--mono-stack,ui-monospace,SFMono-Regular,Menlo,monospace);
  color:var(--text-3); font-variant-numeric:tabular-nums; }

/* Take count + menu. A row with more than one take carries a tiny "2/3" on the
   more-button, and the menu lists them so the user can go BACK to a take. The
   menu is anchored to its own row (position:relative there) and never overlaps
   the row's own text. */
.pe-take-badge{ font:600 9.5px/1 var(--mono-stack,ui-monospace,SFMono-Regular,Menlo,monospace);
  font-variant-numeric:tabular-nums; color:var(--text-3); margin-left:1px; }
.pe-row.has-menu{ z-index:3; }
.pe-menu{ position:absolute; right:10px; top:calc(100% - 4px); z-index:4; min-width:190px;
  padding:5px; border:1px solid var(--border-2); border-radius:10px; background:var(--surface);
  box-shadow:var(--e-2); }
.pe-menu-item{ display:flex; align-items:center; gap:10px; width:100%; padding:7px 9px;
  border:none; border-radius:7px; background:none; cursor:pointer; text-align:left;
  font-family:inherit; font-size:12.5px; font-weight:500; color:var(--text-2); }
.pe-menu-item:hover{ background:var(--hover); color:var(--text); }
.pe-menu-item.is-on{ background:var(--brand-coral-soft); color:var(--text); font-weight:650; }
.pe-menu-meta{ margin-left:auto; font-size:11px; color:var(--text-3); font-variant-numeric:tabular-nums; }
/* Rows in the lower half open their menu upward, or the last row's menu would be
   drawn off the bottom of the editor. */
.pe-row.has-menu.is-low .pe-menu{ top:auto; bottom:calc(100% - 4px); }

/* Player. Sticky so a 40-row list does not put the result out of reach. */
.pe-foot{ position:sticky; bottom:-1px; z-index:2;
  display:flex; align-items:center; gap:12px; margin-top:10px;
  padding:11px 14px; border:1px solid var(--border-2); border-radius:12px;
  background:var(--surface); box-shadow:0 -6px 18px rgba(17,24,39,.05); }
.pe-play{ width:30px; height:30px; flex:none; border:none; border-radius:999px; cursor:pointer;
  background:var(--black,#000); color:#fff; display:flex; align-items:center; justify-content:center; }
.pe-play:hover{ filter:brightness(1.15); }
.pe-track{ flex:1; min-width:0; height:22px; position:relative; border:none; background:none;
  padding:0; cursor:pointer; display:flex; align-items:center; }
.pe-track::before{ content:""; position:absolute; left:0; right:0; height:2px; border-radius:999px;
  background:var(--border-2); }
.pe-track > i{ position:absolute; left:0; height:2px; border-radius:999px; width:0;
  background:linear-gradient(90deg,#ffc9c5,var(--brand-coral)); }
.pe-ticks{ position:absolute; inset:0; }
.pe-ticks s{ position:absolute; top:7px; width:1px; height:7px; background:var(--border-strong); opacity:.6; }
.pe-tm{ flex:none; font:500 10.5px/1 var(--mono-stack,ui-monospace,SFMono-Regular,Menlo,monospace);
  color:var(--text-3); letter-spacing:.02em; font-variant-numeric:tabular-nums; }

/* While the panel is open the composer steps aside.
   THREE surfaces have to go, and each for its own reason:
     · the Generate row, because a Generate button next to a segment list implies
       it re-renders the segments (it does not — it renders the composer's text);
     · the app playbar, because the panel has its own player for the SAME merged
       file. Two players showing one clip is the exact confusion this screen was
       meant to remove, and the panel's player is the one that knows the segment
       ticks and the merge state;
     · the settings rail, because changing the voice mid-session cannot apply to
       audio that was rendered with another voice — the honest thing is to make
       the rail unavailable here rather than let it lie.
   The composer's text is untouched: closing the panel brings all three back. */
body.pe-open .el-top,
body.pe-open .workspace-bar,
body.pe-open .guidance,
body.pe-open .speakers-toolbar,
body.pe-open .pe-entry,
body.pe-open .editor-footer,
body.pe-open .settings{ display:none !important; }
/* One column instead of editor+rail, so the freed width is actually used. */
body.pe-open .main{ grid-template-columns:1fr; }

@media (max-width:860px){
  .pe-txt{ white-space:normal; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; }
  .pe-acts > *{ visibility:visible; }
  .pe-row{ align-items:flex-start; }
  .pe-row .pe-n{ margin-top:2px; }
  .pe-top{ flex-wrap:wrap; row-gap:8px; }
  .pe-meta{ order:3; margin-left:0; }
}

/* ==========================================================================
   Subtitles→Speech — QUIET SURFACE
   One flat surface: no gradient wash, no dot texture, no card-in-card. The
   screen reads as a single sheet with hairline separators, one accent (the
   near-black primary action) and typography doing the hierarchy. Scoped to
   body.mode-subtitles so no other screen changes.
   ========================================================================== */
body.mode-subtitles .layout,
body.mode-subtitles .main,
body.mode-subtitles .editor,
body.mode-subtitles .editor-scroll,
body.mode-subtitles #srtPanel{ background: var(--surface); background-image: none; }
body.mode-subtitles .main{ border:0; box-shadow:none; border-radius:0; }
body.mode-subtitles #srtPanel{ padding-top:6px; }

/* Step numbers: a quiet figure, not a coloured disc. */
body.mode-subtitles .srt-step-n{ width:auto; min-width:12px; height:auto; border:0;
  background:none; color:var(--text-3); font-size:11px; font-weight:700; letter-spacing:.06em; }
body.mode-subtitles .srt-step{ gap:10px; margin-top:16px; }
body.mode-subtitles .srt-step-t{ margin-left:2px; }

/* Disclosure rows are lines in the sheet, not cards. */
body.mode-subtitles .srt-adv,
body.mode-subtitles .srt-adv[open]{ border:0; border-radius:0; background:transparent;
  border-top:1px solid var(--border); margin-top:0; }
body.mode-subtitles .srt-adv-sum{ padding:15px 0; font-weight:600; }
body.mode-subtitles .srt-adv-body{ padding:0 0 18px; }

/* Secondary controls read as text; only hover gives them a surface. */
body.mode-subtitles .srt-voice-chip,
body.mode-subtitles .srt-aud-chip{ border-color:transparent; background:transparent;
  padding-left:0; padding-right:6px; border-radius:8px; }
body.mode-subtitles .srt-voice-chip:hover,
body.mode-subtitles .srt-aud-chip:hover{ background:var(--surface-hover); border-color:transparent; }
body.mode-subtitles .srt-aud-more{ color:var(--text-3); border-color:transparent; }
body.mode-subtitles .srt-restart{ border:0; background:transparent; color:var(--text-3); padding:0 4px; }
body.mode-subtitles .srt-restart:hover{ background:transparent; color:var(--text); }
body.mode-subtitles .srt-mini-btn{ border-color:transparent; background:transparent; color:var(--text-2); padding-left:6px; }
body.mode-subtitles .srt-mini-btn:hover{ border-color:transparent; background:var(--surface-hover); color:var(--text); }
body.mode-subtitles .srt-chips{ margin:10px 0 0; }
body.mode-subtitles .srt-chips .srt-chip{ border:0; background:transparent; padding:0; color:var(--text-3); }

/* The result is one line with a check, not a tinted card. */
body.mode-subtitles .srt-result{ background:transparent; border:0; border-top:1px solid var(--border);
  border-radius:0; padding:13px 0 0; margin:0 0 4px; }
body.mode-subtitles .srt-result.is-partial{ background:transparent; }

/* One accent: a quiet near-black primary action (the gradient pill was the
   loudest thing on the page and read like a progress bar). */
body.mode-subtitles .tr-btn-primary{ background:var(--text); color:var(--surface);
  box-shadow:none; filter:none; text-shadow:none; border-radius:10px; height:44px;
  font-weight:600; letter-spacing:0; }
body.mode-subtitles .tr-btn-primary::before,
body.mode-subtitles .tr-btn-primary::after{ display:none; }
body.mode-subtitles .tr-btn-primary:hover:not(:disabled){ transform:none; filter:none;
  box-shadow:none; opacity:.9; }
body.mode-subtitles .tr-btn-primary:disabled{ background:var(--text); color:var(--surface); opacity:.45; }

/* Upload affordance: keep one dashed drop zone, soften its edge. */
body.mode-subtitles .srt-drop{ border-color:var(--border-2); background:transparent; }
body.mode-subtitles .srt-drop:hover{ border-color:var(--text-3); background:transparent; }
body.mode-subtitles .srt-drop.drag{ background:var(--surface-hover); border-color:var(--text-3); }
/* The inner sheet was a card (border + radius + big shadow) inside a white page:
   that double edge is what read as "many blocks". Flatten it into the page. */
/* One flat sheet: no card chrome, and a reading measure narrow enough that the
   primary action does not stretch the full window. */
#srtPanel .srt-panel,
body.mode-subtitles .srt-panel{ background:transparent; border:0; border-radius:0;
  box-shadow:none; padding:0; width:min(100%,800px); margin:0; }

/* Align the quiet step number with its micro label. */
body.mode-subtitles .srt-step{ align-items:flex-start; }
body.mode-subtitles .srt-step-n{ align-self:flex-start; margin-top:1px; }

/* The one remaining coloured control (the toggle) goes monochrome too. */
body.mode-subtitles .tr-toggle input:checked{ background:var(--text); }
body.mode-subtitles .tr-toggle input:focus-visible{ outline-color:var(--text-3); }
body.mode-subtitles .tr-toggle{ color:var(--text-2); }

/* Utility links (export, change file) are quiet text. */
body.mode-subtitles .srt-inline-link{ color:var(--text-3); }
body.mode-subtitles .srt-inline-link:hover{ color:var(--text); }

/* Upload state: same restraint (one dashed zone, neutral icon + action). */
body.mode-subtitles .tr-pick-cta{ background:var(--text); color:var(--surface); border-radius:10px;
  box-shadow:none; }
body.mode-subtitles .tr-pick-cta:hover{ background:var(--text); opacity:.92; }
body.mode-subtitles .tr-drop-ico{ color:var(--text-3); background:var(--surface-hover); }
body.mode-subtitles .srt-drop .tr-facts .tr-fact{ color:var(--text-3); }

/* Upload state, second pass: the dashed zone stays (it is the affordance) but
   loses its card fill, coral hover and coral icon; the two secondary links stop
   competing with the primary action. */
body.mode-subtitles .srt-upload-group{ background:transparent; border-color:var(--border-2); }
body.mode-subtitles .srt-upload-group:hover{ border-color:var(--text-3); }
#srtPanel #srtUpload .srt-upload-group:hover{ border-color:var(--text-3); }
body.mode-subtitles #srtPanel #srtUpload .srt-upload-group .srt-drop .tr-drop-ico{
  background:var(--surface-hover); color:var(--text-3); }
body.mode-subtitles .srt-link-btn{ color:var(--text-3); text-decoration-color:var(--border-2); }
body.mode-subtitles .srt-link-btn:hover{ color:var(--text); }
body.mode-subtitles .srt-link-btn:focus-visible{ outline-color:var(--text-3); }
body.mode-subtitles .srt-upload-group .tr-pick-cta:hover{ box-shadow:none; filter:none; }

/* ---------------------------------------------------------------------------
   Dub / Voice change (studio/translate/*) — QUIET SURFACE
   Same treatment as the subtitles screen: one flat sheet, no ornamental
   gradient, one accent (a near-black primary), and the explanatory sentences
   that restated the controls are gone. Every hidden line is duplicated on
   screen — by the drop zone, the field label, or the button — so nothing is
   lost for a screen reader (the copy stays in the DOM) or for a returning user.
--------------------------------------------------------------------------- */
body.mode-translate .layout{ background:var(--surface); }
body.mode-translate .main{
  background:var(--surface); border:0; box-shadow:none; border-radius:0;
}
body.mode-translate .editor,
body.mode-translate .editor-scroll,
body.mode-translate #speakers{ background:var(--surface); background-image:none; }
/* The panel overlaps the voice row above it (negative margin), so it must stay
   transparent: an opaque fill here hides the voice chip's lower half. */
body.mode-translate #translatePanel{ background:transparent; background-image:none; }

/* No card-in-card: the upload zone keeps its dashed edge, loses the fill. */
body.mode-translate .tr-upload-group{ background:transparent; border-color:var(--border-2); }
body.mode-translate .tr-upload-group:hover{ border-color:var(--text-3); }
body.mode-translate #translatePanel .tr-drop{ background:transparent; }

/* Text that repeats a control that is already on screen. */
body.mode-translate #translatePanel .tr-subtitle,
body.mode-translate #translatePanel .tr-drop-desc,
body.mode-translate #translatePanel .tr-longfile-hint,
body.mode-translate #translatePanel .tr-setup-kicker,
body.mode-translate #translatePanel .tr-setup-subtitle,
body.mode-translate #translatePanel .tr-setup-cta-hint,
body.mode-translate #dubOnboard{ display:none !important; }

/* One accent: the primary action is near-black, the secondary is an outline. */
body.mode-translate .tr-pick-cta{
  background:var(--text); color:var(--surface); border-radius:10px; box-shadow:none;
  font-weight:600;
}
body.mode-translate .tr-pick-cta:hover{ filter:none; opacity:.92; box-shadow:none; }
body.mode-translate .tr-drop:hover .tr-pick-cta{ filter:none; box-shadow:none; }
/* Specificity has to beat the coral tint on the dub uploader icon. */
body.mode-translate .tr-drop-ico,
body.mode-translate #dubPhase1 .tr-upload-group .tr-drop-ico,
body.mode-translate #dubPhase1:not(.has-file) .tr-upload-group .tr-drop-ico{
  background:var(--surface-hover); color:var(--text-3); border-radius:14px;
}
body.mode-translate .tr-btn-primary{
  background:var(--text); color:var(--surface); box-shadow:none; filter:none;
  text-shadow:none; border-radius:10px; font-weight:600; letter-spacing:0;
}
body.mode-translate .tr-btn-primary::before,
body.mode-translate .tr-btn-primary::after{ display:none; }
body.mode-translate .tr-btn-primary:hover:not(:disabled){ transform:none; filter:none;
  box-shadow:none; opacity:.9; }
body.mode-translate .tr-btn-primary:disabled{ background:var(--text); color:var(--surface);
  opacity:.45; }
body.mode-translate .tr-btn-review{
  background:transparent; color:var(--text); border:1px solid var(--border-2);
  box-shadow:none; border-radius:10px; font-weight:600;
}
body.mode-translate .tr-btn-review:hover{ background:var(--surface-hover); border-color:var(--text-3); }

/* Selected target language: a selected chip, not a red badge. */
body.mode-translate #translatePanel .tr-chip.on{
  color:var(--text); border-color:var(--text-3); background:var(--surface-hover);
  box-shadow:none;
}
/* Disclosure rows read as lines in the sheet, like the subtitles screen. */
body.mode-translate #translatePanel .tr-adv{ border:0; background:transparent;
  border-top:1px solid var(--border-2); border-radius:0; }
body.mode-translate #translatePanel .tr-adv-sum{ padding:12px 0; color:var(--text-2); }

/* The setup dialog is the same sheet, so its chrome follows. */
body.mode-translate #dubPhase1.has-file{ box-shadow:none; border-color:var(--border-2); }

/* A lone voice chip with no label, on a screen where the voice is cloned from
   the source, reads as a setting the user must understand. It comes back in the
   setup dialog and the review, where it mirrors the voice actually in use. */
body.mode-translate:not(.dub-setup-open):not(.dub-review-open) #workspaceBar{
  display:none;
}

/* The setup dialog moves focus to its heading for screen readers; the browser's
   default focus ring then draws a blue box around a title that cannot be
   activated. Same treatment the quota dialog's heading already gets. */
body.mode-translate #dubSetupTitle{ outline:none; }
body.mode-translate #dubSetupTitle:focus{ outline:none; }

/* The setup dialog opened with ~140px of empty air above its own title (the
   space the removed kicker + subtitle used to fill). */
body.mode-translate #dubPhase1.has-file .tr-setup-grid{ padding-top:32px; }
/* The preview already sits next to the file-name field; repeating the name under
   the player adds a third copy of it. */
body.mode-translate #dubPhase1.has-file .tr-setup-preview .tr-file{ display:none !important; }

/* --- Dub audit 2026-11-08: fixes found by walking the flow ------------------ */

/* No duration, no badge. A non-media file (before the type check landed) left an
   empty dark pill in the preview corner that read as a broken control. */
body.mode-translate #dubPreviewDuration:empty{ display:none; }

/* A switch next to a long label must never sit ON the text: the label is the
   flexible side, the switch keeps its size. "Giữ nhạc nền & âm thanh nền" was
   losing its last character under the knob. */
body.mode-translate .tr-toggle{ gap:12px; }
body.mode-translate .tr-toggle > span{ flex:1 1 auto; min-width:0; }

/* Same monochrome switch as the subtitles screen. */
body.mode-translate .tr-toggle input:checked{ background:var(--text); }
body.mode-translate .tr-toggle input:focus-visible{ outline-color:var(--text-3); }

/* The footer carried the metric sentence AND the computed cost on one line, which
   wrapped under the buttons. The number is the information; the sentence is not. */
body.mode-translate #dubCreditsCalc{ display:none; }
body.mode-translate .tr-setup-usage{ color:var(--text-3); font-size:12px; }

/* --- Subtitles review: the lines pane uses the height, not just the width ---
   On a 950px-tall window the review used to stop ~270px above the bottom edge:
   the line list was capped at 460px, so a 28-line file showed three lines and a
   large empty band. The list now takes the rest of the viewport and scrolls
   inside itself (a height on the list, not a flex chain inside <details>: with
   height:max-content the flex children keep their content size and overflow,
   which silently hid every line below the fourth). */
@media (min-width:1100px){
  body.mode-subtitles #srtReviewBox{
    display:block; max-height:none; overflow:visible; height:auto;
  }
  body.mode-subtitles #srtReviewBox #srtList{
    max-height:calc(100dvh - 315px);
    overflow-y:auto;
  }
  /* The option blocks stay directly under the action they belong to; the empty
     space below them is where the eye expects the column to end. */
  body.mode-subtitles #srtPanel .srt-adv-advanced{ margin-top:0; }
}


/* "Play the N finished lines" — only exists while a batch is running. */
body.mode-subtitles .srt-listen-ready{
  display:inline-flex; align-items:center; gap:7px; margin-top:10px;
  height:30px; padding:0 12px 0 10px; border:1px solid var(--border-2);
  border-radius:8px; background:var(--surface); color:var(--text-2);
  font-family:inherit; font-size:12px; font-weight:600; cursor:pointer;
}
body.mode-subtitles .srt-listen-ready:hover{ border-color:var(--text-3); color:var(--text); }
body.mode-subtitles .srt-listen-ready.is-playing{ color:var(--text); border-color:var(--text-3); }
body.mode-subtitles .srt-listen-ready svg{ flex:none; }
body.mode-subtitles .srt-listen-ready[hidden]{ display:none; }

/* The progress sub-line changes length every second ("Xong 3/20 dòng · còn
   0:12"). Without a floor the "play the finished lines" button under it moved
   between samples, which makes it a moving target to click. */
body.mode-subtitles .srt-progress-sub{ min-height:18px; }

/* ==========================================================================
   Dub workspace — full-bleed, monochrome (2026-11-08)
   The translate/video screen is a WORKSPACE, not a page with a card on it:
     • the shell loses its gutters, so the canvas is the whole content area;
     • the upload step fills the height instead of floating in the middle of a
       1226px whitespace field;
     • setup and review stop being centred 1020×650 dialogs with a scrim and
       become full-window work surfaces (inset:0): a title row with one close
       button, the work, and an action bar on the bottom edge;
     • one colour: ink. Every accent role this screen reads (--accent-text, the
       --brand-coral family, the soft tint families, --tr-*) resolves to --text /
       --surface / --surface-hover, so the same rules are correct in light and
       dark — dark simply flips ink to near-white.
   Scope is body:is(.mode-translate,.mode-changer) for a reason: Voice Changer renders into the
   same #translatePanel and keeps its own layout.
   ========================================================================== */

/* ---- 1. Shell: the workspace owns the content area ---------------------- */
body:is(.mode-translate,.mode-changer) main{ padding:0; }
body:is(.mode-translate,.mode-changer) .editor{ overflow:hidden; }
body:is(.mode-translate,.mode-changer) .editor-scroll{
  display:flex; padding:0; overflow:hidden; scrollbar-gutter:auto;
  padding-bottom:0;
}
body:is(.mode-translate,.mode-changer) #speakers.editor-inner{
  display:flex; flex-direction:column; flex:1; min-height:0;
  width:100%; max-width:none; margin:0;
}
body:is(.mode-translate,.mode-changer) #translatePanel{
  flex:1; display:flex; flex-direction:column; min-height:0; min-width:0;
  height:100%; margin:0; padding:0;
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr{
  flex:1; display:flex; flex-direction:column; min-height:0;
  height:100%; margin:0; padding:0; border:0; border-radius:0; background:transparent;
}
body:is(.mode-translate,.mode-changer) #translatePanel .ch-stack{
  display:flex; flex-direction:column; flex:1; min-height:0;
}
body:is(.mode-translate,.mode-changer) #dubPhase1{ flex:1; min-height:0; }

/* ---- 2. One colour: ink ------------------------------------------------ */
/* The .tr-* primitives were written against a coral/indigo palette. Rather than
   rewriting ~90 rules, the palette those rules consume is remapped here: every
   accent role points at ink, every soft tint at a neutral hover grey, and every
   informational ink at body text. Anything with a LITERAL colour is fixed
   individually in §6. */
body:is(.mode-translate,.mode-changer) #translatePanel{
  /* NOTE: --accent itself is deliberately NOT redefined — tests/test_design_tokens
     and tests/test_theme_ratchet forbid styles.css from declaring an accent/text/
     surface/border token, and nothing in this panel reads --accent directly (the
     coral CTAs use --brand-coral*, the links use --accent-text). */
  --accent-text:var(--text);
  --accent-soft:var(--surface-hover);
  --brand-coral:var(--text); --brand-coral-deep:var(--text);
  --brand-coral-soft:var(--surface-hover);
  --coral-tint:var(--surface-hover); --coral-tint-2:var(--surface-hover);
  --info-soft:var(--surface-hover); --info-ink:var(--text); --info-border:var(--border-2);
  --violet-soft:var(--surface-hover); --violet-ink:var(--text); --violet-border:var(--border-2);
  --warn-soft:var(--surface-hover); --warn-tint:var(--surface-hover); --warn-ink:var(--text-2);
  --success-tint:var(--surface-hover);
  --danger-soft:var(--surface-hover);
  --tr-accent:var(--text); --tr-accent-strong:var(--text); --tr-accent-soft:var(--surface-hover);
  --tr-surface:var(--surface); --tr-surface-muted:var(--canvas);
}
/* `.tr` re-declares the same --tr-* palette (coral), so a remap on the panel alone
   was shadowed for everything inside it: sliders, focus rings and file icons kept
   the coral accent. State it where it is declared. */
body:is(.mode-translate,.mode-changer) #translatePanel .tr{
  --tr-accent:var(--text); --tr-accent-strong:var(--text); --tr-accent-soft:var(--surface-hover);
  /* --tr-surface was shadowed the same way (`.tr` pins #ffffff), which kept the
     A/B card white in the dark theme and its time label below AA contrast. */
  --tr-surface:var(--surface); --tr-surface-muted:var(--canvas);
}
/* Plain <a> in this panel (the consent-policy link) fell back to the UA colour:
   #0000ee on light, #9e9eff on dark. Ink, underlined, like every other link here. */
body:is(.mode-translate,.mode-changer) #translatePanel a:not([class]){
  color:var(--text); text-decoration:underline; text-underline-offset:2px;
  text-decoration-color:var(--border-strong);
}
body:is(.mode-translate,.mode-changer) #translatePanel a:not([class]):hover{
  text-decoration-color:currentColor;
}
/* Ornament that survived the last pass: the cyan corner wash behind both
   dialogs and the blurred scrim behind them. A workspace needs neither. */
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file::before,
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2::before,
body:is(.mode-translate,.mode-changer).dub-setup-open::before,
body:is(.mode-translate,.mode-changer).dub-review-open::before{ display:none; }
body:is(.mode-translate,.mode-changer) #translatePanel,
body:is(.mode-translate,.mode-changer) #translatePanel .tr{ background-image:none; }

/* ---- 3. Upload step: full-height canvas + history rail ----------------- */
body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file){
  display:grid; grid-template-columns:minmax(0,1fr);
  grid-template-rows:minmax(0,1fr);
  height:100%; min-height:0;
}
/* Only pay for the rail when there is history to put in it (app.js keeps the
   section hidden until a row exists). */
body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file):has(> .tr-recent:not([hidden])){
  grid-template-columns:minmax(0,1fr) clamp(268px,24vw,348px);
}
/* The panel header ("Translate any video file") sat under the topbar's own h1
   ("Video Dubbing") and repeated the drop card. One title per screen: the
   instruction lives INSIDE the drop zone, where the eye already is. */
body:is(.mode-translate,.mode-changer) #dubPhase1 > .tr-header{ display:none !important; }
/* The Voice Changer re-showed the 4-step map (its own rule beats the older
   `display:none`). The dubbing screen has no stepper and the form already reads
   Upload → voice → consent → button, so both screens now share one header-less
   look. */
body:is(.mode-translate,.mode-changer) #translatePanel .tr-steps{ display:none !important; }
body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) > .tr-upload-stage{
  grid-column:1; grid-row:1; min-height:0; display:flex;
  padding:clamp(16px,2.4vh,26px) clamp(18px,3vw,40px);
}
body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) .tr-upload-group{
  flex:1; display:flex; width:auto; padding:0; border:0; background:transparent;
}
body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) .tr-drop{
  flex:1; width:100%; max-width:none; height:100%; min-height:0;
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:18px;
  padding:40px 24px; border:1px dashed var(--border-strong); border-radius:16px;
  background:var(--surface); box-shadow:none; transition:border-color .15s, background .15s;
}
body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) .tr-drop:hover,
body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) .tr-drop.drag{
  border-color:var(--text); background:var(--surface-hover);
  transform:none; box-shadow:none;
}
/* With the page header gone this is the screen's only title, so it carries the
   drop zone's centre: one instruction, one button, one meta line. */
body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) .tr-drop-title{
  display:block; font-size:clamp(16px,1.35vw,19px); font-weight:600;
  letter-spacing:-.015em; color:var(--text); line-height:1.3;
}
body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) .tr-drop-ico{
  width:56px; height:56px; margin:0; border-radius:16px;
  background:var(--canvas); color:var(--text-3);
}
body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) .tr-drop:hover .tr-drop-ico{ transform:none; }
body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) .tr-pick-cta{
  gap:7px; min-height:44px; padding:0 22px; border-radius:10px;
  background:var(--text); color:var(--surface);
  font-size:14px; font-weight:600; letter-spacing:0;
}
body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) .tr-facts{
  display:flex; flex-wrap:wrap; justify-content:center; align-items:center; gap:0;
  margin:0; max-width:560px;
}
body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) .tr-fact{
  display:inline-flex; align-items:center; gap:6px;
  padding:0; border:0; border-radius:0; background:transparent;
  color:var(--text-3); font-size:12px; line-height:1.5;
}
body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) .tr-fact + .tr-fact::before{
  content:"·"; margin-right:10px; color:var(--border-strong);
}
body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) .tr-fact > svg{ color:var(--text-3); opacity:.75; }
body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) #dubStatus{ flex:none; margin:10px 0 0; }

/* History rail */
body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) > .tr-recent:not([hidden]){
  grid-column:2; grid-row:1; width:auto; max-width:none; margin:0;
  min-height:0; overflow-y:auto;
  padding:clamp(18px,2.6vh,30px) clamp(16px,1.6vw,22px) 24px 22px;
  border-left:1px solid var(--border-2);
}
body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) > .tr-recent[hidden]{ display:none; }
body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) .tr-recent-title{
  margin:0 0 12px; font-size:13px; font-weight:600; letter-spacing:0; color:var(--text-2);
}
body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) .tr-recent-head{ display:none; }
/* Rail rows stack: name on top, language + when underneath. The old 3-column
   table (name | languages | date) cannot hold three columns in 300px, so the
   language chip and the timestamp used to land on top of each other. */
body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) .tr-recent-row{
  grid-template-columns:minmax(0,1fr) auto; grid-template-rows:auto auto;
  row-gap:5px; min-height:0; padding:10px 8px;
  border-bottom:1px solid var(--border-2); border-radius:8px;
}
body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) .tr-recent-name{ grid-column:1 / -1; grid-row:1; }
body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) .tr-recent-row time{
  grid-column:2; grid-row:2; justify-self:end; white-space:nowrap; font-size:12px;
}
body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) .tr-recent-langs{
  grid-column:1; grid-row:2; padding-left:0; font-size:12px;
}
body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) .tr-recent-name{ font-size:13px; }
body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) .tr-recent-thumb{ width:32px; height:24px; border-radius:6px; }
body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) .tr-recent-empty{ padding:14px 8px; font-size:12px; }

/* ---- 4. Clicked-file workspace (was a 1020×650 centred dialog) ---------- */
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file{
  position:fixed; inset:0; z-index:90; transform:none;
  width:auto; height:auto; max-height:none; margin:0;
  display:flex; flex-direction:column; overflow:hidden;
  border:0; border-radius:0; background:var(--surface); box-shadow:none;
  animation:dw-fade .16s ease-out both;
}
@keyframes dw-fade{ from{ opacity:0; } to{ opacity:1; } }
/* Form left and narrow, picture right and large: the preview is the thing the
   user is deciding about, and a 460px form column stops the settings from
   stretching into a 900px-wide strip of one-field-per-row. */
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-setup-grid{
  /* display:grid explicitly: the Voice Changer's own block turns this into a
     plain block (its old single-column design), which silently dropped the
     2-column layout on that screen. */
  display:grid;
  flex:1; min-height:0; overflow-y:auto; overflow-x:hidden;
  grid-template-columns:minmax(300px,460px) minmax(0,1fr);
  gap:clamp(28px,4vw,64px); align-items:start;
  /* align-content:start — a grid stretches its auto rows to fill the box, which
     on a phone put 130px of air between the picture and the first field. */
  align-content:start;
  /* Top padding is the close button's row: nothing may sit under it. */
  padding:60px clamp(18px,3vw,48px) 28px;
}
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-setup-copy{ margin:0 0 24px; }
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-setup-title{
  margin:0; font-size:clamp(20px,1.7vw,24px); line-height:1.2; font-weight:700; letter-spacing:-.025em;
}
/* FIXED, not absolute: the close button lives inside .tr-setup-preview, and a
   sticky ancestor already makes that a containing block — absolute pinned it to
   the top of the PICTURE (y=72) instead of the top of the screen, right on top of
   the duration badge. */
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-setup-close{
  position:fixed; top:12px; right:clamp(14px,2.4vw,40px);
  z-index:4; width:38px; height:38px; border:0; border-radius:10px;
  display:inline-flex; align-items:center; justify-content:center;
  background:transparent; color:var(--text-2);
}
/* Status/error for the selected file: it is a line of the workspace, not an
   absolute chip floating over the title (left:56 right:56 top:18 put it exactly
   where the close button now is). */
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file > #dubStatus{
  position:static; top:auto; left:auto; right:auto; z-index:auto;
  flex:none; height:auto; min-height:42px; margin:12px calc(clamp(18px,3vw,48px) + 50px) 0 clamp(18px,3vw,48px);
  padding:10px 12px; border-radius:10px;
}
/* One colour means state has to come from weight and an edge, not from red.
   The bar keeps the same shape for all four states; only its ink changes. */
body:is(.mode-translate,.mode-changer) #translatePanel .clone-status{
  border:1px solid var(--border-2); background:var(--surface-hover); color:var(--text-2);
  font-size:13px; font-weight:600;
}
body:is(.mode-translate,.mode-changer) #translatePanel .clone-status.err,
body:is(.mode-translate,.mode-changer) #translatePanel .clone-status.warn{
  background:var(--surface); color:var(--text); border-color:var(--text-2);
  box-shadow:inset 3px 0 0 var(--text);
}
body:is(.mode-translate,.mode-changer) #translatePanel .clone-status.ok,
body:is(.mode-translate,.mode-changer) #translatePanel .clone-status.busy,
body:is(.mode-translate,.mode-changer) #translatePanel .clone-status.info{
  background:var(--surface-hover); color:var(--text-2);
}
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-setup-close:hover{
  background:var(--surface-hover); color:var(--text);
}
/* Form controls: flat, one hairline, no fill-in-fill. */
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-block{ margin:0 0 18px; }
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-blk-label{
  margin:0 0 8px; color:var(--text-2); font-size:12px; font-weight:600;
  letter-spacing:0; text-transform:none;
}
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-setup-input,
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-select{
  height:42px; border:1px solid var(--border-2); border-radius:10px;
  background:var(--surface); color:var(--text); font-size:14px;
}
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-setup-input:hover,
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-select:hover{ background:var(--surface); }
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-setup-input:focus,
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-select:focus{
  border-color:var(--text); background:var(--surface);
  box-shadow:0 0 0 3px var(--surface-hover);
}
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-target-picker{
  border:1px solid var(--border-2); border-radius:10px; background:var(--surface);
}
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-target-picker > summary{
  min-height:42px; padding:0 12px; font-size:14px; font-weight:500;
}
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-target-picker[open] > summary{
  border-bottom:1px solid var(--border-2);
}
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file #dubTargets{
  max-height:210px; padding:10px; background:var(--surface); border:0; border-radius:0 0 10px 10px;
}
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-mix-hint{ margin-top:6px; color:var(--text-3); font-size:12px; }
/* Advanced = a line of text, not a boxed panel. */
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-adv{ border:0; border-top:1px solid var(--border-2); }
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-adv > .tr-adv-sum{
  width:100%; padding:14px 0; font-size:13px; color:var(--text-2);
}
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-adv[open] > .tr-adv-body{
  max-height:none; margin:0 0 8px; padding:0 0 4px; border:0; background:transparent;
}
/* Preview: the picture is the anchor of the screen, so it keeps a hard edge. */
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-setup-preview{
  position:sticky; top:0;
  /* The changer used to centre this at 520px inside its single column; in the
     2-column workspace it owns the right column, like the dubbing screen.
     margin:0 (not auto): auto margins make a grid item shrink to fit, and the
     frame inside is width:100% — the pair resolved to a 0-wide column. */
  width:auto; max-width:none; margin:0;
}
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-setup-preview-frame{
  padding:0; border:1px solid var(--border-2); border-radius:14px; overflow:hidden;
  background:var(--black,#0b0b0c); box-shadow:none;
}
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-setup-preview-frame .tr-input-video{
  border-radius:0; background:var(--black,#0b0b0c);
}
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-setup-audio-placeholder{
  inset:0; border-radius:0; color:var(--text-3);
  background:var(--canvas);
}
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-preview-badge{
  border-radius:6px; background:rgba(10,10,10,.72); color:#fff; font-weight:600;
}
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-setup-preview .tr-file{ display:none !important; }
/* Action bar: hairline on top, ink button on the right. */
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-setup-actions{
  position:relative !important; z-index:3; flex:none;
  display:flex; align-items:center; justify-content:flex-start; gap:12px;
  min-height:70px; margin:0 !important;
  padding:12px clamp(18px,3vw,48px) !important;
  border-top:1px solid var(--border-2);
  background:var(--surface) !important; backdrop-filter:none;
}
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-setup-usage{
  flex:1 1 auto; min-width:0; color:var(--text-3); font-size:12px;
}
/* The sentence next to this dot was already removed and the computed cost is
   text, so the dot was a checkmark in front of nothing. */
body:is(.mode-translate,.mode-changer) #translatePanel .tr-credit-dot{ display:none; }
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-setup-cta{
  flex:0 0 auto; margin-left:0; display:flex; align-items:center; gap:10px; order:2;
}
/* Voice Changer's own two extras, same bar: "Preview 10 seconds" is a secondary
   action to the LEFT of the pair, and the "why the button is disabled" sentence
   gets its own line above them instead of floating beside the credits. */
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file #dubPreviewBtn{
  order:1; flex:0 0 auto; min-width:0; height:44px; min-height:44px; padding:0 16px;
  border:1px solid var(--border-2); border-radius:10px; background:var(--surface);
  color:var(--text); font-size:14px; font-weight:600;
}
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file #dubPreviewBtn:hover:not(:disabled){
  border-color:var(--text); background:var(--surface-hover);
}
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file #dubWhyBtn{
  order:-1; flex:1 1 100%; margin:0; text-align:right; color:var(--text-2); font-size:12px;
}
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .ch-preview-note{
  order:-1; flex:1 1 100%; margin:0; text-align:right; color:var(--text-3); font-size:12px;
}
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-setup-cta .tr-btn{
  flex:0 0 auto; min-width:132px; height:44px; min-height:44px; padding:0 18px;
  border-radius:10px; font-size:14px; font-weight:600;
}
body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-setup-actions .tr-cancel{
  order:-1; height:40px; min-height:40px; border-radius:10px;
}

/* Voice Changer takes the same full-window shell as the dubbing screen, but it
   has no picture to show: its input poster was dropped on purpose (a 16:9 music
   note pushing the primary button off screen). So the workspace is ONE centred
   column of controls — the wide right column would be an empty rectangle. */
body.mode-changer #dubPhase1.has-file .tr-setup-grid{
  grid-template-columns:minmax(0,760px);
  justify-content:center;
  align-content:start;
  gap:0;
  padding:60px clamp(18px,3vw,48px) 28px;
}
body.mode-changer #dubPhase1.has-file .tr-setup-preview{
  margin:0 0 14px;
}

/* ---- 5. Review workspace (was a 1160×760 centred dialog) --------------- */
/* The shell itself (fixed, inset:0, 100dvh) is the "DUB REVIEW — full-screen
   workspace" layer earlier in this file. What is overridden here is its own
   colour ramp: the review was built on a second, private palette (--p2-*) of
   rose accents, slate inks and 16px radii. Remapping the ramp is what makes the
   step monochrome without rewriting its ~150 rules. */
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2{
  --p2-ink:var(--text); --p2-text:var(--text-2); --p2-muted:var(--text-3);
  --p2-faint:var(--text-3); --p2-line:var(--border-2); --p2-line-soft:var(--border-2);
  --p2-soft:var(--canvas); --p2-accent:var(--text); --p2-accent-2:var(--text);
  --p2-accent-soft:var(--surface-hover);
  --p2-shadow:none;
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 > .tr-phase2-head .tr-category{
  font-size:15px; font-weight:700; letter-spacing:-.01em; color:var(--text);
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-restart{
  position:absolute; top:15px; right:calc(clamp(16px,2.4vw,36px) + 46px);
  min-height:32px; padding:0 12px; border-radius:8px; font-size:12px; font-weight:600;
  border:1px solid var(--border-2); background:var(--surface); color:var(--text-2);
}
body:is(.mode-translate,.mode-changer).dub-review-open .tr-review-close{
  position:absolute; top:14px; right:clamp(16px,2.4vw,36px);
  width:36px; height:36px; border-radius:9px;
}
/* Copy that narrated the screen instead of labelling it. */
body:is(.mode-translate,.mode-changer).dub-review-open .tr-phase2-lede,
body:is(.mode-translate,.mode-changer).dub-review-open #dubPreviewVHint,
body:is(.mode-translate,.mode-changer).dub-review-open .tr-p2-vhint{ display:none; }
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-p2-grid{
  grid-template-columns:minmax(0,1fr) clamp(320px,38%,520px);
  gap:clamp(20px,2.6vw,36px); align-items:start; padding-bottom:8px;
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-p2-side{ position:sticky; top:24px; }
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-p2-main{ min-width:0; }
/* Cards inside the review are one sheet: separators, not boxes. */
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-p2-card,
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-working-version{
  border:1px solid var(--border-2); border-radius:12px; background:var(--surface);
  padding:14px 16px; margin:0 0 12px;
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-working-version.is-dirty,
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-working-version.is-synced,
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-working-version.is-working{
  border-color:var(--border-2); background:var(--surface);
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-working-badge{
  color:var(--text-2); background:var(--canvas); font-weight:600;
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-actions.tr-sticky{
  margin:16px 0 0 !important; padding:12px 0 16px !important;
  background:var(--surface) !important; box-shadow:0 -1px 0 var(--border-2);
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-actions.tr-sticky .tr-btn{
  min-width:168px; height:44px; min-height:44px; margin-left:auto;
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2{ scroll-padding-bottom:84px; }

/* ---- 5b. Review-step literals the --p2-* remap cannot reach ------------- */
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 #dubRenderBtn{
  background:var(--text) !important; color:var(--surface) !important;
  box-shadow:none !important; border-radius:10px !important; font-weight:600;
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 #dubRenderBtn:hover:not(:disabled){
  transform:none; box-shadow:none; opacity:.9;
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-dl-mini,
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-find-btn{
  color:var(--text); border:1px solid var(--border-2); background:var(--surface);
  border-radius:8px; font-weight:600;
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-dl-mini:hover,
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-find-btn:hover{
  border-color:var(--text); background:var(--surface-hover);
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-find-in:focus{
  border-color:var(--text); box-shadow:0 0 0 3px var(--surface-hover);
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-voice-summary,
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-working-version{
  background:var(--surface); background-image:none;
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-voice-opt.on,
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-voice-opt:hover{
  border-color:var(--text); background:var(--surface); box-shadow:inset 0 0 0 1px var(--text);
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-voice-opt.on .tr-voice-ico{
  background:var(--text); color:var(--surface);
}
/* Undo / "may not fit" / failed-language were green, coral and red respectively.
   In a one-colour surface the WORD carries the state; the tone is weight and
   inversion, not hue. */
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-seg-undo{
  color:var(--text); background:var(--surface-hover);
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-seg-btn-attention{
  color:var(--text); background:transparent; border-color:var(--text);
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-lang-tab.is-untranslated{
  background:var(--surface-hover); color:var(--text-2);
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-lang-tab.is-untranslated.on{
  background:var(--surface); color:var(--text); box-shadow:inset 0 0 0 1px var(--text);
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-result.is-current{
  border-color:var(--text); background:var(--surface);
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-result.is-current .tr-version-state{
  color:var(--surface); background:var(--text);
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-plan{
  border-color:var(--border-2); background:var(--canvas);
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-plan-text{ color:var(--text-2); }
/* Preview card: the picture is the payload, so the card loses its padding, its
   tint and its drop shadow. The BLACK belongs to the stage only — painting the
   card black put "Download video" and the dubbed/original switch on a dark
   background, where the secondary button was unreadable. */
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-p2-video{
  padding:0; border:0; border-radius:0;
  background:transparent; box-shadow:none; overflow:visible;
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-p2-stage{
  border-radius:10px; overflow:hidden; background:var(--black,#0b0b0c);
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-p2-player{ border-radius:0; }
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-preview-actions{ margin-top:12px; }
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-preview-actions .tr-btn{
  min-height:40px; border-radius:9px; font-size:13px; font-weight:600;
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-p2-audio{
  border:1px solid var(--border-2); background:var(--surface); box-shadow:none;
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-pv-switch-btn.on{
  color:var(--surface); background:var(--text); border-color:var(--text);
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-src-tab.on{
  color:var(--surface); background:var(--text);
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-vs-change:hover{
  border-color:var(--text); color:var(--text); background:var(--surface-hover);
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-seglist.is-virtual::-webkit-scrollbar-thumb{
  background:var(--border-strong); border-color:var(--surface);
}

/* Footer: one primary action, right-aligned, on its own bar. The keyboard tip
   underneath is a sentence nobody reads twice (the review is a click-through
   flow) and it was the lowest-contrast text on the screen. */
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-p2-main .tr-actions.tr-sticky .tr-btn{
  /* max-content + margin-left:auto, because .tr-btn is inline-flex: an inline
     box ignores an auto margin, so the button sat at the left edge of a bar it
     was supposed to end. */
  display:flex; width:max-content; min-width:176px; max-width:100%;
  height:44px; min-height:44px; margin-left:auto;
  border-radius:10px; font-size:14px; font-weight:600;
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-render-note,
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-keyboard-hint,
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-stale-hint[data-quiet]{ display:none; }
/* Long voice names must wrap instead of truncating to "Keep original voi…". */
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-vs-text{
  white-space:normal; overflow:visible; text-overflow:clip;
}

/* One convention for "this one is chosen": the INK block with the surface
   colour on it. A dark-theme rule in ds.css paints every one of these labels
   with --on-accent (#15161a) because it assumes a bright coral fill; once the
   fill is ink (near-white in dark) that label becomes light-on-light and the
   selected option disappears. The fill is therefore stated here too, with the
   label that belongs to it, for both themes. */
body:is(.mode-translate,.mode-changer) #translatePanel .tr-chip.on,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-sync-opt.on,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-lang-tab.on,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-src-tab.on,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-pv-switch-btn.on,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-pick-cta,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-btn-primary,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-dl-video,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-voice-opt.on .tr-voice-ico{
  background:var(--text) !important;
  background-image:none !important;
  color:var(--surface) !important;
  border-color:var(--text) !important;
  box-shadow:none !important;
  /* The global "chromatic CTA" layer paints a coral/lime SVG wash on ::before and
     a dark overlay on ::after, plus a drop-shadow and a text-shadow. The dubbing
     screen already hid it (quiet-surface pass); the Voice Changer did not, which
     is why its primary button was a coral bar with a green corner. */
  filter:none !important; text-shadow:none !important;
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-btn-primary::before,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-btn-primary::after{
  display:none !important;
}
/* The segmented TRACK behind those options stays a quiet grey so the ink block
   reads as a raised chip rather than a hole punched in the surface. */
body:is(.mode-translate,.mode-changer) #translatePanel .tr-sync-opts,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-pv-switch,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-src-tabs{
  background:var(--surface-hover); border-color:var(--border-2);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-sync-opt + .tr-sync-opt,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-pv-switch-btn + .tr-pv-switch-btn{
  border-left-color:var(--border-2);
}

/* Actions that belong to the picture (app.js builds them into
   #dubPreviewActions): "Download video" is a <a class="tr-dl tr-dl-video-main">
   and "Share" is a .tr-btn. They were two different weights — a coral-filled
   link next to a bare button — so state them as one pair. */
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-preview-actions .tr-dl{
  display:inline-flex; align-items:center; justify-content:center;
  min-height:40px; padding:0 16px; border:1px solid var(--text); border-radius:9px;
  background:var(--text); color:var(--surface); text-decoration:none;
  font-size:13px; font-weight:600;
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-preview-actions .tr-btn{
  min-height:40px; padding:0 14px; border:1px solid var(--border-2); border-radius:9px;
  background:var(--surface); color:var(--text); font-size:13px; font-weight:600;
}
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-preview-actions .tr-btn:hover{
  border-color:var(--text); background:var(--surface-hover);
}

/* Results area: the "just created" pill and the WHAT'S NEXT card are the last
   two coral things a finished job puts on screen, right where the user looks. */
body:is(.mode-translate,.mode-changer) #translatePanel .tr-fresh-badge{
  background:var(--text); color:var(--surface); letter-spacing:.04em;
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-next{
  border:1px solid var(--border-2); background:var(--surface); border-radius:12px;
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-next-title{ color:var(--text-2); }
body:is(.mode-translate,.mode-changer) #translatePanel .tr-next-btn{
  border:1px solid var(--border-2); background:var(--surface); color:var(--text);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-next-btn:hover{
  border-color:var(--text); background:var(--surface-hover); color:var(--text);
  transform:none;
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-next-btn-primary,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-next-btn-primary:hover{
  background:var(--text); color:var(--surface); border-color:var(--text);
  background-image:none; filter:none;
}
/* The "fresh take" pulse answered "which row is new?" in coral. In one colour the
   same answer is a ring in ink. */
@keyframes dw-fresh-pulse{
  0%{ box-shadow:0 0 0 0 var(--border-strong); }
  100%{ box-shadow:0 0 0 14px transparent; }
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-p2-audio.is-fresh,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-result.is-fresh{
  animation:dw-fresh-pulse 1.1s ease-out 2;
  border-color:var(--text-2);
}
/* "Edit in SaydiDub" is a full-width quiet action, not a coloured banner. */
body:is(.mode-translate,.mode-changer) #translatePanel .tr-saydidub{
  border:1px solid var(--border-2); background:var(--surface); color:var(--text);
  font-weight:600;
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-saydidub:hover{
  border-color:var(--text); background:var(--surface-hover);
}

/* The panels behind "Change" and under "Background music & audio mix" were
   never opened during the earlier passes, so their indigo/coral controls survived.
   Same rule as everywhere else here: ink for the thing you act on, grey for the
   track it sits in. */
body:is(.mode-translate,.mode-changer) #translatePanel .tr-voice-ico{
  color:var(--text-2); background:var(--surface-hover);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-lang-tab:hover{
  border-color:var(--text); background:var(--surface-hover); color:var(--text);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-voice-preview.playing::after{
  border-color:var(--border-2); border-top-color:var(--text);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-export-again:hover:not(:disabled){
  border-color:var(--text); color:var(--text); background:var(--surface-hover);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-seg-undo{ color:var(--text); }
body:is(.mode-translate,.mode-changer) #translatePanel .tr-seg-undo:hover{
  background:var(--surface-hover); border-color:var(--text); color:var(--text);
}
/* Audio mixer: the timeline legend, the clip row and the level meters. */
body:is(.mode-translate,.mode-changer) #translatePanel .tr-lib-play,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-pv-play,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-pv-fill,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-pv-btn.on,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-place-chips .tr-chip.on,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-mix-body .tr-toggle input:checked{
  background:var(--text); background-image:none; color:var(--surface); border-color:var(--text);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-lib-use,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-clip-read{ color:var(--text-2); }
body:is(.mode-translate,.mode-changer) #translatePanel .tr-pv-btn:hover{
  border-color:var(--text); color:var(--text); background:var(--surface-hover);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-ch-mute{
  border-color:var(--border-2); color:var(--text-2);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-ch-mute:hover{
  border-color:var(--text); color:var(--text);
}
/* Muted is a STATE, so it borrows the one state treatment this screen has:
   ink edge + body text, not red. */
body:is(.mode-translate,.mode-changer) #translatePanel .tr-ch-mute.is-muted{
  background:var(--surface-hover); border-color:var(--text); color:var(--text);
}
/* The plan row's leading tick was the only green left on the review screen. */
body:is(.mode-translate,.mode-changer) #translatePanel .tr-plan-ico{ color:var(--text-2); }
body:is(.mode-translate,.mode-changer) #translatePanel #dubChBgmRow .tr-mix-val{ color:var(--text); }
body:is(.mode-translate,.mode-changer) #translatePanel #dubChBgmRow .tr-slider{ accent-color:var(--text); }
body:is(.mode-translate,.mode-changer) #translatePanel .tr-tl-key-speech{ background:var(--text); }
body:is(.mode-translate,.mode-changer) #translatePanel .tr-tl-key-bgm{ background:var(--text-3); }

/* "This browser cannot preview this file" — covers the picture the decoder
   refused, instead of leaving the card's black background to read as a failed
   upload. Inset in the frame, on the neutral canvas, one icon + one sentence. */
body:is(.mode-translate,.mode-changer) .tr-preview-fallback,
body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-preview-fallback{
  position:absolute; inset:0; z-index:2;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:8px; padding:20px; text-align:center;
  background:var(--canvas); color:var(--text-3);
}
body:is(.mode-translate,.mode-changer) .tr-preview-fallback .tr-pf-title{
  margin:0; color:var(--text); font-size:13px; font-weight:600; line-height:1.4;
  max-width:34ch;
}
body:is(.mode-translate,.mode-changer) .tr-preview-fallback .tr-pf-sub{
  margin:0; font-size:12px; line-height:1.4; max-width:38ch;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}

/* Voice Changer: the controls that only exist on that screen. */
/* Disabled primary: the coral gradient at 40% opacity read as a pale pink bar
   with invisible text. Disabled is a STATE, not a colour — grey fill, grey ink,
   still legible, no transparency tricks. */
body:is(.mode-translate,.mode-changer) #translatePanel .tr-btn-primary:disabled,
body:is(.mode-translate,.mode-changer) #translatePanel #dubSetupDubBtn:disabled,
body:is(.mode-translate,.mode-changer) #translatePanel #dubRenderBtn:disabled{
  background:var(--surface-hover) !important;
  background-image:none !important;
  color:var(--text-3) !important;
  border:1px solid var(--border-2) !important;
  opacity:1 !important; box-shadow:none !important; cursor:not-allowed;
  filter:none !important; text-shadow:none !important;
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-btn-primary:disabled::after{
  display:none !important;   /* the chromatic layer's 50%-black overlay */
}
/* The changer has ONE primary action; it should be a button, not a full-width
   bar (the old single-column rule set width:100%). */
body:is(.mode-translate,.mode-changer) #translatePanel #dubPhase1.has-file .tr-setup-cta{
  width:auto;
}
body:is(.mode-translate,.mode-changer) #translatePanel #dubPhase1.has-file .tr-setup-cta .tr-btn{
  width:auto; flex:0 0 auto; min-width:150px;
}
body:is(.mode-translate,.mode-changer) #translatePanel #dubPhase1.has-file #dubSetupDubBtn,
body:is(.mode-translate,.mode-changer) #translatePanel #dubPhase1.has-file #dubPreviewBtn{
  width:auto;
}
/* "Credits are calculated from the recording's duration." — the number next to
   it is the information; the sentence restates the pricing page. */
body:is(.mode-translate,.mode-changer) #translatePanel #dubCreditsCalc{ display:none; }
/* The A/B compare and the record button, in ink. */
body:is(.mode-translate,.mode-changer) #translatePanel .ch-ab-play:focus-visible{
  outline:2px solid var(--text); outline-offset:2px;
}
body:is(.mode-translate,.mode-changer) #translatePanel .ch-ab-time{ color:var(--text-2); }
body:is(.mode-translate,.mode-changer) #translatePanel .ch-rec{
  color:var(--text); border-color:var(--border-2); background:var(--surface);
}
body:is(.mode-translate,.mode-changer) #translatePanel .ch-rec:hover{
  border-color:var(--text); background:var(--surface-hover);
}
body:is(.mode-translate,.mode-changer) #translatePanel .ch-rec.is-recording{
  border-color:var(--text); background:var(--surface-hover); color:var(--text);
}
/* Record sits INSIDE the drop card's column on the landing, not as a full-width
   bar under the dashed zone. */
body:is(.mode-translate,.mode-changer) #translatePanel #dubPhase1:not(.has-file) .tr-upload-group{
  flex-direction:column; gap:10px;
}
body:is(.mode-translate,.mode-changer) #translatePanel #dubPhase1:not(.has-file) .ch-rec{
  width:auto; align-self:center; min-width:190px; margin:0;
  height:40px; padding:0 18px; border-radius:10px; font-size:13px;
}
/* The bare play glyph beside the voice chip needs an edge to read as a button. */
body:is(.mode-translate,.mode-changer) #translatePanel .tr-voice-preview{
  color:var(--text); border:1px solid var(--border-2); background:var(--surface);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-voice-preview:hover,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-voice-preview.playing{
  color:var(--text); border-color:var(--text); background:var(--surface-hover);
}

/* ---- 6. Literal colours that the token remap cannot reach --------------- */
body:is(.mode-translate,.mode-changer) #translatePanel .tr-chip{ border-radius:999px; }
body:is(.mode-translate,.mode-changer) #translatePanel .tr-chip:hover{
  border-color:var(--text-3); background:var(--surface-hover); color:var(--text);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr .tr-chip.on{
  color:var(--surface); border-color:var(--text); background:var(--text); box-shadow:none;
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-select:focus,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-find-in:focus,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-seg-ta:focus,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-gloss:focus{
  border-color:var(--text); box-shadow:0 0 0 3px var(--surface-hover);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-toggle input:checked{ background:var(--text); }
body:is(.mode-translate,.mode-changer) #translatePanel .tr-slider,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-only-bad input{ accent-color:var(--text); }
body:is(.mode-translate,.mode-changer) #translatePanel .tr-bar-fill{
  background:var(--text); box-shadow:none;
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-pct{ color:var(--text-2); }
body:is(.mode-translate,.mode-changer) #translatePanel .tr-stage{ color:var(--text); }
/* Segmented controls: selected = the ink block, unselected = quiet text. */
body:is(.mode-translate,.mode-changer) #translatePanel .tr-sync-opt,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-lang-tab,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-src-tab,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-pv-switch-btn{
  color:var(--text-2); background:transparent;
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-sync-opt.on,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-lang-tab.on,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-src-tab.on,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-pv-switch-btn.on{
  color:var(--surface); background:var(--text); border-color:var(--text); box-shadow:none;
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-sync-opt.on{ color:var(--surface); }
body:is(.mode-translate,.mode-changer) #translatePanel .tr-lang-tab.is-untranslated.on{
  color:var(--surface); background:var(--text);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-pv-switch-btn:focus-visible{
  outline:2px solid var(--text); outline-offset:-3px;
}
/* Subtitle-style chips carry a swatch of the colour they burn in. That colour
   is the artifact, so it stays — desaturated, so the screen stays black/white. */
body:is(.mode-translate,.mode-changer) #translatePanel #dubSubStyleOpts .tr-sync-opt::before{ filter:grayscale(1) contrast(1.1); }
body:is(.mode-translate,.mode-changer) #translatePanel #dubSubStyleOpts .tr-sync-opt.on::before{ filter:none; }

body:is(.mode-translate,.mode-changer) #translatePanel .tr-voice-opt:hover{
  border-color:var(--text-3); background:var(--surface-hover); transform:none;
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-voice-opt.on{
  border-color:var(--text); background:var(--surface); box-shadow:inset 0 0 0 1px var(--text);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-voice-opt.on .tr-voice-ico{
  color:var(--surface); background:var(--text);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-voice-preview:hover,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-voice-preview.playing{
  border-color:var(--text); background:var(--surface-hover);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-restart:hover{
  border-color:var(--text); background:var(--surface-hover); color:var(--text);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-dl{
  color:var(--text); background:var(--surface); border:1px solid var(--border-2);
  border-radius:9px; font-weight:600;
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-dl:hover{ border-color:var(--text); background:var(--surface-hover); }
body:is(.mode-translate,.mode-changer) #translatePanel .tr-find-btn,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-lib-use,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-drop-sm .tr-drop-ico,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-clip-read{ color:var(--text-2); }
body:is(.mode-translate,.mode-changer) #translatePanel .tr-find-btn:hover,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-lib-use:hover{
  border-color:var(--text); background:var(--surface-hover); color:var(--text);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-lib-use{ border-color:var(--border-2); background:var(--surface); }
body:is(.mode-translate,.mode-changer) #translatePanel .tr-place-chips .tr-chip.on,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-mix-body .tr-toggle input:checked{ background:var(--text); }
body:is(.mode-translate,.mode-changer) #translatePanel .tr-drop-sm:hover,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-drop-sm.drag{
  border-color:var(--text); background:var(--surface-hover); color:var(--text);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-tl-key-speech{ background:var(--text); }
body:is(.mode-translate,.mode-changer) #translatePanel .tr-tl-key-bgm{ background:var(--text-3); }
body:is(.mode-translate,.mode-changer) #translatePanel .tr-tl-key-duck{ background:var(--border-strong); }

/* Line rows and their status badges */
body:is(.mode-translate,.mode-changer) #translatePanel .tr-seg-time:hover{
  border-color:var(--text); color:var(--text); background:var(--surface-hover);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-seg.is-playing{
  background:var(--surface-hover); box-shadow:inset 3px 0 0 var(--text);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-seg-btn:hover{
  background:var(--surface-hover); border-color:var(--text); color:var(--text);
}
/* A badge whose meaning is the word in it, not a colour. "over" is the one that
   needs to be seen, so it inverts instead of turning red. */
body:is(.mode-translate,.mode-changer) #translatePanel .tr-seg-fit.ok{
  color:var(--text-3); background:transparent;
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-seg-fit.tight{
  color:var(--text-2); background:var(--surface-hover);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-seg-fit.over{
  color:var(--surface); background:var(--text);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-seg-btn-attention{
  border-color:var(--text); color:var(--text); background:transparent;
}
/* Versions: the current take is marked by ink, not by a pink wash. */
body:is(.mode-translate,.mode-changer) #translatePanel .tr-result{ border-radius:12px; }
body:is(.mode-translate,.mode-changer) #translatePanel .tr-result.is-current{
  border-color:var(--text); background:var(--surface);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-result.is-current .tr-version-state{
  color:var(--surface); background:var(--text);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-version-state{ background:var(--canvas); color:var(--text-2); }
body:is(.mode-translate,.mode-changer) #translatePanel .tr-version-toggle:hover{
  border-color:var(--text); color:var(--text);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-result-orig{
  background:var(--canvas); border-color:var(--border-2);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-result-err{ color:var(--text); font-weight:600; }
body:is(.mode-translate,.mode-changer) #translatePanel .tr-ai-label{
  border-color:var(--border-2); background:var(--canvas); color:var(--text-2);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-ai-label > b{ color:var(--text); }
body:is(.mode-translate,.mode-changer) #translatePanel .tr-target-picker .tr-target-selected:not(.is-placeholder){
  color:var(--text); background:var(--surface-hover);
}
body:is(.mode-translate,.mode-changer) #translatePanel #dubSetupDubBtn:disabled{
  background:var(--text); color:var(--surface); opacity:.4;
}
/* History rows of the OLD inline table (rail uses its own rules in §3). */
body:is(.mode-translate,.mode-changer) #translatePanel .tr-recent-start{
  border:1px solid var(--text); background:var(--text); color:var(--surface);
  border-radius:8px; font-weight:600;
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-recent-start:hover{ background:var(--text); opacity:.9; }
body:is(.mode-translate,.mode-changer) #translatePanel .tr-recent-state.is-done,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-recent-state.is-busy,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-recent-state.is-error{
  color:var(--text-2); background:var(--surface-hover);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-recent-row:focus-visible,
body:is(.mode-translate,.mode-changer) #translatePanel .tr-recent-del:focus-visible{
  outline:2px solid var(--text); outline-offset:-2px;
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-recent-del:hover{
  color:var(--text); background:var(--surface-hover);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-recent-skel-row span{
  background:linear-gradient(90deg,var(--surface-hover) 25%,var(--canvas) 37%,var(--surface-hover) 63%);
  background-size:400% 100%;
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-onboard{ display:none; }
body:is(.mode-translate,.mode-changer) #translatePanel .tr-seg-btn-accent{ color:var(--text-2); }
body:is(.mode-translate,.mode-changer) #translatePanel .tr-seg-btn-accent:hover{ color:var(--text); }
body:is(.mode-translate,.mode-changer) #translatePanel .tr-btn-attention{ box-shadow:0 0 0 3px var(--surface-hover); }
body:is(.mode-translate,.mode-changer) #translatePanel .tr-working-version.is-working .tr-working-badge::before{
  border-color:var(--border-2); border-top-color:var(--text);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-stage-spin{
  border-color:var(--border-2); border-top-color:var(--text);
}
body:is(.mode-translate,.mode-changer) #translatePanel .tr-recent-row.is-opening::after{
  border-color:var(--border-2); border-top-color:var(--text);
}

/* ---- 7b. The one control outside the panel ----------------------------- */
/* The top-bar sign-in is shared chrome, so it is the last hue on an otherwise
   one-colour screen — and its coral label measures 3.84:1 in the dark theme,
   which is below AA. Inside this workspace it takes the same ink as the rest of
   the page. (The brand mark itself is left alone: a logo is not a control.) */
body:is(.mode-translate,.mode-changer) .signin-btn{
  color:var(--text); border-color:var(--border-strong); background:var(--surface);
}
body:is(.mode-translate,.mode-changer) .signin-btn:hover{
  color:var(--text); border-color:var(--text); background:var(--surface-hover);
}

/* ---- 7. Responsive ---------------------------------------------------- */
/* Below 1100px the history rail is wider than the space the canvas needs, so it
   moves under the drop zone and becomes a short scroll area. */
@media (max-width:1100px){
  body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file):has(> .tr-recent:not([hidden])){
    grid-template-columns:minmax(0,1fr);
    grid-template-rows:minmax(0,1fr) minmax(120px,34vh);
  }
  body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) > .tr-recent:not([hidden]){
    grid-column:1; grid-row:2; border-left:0; border-top:1px solid var(--border-2);
    padding:16px clamp(18px,3vw,40px) 20px;
  }
}
@media (max-width:899px){
  /* Setup: one column, preview pinned to the top of the scroll. */
  body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-setup-grid{
    grid-template-columns:minmax(0,1fr); gap:20px;
    padding:52px 16px 24px;
  }
  body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-setup-preview{
    position:static; grid-row:1;
  }
  body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-setup-form{ grid-row:2; }
  body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-setup-copy{ margin-bottom:16px; }
  body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-setup-close{ top:8px; right:8px; }
  body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-setup-actions{
    flex-wrap:wrap; gap:8px 10px; min-height:0;
    padding:10px 16px calc(10px + env(safe-area-inset-bottom,0px)) !important;
  }
  body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-setup-usage{ flex:1 1 100%; order:2; }
  body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-setup-cta{ flex:1 1 100%; margin-left:0; }
  body:is(.mode-translate,.mode-changer) #dubPhase1.has-file .tr-setup-cta .tr-btn{ flex:1 1 0; min-width:0; }
  /* Review: the video stops being a column and becomes the lede. */
  body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2{ padding:0 16px 0; }
  body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 > .tr-phase2-head{
    margin:0 -16px 10px; padding:0 16px; min-height:52px;
  }
  body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-p2-grid{
    grid-template-columns:minmax(0,1fr); gap:18px;
  }
  body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-p2-side{ position:static; }
  body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-restart{
    position:static; flex:none; min-height:32px; margin:0;
  }
  body:is(.mode-translate,.mode-changer).dub-review-open .tr-review-close{
    position:static; flex:none; width:36px; height:36px;
  }
  body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-actions.tr-sticky{
    margin:16px 0 0 !important; padding:10px 0 calc(12px + env(safe-area-inset-bottom,0px)) !important;
  }
  body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-actions.tr-sticky .tr-btn,
  body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2 .tr-p2-main .tr-actions.tr-sticky .tr-btn{
    width:100%; min-width:0;
  }
}
@media (max-width:560px){
  body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) > .tr-header{ padding:14px 16px 10px; }
  body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) > .tr-upload-stage{ padding:0 16px 16px; }
  body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) .tr-drop{ padding:28px 16px; gap:14px; }
  body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) .tr-drop-ico{ width:46px; height:46px; }
  /* Wrapped onto three lines, a "·" before each item starts the line with a
     dangling dot. Below 560px the gap does the separating instead. */
  body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) .tr-facts{ gap:6px 8px; }
  body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) .tr-fact{ font-size:11px; }
  body:is(.mode-translate,.mode-changer) #dubPhase1:not(.has-file) .tr-fact + .tr-fact::before{ content:none; margin:0; }
}
@media (prefers-reduced-motion:reduce){
  body:is(.mode-translate,.mode-changer) #dubPhase1.has-file,
  body:is(.mode-translate,.mode-changer).dub-review-open #dubPhase2{ animation:none; }
}

/* ==========================================================================
   Voice Design workspace — full-bleed, monochrome (2026-11-08)
   Same treatment as the dubbing workspace, in its own shape: this screen has no
   "upload then review" journey, it has a BUILDER. So the whole width is used for
   two things at once — attributes + recipe on the left, templates in a right
   rail that scrolls on its own — instead of one 1080px column that ran 2,300px
   tall and buried the recipe under sixteen poster cards.
   Scoped to body.mode-design (a hook added in setMode); the panel only exists on
   this screen.
   ========================================================================== */

/* ---- shell ---- */
body.mode-design main{ padding:0; }
body.mode-design .editor{ overflow:hidden; }
body.mode-design .editor-scroll{
  display:flex; padding:0; overflow:hidden; scrollbar-gutter:auto;
}
body.mode-design #speakers.editor-inner{
  display:flex; flex-direction:column; flex:1; min-height:0;
  width:100%; max-width:none; margin:0;
}
body.mode-design #designPanel{
  flex:1; min-height:0;
  /* Two panes, each its own scroller: a tall template grid must not stretch the
     builder column (the grid version of this distributed the rail's height into
     the left column's rows and pushed the chips down). */
  display:flex; align-items:stretch;
  gap:clamp(24px,3vw,48px);
  height:100%; overflow:hidden;
  padding:clamp(16px,2.4vh,26px) clamp(18px,3vw,40px);
  background:var(--surface);
}
body.mode-design #designPanel .vd-main{
  flex:1 1 auto; min-width:0; min-height:0;
  overflow-y:auto; padding-right:4px;
}
body.mode-design #designPanel > .vd-or-build{ display:none; }
/* Left pane: section label, chips, recipe. */
body.mode-design #designPanel .vd-main > .vd-or-build{
  display:block; margin:0 0 10px;
  font-size:12px; font-weight:600; letter-spacing:.04em;
  text-transform:uppercase; color:var(--text-3);
}
body.mode-design #designPanel .vd-main > .vd-or-build::before,
body.mode-design #designPanel .vd-main > .vd-or-build::after{ content:none; }
body.mode-design #designPanel .vd-recipe{ margin-top:14px; }
/* The right rail: its heading is the templates title, so the second "Or start
   from a template" divider is a duplicate and goes. */
body.mode-design #designPanel > .vd-templates{
  flex:0 0 clamp(300px,32%,440px); min-width:0; min-height:0;
  margin:0; overflow-y:auto;
  padding:0 4px 8px 0;
}
/* ---- copy ---- */
/* The page header repeated the top-bar title and carried a coral CTA whose only
   job was scrolling to a builder that now sits right there. */
body.mode-design #designPanel .vd-header{ display:none !important; }
body.mode-design #designPanel .vd-tpl-title{
  margin:0 0 12px; font-size:12px; font-weight:600; letter-spacing:.04em;
  text-transform:uppercase; color:var(--text-3);
}
/* ---- one colour ---- */
/* NOTE: --accent itself is NOT redefined — tests/test_design_tokens forbids
   styles.css from declaring accent/text/surface/border tokens. This panel reads
   --accent-text and the coral tint family, which are aliases. */
body.mode-design #designPanel{
  --accent-text:var(--text);
  --brand-coral:var(--text); --brand-coral-deep:var(--text);
  --brand-coral-soft:var(--surface-hover);
  --coral-tint:var(--surface-hover); --coral-tint-2:var(--surface-hover);
}
body.mode-design #designPanel .vd-builder,
body.mode-design #designPanel .vd-recipe{
  border:1px solid var(--border-2); border-radius:14px; background:var(--surface);
  padding:16px 18px;
}
body.mode-design #designPanel .vd-group-label{ color:var(--text-2); font-weight:600; }
body.mode-design #designPanel .vd-chip{
  border:1px solid var(--border-2); background:var(--surface); color:var(--text-2);
  font-weight:500; height:34px;
}
body.mode-design #designPanel .vd-chip:hover{ border-color:var(--text); color:var(--text); }
body.mode-design #designPanel .vd-chip.is-on{
  background:var(--text) !important; background-image:none !important;
  border-color:var(--text) !important; color:var(--surface) !important; box-shadow:none;
}
body.mode-design #designPanel .vd-recipe-clear{ color:var(--text-2); }
body.mode-design #designPanel .vd-recipe-clear:hover{
  color:var(--text); background:var(--surface-hover);
}
/* The dashed violet box around the recipe read as an unfinished input. */
body.mode-design #designPanel .vd-result-box{
  border:1px solid var(--border-2); border-radius:12px; background:var(--canvas);
  margin-top:10px; padding:16px;
}
body.mode-design #designPanel .vd-recipe-val{
  color:var(--text); font-size:14px; font-weight:500;
}
body.mode-design #designPanel .vd-recipe-val.is-empty{ color:var(--text-3); }
body.mode-design #designPanel .vd-listen-row{ border-top-color:var(--border-2); }
/* Listen is the secondary, Save is the ink primary. `.vd-btn-dark` reads
   var(--black), which is not declared on screen outside the print block. */
body.mode-design #designPanel .vd-btn-dark{
  background:var(--surface); color:var(--text); border:1px solid var(--border-2);
  font-weight:600;
}
body.mode-design #designPanel .vd-btn-dark:hover:not(:disabled){
  border-color:var(--text); background:var(--surface-hover);
}
body.mode-design #designPanel .vd-btn-dark:disabled{ background:var(--surface-hover); color:var(--text-3); }
body.mode-design #designPanel .vd-btn-primary{
  background:var(--text) !important; background-image:none !important;
  color:var(--surface) !important; border:1px solid var(--text);
  font-weight:600; border-radius:12px; filter:none !important; text-shadow:none !important;
}
/* "Save to My Voices" is in the global chromatic-CTA list, whose ::before paints
   the coral/lime wash over any ink fill. */
body.mode-design #designPanel .vd-btn-primary::before,
body.mode-design #designPanel .vd-btn-primary::after{ display:none !important; }
body.mode-design #designPanel .vd-btn-primary:disabled{
  background:var(--surface-hover) !important; color:var(--text-3) !important;
  border-color:var(--border-2);
}
/* The sample player is a shared component (.vc-*) with a coral play button and
   progress fill. */
body.mode-design #designPanel .vc-play-btn{
  background:var(--text); background-image:none; color:var(--surface); box-shadow:none;
}
body.mode-design #designPanel .vc-play-btn:hover{ filter:none; background:var(--text); opacity:.9; }
body.mode-design #designPanel .vc-bar-fill,
body.mode-design #designPanel .pb-fill{
  background:var(--text); background-image:none;
}
/* Template cards. They are the shared "featured card" (.vfeat: poster on top,
   name/tags/Save underneath) — the .vd-tpl* rules above this block style an older
   markup that no longer renders, so the ink pass has to live here. */
body.mode-design #designPanel .vd-tpl-list{
  grid-template-columns:repeat(2, minmax(0,1fr)); gap:10px;
}
body.mode-design #designPresets .vfeat{
  border:1px solid var(--border-2); border-radius:12px; overflow:hidden;
  background-color:var(--canvas);
}
body.mode-design #designPresets .vf-media{ background-color:var(--canvas); }
body.mode-design #designPresets .vf-name{ color:var(--text); font-size:13px; font-weight:600; }
body.mode-design #designPresets .vf-tags{ color:var(--text-3); }
/* "Save" was the coral pill; it is the card's one action, so it takes the ink. */
body.mode-design #designPresets .vf-use{
  background:var(--text); background-image:none; color:var(--surface);
  border:1px solid var(--text); border-radius:8px; font-weight:600;
}
body.mode-design #designPresets .vf-use:hover:not(:disabled){
  background:var(--text); filter:none; opacity:.9;
}
body.mode-design #designPresets .vf-play{ color:var(--text); background:var(--surface); }
body.mode-design #designPresets .vf-play:hover{ background:var(--surface-hover); }
body.mode-design #designPanel .vd-tpl-pagination{ padding:12px 0 0; justify-content:flex-start; }
body.mode-design #designPanel .pg-btn{
  border-radius:8px; font-size:13px; min-width:32px; height:32px;
}
body.mode-design #designPanel .pg-btn.active{
  background:var(--text); border-color:var(--text); color:var(--surface);
}

/* One column on narrow screens: attributes, recipe, then the templates below. */
@media (max-width:1000px){
  body.mode-design #designPanel{
    display:block; overflow-y:auto; padding:14px 16px 24px;
  }
  body.mode-design #designPanel > .vd-templates{
    overflow:visible; margin-top:20px; padding:0;
  }
  body.mode-design #designPanel .vd-main{ overflow:visible; padding-right:0; }
  body.mode-design #designPanel .vd-tpl-list{ grid-template-columns:repeat(2, minmax(0,1fr)); }
  body.mode-design #designPanel .vd-recipe{ margin-top:12px; }
}
@media (max-width:560px){
  body.mode-design #designPanel .vd-group{ align-items:flex-start; }
  body.mode-design #designPanel .vd-group-label{ flex:1 1 100%; margin-bottom:2px; }
  body.mode-design #designPanel .vd-chip{ height:32px; font-size:12px; padding:0 12px; }
  body.mode-design #designPanel .vd-builder,
  body.mode-design #designPanel .vd-recipe{ padding:14px; }
}

/* ==========================================================================
   Guidance samples — studio/tts (2026-11-06)
   A row of ready-made scripts under the editor. Clicking one loads the script
   into the editor and plays a clip that was rendered AHEAD of time
   (/static/guidance/<lang>/<id>.mp3, scripts/gen_guidance_audio.py), so the
   first sound is a file read instead of a GPU render.
   The chips are styled as siblings of .add-speaker so the bottom of the editor
   stays one rhythm: white surface, 1px border, radius var(--r-sm), 13px/600.
   ========================================================================== */
.guidance{ margin:2px 0 10px; padding-top:16px; border-top:1px solid var(--border); }

.guidance-head{ display:flex; align-items:center; justify-content:space-between; gap:10px; }

.guidance-title{ margin:0; font-size:13px; font-weight:650; color:var(--text-2); }

.guidance-toggle{ flex:none; display:inline-flex; align-items:center; justify-content:center;
  width:26px; height:26px; padding:0; border:0; border-radius:6px; background:transparent;
  color:var(--text-3); cursor:pointer; }
.guidance-toggle:hover{ background:var(--surface-hover); color:var(--text); }
.guidance-toggle svg{ transition:transform var(--t-fast); }
.guidance.is-collapsed .guidance-toggle svg{ transform:rotate(-90deg); }

.guidance-body{ margin-top:12px; }
.guidance.is-collapsed .guidance-body{ display:none; }

.guidance-list{ display:flex; flex-wrap:wrap; gap:8px; margin:0; padding:0; list-style:none; }

.guidance-item{ display:flex; min-width:0; max-width:100%; }

.guidance-item-btn{ display:inline-flex; align-items:center; gap:7px; min-width:0;
  min-height:38px; padding:8px 12px; border:1px solid var(--border); border-radius:var(--r-sm);
  background:var(--surface); color:var(--text-2); font:inherit; font-size:13px; font-weight:600;
  cursor:pointer; white-space:nowrap;
  transition:background var(--t-fast), border-color var(--t-fast), color var(--t-fast); }
.guidance-item-btn > svg{ flex:none; color:var(--text-3); }
.guidance-item-btn > span{ overflow:hidden; text-overflow:ellipsis; }
.guidance-item-btn:hover:not(:disabled){ background:var(--surface-hover); color:var(--text); }
.guidance-item-btn:disabled{ cursor:progress; opacity:.62; }
/* "This is the sample currently in the editor" — not a primary action. */
.guidance-item-btn[aria-pressed="true"]{ border-color:var(--accent); box-shadow:inset 0 0 0 1px var(--accent);
  background:var(--accent-soft); color:var(--accent); }
.guidance-item-btn[aria-pressed="true"] > svg{ color:var(--accent); }


/* Phone: ONE scrollable row instead of a 4-row wall of chips.
   On desktop the samples sit under the script and may wrap freely; on a 390 px
   phone that wrap turns into ~165 px of chips that pushes the editor up and
   competes with the Generate footer. A single row keeps the offer visible and
   quiet — the third chip peeking at the right edge is the "there is more"
   signal, so no second control is needed. The list stays keyboard/Tab
   reachable and every chip keeps its 34 px touch target. */
@media (max-width:640px){
  .guidance{ padding-top:14px; }
  .guidance-list{ flex-wrap:nowrap; gap:7px; overflow-x:auto; overflow-y:hidden;
    scroll-snap-type:x proximity; -webkit-overflow-scrolling:touch;
    overscroll-behavior-x:contain; scrollbar-width:none;
    margin:-2px -2px 0; padding:2px 2px 4px; }
  .guidance-list::-webkit-scrollbar{ display:none; }
  .guidance-item{ flex:0 0 auto; scroll-snap-align:start; }
  .guidance-item-btn{ min-height:34px; padding:6px 10px; font-size:12.5px; }
  .guidance-item-btn > span{ max-width:46vw; }
}

/* ---------------------------------------------------------------------------
   Subtitles→Speech — TWO PANES ON A WIDE SCREEN
   Controls and content used to share one long column: the voice row, the action
   row, the options and the lines all stacked, which left a third of the window
   empty and made one line of buttons carry the result, the download and the
   toggle at once. On a wide screen the sheet now splits: a narrow control column
   on the left, the lines (the actual content of this screen) on the right,
   separated by a hairline. Below 1100px everything stays in one stacked column.
--------------------------------------------------------------------------- */
body.mode-subtitles #srtPanel .srt-panel{ width:auto; max-width:none; margin:0; padding:0; }

@media (min-width:1100px){
  body.mode-subtitles #srtPanel .srt-panel{
    display:grid;
    grid-template-columns:minmax(320px, 400px) minmax(0, 1fr);
    column-gap:48px;
    align-items:start;
  }
  body.mode-subtitles #srtPanel .srt-head{ grid-column:1 / -1; }
  body.mode-subtitles #srtPanel .srt-chips{ grid-column:1 / -1; }

  body.mode-subtitles #srtPanel .srt-step-voice{ grid-column:1; grid-row:2; margin-top:0; }
  body.mode-subtitles #srtPanel .srt-step-create{ grid-column:1; grid-row:3; }
  body.mode-subtitles #srtPanel .srt-adv-advanced{ grid-column:1; grid-row:4; }

  /* The lines get their own pane, top-aligned with the first control. */
  body.mode-subtitles #srtReviewBox{
    grid-column:2; grid-row:2 / span 3;
    border-left:1px solid var(--border-2);
    padding-left:40px;
    margin-top:0;
  }

  /* In a 400px column the controls stack, they do not stretch sideways: the
     primary action fills the column, the facts sit under it, the option last. */
  body.mode-subtitles #srtPanel .srt-actions{ margin-top:16px; }
  body.mode-subtitles #srtPanel .srt-actions .tr-btn{ width:100%; }
  body.mode-subtitles #srtPanel #srtInfoLine{ margin-left:0; }
  body.mode-subtitles #srtPanel .srt-merge-row{ margin-top:16px; }
}

/* Controls read as a stack, never as a strip of buttons. */
body.mode-subtitles .srt-tool-row{ margin:0; }
body.mode-subtitles .srt-aud-lead{ margin-bottom:6px; }

/* The result summary was a green card with its own pill button; on a screen that
   already has a playbar and a download button that is a third surface for one
   fact. It is now a quiet line: check, numbers, and a plain "listen" action. */
body.mode-subtitles .srt-result{
  background:transparent; border:0; border-radius:0; padding:0; margin:0 0 12px;
  color:var(--text-2); font-size:13px; gap:8px; }
body.mode-subtitles .srt-result svg{ color:var(--success); }
body.mode-subtitles .srt-result.is-partial svg{ color:var(--warn-ink); }
body.mode-subtitles .srt-play-full{
  height:26px; padding:0 8px 0 6px; border:0; border-radius:6px; background:transparent;
  color:var(--text-2); font-size:12px; font-weight:600; }
body.mode-subtitles .srt-play-full:hover{ background:var(--surface-hover); color:var(--text); }
body.mode-subtitles .srt-play-full:focus-visible{ outline:2px solid var(--focus); outline-offset:2px; }

/* A line that already has a clip was a green card. The dot says "ready" and the
   row says nothing else needs attention; the colour was decoration. */
body.mode-subtitles .srt-seg.is-ready{ background:transparent; border-color:transparent;
  border-bottom-color:var(--border-2); }

/* Per-line audition is a secondary action: text, not an accent link. */
body.mode-subtitles .tr-seg-btn-accent{ color:var(--text-2); }
body.mode-subtitles .tr-seg-btn-accent:hover{ color:var(--text); }

/* The per-line "fits the slot" badge is computed from the audio that this screen
   does not have yet, so on upload it reads "vừa" for every line — a claim with
   nothing behind it. Hide it until the row actually has a clip. */
body.mode-subtitles .tr-seg-fit{ display:none; }
body.mode-subtitles .srt-row.is-ready .tr-seg-fit{ display:inline-block; }

/* Per-line audition is a secondary action: text, not an accent link. */
body.mode-subtitles .tr-seg-btn-accent{ color:var(--text-2); }
body.mode-subtitles .tr-seg-btn-accent:hover{ color:var(--text); }

/* With the "fits the slot" badge gone, space-between pushed the line number to
   the far right edge. Keep the row header left-aligned like a document. */
body.mode-subtitles .tr-seg-head{ justify-content:flex-start; }
body.mode-subtitles .tr-seg-spk{ margin-right:auto; }

/* The summary line reads as one sentence: check · numbers · listen. The old
   margin-left:auto pushed "listen" to the far edge of the column. */
body.mode-subtitles .srt-result .srt-play-full{ margin-left:4px; }

/* --- Entry screen: the saved projects are visible without scrolling -------
   "Phụ đề gần đây" sat under the upload card, i.e. below the fold on a laptop
   window (card bottom ~619 on a 578px-tall viewport). A project the user just
   made looked missing. On a wide screen the entry is two columns: upload on the
   left, the saved projects on the right. */
@media (min-width:1100px){
  body.mode-subtitles #srtUpload{
    display:grid;
    grid-template-columns:minmax(0,1fr) minmax(300px,360px);
    column-gap:40px;
    align-items:start;
  }
  body.mode-subtitles #srtUpload > .srt-head{ grid-column:1 / -1; }
  body.mode-subtitles #srtUpload > .srt-upload-group{ grid-column:1; }
  body.mode-subtitles #srtUpload > #srtProgress{ grid-column:1; }
  body.mode-subtitles #srtUpload > #srtRecentSection{
    grid-column:2; margin:0; align-self:start;
    border-left:1px solid var(--border-2); padding-left:28px; min-height:1px;
  }
  body.mode-subtitles #srtUpload > #srtRecentSection .tr-recent-head{ display:none; }
}

/* The saved-project row is a 3-column grid sized for a full-width table
   (1fr | 220px | 145px). In the 300-360px side column that left the NAME at zero
   width — only the file icon and the line count were visible. Compact it. */
@media (min-width:1100px){
  body.mode-subtitles #srtUpload > #srtRecentSection .tr-recent-row{
    grid-template-columns:minmax(0,1fr) auto;
    grid-template-rows:auto auto;
    column-gap:10px; row-gap:2px;
    padding:8px 6px;
  }
  body.mode-subtitles #srtUpload > #srtRecentSection .tr-recent-name{ grid-column:1; grid-row:1; }
  body.mode-subtitles #srtUpload > #srtRecentSection .tr-recent-langs{ grid-column:2; grid-row:1; justify-self:end; }
  body.mode-subtitles #srtUpload > #srtRecentSection .tr-recent-row time{ grid-column:1 / -1; grid-row:2; font-size:11px; }
  body.mode-subtitles #srtUpload > #srtRecentSection .tr-recent-thumb{ width:30px; height:24px; border-radius:6px; }
  /* The empty state reads as one sentence, not a table cell. */
  body.mode-subtitles #srtUpload > #srtRecentSection .tr-recent-empty{ font-size:12px; line-height:1.5; }
}

/* ---------------------------------------------------------------------------
   One studio canvas: the subtitles entry uses the SAME frame as the dubbing
   entry (studio/translate/video), which is the calmer of the two:
     · no second page title inside the panel (the topbar already names the screen)
     · one full-bleed dashed drop zone: icon, one line, one button, facts
     · the saved projects sit under the zone, on the same sheet
   Switching tabs used to move the sheet, the drop box and the title around; with
   the same paddings and the same single target, the frame stays put.
   Specificity note: the old hero rules use two ids (#srtPanel #srtUpload …), so
   these overrides carry the same ids plus the body hook to win.
--------------------------------------------------------------------------- */
body.mode-subtitles .layout{ background:var(--surface); }
body.mode-subtitles .editor-scroll{ padding:0 0 40px; }
body.mode-subtitles #srtPanel{ margin:0; padding:0; }

/* Upload state ------------------------------------------------------------- */
body.mode-subtitles #srtPanel #srtUpload{ display:block; }
body.mode-subtitles #srtPanel #srtUpload > .srt-head{ display:none; }

body.mode-subtitles #srtPanel #srtUpload > .srt-upload-group{
  display:flex; flex-direction:column; justify-content:center; align-items:stretch;
  /* Tall enough to read as the same full-bleed target as the dubbing screen,
     short enough that the saved projects under it stay on screen at 578px. */
  width:100%; min-height:clamp(240px, calc(100dvh - 240px), 520px);
  padding:40px 24px;
  border:1.5px dashed var(--border-2); border-radius:16px; background:transparent;
  margin:0;
}
body.mode-subtitles #srtPanel #srtUpload .srt-drop{
  width:100%; min-height:0; margin:0; padding:0; gap:14px;
  flex-direction:column; align-items:center; justify-content:center;
  border:0; border-radius:0; background:none; box-shadow:none;
}
body.mode-subtitles #srtPanel #srtUpload .srt-drop:hover{ transform:none; box-shadow:none; background:none; }
body.mode-subtitles #srtPanel #srtUpload .srt-drop .tr-drop-ico{
  display:flex; width:68px; height:68px; margin:0 0 6px; border-radius:16px;
  background:var(--surface-hover); color:var(--text-3);
}
body.mode-subtitles #srtPanel #srtUpload .srt-drop .tr-drop-title{
  display:block; min-height:0; padding:0; border:0; border-radius:0; background:none;
  box-shadow:none; white-space:normal; font-size:20px; line-height:1.3; font-weight:800;
  letter-spacing:-.02em; color:var(--text); text-align:center;
}
body.mode-subtitles #srtPanel #srtUpload .srt-drop .tr-drop-desc{ display:none; }
body.mode-subtitles #srtPanel #srtUpload .srt-drop .tr-pick-cta{ margin-top:6px; }
body.mode-subtitles #srtPanel #srtUpload .srt-drop .tr-facts{ justify-content:center; margin-top:18px; }
/* The two secondary links read as sheet text, not as part of the drop target. */
body.mode-subtitles #srtPanel #srtUpload .srt-help-details{ margin-top:18px; text-align:center; }
body.mode-subtitles #srtPanel #srtUpload .srt-alt-row{ margin-top:10px; text-align:center; }

body.mode-subtitles #srtPanel #srtUpload #srtRecentSection{
  margin:26px 0 0; padding:0; border:0; min-height:0;
}
body.mode-subtitles #srtPanel #srtUpload #srtRecentSection .tr-recent-row{
  grid-template-columns:minmax(0,1fr) auto;
}
body.mode-subtitles #srtPanel #srtUpload #srtRecentSection .tr-recent-langs{ grid-column:2; justify-self:end; }
body.mode-subtitles #srtPanel #srtUpload #srtRecentSection .tr-recent-row time{ grid-column:1 / -1; }

/* Below the two-pane breakpoint the sheet keeps side padding: without it the
   drop zone and the review list touch the screen edges. */
@media (max-width:1099px){
  body.mode-subtitles .editor-scroll{ padding:12px 16px 32px; }
  body.mode-subtitles #srtPanel #srtUpload > .srt-upload-group{ padding:28px 16px; }
}

/* The two secondary links sit under the drop zone, like a caption on the sheet —
   the dashed target now holds exactly what the dubbing screen's holds. */
body.mode-subtitles #srtPanel #srtUpload .srt-alt-outside{ margin-top:14px; text-align:center; }
body.mode-subtitles #srtPanel #srtUpload .srt-alt-outside .srt-alt-row{ margin:0; }
body.mode-subtitles #srtPanel #srtUpload .srt-alt-outside .srt-help-details{ margin-top:8px; }
body.mode-subtitles #srtPanel #srtUpload .srt-alt-outside .srt-help-details summary{ font-size:12px; }
