:root{
  --bg:#0f1115; --panel:#171a21; --panel2:#1d212b; --line:#272c38; --ink:#e8eaf0;
  --muted:#98a2b8; --accent:#5b8cff; --accent2:#7c5cff;
  --green:#39c07a; --amber:#e6b53a; --red:#ff6b6b; --chip:#222836;
}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--ink);font-family:'Segoe UI',system-ui,Arial,sans-serif;font-size:14px}
button{font-family:inherit;cursor:pointer}
a{color:var(--accent);text-decoration:none}
.hidden{display:none !important}
/* THE WORKSPACE SCROLLS; THE PAGE DOES NOT.
 *
 * `.main` already carried `overflow:auto`, but the shell had no height — so the
 * BODY scrolled and `.main` never did. That is why `position: sticky` on the
 * video did nothing: a sticky element sticks inside its nearest scroll
 * container, and `.main`'s scroll position was permanently zero while the whole
 * page moved underneath it. The player scrolled away with everything else.
 *
 * Giving the shell the viewport's height makes `.main` the real scrollport, and
 * the sticky player then behaves the way it was written to. The sidebar stops
 * scrolling away as a free consequence. */
html,body{height:100%}
.app{display:grid;grid-template-columns:232px 1fr;height:100vh;min-height:100vh;
  overflow:hidden;transition:grid-template-columns .16s ease}
/* CLOSED IS 0px. A 56px rail is still a panel: it keeps a border, a
 * background and a column, and it was reported as "half closed". Shut, the
 * sidebar is display:none and its grid column is gone, so the workspace gets
 * the whole width. The control that reopens it therefore cannot live inside
 * it — `#navReopen` is fixed, sits outside the grid, and costs the workspace
 * nothing. */
.app.nav-collapsed{grid-template-columns:0 1fr}
/* NOT `display:none`: that takes the sidebar out of the grid flow, and the
 * workspace then lands in column ONE — which is the 0px one. It stays a
 * grid item and becomes nothing: no width, no padding, no border, no
 * background, and its content is not rendered. */
.app.nav-collapsed .sidebar{width:0;min-width:0;padding:0;border:0;
  background:none;overflow:hidden;visibility:hidden}
#navReopen{position:fixed;left:0;top:14px;z-index:60;width:20px;height:46px;
  border:1px solid var(--line);border-left:0;border-radius:0 9px 9px 0;
  background:#12141a;color:var(--muted);cursor:pointer;font-size:13px;
  line-height:1;padding:0;display:flex;align-items:center;justify-content:center}
#navReopen:hover{color:var(--ink);background:#1b1f28}
.sidebar{background:#12141a;border-right:1px solid var(--line);padding:16px 12px;display:flex;flex-direction:column;gap:6px;
  height:100vh;overflow-y:auto;overflow-x:hidden}

/* Collapsing the navigation. The centre workspace is what the user came for,
 * and on a 9:16 film with a repair list beside it every pixel of width counts. */
.navtoggle{position:absolute;left:0;top:0;width:100%;border:0;background:transparent;
  color:var(--muted);font-size:13px;padding:6px 8px;text-align:left;display:none}
.sidebar .navtoggle{display:block;position:static;margin:-6px 0 2px;border-radius:8px}
.sidebar .navtoggle:hover{background:#1b1f28;color:var(--ink)}
/* NO PADDING. This rule used to say `padding:16px 6px` and, being LATER in the
 * file at the same specificity, it beat the `padding:0` fifteen lines above —
 * so a "closed" sidebar measured 12px wide at every viewport. That is the
 * residual half-panel the closed state exists to remove, and it was put there
 * by the previous design, where closed meant a narrow rail of icons rather than
 * nothing. The rest of this block only hides labels on a rail that no longer
 * exists; it is harmless and left alone. */
.app.nav-collapsed .sidebar{align-items:center}
.app.nav-collapsed .brand small,
.app.nav-collapsed .brand div,
.app.nav-collapsed .navsec,
.app.nav-collapsed .navi span:not(.ic){display:none}
.app.nav-collapsed .navi{justify-content:center;padding:9px 6px}
.app.nav-collapsed .navtoggle{text-align:center}
.brand{display:flex;align-items:center;gap:10px;padding:6px 8px 14px}
.brand b{font-size:15px}.brand small{display:block;color:var(--muted);font-size:11px}
/* §3 — the approved mark, on its own, at its own proportions. It used to be the
   letter «A» in a gradient tile; the tile is gone because the mark IS the
   gradient and a coloured box behind it hides the interlace. */
.brand .logo{width:auto;height:22px;display:block;border-radius:0;background:none;
  filter:drop-shadow(0 0 10px rgba(34,211,238,.45))}
.navsec{color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:.5px;margin:14px 8px 4px}
.navi{display:flex;align-items:center;gap:10px;padding:9px 10px;border-radius:8px;border:0;background:transparent;color:var(--ink);text-align:left;width:100%;font-size:13px}
.navi:hover{background:#1b1f28}
.navi.active{background:#232a3a;color:#fff}
.navi .ic{width:18px;text-align:center;opacity:.85}
.main{padding:22px 26px;height:100vh;overflow-y:auto;overflow-x:hidden;scroll-behavior:auto}
.pagehead{display:flex;align-items:center;justify-content:space-between;margin-bottom:18px}
.pagehead h1{font-size:20px;margin:0}
.pagehead p{color:var(--muted);margin:2px 0 0;font-size:13px}
.row{display:flex;gap:12px;flex-wrap:wrap}
.card{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:18px}
.btn{border:1px solid var(--line);background:var(--panel2);color:var(--ink);padding:9px 14px;border-radius:9px;font-size:13px}
.btn:hover{border-color:#3a4560}
.btn.primary{background:var(--accent);border-color:var(--accent);color:#fff;font-weight:600}
.btn.primary:hover{filter:brightness(1.08)}
.btn.ghost{background:transparent}
.btn:disabled{opacity:.45;cursor:not-allowed}
.btn.sm{padding:6px 10px;font-size:12px;border-radius:8px}
/* home */
.home-cards{display:grid;grid-template-columns:1fr 1fr;gap:18px;max-width:900px}
.prod{padding:26px;border-radius:18px;border:1px solid var(--line);background:linear-gradient(160deg,#191d27,#141821)}
.prod .em{font-size:30px}
.prod h2{margin:12px 0 6px;font-size:22px}
.prod p{color:var(--muted);margin:0 0 18px;min-height:38px}
.recent{margin-top:26px}
.recent h3{font-size:14px;color:var(--muted);margin:0 0 8px}
.proj{display:flex;align-items:center;justify-content:space-between;padding:11px 14px;border:1px solid var(--line);border-radius:10px;margin-bottom:8px;background:var(--panel)}
.proj:hover{border-color:#3a4560;cursor:pointer}
.proj small{color:var(--muted)}
/* forms */
.field{margin-bottom:14px;max-width:560px}
.field label{display:block;font-size:13px;margin-bottom:6px}
.field .opt{color:var(--muted);font-weight:400}
.drop{border:1.5px dashed #333b4d;border-radius:12px;padding:18px;text-align:center;color:var(--muted);background:#141821}
.drop.ok{border-color:var(--green);color:var(--ink)}
.drop input{display:none}
select,input[type=text]{width:100%;max-width:560px;background:var(--panel2);border:1px solid var(--line);color:var(--ink);padding:9px 10px;border-radius:9px}
/* summary tiles */
.tiles{display:flex;gap:10px;flex-wrap:wrap;margin:8px 0 14px}
.tile{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:10px 14px;min-width:96px}
.tile b{display:block;font-size:22px}
.tile span{color:var(--muted);font-size:12px}
.tile.g b{color:var(--green)} .tile.a b{color:var(--amber)} .tile.r b{color:var(--red)}
/* filters */
.filters{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px}
.chip{border:1px solid var(--line);background:var(--chip);color:var(--ink);padding:6px 12px;border-radius:20px;font-size:12px}
.chip.active{background:var(--accent);border-color:var(--accent);color:#fff}
/* rows */
.split{display:grid;grid-template-columns:1fr 380px;gap:16px;align-items:start}
.listwrap{border:1px solid var(--line);border-radius:12px;background:var(--panel);overflow:hidden}
.vlist{height:62vh;overflow:auto;position:relative}
.vrow{position:absolute;left:0;right:0;padding:9px 12px;border-bottom:1px solid var(--line);display:flex;gap:10px;align-items:flex-start}
.vrow:hover{background:#1b202b;cursor:pointer}
.vrow.sel{background:#232c3f}
.vrow .bn{color:var(--muted);font-size:11px;min-width:34px}
.vrow .tx{flex:1;min-width:0}
.vrow .tx .mn{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.vrow .tx .src{color:var(--muted);font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.vrow .meta{display:flex;gap:6px;align-items:center;flex-shrink:0}
.dot{width:9px;height:9px;border-radius:50%}
.dot.g{background:var(--green)} .dot.a{background:var(--amber)} .dot.r{background:var(--red)}
.cps{font-size:11px;color:var(--muted)}
/* editor panel */
.editor{border:1px solid var(--line);border-radius:12px;background:var(--panel);padding:16px;position:sticky;top:10px}
.editor .ctx{color:var(--muted);font-size:12px;padding:6px 8px;border-left:2px solid var(--line);margin-bottom:6px}
.editor .ts{color:var(--muted);font-size:12px;margin-bottom:8px}
.editor textarea{width:100%;min-height:70px;background:var(--panel2);border:1px solid var(--line);color:var(--ink);border-radius:9px;padding:9px;font-size:14px;resize:vertical}
.editor .issue{font-size:12px;padding:6px 8px;border-radius:8px;margin:6px 0;background:#20242e}
.editor .issue.r{border-left:3px solid var(--red)} .editor .issue.a{border-left:3px solid var(--amber)}
/* Not a defect: dubbing direction and non-actionable measurements. */
.editor .issue.note{border-left:3px solid #3a4152;opacity:.85}
.editor .sugg{background:#182234;border:1px solid #26406b;border-radius:8px;padding:9px;margin:8px 0}
.editor .acts{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}
.bulkbar{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}
.toast{position:fixed;bottom:18px;right:18px;background:#20242e;border:1px solid var(--line);padding:11px 15px;border-radius:10px;z-index:50;max-width:360px}
.toast.err{border-color:var(--red)}
.stepbar{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:18px}
.step{padding:8px 12px;border:1px solid var(--line);border-radius:9px;background:var(--panel);font-size:13px;color:var(--muted)}
.step.active{background:var(--accent);border-color:var(--accent);color:#fff}
.step.done{border-color:var(--green);color:var(--ink)}
.banner{background:#2a1d1d;border:1px solid #5a3030;color:#ffd9d9;padding:10px 14px;border-radius:10px;margin-bottom:12px;display:flex;justify-content:space-between;align-items:center;gap:10px}
.progress{height:8px;background:#222836;border-radius:6px;overflow:hidden;margin:8px 0}
.progress i{display:block;height:100%;background:var(--accent);width:0}
.muted{color:var(--muted)}
.spin{width:16px;height:16px;border:2px solid #2a2f3a;border-top-color:var(--accent);border-radius:50%;animation:sp .8s linear infinite;display:inline-block;vertical-align:middle}
@keyframes sp{to{transform:rotate(360deg)}}

/* --- bulk repair centre (Хадмал шалгалт) ------------------------------- */
.repair-centre { margin: 10px 0 14px; }
.repair-groups { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 10px; }
.repair-group { border: 1px solid #2a3550; border-radius: 8px; padding: 8px 10px; }
.repair-group h4 { margin: 0 0 6px; font-size: 12px; letter-spacing: .04em; display: flex; justify-content: space-between; }
.repair-group h4 span { background: rgba(255,255,255,.10); border-radius: 8px; padding: 0 7px; }
.repair-group.auto { border-color: #2f7a52; }
.repair-group.ai { border-color: #2f5fa8; }
.repair-group.manual { border-color: #8a6a1f; }
/* Deliberately grey, not a warning colour: neither is a defect. */
.repair-group.dubbing, .repair-group.info { border-color: #3a4152; opacity: .82; }
.repair-group ul { list-style: none; margin: 0; padding: 0; }
.repair-group li { display: flex; justify-content: space-between; align-items: center; font-size: 12px; padding: 1px 0; }
.repair-group .linkish { background: none; border: 0; color: inherit; padding: 0; cursor: pointer; text-decoration: underline dotted; font: inherit; }
.repair-counts { display: flex; gap: 8px; flex-wrap: wrap; margin: 10px 0 6px; font-size: 12px; }
.repair-counts span { background: rgba(255,255,255,.06); border-radius: 8px; padding: 2px 8px; }
.repair-actions { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 6px; }
.repair-note { font-size: 12px; opacity: .85; min-height: 16px; }

.proj-identity { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; font-size: 12px; margin: 2px 0 4px; }
.proj-identity b { background: rgba(255,255,255,.10); border-radius: 8px; padding: 1px 8px; }
.proj-identity.copy b { background: rgba(217,169,42,.30); }
.proj-identity em { opacity: .85; font-style: normal; }
.repair-reasons { display: flex; gap: 8px; flex-wrap: wrap; font-size: 12px; opacity: .85; margin-bottom: 6px; }
.repair-reasons i { font-style: normal; opacity: .7; }
.repair-ai-cats { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin: 8px 0 6px; font-size: 12px; }

/* --- root cause + its consequences, in ONE card ------------------------- */
.root-causes { margin: 10px 0 12px; }
.root-causes ul { list-style: none; margin: 6px 0 0; padding: 0; }
.root-cause { border: 1px solid #2a3550; border-left: 3px solid var(--red); border-radius: 8px;
  padding: 8px 10px; margin-bottom: 6px; font-size: 12px; }
.root-cause .root { margin-bottom: 4px; }
.root-cause .consequences { list-style: none; margin: 0 0 6px; padding: 0 0 0 14px; opacity: .82; }
.root-cause .consequences li { padding: 1px 0; }

/* --- two-subtitle check (Хоёр хадмалыг шалгах) -------------------------- */
.dual-check { margin: 10px 0 12px; }
.dual-list { list-style: none; margin: 8px 0 4px; padding: 0; display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 2px 14px; }
.dual-list li { display: flex; gap: 6px; align-items: center; font-size: 12px; padding: 1px 0; }
.dual-list li b { margin-left: auto; }
.dual-list li small { flex-basis: 100%; opacity: .7; }
.dual-note { font-size: 12px; opacity: .85; min-height: 16px; margin: 4px 0 0; }
.dual-note.err { color: #ff9b9b; opacity: 1; }
.sel-sm { background: #12131f; color: inherit; border: 1px solid #2a3550; border-radius: 8px;
  padding: 4px 8px; font: inherit; font-size: 12px; max-width: 260px; }

/* --- экспортын төлөв (idle → preparing → saving → success/cancel/error) -- */
.export-status:empty { display: none; }
.export-status { margin-top: 8px; font-size: 12px; line-height: 1.5;
  border-left: 3px solid #2a3550; padding: 6px 10px; border-radius: 0 6px 6px 0;
  background: rgba(255,255,255,.03); word-break: break-all; }
.export-status .ok { color: #6fd39a; }
.export-status .err { color: #ff8a8a; }
.export-status .btn { margin-right: 6px; }

/* --- мөр шилжилт ба өгүүлбэрийн хуваарилалт ---------------------------- */
.align-check { margin: 10px 0 12px; }
.align-counts { display: flex; gap: 8px; flex-wrap: wrap; margin: 10px 0 6px; font-size: 12px; }
.align-counts span { background: rgba(255,255,255,.06); border-radius: 8px; padding: 2px 8px; }
.align-counts .ok { background: rgba(47,122,82,.25); }
.align-windows { display: flex; flex-direction: column; gap: 8px; }
.align-window { border: 1px solid #2a3550; border-left: 3px solid #3a4152; border-radius: 8px;
  padding: 8px 10px; font-size: 12px; }
/* Colour says what the reviewer may do, not how bad it is. */
.align-window.auto { border-left-color: #2f7a52; }
.align-window.review { border-left-color: #d9a92a; }
.align-window.manual { border-left-color: #8a6a1f; }
/* A window whose last AI attempt failed: retryable on its own, and only it. */
.align-window.failed { border-left-color: #a33; }
.align-head { display: flex; gap: 8px; align-items: center; cursor: pointer; flex-wrap: wrap; }
.align-head .align-band { margin-left: auto; background: rgba(255,255,255,.10);
  border-radius: 8px; padding: 1px 8px; }
.align-head .align-ai { background: rgba(255,255,255,.06); border-radius: 8px;
  padding: 1px 8px; font-size: 11px; }
.align-head .align-ai.ok { background: rgba(47,122,82,.25); }
/* Why THIS window's AI request failed, and whether trying again can help. */
.align-failure details { margin-top: 4px; }
.align-failure summary { cursor: pointer; font-size: 11px; opacity: .8; }
/* The AI-free repair panel: the path that works with no provider at all. */
.align-manual:empty { display: none; }
.manual-box { border: 1px solid #2a3550; border-radius: 8px; padding: 8px 10px; margin: 6px 0;
  background: rgba(255,255,255,.02); }
.manual-ops { display: grid; gap: 4px; margin: 4px 0; }
.manual-op { display: flex; gap: 8px; align-items: flex-start; padding: 3px 4px; border-radius: 6px; }
.manual-op:hover { background: rgba(255,255,255,.04); }
.manual-op.off { opacity: .45; }
.manual-op small { display: block; font-size: 11px; }
.manual-plan { margin-top: 6px; }
.manual-plan tr.chg td { background: rgba(47,122,82,.14); }
.ast-steps { list-style: none; padding: 0; margin: 6px 0 0; display: grid; gap: 3px; }
.ast-steps li { display: flex; gap: 6px; align-items: center; }
.align-types { display: flex; gap: 6px; flex-wrap: wrap; margin: 4px 0; }
.align-types span { background: rgba(217,169,42,.20); border-radius: 8px; padding: 1px 7px; }
.align-reason { margin: 4px 0 6px; opacity: .9; }
.align-table { margin-top: 6px; overflow-x: auto; }
.align-table > b { font-size: 11px; letter-spacing: .05em; opacity: .7; }
.align-table table { border-collapse: collapse; width: 100%; margin-top: 2px; }
.align-table th { text-align: left; font-weight: 500; opacity: .6; padding: 2px 6px 2px 0;
  border-bottom: 1px solid #2a3550; }
.align-table td { padding: 2px 6px 2px 0; vertical-align: top; }
.align-table td.src { opacity: .75; }
.align-table td.new { color: #9fe0b6; }
.align-table td.num { white-space: nowrap; opacity: .7; }

/* ---- «Хадмал бэлтгэх» wizard (the simple main subtitle screen) ----
   Deliberately plain: one step name, three numbers, four buttons. Everything
   technical is inside .wz-details and nowhere else on this screen. */
.wz-now{max-width:900px;margin-bottom:14px}
.wz-eyebrow{color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:.5px}
.wz-title{margin:6px 0 4px;font-size:24px}
.wz-hint{margin:0 0 12px;font-size:13px;line-height:1.6;max-width:680px}
.wz-tiles{margin:0 0 8px}
.wz-bar{height:6px;background:var(--chip);border-radius:4px;overflow:hidden;margin:0 0 16px;max-width:520px}
.wz-bar i{display:block;height:100%;background:var(--accent);border-radius:4px}
.wz-actions{margin:0 0 10px}
.wz-paid{font-size:12px;margin:0 0 10px}
.wz-note{font-size:12px;min-height:16px}
.wz-note.err{color:var(--red)}
.wz-badges{display:flex;gap:8px;flex-wrap:wrap;margin:6px 0}
.wz-badge{font-size:12px;padding:6px 11px;border-radius:20px;border:1px solid var(--line);background:var(--panel2);color:var(--ink)}
.wz-badge.ok{color:var(--green);border-color:rgba(57,192,122,.4);background:rgba(57,192,122,.08)}
.wz-badge.muted{color:var(--muted)}

.wz-steps{max-width:900px;padding:6px 0;margin-bottom:14px}
.wz-step{display:flex;align-items:center;gap:12px;padding:12px 18px;cursor:pointer}
.wz-step+.wz-step{border-top:1px solid var(--line)}
.wz-step:hover{background:rgba(91,140,255,.06)}
.wz-step.now{background:rgba(91,140,255,.10)}
.wz-num{width:25px;height:25px;border-radius:50%;border:1.5px solid #3a4560;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:600;color:var(--muted);flex:none}
.wz-step.done .wz-num{border-color:var(--green);color:var(--green)}
.wz-step.now .wz-num{border-color:var(--accent);color:var(--accent)}
.wz-name{flex:1;min-width:0;font-size:13.5px}
.wz-ct{font-size:12px;font-weight:600;color:var(--amber);background:rgba(230,181,58,.12);border-radius:8px;padding:3px 8px}
.wz-state{font-size:12px;color:var(--muted);min-width:120px;text-align:right}
.wz-step.done .wz-state{color:var(--green)}

.wz-details{max-width:900px}
.wz-details>summary{cursor:pointer;color:var(--muted);font-size:13px}
.wz-details[open]>summary{color:var(--ink);margin-bottom:12px}
.wz-dt{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:18px}
.wz-dth{font-size:11px;text-transform:uppercase;letter-spacing:.5px;color:var(--muted);margin-bottom:6px}
.wz-dtrow{display:flex;justify-content:space-between;gap:12px;font-size:12px;color:var(--muted);padding:3px 0}
.wz-dtrow b{color:var(--ink);text-align:right;word-break:break-all}

/* ---- step 1: видео → эх хадмал (local ASR) ---- */
.wz-asr{max-width:900px;margin-bottom:14px}
.asr-grid{display:grid;grid-template-columns:minmax(220px,300px) 1fr;gap:18px;align-items:start}
@media (max-width:760px){.asr-grid{grid-template-columns:1fr}}
.asr-video video{width:100%;border-radius:10px;background:#000;display:block}
.asr-novideo{border:1.5px dashed #333b4d;border-radius:10px;padding:26px 14px;text-align:center;color:var(--muted);font-size:12.5px;line-height:2}
.asr-controls{display:flex;flex-direction:column;gap:10px;min-width:0}
.asr-label{font-size:12px;color:var(--muted)}
.asr-controls select{background:var(--panel2);color:var(--ink);border:1px solid var(--line);border-radius:9px;padding:8px 10px;font-size:13px;max-width:260px}
.asr-buttons{gap:8px}
.asr-progress{background:var(--panel2);border:1px solid var(--line);border-radius:10px;padding:10px 12px;display:flex;flex-direction:column;gap:7px}
.asr-progress .wz-bar{margin:0;max-width:none}
.asr-progress-row{display:flex;justify-content:space-between;align-items:center;gap:10px;font-size:12px;flex-wrap:wrap}
.asr-note{font-size:12px;color:var(--muted);background:var(--panel2);border:1px solid var(--line);border-radius:9px;padding:8px 11px;line-height:1.5}
.asr-note.err{color:var(--red);border-color:rgba(255,107,107,.35)}
.asr-tiles{margin:2px 0 0}
.asr-tiles .tile{min-width:110px}

.asr-review{margin-top:14px;border-top:1px solid var(--line);padding-top:12px}
.asr-review-head{font-size:12px;color:var(--muted);margin-bottom:8px}
.asr-cue{display:flex;align-items:center;gap:12px;padding:8px 10px;border-radius:9px;cursor:pointer;font-size:13px}
.asr-cue:hover{background:rgba(91,140,255,.08)}
.asr-cue-t{font-variant-numeric:tabular-nums;color:var(--accent);min-width:52px}
.asr-cue-x{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.asr-cue-why{font-size:11px;color:var(--amber);background:rgba(230,181,58,.12);border-radius:8px;padding:3px 8px;white-space:nowrap}

/* ── structural review: two subtitles, side by side ───────────────────────── */
/* One row per SOURCE cue. Four states, four counters, three buttons — the old
   screen's dozen detectors and dozen small buttons are still in «Нарийвчилсан
   засвар», because none of them answered "which line is this and is it in the
   right place?", which is the only question this screen exists for. */
.st { display: flex; flex-direction: column; gap: 14px; }

/* THE FILM TAKES ITS OWN SHAPE — the same instrument the dubbing editor got.
 *
 * This was a full-width black box capped at 34vh with the picture centred in
 * it. On a 9:16 film that is a 207px-wide picture floating in a 1636px black
 * band: the film is a thumbnail and the band is most of the screen. Measured
 * on the OCR screen — the screen a subtitle job is actually worked on — it was
 * 207×367 out of a 1920×1080 window.
 *
 * The box now carries the media's REAL ratio in `--st-ar`, set from
 * videoWidth/videoHeight the moment metadata arrives, and takes its size from
 * the viewport HEIGHT so the WIDTH follows the picture instead of the column.
 * No band, and a vertical film is not a second-class citizen here either. */
.st-video { display: flex; align-items: center; justify-content: center;
  background: #000; border-radius: 10px; overflow: hidden;
  aspect-ratio: var(--st-ar, 16 / 9);
  height: var(--st-h, clamp(240px, 38vh, 480px));
  /* `width: auto` + auto side margins: in a column flex parent an auto margin
     is what stops `align-items: stretch` from widening the box back to the
     full column and undoing the aspect ratio. */
  width: auto; max-width: 100%; margin: 0 auto; align-self: center; }
.st-video video { display: block; width: 100%; height: 100%; object-fit: contain;
  max-height: none; max-width: none; }
/* §25 — THE ONLY LOADING STATE A SEEK GETS, and only while the browser is
 * genuinely short of data.
 *
 * A row click is a seek on a mounted player, so on a buffered or local film
 * there is nothing to wait for and nothing should be drawn — a spinner that
 * appears on every click is a spinner that teaches people the app is slow when
 * it is not. `cue_player.js` puts this class on at `waiting`/`stalled` and takes
 * it off at `playing`/`seeked`/`canplay`, so it cannot be left behind.
 *
 * A corner mark rather than a centred overlay: it must not cover the frame the
 * user is trying to look at, and it must not move the Mongolian subtitle. Both
 * players that can hold a preview carry it, from one rule. */
.st-video, .sw-stage, .de-stage { position: relative; }
.st-video.cue-busy::after, .sw-stage.cue-busy::after, .de-stage.cue-busy::after {
  content: ""; position: absolute; top: 10px; right: 10px;
  width: 14px; height: 14px; border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, .28); border-top-color: #fff;
  animation: sp .8s linear infinite; pointer-events: none; z-index: 3; }

/* A vertical film is taller for the same amount of picture. */
.st-video.st-portrait { --st-h: clamp(300px, 44vh, 520px); }
/* An audio-only project has no picture to shape. */
.st-video:has(audio) { aspect-ratio: auto; height: auto; width: 100%; }

/* Auto / 9:16 / 16:9 — the workspace shape, for checking a vertical delivery
   of a landscape film without leaving the screen. «Авто» is the default and
   means the film's own ratio, which is what the `--st-ar` above computes. */
.st-stagebar { display: flex; align-items: center; gap: 8px; margin: 0 auto 8px; }
.st-aspect { display: inline-flex; gap: 0; border: 1px solid var(--line, #262a36);
  border-radius: 9px; overflow: hidden; }
.st-aspect button { background: transparent; border: 0; color: inherit; cursor: pointer;
  font: inherit; font-size: 12px; padding: 5px 11px; }
.st-aspect button.on { background: var(--accent, #4c7dff); color: #fff; }

/* ══ THE FIXED SUBTITLE WORKSPACE ═════════════════════════════════════════
 *
 * One screen, and it does not move. The shell already gave `.main` the
 * viewport's height, but every subtitle screen was a tall column inside it, so
 * `.main` scrolled — and from a chair that IS the page scrolling: the player
 * left the top of the window, the counters and filters went with it, and the
 * work moved under the cursor. `position: sticky` on the film was a patch over
 * that, and it never fixed the rest.
 *
 * So the screen is a grid the size of the workspace, and NOTHING in it scrolls
 * except the two lists:
 *
 *   portrait          landscape
 *   ┌──────┬───────┐  ┌──────────────┬────────┐
 *   │      │ side  │  │              │ side   │
 *   │media ├───────┤  │    media     │        │
 *   │      │ band  │  ├──────────────┴────────┤
 *   │      ├───────┤  │        band           │
 *   │      │ list ↕│  ├───────────────────────┤
 *   └──────┴───────┘  │        list ↕         │
 *                     └───────────────────────┘
 *
 * `minmax(0, 1fr)` on the list row and `min-height: 0` on every ancestor of a
 * scrollport are the whole trick: without them a grid/flex child refuses to be
 * shorter than its content and the overflow lands back on the page. */
.main.sw-host { padding: 0; overflow: hidden; }
/* THREE SECTIONS THE USER OWNS.
 *
 * The heights come from variables so each boundary can be dragged, and the last
 * section is `1fr` so the rows always run to the bottom of the workspace — no
 * panel stops early and leaves the background showing under it. Each section
 * scrolls its own overflow, so a section with many buttons moves inside itself
 * instead of pushing the others off the screen. */
.sw { height: 100%; min-height: 0; display: grid; gap: 10px; padding: 10px 14px 12px;
  overflow: hidden; position: relative;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-rows: var(--sw-r1, auto) var(--sw-r2, auto) minmax(0, 1fr);
  grid-template-areas: "media side" "media band" "media list"; }

/* The grab bars. Absolutely placed over the boundary so the grid keeps exactly
   three rows and no layout depends on them existing. */
.sw-grip { position: absolute; z-index: 5; height: 10px; cursor: row-resize;
  border-radius: 5px; background: transparent; }
.sw-grip::after { content: ""; position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%); width: 46px; height: 3px; border-radius: 3px;
  background: var(--line); transition: background .12s ease; }
.sw-grip:hover::after, .sw-grip.on::after { background: var(--accent); }
.sw.sw-landscape {
  grid-template-columns: minmax(0, 1fr) minmax(230px, 310px);
  grid-template-areas: "media side" "band band" "list list"; }
/* A project with no film — a subtitle job that was never given a video — is
   one column. The empty half used to hold an aspect control over nothing. */
.sw.sw-nomedia, .sw.sw-nomedia.sw-landscape {
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas: "side" "band" "list"; }
.sw.sw-nomedia .sw-media { display: none; }
.sw.sw-nomedia .sw-side { max-height: min(30vh, 260px); }

.sw-media { grid-area: media; display: flex; flex-direction: column; gap: 8px;
  min-height: 0; min-width: 0; width: var(--sw-media-w, auto); }
.sw.sw-landscape .sw-media { width: auto; align-items: center; }
/* THE SUMMARY COLUMN IS BOUNDED, and that is what protects the list.
 *
 * It is an `auto` grid row, so on a 768px screen a screen with a source badge,
 * a done panel, two toolbars and three notes took 400px of the workspace and
 * left the list two pixels of it — a scrollport in name only. Capped, it
 * scrolls its own overflow instead of taking the list's height. In landscape
 * it shares a row with the film, so its ceiling is the film's own height:
 * that way the row is the size of the picture and never more. */
.sw-side { grid-area: side; min-width: 0; min-height: 0; display: flex;
  flex-direction: column; gap: 8px; overflow-y: auto; overflow-x: hidden;
  max-height: min(38vh, 330px); }
/* Once a boundary has been dragged the row IS the height, and a ceiling from
   the default sizing would fight it. */
.sw.sw-sized .sw-side, .sw.sw-sized.sw-landscape .sw-side { max-height: none; }
.sw.sw-sized .sw-band-in, .sw.sw-sized.sw-landscape .sw-band-in { max-height: none; }
.sw.sw-sized .sw-band { min-height: 0; overflow: hidden; display: flex; }
.sw.sw-sized .sw-band > * { min-height: 0; flex: 1 1 auto; overflow-y: auto; }
.sw.sw-landscape .sw-side { max-height: var(--sw-stage-h, 330px); }
@media (max-height: 800px) {
  .sw-side { max-height: min(30vh, 240px); }
}
/* §3 — the step this screen is, and the two beside it. Compact on purpose:
   it is orientation, and the summary column it sits at the top of is capped. */
.sw-stagebar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 7px 9px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--panel, #14161e); font-size: 11.5px; }
.sw-stagebar .btn { flex: none; }
.sw-stage-now { flex: 1 1 auto; min-width: 0; display: flex; flex-wrap: wrap;
  align-items: baseline; gap: 6px; }
.sw-stage-now b { font-size: 12px; }
.sw-stage-ok { color: #43cf90; }
.sw-stage-todo { color: #e8ab3f; }
.sw-stage-note { color: var(--muted); }
.sw-stage-end { color: var(--muted); opacity: .6; padding: 0 4px; }

.sw-band { grid-area: band; min-width: 0; }
/* A FLOOR, so nothing above can squeeze the list to two rows. The film and the
   band both scroll or shrink; the list is where the work happens and it keeps
   a readable height on both screens this is used on. */
.sw-list { grid-area: list; min-height: 220px; min-width: 0;
  overflow-y: auto; overflow-x: hidden;
  border: 1px solid var(--line); border-radius: 10px; background: var(--panel); }
@media (max-height: 800px) { .sw-list { min-height: 180px; } }

/* The film. Its box is given an exact pixel size by `swLayout()` — computed
   from the workspace's real height and the media's real ratio — so the picture
   fills it and the black band that used to surround a vertical film is gone by
   construction rather than by a max-height that happened to fit. */
.sw-stage { flex: none; background: #000; border-radius: 10px; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  width: var(--sw-stage-w, 100%); height: var(--sw-stage-h, auto);
  align-self: center; }
.sw-stage video { display: block; width: 100%; height: 100%; object-fit: contain; }
.sw-stage audio { width: 100%; }
/* An audio-only project has no picture to shape. */
.sw-stage.sw-audio { height: auto; width: 100%; background: transparent; }

.sw-head { display: flex; align-items: baseline; gap: 10px; flex: none; }
.sw-head h2 { font-size: 15px; margin: 0; }
.sw-head p { margin: 0; font-size: 11.5px; color: var(--muted); }
.sw-bar { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; flex: none; }
.sw-bar .grow { flex: 1; }
/* A toolbar is allowed two lines and no more; past that it is scrolled
   sideways rather than being allowed to push the film down the screen. */
.sw-tools { max-height: 68px; overflow-y: auto; }

/* Counters: pills, not cards. Six of these as `.st-meter` cards cost 96px of
   height each and three rows of the workspace. */
.sw-counts { display: grid; gap: 5px; grid-template-columns: repeat(2, minmax(0, 1fr));
  flex: none; }
.sw-count { display: flex; align-items: baseline; gap: 6px; padding: 4px 8px;
  border: 1px solid var(--line); border-radius: 8px; background: var(--panel2);
  font-size: 11px; color: var(--muted); line-height: 1.25; min-width: 0; }
.sw-count b { font-size: 14px; color: var(--ink); font-variant-numeric: tabular-nums; }
.sw-count span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sw-count.g b { color: var(--green); }
.sw-count.w b { color: var(--amber); }
.sw-count.r b { color: var(--red); }

/* The row under review, lifted OUT of the list. In the list it was a card that
   grew to several hundred pixels, pushed every other row down and took the
   scroll position with it every time the selection moved. */
.sw-band-in { border: 1px solid var(--line); border-radius: 10px;
  background: var(--panel); padding: 8px 10px; display: grid; gap: 8px;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(240px, 22rem);
  /* A ceiling in BOTH units. `34vh` alone is 261px on a 768px screen, and in
     the landscape layout — where the band spans the full width under the film
     — that left the list 58px tall: a scrollport too short to read a row in.
     The band scrolls itself past this height rather than taking the list's. */
  max-height: min(26vh, 200px); overflow-y: auto; }
.sw.sw-landscape .sw-band-in { max-height: min(20vh, 170px); }
@media (max-height: 800px) {
  .sw-band-in { max-height: min(20vh, 155px); }
  .sw.sw-landscape .sw-band-in { max-height: min(18vh, 135px); }
}
.sw-band-in.sw-band-2 { grid-template-columns: minmax(0, 1fr) minmax(240px, 24rem); }
.sw-band-in.sw-band-1 { grid-template-columns: minmax(0, 1fr); }
.sw-band-col { min-width: 0; display: grid; gap: 4px; align-content: start; }
.sw-band-k { font-size: 10.5px; letter-spacing: .06em; color: var(--muted);
  text-transform: uppercase; }
.sw-band-empty { color: var(--muted); font-size: 12px; font-style: italic; }
.sw-band .sc-tools, .sw-band .lq-tools { margin-top: 0; }
.sw-ctx { font-size: 11.5px; color: var(--muted); display: flex; gap: 6px;
  cursor: pointer; }
.sw-ctx b { color: #8d97ad; font-weight: 500; flex: none; }
.sw-ctx span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sw-cur { font-size: 13px; line-height: 1.35; }
.sw-cur .st-time { font-size: 11px; color: var(--muted);
  font-variant-numeric: tabular-nums; }

/* Compact timing, as two labelled runs of nudges. */
.sw-timing { display: grid; gap: 3px; grid-template-columns: auto repeat(4, auto);
  align-items: center; justify-content: start; }
.sw-timing .k { font-size: 10.5px; color: var(--muted); padding-right: 4px; }
.sw-timing .btn { padding: 3px 7px; font-size: 11px; }

/* THE LIST — TWO LINES PER ROW, and that is the point.
 *
 * It used to be one: flag | #index+timestamp | source | Mongolian | badge, with
 * the whole stamp «#55  00:04:29,900 → 00:04:30,200» squeezed into a 96px
 * column and told `white-space: nowrap` with nothing to clip it. A nowrap
 * column narrower than its text does not shrink and does not scroll — it PAINTS
 * OVER THE NEXT ONE, so at 1366×768 the timestamp lay across the source
 * sentence and both were unreadable. Widening the column only moves the problem
 * to whichever screen has the longest stamp.
 *
 * So the row header — number, time, state — gets a line of its own across the
 * full width, and the two texts get the line under it, one column each. Nothing
 * has to be narrower than its content, so nothing can overlap anything.
 * Five children in a fixed order, shared by all five lists that draw one. */
.sw-list .st-row, .sw-list .sw-row { padding: 5px 9px; }
.sw-row { display: grid; cursor: pointer; font-size: 12.5px;
  border-bottom: 1px solid var(--line); border-left: 3px solid transparent;
  grid-template-columns: 20px minmax(0, 1fr) minmax(0, 1fr) auto;
  grid-template-areas:
    "flag meta  meta  badge"
    "flag texta textb badge";
  column-gap: 8px; row-gap: 2px;
  align-items: start; padding: 5px 9px; }
.sw-row > * { min-width: 0; }
.sw-row > *:nth-child(1) { grid-area: flag; }
.sw-row > *:nth-child(2) { grid-area: meta; }
.sw-row > *:nth-child(3) { grid-area: texta; }
.sw-row > *:nth-child(4) { grid-area: textb; }
.sw-row > *:nth-child(5) { grid-area: badge; align-self: start; }
.sw-row:hover { background: rgba(255, 255, 255, .03); }
.sw-row.active { background: rgba(91, 140, 255, .12); border-left-color: var(--accent); }
.sw-row.g { border-left-color: var(--green); }
.sw-row.w { border-left-color: var(--amber); }
.sw-row.r { border-left-color: var(--red); }
.sw-row.ok { opacity: .72; }
.sw-row .sw-t { font-size: 11px; color: var(--muted);
  font-variant-numeric: tabular-nums; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; }
/* Two lines, then an ellipsis. A subtitle is at most two lines on screen, so a
   row that shows two of them has shown the line. */
.sw-row .sw-x { min-width: 0; overflow: hidden; overflow-wrap: anywhere;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
.sw-row .sw-x.mute { color: var(--muted); font-style: italic; }
/* The index range that says what was joined to what. */
.sw-row .sw-lab { color: var(--muted); font-weight: 500; font-size: 11px; }
.sw-row .sw-s { white-space: nowrap; font-size: 11px; text-align: right; }
.sw-row .sw-n { color: var(--fg, #e8eaf0); font-weight: 600; }
.sw-row .sw-sep { opacity: .35; margin: 0 5px; }
.sw-empty { padding: 20px; color: var(--muted); }

/* Under about 1100px the two columns stop being two columns. The workspace
   still does not scroll — the list simply takes what is left. */
@media (max-width: 1100px) {
  .sw, .sw.sw-landscape { grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto auto auto minmax(0, 1fr);
    grid-template-areas: "media" "side" "band" "list"; }
  .sw-media { width: auto; align-items: center; }
  .sw-band-in { grid-template-columns: minmax(0, 1fr); max-height: 30vh; }
  /* Narrow: the two texts stack instead of sharing a line. The header keeps
     its own line either way, which is what stops the overlap. */
  .sw-row { grid-template-columns: 20px minmax(0, 1fr) auto;
    grid-template-areas: "flag meta badge" "flag texta texta" "flag textb textb"; }
}

.st-meters { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
.st-meter { background: var(--card, #14161e); border: 1px solid var(--line, #262a36);
  border-radius: 10px; padding: 10px 12px; }
.st-meter-k { font-size: 11px; opacity: .65; letter-spacing: .04em; }
.st-meter-v { font-size: 21px; font-weight: 600; margin-top: 3px; }
.st-meter.g .st-meter-v { color: #43cf90; }
.st-meter.y .st-meter-v { color: #e8ab3f; }
.st-meter.r .st-meter-v { color: #ef6a6a; }

.st-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.st-actions .grow { flex: 1; }

.st-head { display: flex; gap: 12px; padding: 0 4px; font-size: 11px; letter-spacing: .06em;
  opacity: .6; }
.st-head .st-flag { visibility: hidden; }

.st-rows { border: 1px solid var(--line, #262a36); border-radius: 10px;
  background: var(--card, #14161e); max-height: 58vh; overflow: auto; }
.st-row { display: flex; gap: 12px; padding: 10px 12px; cursor: pointer;
  border-bottom: 1px solid var(--line, #262a36); border-left: 3px solid transparent; }
.st-row:hover { background: rgba(255, 255, 255, .03); }
.st-row.sel { background: rgba(255, 255, 255, .06); }
.st-row.g { border-left-color: #43cf90; }
.st-row.y { border-left-color: #e8ab3f; }
.st-row.r { border-left-color: #ef6a6a; }
.st-flag { flex: none; width: 20px; text-align: center; font-size: 14px; }
.st-flag.g { color: #43cf90; }
.st-flag.y { color: #e8ab3f; }
.st-flag.r { color: #ef6a6a; }
.st-side { flex: 1 1 0; min-width: 0; }
.st-num { font-size: 11.5px; font-weight: 600; opacity: .7; }
.st-time { font-size: 11px; opacity: .5; font-variant-numeric: tabular-nums; }
.st-text { font-size: 13px; margin-top: 3px; white-space: pre-wrap; word-break: break-word; }
.st-text.src { opacity: .85; }
.st-text.none { color: #ef6a6a; font-style: italic; }
/* The timestamps the translation arrived with, and what a repair moved: small
   and dim on purpose — the source timing above them is the authoritative one. */
.st-was { font-size: 10.5px; opacity: .5; margin-top: 4px; line-height: 1.5;
  font-variant-numeric: tabular-nums; }
.st-badge { display: inline-block; margin-left: 6px; padding: 1px 6px; border-radius: 999px;
  background: rgba(232, 171, 63, .16); color: #e8ab3f; font-size: 10.5px; }

/* The repair tools, in the order the ROW needs them: the sentence saying what
   is wrong, the fix for it, the three every row has, and the rest folded away.
   Flat, wrapping and padded away from the left edge, they read as nine equal
   options and a user cannot tell which is safe. */
.st-tools { display: flex; flex-direction: column; align-items: stretch; gap: 6px;
  padding: 2px 0 8px; }
.st-tools > .btn { width: 100%; justify-content: center; }
.st-tools-line { display: flex; gap: 6px; flex-wrap: wrap; }
.st-tools-line .btn { flex: 1 1 auto; }
.st-why { font-size: 11.5px; color: var(--muted); line-height: 1.45; }
.st-more { margin-top: 2px; }
.st-more > summary { cursor: pointer; font-size: 11.5px; color: var(--muted);
  padding: 3px 0; list-style: none; }
.st-more > summary::-webkit-details-marker { display: none; }
.st-more > summary:hover { color: var(--fg, #e8eaf0); }
.st-more .st-tools-line { margin-top: 6px; }
.ct-k { font-size: 11px; color: var(--muted); min-width: 46px; }
.ct-note { min-height: 15px; font-size: 11px; color: var(--muted); margin-top: 6px;
  line-height: 1.4; }
.ct-note.warn { color: #e8ab3f; }

.st-loose { border: 1px solid var(--line, #262a36); border-radius: 10px; padding: 12px 14px;
  background: var(--card, #14161e); max-height: 24vh; overflow: auto; }
.st-loose-row { display: flex; gap: 10px; padding: 5px 0; font-size: 12.5px;
  border-bottom: 1px solid var(--line, #262a36); }

@media (max-width: 1000px) {
  .st-meters { grid-template-columns: repeat(2, 1fr); }
  .st-row { flex-wrap: wrap; }
  .st-side { flex-basis: 100%; }
}
.btn.small { padding: 6px 11px; font-size: 12px; }

/* The plain statement of what a repair achieved, and the way onward. */
.st-done { border: 1px solid #2c6b4f; border-radius: 10px; padding: 14px 16px;
  background: rgba(67, 207, 144, .07); }
.st-done h3 { margin: 0 0 10px; font-size: 15px; color: #43cf90; }
.st-done-row { display: flex; gap: 10px; align-items: baseline; font-size: 13px; padding: 2px 0; }
.st-done-row span:first-child { color: #43cf90; }
.st-done-row span:nth-child(2) { flex: 1; opacity: .8; }
.st-done-row b { font-variant-numeric: tabular-nums; }
.st-done .btn { margin-top: 12px; }

/* ── Phase 4: three columns, five states ──────────────────────────────────── */
.lq-meters { grid-template-columns: repeat(5, 1fr); }
.lq-filters { display: flex; gap: 7px; flex-wrap: wrap; }
.lq-row .st-side { flex: 1 1 0; }
.lq-head .st-side { flex: 1 1 0; }
.lq-tools { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
.lq-gloss { border: 1px solid var(--line, #262a36); border-radius: 10px; padding: 10px 14px;
  background: var(--card, #14161e); }
.lq-gloss summary { cursor: pointer; font-size: 13px; }
.lq-gloss table { width: 100%; border-collapse: collapse; margin-top: 10px; font-size: 12.5px; }
.lq-gloss th { text-align: left; opacity: .6; font-weight: 500; padding-bottom: 6px; }
.lq-gloss td { padding: 5px 8px 5px 0; border-top: 1px solid var(--line, #262a36);
  vertical-align: top; }
@media (max-width: 1200px) { .lq-meters { grid-template-columns: repeat(3, 1fr); } }

/* Which source the comparison is against — invisible before, and sometimes wrong. */
.src-badge { display: flex; align-items: center; gap: 8px; font-size: 12.5px;
  padding: 8px 12px; border-radius: 9px; border: 1px solid var(--line, #262a36);
  background: var(--card, #14161e); }
.src-badge.ok { border-color: #2c6b4f; }
.src-badge.warn { border-color: #6b5a2c; }


/* A batch of two hundred lines, counted by the backend rather than by a clock. */
.lq-progress { border: 1px solid #3b4a6b; border-radius: 11px; padding: 12px 14px;
  background: var(--card, #14161e); display: grid; gap: 9px; }
.lq-progress-head { display: flex; align-items: center; gap: 10px; font-size: 13px; }
.lq-bar { height: 7px; border-radius: 5px; background: #1c2030; overflow: hidden; }
.lq-bar-fill { height: 100%; background: #4d7cff; transition: width .25s ease; }
.lq-fails { border: 1px solid #6b3a3a; border-radius: 11px; padding: 12px 14px;
  background: var(--card, #14161e); display: grid; gap: 7px; justify-items: start; }
.lq-failnote { font-size: 12px; color: #e08a8a; margin-top: 6px; }
.lq-row.lq-failed { border-color: #6b3a3a; }
/* Why there is no film on screen — never an empty box, never a black one. */
.sw-media-why {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 8px; text-align: center; padding: 22px 18px; height: 100%;
  box-sizing: border-box;
}
.sw-media-x { font-size: 22px; color: #e0a33a; }
.sw-media-file {
  font: 11px/1.4 ui-monospace, Consolas, monospace; word-break: break-all; opacity: .7;
}
.sw-media-fail {
  position: absolute; inset: 0; z-index: 4;
  background: rgba(10, 11, 16, .93); border-radius: inherit;
}
.sw-stage { position: relative; }

/* Search over every cue, and the way back to the rest of them. */
.lq-find {
  flex: 1; min-width: 160px; max-width: 340px;
  background: var(--field, #0f1218); color: inherit;
  border: 1px solid var(--field-border, #313846);
  border-radius: 7px; padding: 5px 9px; font: inherit; font-size: 12.5px;
}
.lq-more { padding: 10px; text-align: center; }

.lq-whys { margin-top: 7px; display: grid; gap: 3px; }
.lq-why { font-size: 12px; opacity: .72; line-height: 1.45; }
.lq-pick { margin-bottom: 5px; cursor: pointer; }
.st-done.warn { border-color: #6b5a2c; }

/* The audio screen: cards, because a person has to LISTEN before they can judge. */
/* COMPACT ROWS. A cue is one short line of text and three buttons; at 13/15px
   padding and 12px between cards a 1080p window held about five of them, so a
   256-cue reading was fifty screens of mostly empty card. The row furniture is
   unchanged — only the space around it. */
.am-cards { display: grid; gap: 7px; }
.am-card { border: 1px solid var(--line, #262a36); border-radius: 12px; padding: 9px 12px;
  background: var(--card, #14161e); display: grid; gap: 6px; }
/* THE FILM BESIDE THE READING, not above it.
 *
 * A full-size film stacked on top of a 256-cue list means the first row starts
 * below the fold and the film is gone by row 20 — which is the layout the
 * dubbing editor was rebuilt away from. The same answer applies here: the film
 * gets a column and stays in it while the list scrolls, so both are on screen
 * at once and the film can be big without costing a single row. */
/* THE SHAPE OF THE FILM DECIDES THE SHAPE OF THE SCREEN.
 *
 * A vertical film is tall and narrow, so it takes a column and the list sits
 * beside it — both on screen, and the film can be big without costing a row.
 * A landscape film is wide and short: putting THAT in a half-width column
 * makes it smaller than the box it replaced, so it keeps the full width and
 * the list goes underneath, where a short film leaves room for it.
 * `media-portrait` is set from the media's real dimensions, in shapeStages(). */
.ocr-work { display: grid; gap: 14px; align-items: start; grid-template-columns: 1fr; }
.ocr-work.media-portrait { grid-template-columns: minmax(260px, 0.5fr) minmax(0, 1fr); }
.ocr-media { position: sticky; top: 6px; }
.ocr-list { display: grid; gap: 10px; align-content: start; min-width: 0; }
/* Landscape: driven by the WIDTH it is given, so it can never be wider than
   its column and can never letterbox. Vertical: driven by height, because a
   9:16 film sized by width would be three times the height of the window. */
.st-video.st-stage-lg { width: 100%; height: auto; position: static; }
/* `--st-h` is SET here rather than passed as a var() fallback: the base
   `.st-portrait` rule already defines it for the small in-page stages, and a
   fallback only applies to a variable that is undefined. */
.ocr-work.media-portrait .st-video.st-stage-lg {
  width: auto; --st-h: clamp(340px, 72vh, 820px); height: var(--st-h); }
@media (max-width: 1180px) {
  .ocr-work.media-portrait { grid-template-columns: 1fr; }
  .ocr-media { position: static; }
  .ocr-work.media-portrait .st-video.st-stage-lg { --st-h: clamp(240px, 40vh, 480px); }
}

/* The one-line cue row: WHEN it is, WHAT it says, and what can be done to it.
   The middle column is the one that grows, because the text is the only part
   whose length varies. */
.ocrrow { grid-template-columns: minmax(190px, auto) minmax(0, 1fr) auto;
  align-items: center; gap: 10px; }
.ocr-when { display: flex; align-items: center; gap: 7px; font-size: 12.5px;
  white-space: nowrap; }
.ocr-time { opacity: .78; font-variant-numeric: tabular-nums; }
.ocr-body { display: grid; gap: 4px; min-width: 0; }
.ocr-body .am-text { min-height: 30px; }
.ocr-acts { display: flex; align-items: center; gap: 6px; }
@media (max-width: 1100px) {
  .ocrrow { grid-template-columns: 1fr; }
  .ocr-acts { justify-content: flex-end; }
}
.am-card.y { border-color: #6b5a2c; }
.am-card.r { border-color: #6b3a3a; }
.am-card.am-added { opacity: .78; border-color: #2c6b4f; }
.am-card.am-ignored { opacity: .5; }
.am-head { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; font-size: 13px; }
.am-mark { width: 24px; height: 24px; border-radius: 7px; display: grid; place-items: center;
  background: #1c2030; font-size: 13px; }
.am-mark.y { background: #3a3218; }
.am-mark.r { background: #3a1e1e; }
.am-why { font-size: 12.5px; opacity: .78; line-height: 1.5; }
.am-context { display: grid; gap: 7px; }
.am-ctx { font-size: 12.5px; opacity: .85; }
.am-heard { display: grid; gap: 5px; font-size: 12.5px; }
.am-text { width: 100%; resize: vertical; font: inherit; font-size: 13.5px;
  padding: 6px 9px; border-radius: 8px; border: 1px solid var(--line, #262a36);
  background: #101219; color: inherit; }
.am-scanned { font-size: 12.5px; }

/* A CORRECTION IN PROGRESS HAS TO LOOK LIKE ONE.
   The middle state — «Гараар засаж байна» — is the whole point of the manual
   edit: the words in the box are the user's and are not in the subtitle yet.
   A box that looks identical whether it holds the reader's text or an unsaved
   draft is how somebody walks away from a correction believing it was kept. */
.am-text.editing { border-color: #e0b784; background: #14161f;
  box-shadow: 0 0 0 2px rgba(224, 183, 132, .16); }
/* The delete question, asked in the page. Never a browser dialog: the desktop
   shell answers `window.confirm` with false without showing anything, so a real
   removal there would silently never happen. */
.ocr-ask { margin-top: 8px; display: grid; gap: 6px; }

/* THE SUBTITLE BAND, DRAWN ON THE FILM.
   The layer is sized in script to the PICTURE inside the <video>, not to the
   element, so a 9:16 film in a 16:9 stage puts the rectangle on the frame and
   the shade never falls on the letterbox. */
/* THE OVERLAY IS INERT UNTIL THE MODE IS ON, and this is the only place that
   decides it. It used to be `cursor: crosshair` and hit-testable for ever, so
   one visit to «Хадмал байгаа хэсгийг сонгох» left the crosshair on the film
   and every click, seek and play went into the drag handler instead of the
   player. `.on` is applied by `enterRegionSelection` and removed by
   `exitRegionSelection` — nowhere else toggles a cursor. */
.ocr-region-layer { position: absolute; cursor: default; touch-action: none;
  overflow: hidden; z-index: 3; pointer-events: none; }
.ocr-region-layer.on { cursor: crosshair; pointer-events: auto; }
.ocr-region-layer.on.dragging { cursor: grabbing; }
/* Saved but not being edited: the band is still SHOWN — a person has to be able
   to see the region they chose — and it is not grabbable. */
.ocr-region-layer:not(.on) .ocr-grip { display: none; }
.ocr-region-layer:not(.on) .ocr-region-box { cursor: default; }
.ocr-region-box { position: absolute; cursor: move;
  border: 1.5px solid #7fd7ff; background: rgba(127, 215, 255, .10);
  /* Everything OUTSIDE the box, darkened in one paint: the shadow is clipped by
     the layer's own overflow, so it covers the frame and nothing else. */
  box-shadow: 0 0 0 4000px rgba(0, 0, 0, .55); }
.ocr-grip { position: absolute; width: 12px; height: 12px; border-radius: 3px;
  background: #7fd7ff; border: 1px solid #05202b; }
.ocr-grip-nw { left: -6px; top: -6px; cursor: nwse-resize; }
.ocr-grip-ne { right: -6px; top: -6px; cursor: nesw-resize; }
.ocr-grip-se { right: -6px; bottom: -6px; cursor: nwse-resize; }
.ocr-grip-sw { left: -6px; bottom: -6px; cursor: nesw-resize; }
.ocr-grip-n { left: 50%; top: -6px; margin-left: -6px; cursor: ns-resize; }
.ocr-grip-s { left: 50%; bottom: -6px; margin-left: -6px; cursor: ns-resize; }
.ocr-grip-w { left: -6px; top: 50%; margin-top: -6px; cursor: ew-resize; }
.ocr-grip-e { right: -6px; top: 50%; margin-top: -6px; cursor: ew-resize; }
.ocr-region-panel .lq-tools { margin-top: 8px; }
.ocr-region-state { margin-top: 6px; }

/* A gap in the subtitles the SOUND found, being turned into a line. */
.ocr-orphan-draft { display: grid; gap: 6px; margin-top: 6px; }
.ocr-orphan-draft .am-text { min-height: 44px; }
.ocr-orphan-warn { color: #e0b784; font-size: 12px; }

/* Why a row is «Шалгах». A red mark with nothing behind it cannot be acted on. */
.st-reason { margin-top: 7px; font-size: 12px; color: #e0b784; line-height: 1.45; }
.st-evidence { display: block; margin-top: 3px; font-size: 11.5px; opacity: .6; }

/* Which of the three sources is the original — the user's answer, not a guess. */
.src-pick { flex-wrap: wrap; gap: 12px; }
.src-opt { display: inline-flex; align-items: center; gap: 5px; cursor: pointer; }
.ocr-roi { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 10px; }
.ocr-roi-f { display: grid; gap: 4px; font-size: 12px; opacity: .8; }
.ocr-roi-f input { width: 84px; padding: 6px 8px; border-radius: 7px;
  border: 1px solid var(--line, #262a36); background: #101219; color: inherit; }
.am-witness { font-size: 12.5px; color: #7fd6a4; font-weight: 500; }

/* Дүр ба хоолой — a picker, where there used to be a read-only label. */
.vs-card { border: 1px solid var(--line, #262a36); border-radius: 11px; padding: 12px 14px;
  margin-top: 10px; background: #101219; display: grid; gap: 9px; }
.vs-head { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; font-size: 13.5px; }
.vs-row { display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap; }
.vs-row label { display: grid; gap: 4px; font-size: 12px; opacity: .85; }
.vs-sel { min-width: 190px; max-width: 340px; padding: 7px 9px; border-radius: 8px;
  border: 1px solid var(--line, #262a36); background: #14161e; color: inherit;
  font-size: 13px; }
.vs-search { width: 190px; padding: 6px 9px; border-radius: 8px;
  border: 1px solid var(--line, #262a36); background: #14161e; color: inherit; }
.vs-empty { font-size: 12.5px; opacity: .7; padding: 7px 0; display: inline-block; }
.vs-warn { font-size: 12px; color: #e0b784; line-height: 1.45; }
.vs-ok { font-size: 12px; color: #7fd6a4; }
.vs-fail { font-size: 12px; color: #e08a8a; display: inline-flex; gap: 7px; align-items: center; }

/* ---------- Мөрийн засвар (dubbing line editor) ------------------------- */
/* THE JOB BAR IS NOT THE SUBJECT OF THE SCREEN. Every pixel it spends is a
   pixel the film does not get, and on a 1366x768 laptop this card was pushing
   the player 370px down the page. Same controls, same order, less air. */
.de-top { margin-bottom: 8px; padding: 9px 12px; }
.de-actions { gap: 6px; align-items: center; flex-wrap: wrap; }
.de-actions + .de-actions { margin-top: 6px; }
/* Which of the eight states the run is in, first in the row and never a guess. */
.de-state { display: inline-block; padding: 4px 10px; border-radius: 999px;
  border: 1px solid var(--line, #262a36); background: rgba(255, 255, 255, .04);
  font-size: 11.5px; letter-spacing: .03em; white-space: nowrap; }
.de-state.g { color: #43cf90; border-color: rgba(67, 207, 144, .4); }
.de-state.y { color: #e8ab3f; border-color: rgba(232, 171, 63, .45); }
.de-state.w { color: #c8cede; }
.de-state.r { color: #ef6a6a; border-color: rgba(239, 106, 106, .45); }
.de-global { gap: 10px; align-items: center; margin-top: 6px; color: var(--muted); font-size: 12.5px; }
.de-global input[type=range] { width: 200px; max-width: 40vw; }
.de-global b { color: var(--ink); min-width: 26px; }
.de-job { margin-top: 6px; }
.de-jobtext { font-size: 12.5px; color: var(--muted); }
.de-job.on .de-jobtext { color: var(--ink); }

/* THE PLAYER IS THE INSTRUMENT, so it gets the room.
 *
 * The old grid put the film in one of three equal-ish columns and capped it at
 * 40vh, which on a 1366x768 laptop left a picture too small to judge lip sync
 * in. The workspace is now two rows: the film and the line being worked on side
 * by side, and the subtitle comparison full width underneath, where a wide
 * ORIGINAL | MONGOLIAN pair actually fits. */
.dubedit { display: grid; gap: 12px; align-items: start;
  grid-template-columns: minmax(0, 1fr) minmax(300px, 380px);
  grid-template-areas: "media detail" "lines lines"; }
.de-media { grid-area: media; }
.de-detail { grid-area: detail; }
.de-lines { grid-area: lines; }

/* A vertical film is not a second-class citizen: it keeps the same workspace
   and takes its size from the viewport HEIGHT instead of the column width, so
   it never collapses into a thumbnail beside a wide panel. */
.dubedit.ar-916 { grid-template-columns: minmax(0, 1fr) minmax(320px, 1.15fr); }

@media (max-width: 1180px) {
  .dubedit, .dubedit.ar-916 { grid-template-columns: 1fr;
    grid-template-areas: "media" "detail" "lines"; }
  .de-detail { position: static; max-height: none; }
}

.de-media, .de-lines, .de-detail { background: var(--panel); border: 1px solid var(--line);
  border-radius: 14px; padding: 12px; }

/* `contain` on the video is what guarantees no distortion: the box has the
   aspect ratio, the picture keeps its own. */
.de-stage { background: #000; border-radius: 10px; overflow: hidden;
  display: flex; align-items: center; justify-content: center; }
.de-stage video { display: block; width: 100%; height: 100%; object-fit: contain; }
.de-stage audio { width: 100%; }
.ar-169 .de-stage { aspect-ratio: 16 / 9; max-height: clamp(260px, 58vh, 760px); }
/* A short-viewport rule that shrank the film here was tried and removed: it
   cost 70px of picture and revealed no extra rows, because the row's height is
   set by the DETAIL PANEL beside it, not by the film. The comparison lives one
   scroll below on a 768px screen, and the film keeps its size — which is the
   priority this layout was rebuilt for. */
.ar-916 .de-stage { aspect-ratio: 9 / 16; height: clamp(340px, 72vh, 900px);
  width: auto; max-width: 100%; margin: 0 auto; }
/* An audio-only project has no picture to shape. */
.de-stage:has(audio) { aspect-ratio: auto; height: auto; }

.de-aspect { display: inline-flex; gap: 0; border: 1px solid var(--line);
  border-radius: 9px; overflow: hidden; }
.de-aspect button { background: var(--panel2); color: var(--muted); border: 0;
  padding: 5px 12px; font: inherit; font-size: 12px; cursor: pointer; }
.de-aspect button.on { background: var(--accent); color: #0d1017; font-weight: 600; }
.de-transport { gap: 8px; margin-top: 10px; flex-wrap: wrap; }

.de-filters { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 10px; }
.de-filters .chip b { color: var(--amber, #e0b784); margin-left: 4px; }
/* SHORT LINE, SHORT ROW. Most subtitle cues are a handful of words, and a
   list where every one of them occupies a tall card shows four lines of a
   film per screen. Nothing here sets a minimum height; a row is as tall as
   its text, and a long line grows on its own. */
.de-list { max-height: clamp(220px, 46vh, 560px); overflow-y: auto;
  display: flex; flex-direction: column; gap: 4px; }
.derow { display: flex; gap: 8px; align-items: flex-start; padding: 5px 9px; border-radius: 9px;
  border: 1px solid var(--line); background: var(--panel2); cursor: pointer; }

/* ORIGINAL | MONGOLIAN, side by side — the comparison this screen exists for.
   It collapses to stacked rather than squeezing two unreadable columns. */
.derow-texts { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 14px; align-items: start; }
.derow-src { color: var(--muted); font-size: 12.5px; line-height: 1.35; overflow-wrap: anywhere; }
@media (max-width: 900px) { .derow-texts { grid-template-columns: 1fr; } }
.derow:hover { border-color: #37405a; }
.derow.sel { border-color: var(--accent); background: #1b2233; }
/* The line the FILM is on, which is not the line the user selected. Both can
   be true at once, so the marks have to be distinguishable: selection colours
   the border, playback puts a bar down the leading edge. */
.derow.playing { box-shadow: inset 3px 0 0 var(--accent); background: #17202f; }
.derow.playing.sel { background: #1b2233; }
.derow-main { flex: 1; min-width: 0; }
.de-list .derow-head { display: flex; align-items: center; gap: 6px; font-size: 11.5px;
  color: var(--muted); margin-bottom: 2px; }
.derow-text { line-height: 1.35; overflow-wrap: anywhere; }
.derow-acts { display: flex; gap: 4px; }
.deflag { font-size: 10.5px; border-radius: 20px; padding: 1px 7px; border: 1px solid var(--line); }
.deflag.warn { color: #e0b784; border-color: #55452c; }
.deflag.bad { color: #e08a8a; border-color: #5a3030; }

.de-detail { position: sticky; top: 10px; max-height: 88vh; overflow-y: auto; }

/* §18 — THE DUBBING EDITOR AS A FIXED WORKSPACE.
 *
 * The same architecture as `.sw`, for the same reason and with the same trick:
 * `minmax(0, 1fr)` on the row that scrolls and `min-height: 0` on every
 * ancestor of a scrollport. The page itself does not scroll, so the film, the
 * job controls and the selected line's panel stay exactly where they are while
 * the user works down the list. */
.de-shell { height: 100%; min-height: 0; display: grid; gap: 10px;
  padding: 10px 14px 12px; overflow: hidden;
  grid-template-rows: auto auto minmax(150px, 1fr); }
/* Four fixed rows: title+steps, job controls, the counters, and the workspace
   that takes the rest. The counters used to live inside the list's own box,
   which in a bounded layout means they take the list's height. */
.de-shell-4 { grid-template-rows: auto auto auto minmax(160px, 1fr); }
.de-shell > .de-summary-wrap { min-height: 0; }
.de-shell > .de-summary-wrap .de-summary { margin: 0; }
/* The title and the step bar, compact: on this screen they are orientation,
   not the subject, and every pixel they take comes out of the list. */
.de-shell-top { min-height: 0; max-height: min(18vh, 130px); overflow-y: auto; }
/* One line, scrolled sideways rather than wrapped onto a second. Eleven steps
   wrapped is 45px of a 768px screen, and it comes out of the film. */
.de-shell-top .stepbar { flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden;
  padding-bottom: 4px; scrollbar-width: thin; }
.de-shell-top .stepbar .step { flex: none; }
/* Compact counters, for the same reason. */
.de-shell .de-summary { margin: 0; gap: 6px; }
.de-shell .de-tile { padding: 3px 9px; }
.de-shell .de-tile b { font-size: 14px; }
.de-shell .de-tile span { font-size: 10px; }
/* Settings, folded. The run's own controls are what the top of this screen is
   for; a bulk AI pass and a quality slider are not the job. */
.de-more > summary { cursor: pointer; font-size: 12px; color: var(--muted);
  padding: 4px 0; list-style: none; }
.de-more > summary::-webkit-details-marker { display: none; }
.de-more > summary:hover { color: var(--ink, #e8eaf0); }
.de-shell-top .pagehead { margin: 0 0 6px; }
.de-shell-top .pagehead h1 { font-size: 17px; margin: 0; }
/* The job controls stay on screen — ЗОГСООХ is in here — but they cannot take
   the list's height either, so they scroll their own overflow if they must. */
.de-shell > .de-top { margin: 0; min-height: 0; max-height: min(30vh, 230px);
  overflow-y: auto; overscroll-behavior: contain; }
/* `stretch`, not `start`. With `align-items: start` a grid item keeps its
   CONTENT height and simply overflows its row, so bounding the rows achieved
   nothing: the list came out 13,966px tall inside a 120px row and the rows
   below the fold were unreachable, because the page no longer scrolls either. */
/* The panel for the selected line takes the FULL height of the right column
   and scrolls inside itself; the film and the list share the left one. That
   way the list is as tall as the workspace allows rather than as tall as
   whatever the film left over. */
/* THE SAME SHAPE AS ХАДМАЛ СТУДИ.
 *
 * Хадмал студи puts the film down the left at its own width and stacks the
 * work — the summary, the row being edited, the list — down the right. This
 * screen used to put the film across the top-left and the line list under it,
 * so the film was a wide short letterbox (1210x267 on a 1080p screen, a 474px
 * picture inside it) and the two columns disagreed about what the screen is.
 *
 * It is the same three-part shape now: film on the left spanning the workspace,
 * the line being worked on top-right, the list under it. The film gets height
 * instead of dead width, and the panel beside it gets the width the black bars
 * were wasting. */
.de-shell .dubedit { min-height: 0; height: 100%; overflow: hidden; gap: 10px;
  align-items: stretch;
  position: relative;
  grid-template-columns: minmax(0, 1.3fr) minmax(360px, 1fr);
  /* The boundary between the line being worked on and the list is the user's,
     exactly as it is in Хадмал студи; the list is `1fr` so the rows always run
     to the bottom. */
  grid-template-rows: var(--de-r1, minmax(0, 48%)) minmax(140px, 1fr);
  grid-template-areas: "media detail" "media lines"; }
.de-grip { position: absolute; z-index: 5; height: 10px; cursor: row-resize;
  border-radius: 5px; background: transparent; }
.de-grip::after { content: ""; position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%); width: 46px; height: 3px; border-radius: 3px;
  background: var(--line); transition: background .12s ease; }
.de-grip:hover::after, .de-grip.on::after { background: var(--accent); }
.de-shell .de-media { min-height: 0; min-width: 0; overflow: hidden;
  display: flex; flex-direction: column; }
/* The film takes what the row gives it and keeps its own shape inside that.
   A fixed `aspect-ratio` box cannot shrink, so on a 768px screen it simply
   overflowed the row it was told to fit in. */
/* The stage gives way before the transport does: the mode selector and the
   prev/next line buttons are the controls this pane exists to offer, and a
   stage with a floor high enough to push them out of a clipped column is a
   stage that has deleted them. */
/* The film keeps its own shape and sits at the TOP of its column rather than
   floating in the middle of a black field: the box is the picture, and what is
   left over goes to the panel beside it, not around it. */
.de-shell .de-stage { flex: 0 1 auto; min-height: 90px; height: auto;
  max-height: 100%; width: 100%; }
.de-shell .ar-169 .de-stage { aspect-ratio: 16 / 9; }
.de-shell .ar-916 .de-stage { aspect-ratio: 9 / 16; width: auto; max-width: 100%;
  margin-inline: 0 auto; }
.de-shell .de-stage:has(audio) { aspect-ratio: auto; height: auto; width: 100%; }
.de-shell .de-media { justify-content: flex-start; }
.de-shell .de-media > .row:first-child, .de-shell .de-transport { flex: none; }
.de-shell .de-stage video { max-height: 100%; }
.de-shell .de-transport { margin-top: 6px; }
/* Not sticky inside a grid cell that is already the right height — and it
   scrolls its own overflow (§24) rather than growing the row. */
.de-shell .de-detail { position: static; top: auto; min-height: 0; max-height: none;
  overflow-y: auto; overscroll-behavior: contain; }
/* §11 — the six things a reviewer does to a line, pinned to the bottom of the
   panel that scrolls. Measured at 1920×1080 on the real film: every one of them
   was below the fold of a 389px box holding ~900px of content, so they rendered
   and could not be pressed without scrolling past the whole line first. */
/* The bar overlays whatever is in flow behind it, so the panel keeps a strip
   of padding the height of the bar: without it the tail of the panel can never
   be scrolled out from under the thing that is pinned over it. */
.de-shell .de-detail { scroll-padding-bottom: 52px; }
.de-actionbar { position: sticky; bottom: 0; z-index: 3; margin: 10px -12px -12px;
  padding: 8px 12px; display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
  background: var(--panel); border-top: 1px solid var(--line); }
.de-actionbar .btn { flex: 0 0 auto; }
/* The fine settings, folded (§9). Жүжиглэлт stays outside this — it is the
   judgement, not a setting. */
.de-fine { margin: 8px 0 4px; }
.de-fine > summary { cursor: pointer; font-size: 12px; color: var(--muted);
  padding: 4px 0; list-style: none; }
.de-fine > summary::-webkit-details-marker { display: none; }
.de-fine > summary:hover { color: var(--ink, #e8eaf0); }

/* §10/§24 — THE CHROME ABOVE THE WORKSPACE, ON A LAPTOP.
 *
 * Measured at 1366×768 on the user's own film: the title bar, the job card and
 * the six counters took 218 of 768 pixels — 28% of the screen — before the film
 * and the list got anything, and what was left gave the list 146px: two rows of
 * a hundred and one. The same three things are worth about 150px, and the
 * seventy that come back go to the picture and to the rows. */
@media (max-height: 880px) {
  .de-shell { gap: 7px; padding: 7px 12px 9px; }
  .de-shell-top { max-height: 40px; }
  /* The step bar already says which step this is; the <h1> repeats it. */
  .de-shell-top .pagehead { display: none; }
  .de-shell > .de-top { max-height: 76px; padding: 6px 10px; }
  /* A finished run's progress bar is a fact about the past. It can have the
     room back on a laptop; the state chip beside the buttons still says what
     happened, and a RUNNING job's bar is not touched. */
  .de-shell > .de-top .de-job:not(.on) { display: none; }
  .de-shell .de-summary { gap: 5px; }
  .de-shell .de-tile { padding: 1px 7px; }
  .de-shell .de-tile b { font-size: 13px; }
  .de-shell .de-tile span { font-size: 9.5px; }
  .de-shell .de-detail, .de-shell .de-lines, .de-shell .de-media { padding: 9px 10px; }
  .de-actionbar { margin: 8px -10px -9px; padding: 7px 10px; }
  /* A few percent more of the workspace to the line being worked on. At 48% the
     panel came out 278px against 285px of controls, so Provider and Хоолой fell
     seven pixels short of the fold — and the list keeps three rows either way.
     The user's grip still overrides this; it is only where the split starts. */
  .de-shell .dubedit { grid-template-rows: var(--de-r1, minmax(0, 54%)) minmax(120px, 1fr); }
  /* A shorter bar on a shorter screen — the same six controls. */
  .de-actionbar { padding: 4px 10px; }
  .de-actionbar .btn { padding: 2px 7px; font-size: 11px; }
}

/* The cue-timing editor is the SAME component everywhere (§28), but inside the
   dubbing line panel it shares a 260px box with the text, the character, the
   voice and the acting. Compact there and nowhere else: same controls, same
   handlers, less air. */
.de-detail .ct-box { padding: 7px 8px; }
.de-detail .ct-row { gap: 4px; }
.de-detail .ct-row + .ct-row { margin-top: 4px; }
.de-detail .ct-row .btn.sm { padding: 2px 6px; font-size: 11px; }
.de-detail .ct-time { padding: 3px 5px; font-size: 12px; }
.de-detail .ct-room { font-size: 11px; }
.de-detail .ct-k { font-size: 10.5px; min-width: 38px; }
.de-shell .de-lines { min-height: 0; display: flex; flex-direction: column;
  overflow: hidden; }
.de-shell .de-list { max-height: none; flex: 1 1 auto; min-height: 0;
  overflow-y: auto; overscroll-behavior: contain; }
/* The film is bounded by the workspace it sits in, not by the viewport: at 58vh
   the picture alone left the list below the fold that the fixed layout exists
   to remove. */
@media (max-width: 1180px) {
  /* Narrow: the panel goes under the film and the shell gains its row. */
  .de-shell .dubedit { grid-template-columns: minmax(0, 1fr);
    grid-template-rows: minmax(0, auto) auto minmax(120px, 1fr);
    grid-template-areas: "media" "detail" "lines"; }
  .de-shell .de-detail { max-height: 34vh; }
}

/* The original for the line being worked on — read once, never edited here. */
.de-src { border-left: 2px solid var(--line); padding: 2px 0 2px 8px; margin: 0 0 8px;
  color: var(--muted); font-size: 12.5px; line-height: 1.4; overflow-wrap: anywhere; }
.derow-texts.derow-mn-only { grid-template-columns: minmax(0, 1fr); }
.de-head { display: flex; align-items: baseline; justify-content: space-between; }
.de-head h3 { margin: 0 0 8px; }
.de-field { display: block; margin-bottom: 10px; }
.de-field > span { display: block; font-size: 12px; color: var(--muted); margin-bottom: 4px; }
.de-field textarea { width: 100%; box-sizing: border-box; background: var(--panel2);
  border: 1px solid var(--line); color: var(--ink); border-radius: 9px; padding: 8px 10px;
  font: inherit; resize: vertical; }
.de-field select { max-width: 100%; }
.de-two { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.de-block { border: 1px solid var(--line); border-radius: 10px; padding: 9px 11px; margin-bottom: 10px;
  background: var(--panel2); }
.de-kv { display: flex; justify-content: space-between; gap: 10px; font-size: 12.5px; padding: 2px 0; }
.de-kv span { color: var(--muted); }
.de-note { font-size: 12.5px; margin-top: 6px; line-height: 1.45; }
.de-note.ok { color: #7fd6a4; }
.de-note.warn { color: #e0b784; }
.de-note.bad { color: #e08a8a; }
.de-slider { display: flex; align-items: center; gap: 9px; margin-bottom: 9px; font-size: 12.5px; }
.de-slider > span { color: var(--muted); flex: 1; }
.de-slider input[type=range] { width: 130px; }
.de-slider b { min-width: 40px; text-align: right; }
.de-play { gap: 8px; margin-top: 8px; flex-wrap: wrap; }
.de-err { border: 1px solid #5a3030; background: #2a1d1d; color: #ffd9d9; border-radius: 10px;
  padding: 9px 11px; margin-bottom: 10px; font-size: 12.5px; }
.de-err button { margin-top: 7px; }

.de-modal { position: fixed; inset: 0; background: rgba(6,8,12,.72); display: flex;
  align-items: center; justify-content: center; z-index: 60; }
.de-modal-card { background: var(--panel); border: 1px solid var(--line); border-radius: 14px;
  padding: 20px; width: min(440px, 92vw); }
.de-modal-card h3 { margin: 0 0 14px; }
.de-modal-card .de-note { display: block; margin: 10px 0; }
.de-modal-card .de-note .src-opt { display: flex; margin-top: 6px; }

/* Duration QA summary, AI proposals, danger action */
.de-summary { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
.de-tile { border: 1px solid var(--line); border-radius: 10px; padding: 6px 11px;
  background: var(--panel2); min-width: 74px; }
.de-tile span { display: block; color: var(--muted); font-size: 11px; }
.de-tile b { font-size: 16px; }
.de-tile.ok b { color: #7fd6a4; }
.de-tile.warn b { color: #e0b784; }
.de-tile.bad b { color: #e08a8a; }
.deflag.ok { color: #7fd6a4; border-color: #2f5340; }
.de-proposal { border: 1px solid #35507a; background: #182130; border-radius: 10px;
  padding: 10px 11px; margin-bottom: 10px; }
.de-proposal-head { display: flex; justify-content: space-between; align-items: baseline;
  margin-bottom: 6px; }
.de-proposal-old { color: var(--muted); font-size: 12.5px; text-decoration: line-through;
  margin-bottom: 6px; overflow-wrap: anywhere; }
.de-proposal textarea { width: 100%; box-sizing: border-box; background: var(--panel2);
  border: 1px solid var(--line); color: var(--ink); border-radius: 8px; padding: 7px 9px;
  font: inherit; resize: vertical; margin-bottom: 6px; }
.btn.danger { background: #3a1f1f; border-color: #5a3030; color: #ffd9d9; }
.btn.danger:hover { background: #4a2626; }
.de-mode { width: auto; min-width: 118px; padding: 6px 8px; font-size: 12.5px; }

/* Which environment this is. Only ever drawn for a non-production instance —
   a badge that is always there is a badge nobody reads. */
.env-badge {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: 4px;
  background: #7a3d00;
  color: #ffd9a8;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .04em;
  vertical-align: middle;
}
body.env-nonprod .sidebar { border-right: 2px solid #7a3d00; }

/* The two accounts of a missing cue: what the gate refused, and what a person
   removed. Both are collapsed-but-present — a count that dropped for an unseen
   reason is indistinguishable from a bug. */
.ocr-rejected { border-color: #5a4a2a; }
.ocr-rejected > summary { cursor: pointer; font-size: 14px; color: #e8ab3f; list-style: none; }
.ocr-rejected > summary::-webkit-details-marker { display: none; }
.ocr-rejected > summary::before { content: "▸ "; }
.ocr-rejected[open] > summary::before { content: "▾ "; }
.ocr-rejected p { margin: 8px 0 0; font-size: 12.5px; }
.ocr-deleted { border-color: #5a3030; }
.ocr-deleted h3 { color: #ffd9d9; }
.ocr-reject-list { list-style: none; margin: 10px 0 0; padding: 0; }
.ocr-reject-list li { display: flex; gap: 10px; align-items: center; padding: 5px 0;
  border-top: 1px solid #232634; font-size: 13px; }
.ocr-reject-list code { background: #1b1d28; padding: 2px 7px; border-radius: 5px;
  white-space: pre-wrap; max-width: 40ch; overflow: hidden; text-overflow: ellipsis; }
.ocr-reject-list .muted { flex: 1; font-size: 11.5px; }

/* A step that is waiting on an earlier one. Waiting is not failure and it is
   not hidden: the row stays, dimmed, and its title says which step supplies
   what it needs — a user who cannot see why a button does nothing presses it
   again. */
.wz-blocked { margin: 0 0 10px; color: #e8ab3f; font-size: 13px; }
.wz-step.blocked:not(.done) { opacity: .55; }
.wz-step.blocked:not(.done) .wz-state { color: #e8ab3f; }

/* Cue timing controls — ONE block, rendered by cueTimingHtml() in studio.js and
   used unchanged by Хадмал студи and Дубляж студи. Two stylesheets for the same
   component is how two screens start looking like two features. */
.ct-box { margin: 8px 0 10px; padding: 10px; border: 1px solid var(--line);
  border-radius: 10px; background: var(--panel2); }
.ct-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.ct-row + .ct-row { margin-top: 8px; }
.ct-time { flex: 1 1 110px; min-width: 92px; padding: 6px 8px; border-radius: 8px;
  border: 1px solid var(--line); background: var(--panel); color: var(--text);
  font-variant-numeric: tabular-nums; text-align: center; font-size: 12.5px; }
.ct-arrow { color: var(--muted); }
.ct-dur { margin-left: auto; font-size: 11.5px; color: var(--muted); white-space: nowrap; }
.ct-dur b { color: var(--text); font-variant-numeric: tabular-nums; }
.ct-warn { margin-top: 8px; padding: 8px 10px; border-radius: 8px;
  border: 1px solid #e8ab3f; background: rgba(232, 171, 63, .12);
  font-size: 12.5px; line-height: 1.55; }
.ct-warn b { font-variant-numeric: tabular-nums; }
.ct-warn button { margin-top: 6px; }

/* Timing, on the quality screen itself — the whole point of this patch is that
   a user who can SEE «Хугацаандаа багтахгүй» can fix it without being sent into
   Нарийвчилсан засвар to find the same button. */
.lq-bulk { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 10px 0 4px; padding: 10px 12px; border: 1px solid var(--line);
  border-radius: 10px; background: var(--panel2); }
.lq-bulk-note { flex-basis: 100%; font-size: 12.5px; color: var(--text); }
.lq-timing { margin-top: 8px; padding: 8px 10px; border-radius: 8px;
  border: 1px solid #e8ab3f; background: rgba(232, 171, 63, .10); }
/* §6 — the manual stamps on the language-QA row. Neutral, not amber: the amber
   panel above it means «this line has a timing PROBLEM», and these controls are
   offered on every line whether or not it does. */
.lq-manual { margin-top: 8px; padding: 8px 10px; border-radius: 8px;
  border: 1px solid var(--line); background: var(--panel2); }
.lq-timing > .lq-manual { margin-top: 10px; }
.lq-manual-k { font-size: 11px; letter-spacing: .04em; text-transform: uppercase;
  color: var(--muted); margin-bottom: 6px; }
.lq-manual .ct-box { margin: 0; }
.lq-timing-need { font-size: 12.5px; margin-bottom: 6px; }
.lq-timing-need b { font-variant-numeric: tabular-nums; }
.lq-timing-res { font-size: 12.5px; margin-bottom: 6px; font-variant-numeric: tabular-nums; }
.lq-timing-res.ok { color: #5ec98a; }
.lq-timing-res.warn { color: #e8ab3f; }
.lq-timing-btns { display: flex; gap: 6px; flex-wrap: wrap; }

/* THE PLAYER STAYS PUT WHILE THE LIST SCROLLS.
   A reviewer works down a long list pressing ▶ on row after row; a player that
   scrolls away with the rows means the picture they are judging is off screen
   by the time it plays. Sticky rather than fixed so it still sits inside the
   page's column and never covers the rows. */
.sticky-player {
  position: sticky;
  top: 0;
  z-index: 20;
  padding: 6px 0 10px;
  background: var(--bg);
  box-shadow: 0 10px 24px rgba(0, 0, 0, .45);
}
/* ONE RULE FOR BOTH ORIENTATIONS.
 *
 * A 16:9 film is limited by width and a 9:16 film by height, and the old
 * `max-height: 30vh` alone let a vertical film grow until it owned the screen
 * and pushed the list it is meant to accompany off the bottom. Constraining
 * BOTH — and letting the element keep its own aspect — is what makes the two
 * behave the same way without the screen having to know which it has. */
/* The sticky screens keep the full-width BAR — a film that hugged its own
   width here would float with list rows visible either side of it and read as
   a broken layout while the list scrolls under it. So the shaped-stage rules
   above are switched back off for these four, and only the SIZE changes. */
/* `margin: 0` MATTERS. The shaped-stage rule above centres itself with auto
   side margins, and in a column flex container an auto cross-axis margin turns
   OFF `align-items: stretch` — so this bar stopped being a bar and shrank to
   the width of the film. Stuck to the top of a scrolled list that reads as a
   film floating over the rows with the page showing either side of it. */
.st-video.sticky-player { aspect-ratio: auto; height: auto; width: auto;
  align-self: auto; display: block; margin: 0; }
.sticky-player video {
  display: block;
  margin: 0 auto;
  /* Was a flat 32vh. A reviewer judging a burned-in subtitle against the
     picture was reading it at a third of the window; both orientations now get
     a working size, and the vertical one gets the taller allowance it needs to
     show the same amount of FILM. */
  max-height: var(--st-h, 42vh);
  max-width: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  background: #000;
}
/* A vertical film needs more height for the same amount of picture — but this
   one STICKS, so it is capped at about half the window: the rows it is meant
   to accompany have to stay on screen beside it. */
.st-video.sticky-player.st-portrait { --st-h: 52vh; }
@media (max-height: 820px) { .sticky-player video { max-height: var(--st-h, 36vh); }
  .st-video.sticky-player.st-portrait { --st-h: 46vh; } }
@media (max-height: 680px) { .sticky-player video { max-height: var(--st-h, 30vh); }
  .st-video.sticky-player.st-portrait { --st-h: 40vh; } }

/* Дүрс ↔ Эх SRT — two cue columns and the row's own actions. Deliberately the
   same row furniture as the other compare screen: a user who has learned one
   has learned both. */
/* A row is a GROUP — N cues off the picture against M in the SRT — so the row
   is a block and the two columns live inside `.sc-main`. The context blocks
   above and below it are siblings, dimmed, and belong to other groups. */
.sc-head, .sc-main { display: grid; grid-template-columns: 34px 1fr 1fr 320px; gap: 10px; }
.sc-row { display: block; }
.sc-cell { min-width: 0; }
.sc-cell.missing { display: flex; flex-direction: column; justify-content: center; }
.sc-none { font-size: 12px; color: #e8ab3f; font-style: italic; }
.sc-idx { font-size: 11px; letter-spacing: .04em; text-transform: uppercase;
  color: var(--muted); margin-bottom: 3px; }
.sc-part + .sc-part { margin-top: 5px; padding-top: 5px;
  border-top: 1px dashed var(--line); }
.sc-side { min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.sc-why { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }
.sc-approved { font-size: 12px; color: var(--muted); }
.sc-approved b { color: var(--text); }
.sc-state { font-size: 12px; color: var(--muted); }
.sc-state.ok { color: #4fbf7d; }
.sc-tools { display: flex; flex-wrap: wrap; gap: 5px; }
.sc-note { margin: 6px 0; padding: 8px 11px; border-radius: 8px;
  border: 1px solid #e8ab3f; background: rgba(232, 171, 63, .10); font-size: 12.5px; }

/* Why a reading was not trusted, on the row that needs a person. «Шалгах» with
   no reason behind it is a verdict nobody can act on. */
.sc-doubt { display: flex; flex-wrap: wrap; gap: 4px; }

/* The source-only case gets a sentence, not just buttons: «keep» and «delete»
   are only obvious answers once it is clear the SRT itself may be wrong. */
.sc-note-inline { flex-basis: 100%; font-size: 12px; color: #e8ab3f;
  line-height: 1.45; margin-bottom: 2px; }

/* Manual timing, on the row. ±0.25 / ±0.50 on either edge, beside the safe
   automatic extension rather than instead of it. */
.sc-timing { flex-basis: 100%; display: flex; flex-wrap: wrap; gap: 4px;
  align-items: center; padding-top: 4px; margin-top: 2px;
  border-top: 1px dashed var(--line); }
.sc-timing .muted { font-size: 11px; text-transform: uppercase;
  letter-spacing: .04em; margin-right: 2px; }

/* Нэг яригч / Өгүүлэгч — the first thing on the character screen, because for
   a narration it is the whole of it. */
.ss-panel .banner { margin: 8px 0; }

/* The row being reviewed is the only one wearing its actions. */
.sc-row.active { outline: 2px solid var(--accent); outline-offset: 2px;
  border-radius: 8px; }
.sc-row:not(.active) { cursor: pointer; }

/* Context: two groups before, two after — enough to see that a "missing" line
   was folded into the sentence above or below. */
/* Same column track as `.sc-main`, so a context line sits directly under the
   column it belongs to instead of floating across them. */
.sc-ctx { display: grid; grid-template-columns: 34px 1fr 1fr 320px; gap: 10px;
  padding: 4px 0; font-size: 12px; color: var(--muted); opacity: .72;
  cursor: pointer; }
.sc-ctx:hover { opacity: 1; }
.sc-ctx-tag { font-size: 9.5px; text-transform: uppercase; letter-spacing: .04em;
  text-align: right; }
/* Long meter labels — «бүлэг автоматаар зөв таарсан» — need air after the
   number they belong to. */
.sc .st-meter span { margin-left: 6px; }
.sc-ctx-side { min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
.sc-ctx-side b { color: var(--text); font-weight: 600; margin-right: 5px; }

@media (max-width: 1200px) {
  .sc-head, .sc-main { grid-template-columns: 28px 1fr 1fr; }
  .sc-side { grid-column: 1 / -1; }
  .sc-ctx { grid-template-columns: 28px 1fr 1fr; }
}

/* Дубляжтай бичлэг шалгах — watch it through, fix what you hear.
   The player is sticky and the LIST scrolls, never the page: a row that
   follows playback must not drag the video off the screen. */
/* THE FILM IS THE POINT OF THIS SCREEN. It is what is being judged — the lip
   sync, the Mongolian line over the picture, whether the take belongs to the
   shot — and it was sharing the window with a list that had half of it, which
   under the short-window rules below left the player a strip. The list is
   scrolled a row at a time, not read whole, so it is the one that gives room
   up. Sizes only; nothing about this screen's structure changes. */
.dubrev .sticky-player video { max-height: var(--st-h, 54vh); }
.dubrev .st-video.sticky-player.st-portrait { --st-h: 62vh; }
.dubrev-rows { max-height: 34vh; overflow-y: auto; }
@media (max-height: 820px) {
  .dubrev .sticky-player video { max-height: var(--st-h, 48vh); }
  .dubrev .st-video.sticky-player.st-portrait { --st-h: 56vh; }
  .dubrev-rows { max-height: 30vh; }
}
.dr-row { display: grid; grid-template-columns: 30px 1fr 300px; gap: 10px;
  align-items: start; cursor: pointer; }
.dr-main { min-width: 0; }
.dr-meta { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
.dr-tools { display: flex; flex-wrap: wrap; gap: 4px; justify-content: flex-end; }
/* The line the film is on. Deliberately a background rather than an outline —
   the row is moving under the reader's eye and a border flickers. */
.dr-row.playing { background: rgba(90, 150, 255, .16); border-radius: 8px; }
.dr-row.playing .st-text { color: var(--text); font-weight: 600; }

/* The step bar now reports the PROJECT's state, so it needs to say what is
   finished, what is next, and what cannot be entered yet. */
.stepbar.wf .step.done { opacity: .85; }
.stepbar.wf .step.next { outline: 2px solid var(--accent); outline-offset: 2px; }
.stepbar.wf .step.blocked { opacity: .42; cursor: not-allowed; }

@media (max-width: 1100px) {
  .dr-row { grid-template-columns: 26px 1fr; }
  .dr-tools { grid-column: 1 / -1; justify-content: flex-start; }
}

/* Which localizer will read the film, and what the last run cost. Both are
   deliberately loud: the difference between them is minutes versus half an
   hour, and it used to be invisible on this screen. */
.ocr-engine { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px;
  padding: 10px 12px; border-radius: 9px; border: 1px solid #2f3a46;
  background: #141a21; margin: 10px 0; font-size: 13px; }
.ocr-engine.ok { border-color: #2f5a3a; }
.ocr-engine.warn { border-color: #5a4a2a; background: #1d1a12; }
.ocr-engine.warn > span:first-of-type { color: #e8ab3f; font-weight: 600; }
.ocr-engine.ok > span:first-of-type { color: #56c07a; font-weight: 600; }
.ocr-engine-why { flex-basis: 100%; }
.ocr-method { display: flex; flex-wrap: wrap; gap: 14px; padding: 8px 12px;
  border-radius: 9px; border: 1px solid #2f3a46; background: #141a21;
  margin: 8px 0; font-size: 12px; }
.ocr-method.warn { border-color: #5a4a2a; }
.ocr-method > div { flex-basis: 100%; }

/* The three witnesses and whether each actually testified. Deliberately shown
   even when the check passes: «зөрчилгүй» over one source and «зөрчилгүй» over
   three used to render identically, and that difference is this screen's job. */
.mi-readiness { display: grid; gap: 6px; margin: 10px 0; }
.mi-src { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px;
  padding: 7px 10px; border-radius: 8px; background: #141a21;
  border: 1px solid #2f3a46; font-size: 13px; }
.mi-src.ok { border-color: #2f5a3a; }
.mi-src.todo { border-color: #5a4a2a; }
.mi-src-mark { font-weight: 700; width: 1.1em; text-align: center; }
.mi-src.ok .mi-src-mark { color: #56c07a; }
.mi-src.todo .mi-src-mark { color: #e8ab3f; }
.mi-src-state { opacity: .85; }
.mi-src-why { flex-basis: 100%; font-size: 12px; }
.mi-src .btn { margin-left: auto; }

/* The lines either side of the one under review, in both sources at once. A
   flagged line alone cannot be told apart from a split, a merge or a drift. */
.mi-ctx { margin-top: 8px; }
.mi-ctx > summary { cursor: pointer; font-size: 12px; opacity: .75; }
.mi-ctx-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px;
  margin-top: 6px; }
@media (max-width: 700px) { .mi-ctx-grid { grid-template-columns: 1fr; } }
.mi-ctx-side { min-width: 0; border: 1px solid #2f3a46; border-radius: 8px;
  padding: 6px 8px; background: #10151b; }
.mi-ctx-side > b { display: block; font-size: 11px; letter-spacing: .04em;
  opacity: .7; margin-bottom: 4px; }
.mi-ctx-row { display: flex; gap: 8px; font-size: 12px; padding: 3px 4px;
  border-radius: 5px; }
.mi-ctx-row.now { background: #1d2733; outline: 1px solid #3c6ea5; }
.mi-ctx-row.empty { opacity: .55; font-style: italic; }
.mi-ctx-t { flex: 0 0 auto; opacity: .6; font-variant-numeric: tabular-nums; }
.mi-ctx-x { min-width: 0; overflow-wrap: anywhere; }

/* ── Монгол хэлний чанар: the count, the context, the timing panel ──────────
   Added in v4.26. Every rule here exists because a user could not answer a
   question from the screen: what is that number, what came before this line,
   and where is this cue allowed to go. */

.lq-meters { grid-template-columns: repeat(6, 1fr); }
@media (max-width: 1200px) { .lq-meters { grid-template-columns: repeat(3, 1fr); } }

/* The breakdown under the AI button. A single figure with no parts is a figure
   the user has to trust; these are clickable, so «Найруулга: 5» is also the way
   to see those five. */
.lq-breakdown { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 8px 0 2px; font-size: 12.5px; }
.lq-breakdown > b { font-size: 13px; }
.lq-bd { border: 1px solid var(--line); background: var(--card2); color: var(--text);
  border-radius: 999px; padding: 4px 11px; font-size: 12px; cursor: pointer; }
.lq-bd:hover { border-color: #4c6ea5; }
.lq-bd b { font-variant-numeric: tabular-nums; }
.lq-breakdown .muted { flex-basis: 100%; font-size: 11.5px; line-height: 1.45; }

/* Neighbour context. Dimmed and compact on purpose: it is there to be read
   without becoming the thing the eye lands on. */
.lq-ctx { display: grid; gap: 2px; margin: 4px 0; }
.lq-ctx-row { display: grid; grid-template-columns: 74px 1fr 1fr; gap: 8px;
  font-size: 11.5px; line-height: 1.4; opacity: .58; padding: 2px 4px;
  border-left: 2px solid var(--line); }
.lq-ctx-row:hover { opacity: .9; }
.lq-ctx-when { font-variant-numeric: tabular-nums; }
.lq-ctx-src, .lq-ctx-tgt { min-width: 0; overflow-wrap: anywhere; }
.lq-ctx-tgt { color: var(--text); }

/* The fit verdict and the number it was reached by. */
.lq-fit { border-radius: 7px; padding: 7px 9px; margin-bottom: 7px;
  border: 1px solid var(--line); }
.lq-fit.ok { border-color: #34603f; }
.lq-fit.warn { border-color: #6b5326; }
.lq-fit-verdict { font-size: 12.5px; margin-bottom: 5px; }
.lq-fit.ok .lq-fit-verdict { color: #5ec98a; }
.lq-fit.warn .lq-fit-verdict { color: #e8ab3f; }
.lq-fit-verdict .muted { font-size: 11.5px; }
.lq-fit-grid { display: grid; grid-template-columns: 1fr auto; gap: 1px 10px;
  font-size: 12px; color: var(--muted); }
.lq-fit-grid b { color: var(--text); font-variant-numeric: tabular-nums; }
.lq-fit-need { margin-top: 5px; font-size: 12px; line-height: 1.5; }
.lq-fit-need b { font-variant-numeric: tabular-nums; }

/* Where the cue may go, in absolute timecodes AND as room either side. */
.lq-range { display: grid; gap: 2px; margin-bottom: 7px; font-size: 12px; }
.lq-range-row { display: grid; grid-template-columns: 132px auto 1fr; gap: 8px;
  align-items: baseline; }
.lq-range-row > span { color: var(--muted); }
.lq-range-row > b { font-variant-numeric: tabular-nums; }
.lq-range-row > i { font-style: normal; color: var(--muted); font-size: 11.5px; }
.lq-range-row.hi > b { color: #7fb3f0; }

/* The proposal card: text, reason, and whether it will actually fit. */
.lq-p { display: grid; gap: 5px; }
.lq-p-why { font-size: 12px; line-height: 1.5; color: var(--muted); }
.lq-p-why b { color: var(--text); }
.lq-p-fit { font-size: 12px; line-height: 1.45; }
.lq-p-fit.ok { color: #5ec98a; }
.lq-p-fit.warn { color: #e8ab3f; }

/* A row whose problem is gone stays on screen as evidence rather than
   disappearing, which is what made users think work had been lost. */
.lq-row.lq-done { opacity: .82; }
.st-was.ok { border-left: 2px solid #4f9d69; padding-left: 8px; }

/* The safe window, drawn. Numbers say where a cue MAY go; a strip lets the user
   try a position instead of typing one — and the drag is clamped to the same
   bounds the server computed, so nothing here can offer a placement the server
   would refuse. */
.lq-bar-wrap { margin: 2px 0 9px; }
.lq-bar-track { position: relative; height: 26px; border-radius: 6px;
  background: #12151d; border: 1px solid var(--line); overflow: hidden; }
.lq-bar-seg { position: absolute; top: 0; bottom: 0; }
.lq-bar-seg.prev, .lq-bar-seg.next { background: #232a36; }
.lq-bar-seg.safe { background: rgba(91, 140, 255, .14);
  border-left: 1px dashed #3c5b96; border-right: 1px dashed #3c5b96; }
.lq-bar-cue { position: absolute; top: 3px; bottom: 3px; border-radius: 4px;
  background: linear-gradient(180deg, #4d7fe0, #3a63b4); box-shadow: 0 1px 4px rgba(0,0,0,.4); }
.lq-bar-grip { position: absolute; top: -3px; bottom: -3px; width: 9px;
  cursor: ew-resize; background: #cbd8f0; border-radius: 3px; }
.lq-bar-grip.s { left: -4px; }
.lq-bar-grip.e { right: -4px; }
.lq-bar-grip:hover { background: #fff; }
.lq-bar-read { display: flex; align-items: baseline; gap: 8px; margin-top: 4px;
  font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.lq-bar-read b { color: var(--ink); }
.lq-bar-read .grow { flex: 1; }

/* ── the right-hand panel: two states ───────────────────────────────────────
 *
 * Shut is SHUT. The panel's content is display:none and its grid column is
 * gone, so the film and the line list get the width back — measured at
 * 1920x1080 the picture goes from 1129px wide to 1521px, and on a 1366 laptop
 * from 741px to 1133px. There is deliberately no narrow "peek" state: a strip
 * of half-readable panel is the thing that was reported, not a feature.
 * The reopen control lives in the media pane's own header, which is always on
 * screen, so nothing has to stay behind to carry it. */
.dubedit.de-panel-shut { grid-template-columns: minmax(0, 1fr);
  grid-template-areas: "media" "lines"; }
.dubedit.de-panel-shut > .de-detail { display: none; }
.de-shell .dubedit.de-panel-shut { grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr);
  grid-template-areas: "media lines" "media lines"; }
@media (max-width: 1180px) {
  .dubedit.de-panel-shut, .de-shell .dubedit.de-panel-shut {
    grid-template-columns: 1fr; grid-template-areas: "media" "lines"; }
}

/* ── what a timing save leaves on the screen ─────────────────────────────── */
.ct-saved { margin: 6px 0 2px; padding: 7px 9px; border-radius: 9px;
  background: rgba(46, 160, 87, .12); border: 1px solid rgba(46, 160, 87, .45);
  color: var(--ink); font-size: 12.5px; line-height: 1.5;
  font-variant-numeric: tabular-nums; }

/* ── the in-page confirmation that replaced window.confirm ───────────────── */
.de-confirm-rows { margin: 8px 0 12px; display: grid; gap: 5px; }
.de-confirm-rows .row { justify-content: space-between; gap: 18px; }

/* The room a line has, shown before a stamp is typed rather than after it
   collides: `min_start` is the previous line's end, `max_end` the next one's
   start, and both come from the server that enforces them. */
.ct-room { display: flex; flex-wrap: wrap; gap: 14px; margin: 5px 0 2px;
  font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.ct-room b { color: var(--ink); }

/* The dubbing editor's step row: the same compact «← Өмнөх · N/9 · Дараах →»
   Хадмал студи uses, in place of the nine-chip bar that needed its own
   horizontal scrollbar and took the top of the screen the film is on. */
.de-shell-head { padding: 8px 14px 0; }
.de-shell-head .sw-stagebar { margin: 0; }

/* ---------- ASHD Data (§14) ------------------------------------------------
 * A review list, not a product. Rows read left to right: what was approved,
 * what kind of evidence it is, and the two decisions available.
 */
.ad-list { display: flex; flex-direction: column; gap: 8px; max-width: 1100px; }
.ad-row { display: flex; gap: 14px; align-items: flex-start; justify-content: space-between;
  padding: 10px 12px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--panel); }
.ad-main { min-width: 0; flex: 1 1 auto; }
.ad-text { font-size: 14px; line-height: 1.45; overflow-wrap: anywhere; }
.ad-src { font-size: 12px; color: var(--muted); margin-top: 3px; overflow-wrap: anywhere;
  border-left: 2px solid var(--line); padding-left: 8px; }
.ad-meta { display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  margin-top: 6px; font-size: 11.5px; color: var(--muted); }
.ad-kind, .ad-status { padding: 1px 7px; border-radius: 999px; border: 1px solid var(--line);
  font-size: 11px; }
/* «Текст + дуу» is the only claim worth colouring: it is the one that says this
   row could be a speech-training pair. Text-only stays neutral on purpose. */
.ad-kind.ok { border-color: #2f7d52; color: #7fdca4; }
.ad-status.ok { border-color: #2f7d52; color: #7fdca4; }
.ad-status.bad { border-color: #7d3a3a; color: #f0a0a0; }
.ad-hist { margin-top: 5px; font-size: 11.5px; color: var(--muted); }
.ad-acts { display: flex; flex-direction: column; gap: 6px; flex: none; align-items: stretch; }
@media (max-width: 820px) {
  .ad-row { flex-direction: column; }
  .ad-acts { flex-direction: row; flex-wrap: wrap; }
}

/* The «new character» button belongs beside the picker it belongs to. Its own
   full-height row was, measured, the 31 pixels that pushed Provider and Хоолой
   off the bottom of the line panel on a 1366×768 screen. */
.de-detail .de-addchar { gap: 6px; margin: -8px 0 4px; }
.de-detail .de-addchar .btn { padding: 1px 7px; font-size: 11px; }

/* AI зардал. Босгын өнгө нь шийдвэрийг давхарладаг: шар — анхааруулга (юу ч
   зогсоохгүй), улбар шар — үнэтэй model рүү өсөхийг хаасан, улаан — төлбөртэй
   AI зогссон. Улаан нь «бүх зүйл зогслоо» гэсэн үг БИШ: засварлах, тоглуулах,
   экспорт бүгд үргэлжилнэ, учир нь төсөв бол зарлагын хязгаар болохоос
   хэрэглэгчийн оройн хязгаар биш. */
.costbars { display: flex; flex-direction: column; gap: 9px; margin-top: 12px; }
.costbar { display: grid; grid-template-columns: 96px 1fr auto auto; align-items: center;
  gap: 12px; font-size: 13px; }
.costbar > span { color: var(--muted); }
.costbar b { font-variant-numeric: tabular-nums; font-weight: 600; }
.costbar small { color: var(--muted); min-width: 38px; text-align: right; }
.costbar.none { grid-template-columns: 96px auto 1fr; }
.costtrack { display: block; height: 8px; border-radius: 999px; background: var(--line);
  overflow: hidden; }
.costtrack > u { display: block; height: 100%; border-radius: 999px; background: #3f9d5c;
  text-decoration: none; transition: width .25s ease; }
.costbar.warn .costtrack > u { background: #c9a227; }
.costbar.blocked .costtrack > u { background: #d1782c; }
.costbar.stop .costtrack > u { background: #c4433c; }
.costcards { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 12px; margin: 14px 0; }
.costcard { background: var(--panel); border: 1px solid var(--line); border-radius: 12px;
  padding: 14px; }
.costcard span, .costcard small { display: block; color: var(--muted); font-size: 12px; }
.costcard b { display: block; margin: 7px 0 3px; font-size: 19px; font-variant-numeric: tabular-nums; }
.costtable { width: 100%; border-collapse: collapse; font-size: 13px; margin-top: 10px; }
.costtable th, .costtable td { padding: 7px 10px; text-align: right;
  border-bottom: 1px solid var(--line); }
.costtable th:first-child, .costtable td:first-child { text-align: left; }
.costtable th { color: var(--muted); font-weight: 500; }
.costtable td { font-variant-numeric: tabular-nums; }
.costform { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px; margin: 12px 0 14px; }
.costform label { display: flex; flex-direction: column; gap: 5px; font-size: 12px;
  color: var(--muted); }
.costform input { background: var(--panel2); border: 1px solid var(--line); color: var(--ink);
  border-radius: 8px; padding: 8px 10px; font-size: 13px; }
/* The card stack needs air between its members; every other screen gets it from
   its own wrapper and this one has none. */
#main .card + .card, #main .costcards + .card { margin-top: 14px; }
@media (max-width: 900px) {
  .costbar { grid-template-columns: 84px 1fr auto; }
  .costbar small { display: none; }
}

/* «Playing the source while the light copy is made» — said over the player it
   is about, not in a toast that has already gone by the time the user wonders. */
.sw-proxy-note {
  position: absolute;
  left: 8px; right: 8px; bottom: 44px;
  padding: 4px 8px;
  border-radius: 6px;
  background: rgba(0, 0, 0, .55);
  font-size: 11px;
  line-height: 1.35;
  pointer-events: none;
}

/* The machine-readable verdict beside the human one: a user pasting
   «AUTH_FAILED» into a message gets a faster answer than «алдаа гарлаа». */
.prov-health { align-items: flex-start; }
.prov-health-code {
  font: 600 10px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: .06em;
  opacity: .6;
  margin-right: 8px;
}

/* Which build this is. Muted enough to ignore, present enough to read without
   being told where to look — the four environments draw identical screens. */
.build-stamp {
  margin-top: auto;
  padding: 10px 14px 12px;
  font: 500 10px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: .03em;
  opacity: .45;
  word-break: break-all;
}
.build-stamp:hover { opacity: .8; }


/* The AI picker and the «which AI answered» line on the language screen. */
.lq-prov { display: inline-flex; align-items: center; gap: 6px; margin-right: 8px; }
.lq-prov select { background: #1b1e2b; color: #cdd6f4; border: 1px solid #313449;
  border-radius: 6px; padding: 3px 6px; font-size: 12px; }
.lq-route { margin-top: 8px; }
.lq-route .st-badge.warn { background: rgba(224,175,104,.15); color: #e0af68;
  border: 1px solid rgba(224,175,104,.36); }
.lq-route .muted { font-size: 11.5px; margin-top: 4px; }

/* ── БИЧЛЭГИЙН ДАРААЛАЛ — the clip list (§2, §3, §12) ──────────────────────
 *
 * One row per clip, and the row IS the drag target. The grip is a separate
 * handle because on a phone the whole row has to stay scrollable: a list where
 * dragging anywhere starts a reorder is a list you cannot scroll past.
 *
 * Responsive by column collapse rather than by a second layout — under 640px
 * the thumbnail and the facts stack and the controls stay on the right, so the
 * same markup serves the desktop and the phone (§2). */
.mc-host{margin:14px 0}
.mc-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;
  margin:0 0 10px;font-size:12px;letter-spacing:.6px;text-transform:uppercase;color:var(--muted)}
.mc-head b{color:var(--ink);font-size:12px;letter-spacing:.6px}
.mc-total{color:var(--ink);font-weight:600;text-transform:none;letter-spacing:0;font-size:14px}
.mc-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}
.mc-clip{display:flex;align-items:center;gap:10px;padding:8px 10px;border:1px solid var(--line);
  border-radius:10px;background:var(--panel2);cursor:default}
.mc-clip.mc-dragging{opacity:.45}
.mc-clip.mc-over{border-color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent)}
.mc-grip{cursor:grab;color:var(--muted);font-size:16px;line-height:1;padding:2px 4px;
  touch-action:none;user-select:none}
.mc-grip:active{cursor:grabbing}
.mc-num{color:var(--muted);font-variant-numeric:tabular-nums;font-size:12px;min-width:22px}
.mc-thumb{width:auto;height:44px;max-width:80px;border-radius:6px;object-fit:cover;
  background:#0b0d12;border:1px solid var(--line);flex:0 0 auto}
.mc-thumb-none{display:inline-block;width:66px}
.mc-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}
.mc-name{font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.mc-body small{font-size:11.5px}
.mc-span{font-variant-numeric:tabular-nums}
.mc-state{min-width:70px;text-align:right;font-size:12px}
.mc-pct{color:var(--accent);font-variant-numeric:tabular-nums}
.mc-ok{color:var(--green);font-size:15px}
.mc-batch{margin:0 0 10px}
.mc-batch-bar{height:6px;border-radius:4px;background:var(--chip);overflow:hidden}
.mc-batch-bar i{display:block;height:100%;background:var(--accent);transition:width .2s linear}
.mc-actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:14px}
.mc-more.disabled{opacity:.45;pointer-events:none}
.mc-warn{border-color:var(--amber);margin-bottom:12px}
.mc-missing{margin:6px 0 10px 18px;color:var(--muted);font-size:12.5px}
.mc-resume{margin-bottom:14px;border-color:var(--accent)}
.mc-preview video{border-radius:10px;background:#000}
@media (max-width:640px){
  .mc-clip{flex-wrap:wrap}
  .mc-body{flex-basis:100%;order:5}
  .mc-thumb{height:36px}
  .mc-state{min-width:0}
}

/* ── §5/§13 — the numbers behind a verdict, and behind a suggestion ────────
 *
 * «Хэт урт» with nothing beside it is an assertion. These are what turn it
 * into a claim an editor can check: what the line takes to say, the cue it is
 * shown in, and the room it may actually be spoken in. */
.de-fitnums{display:flex;flex-wrap:wrap;gap:4px 14px;align-items:baseline;
  margin-top:6px;font-size:12px;color:var(--muted)}
.de-fitnums b{color:var(--ink);font-variant-numeric:tabular-nums}
.de-fitnums small{flex-basis:100%;font-size:11px}
.lq-p-refused{border-left:3px solid var(--amber);padding-left:10px}
.lq-p-lost{margin:4px 0 0 16px;color:var(--muted);font-size:12px}
.lq-p-diag{margin-top:6px;font-size:11.5px}
.lq-p-alt{margin-top:6px;font-size:12.5px}
.lq-p-alt summary{cursor:pointer;color:var(--muted)}

/* ── Дүрсний хадмал → Хугацаа засах → Дутуу/зөрүү ──────────────────────────
   THE WHOLE JOB, ON ONE STRIP. The three steps used to be one screen plus two
   things that happened invisibly inside it, so «what has this done to my film»
   had no answer anywhere. The strip says where the user is; each step's own
   approve bar is the only way forward, and it is always a person pressing it. */
.lg-strip { display: flex; gap: 6px; margin: 0 0 10px; }
.lg-step { flex: 1; display: flex; align-items: center; gap: 8px; text-align: left;
  padding: 8px 10px; border-radius: 9px; border: 1px solid var(--line, #262a36);
  background: #101219; color: inherit; font: inherit; font-size: 12.5px;
  cursor: pointer; }
.lg-step b { display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; border-radius: 999px; background: #1b1f2b;
  font-size: 11.5px; flex: none; }
.lg-step.on { border-color: #4b7cc4; background: rgba(75, 124, 196, .12); }
.lg-step.on b { background: #4b7cc4; color: #0b0d13; }
.lg-step.ok { border-color: #2c6b4f; }
.lg-step.ok b { background: #43cf90; color: #0b0d13; }
.lg-step.off { opacity: .5; }
.lg-approve { margin: 10px 0 4px; }
.lg-approve .muted { font-size: 12.5px; }

/* The numbers of §12, as plain rows. A status that has to be inferred from a
   list is a status nobody reads. */
.lg-status { margin-top: 10px; }
.lg-stat { display: flex; gap: 10px; align-items: baseline; font-size: 13px;
  padding: 2px 0; }
.lg-stat-k { flex: 1; opacity: .8; }
.lg-stat-v { font-variant-numeric: tabular-nums; }
.lg-stat-v.w { color: #e0af68; }
.lg-stat-v.g { color: #43cf90; }

/* Picture → sound → final, all three at once. A timing that moved without
   saying where it came from is a timing the user cannot trust. */
.lg-times { display: grid; gap: 4px; font-size: 12.5px;
  font-variant-numeric: tabular-nums; margin: 6px 0 8px; }
.lg-times-k { display: inline-block; min-width: 118px; opacity: .7; }
.lg-times .lg-final { color: #43cf90; }
.lg-ctx { display: grid; gap: 4px; font-size: 12.5px; opacity: .85; margin-bottom: 8px; }
.lg-sources code { opacity: .9; }
.lg-final-panel { margin-top: 10px; }
.lg-final-panel .ok { color: #43cf90; }
.am-text.ro { min-height: 30px; white-space: pre-wrap; }
