/* =========================================================
   UGCDock — Video editor (EDX v8) — CapCut-class 3-pane editor
   ISOLATION: loaded ONLY from caption.html, AFTER app.css. Every rule is
   scoped under `body.edx-page`. app.css is never touched. New markup uses the
   .edx-* prefix; a handful of shared classes are reused verbatim
   (.tlicon .tlsep .tlzoom .minibtn .cap__* .playbtn .uiov .limits-note) and
   only lightly themed here — never restyled globally.

   Palette: variables.css only — --violet #6965db / --violet-light #e0dfff,
   --bg / --bg-soft / --bg-card / --bg-elev / --border / --border-lt,
   --text / --text-mute / --text-dim. Thin, tasteful type. Needle playhead 1px.
   ========================================================= */

body.edx-page {
  /* editor-local tokens */
  /* (A) rail must fit 4 controls (kind glyph + mute + lock + eye) WITHOUT
     overflowing — the ruler / playhead / dropline / empty-state all start at
     var(--edx-railw), so any overflow used to paint under them. 116px is the
     measured fit: 10 left + 16 glyph + 3×20 buttons + 3×4 gaps + 8 right = 106. */
  --edx-railw: 116px;         /* timeline track rail width — JS RAILW must match */
  --edx-pps: 12px;            /* base px per second at zoom 1 (JS multiplies) */
  --edx-track-h: 48px;        /* track lane height */
  --edx-frame-w: 300px;       /* set by JS (sizeStage) */
  --edx-frame-h: 533px;       /* set by JS (sizeStage) */
  /* user-resizable panes / timeline (JS drives + persists to localStorage) */
  --edx-lw: 280px;            /* left pane width  (clamp 200–460) */
  --edx-rw: 288px;            /* right pane width (clamp 240–520) */
  /* (B) smaller default — the old 28vh floor left a huge dead void under the
     tracks. JS must ALSO clamp any restored localStorage value into
     [140px, 62vh] and ignore stored values > 45vh (stale oversized drags). */
  --edx-tlh: clamp(160px, 22vh, 240px); /* timeline height (clamp 140px…62vh) */
  --edx-pad: 12px;
  --edx-line: 1px solid var(--border);
  --edx-accent: var(--violet);
  --edx-accent-soft: rgba(105,101,219,.16);
  --edx-accent-line: rgba(105,101,219,.55);
}

/* the editor owns the viewport — no page scroll.
   (V5) `calc(100vh - 64px)` was a GUESS in three places: the appbar's border-box
   is really 65px, and it changes again with browser zoom / a wrapped account name
   / different font metrics — the mismatch showed up as a black band under the
   timeline. The engine (syncViewportVar) measures the appbar's real bottom edge
   on load + resize + focus toggle + fullscreenchange and publishes --edx-vh on
   <html>; the calc() stays only as a pre-JS fallback. */
body.edx-page { overflow: hidden; }
body.edx-page .content {
  height: var(--edx-vh, calc(100vh - 64px));
  overflow: hidden;
  min-width: 0;
}
/* app.css gives the sidebar `height:100vh`, which — measured from BELOW the
   appbar — used to hang 64px past the viewport and rely on the page scroll to
   reach its last item. With the page scroll gone that would clip Settings, so
   the sidebar gets the same measured height (it scrolls internally already).
   Scoped to the editor page only; app.css is untouched. */
body.edx-page .sidebar {
  height: var(--edx-vh, calc(100vh - 64px));
  max-height: var(--edx-vh, calc(100vh - 64px));
}
@media (max-width: 900px) {
  body.edx-page .sidebar { height: auto; max-height: none; }
}

/* =========================================================
   1) FOCUS / TRUE-FULLSCREEN — hide appbar + sidebar, editor fills the screen
   ========================================================= */
body.edx-page.edx-focus .appbar { display: none; }
body.edx-page.edx-focus .layout,
body.edx-page.edx-focus .layout.has-sidebar { grid-template-columns: 1fr; min-height: 100vh; }
body.edx-page.edx-focus .sidebar { display: none; }
/* the appbar is display:none here, so syncViewportVar() recomputes --edx-vh to
   the FULL viewport height — the var stays correct in focus mode too */
body.edx-page.edx-focus .content { height: var(--edx-vh, 100vh); }
/* tighter chrome in focus mode */
body.edx-page.edx-focus .edx { --edx-pad: 8px; }
body.edx-page.edx-focus .edx-top { padding-top: 8px; padding-bottom: 8px; }

/* the fullscreen element (documentElement) — belt & suspenders */
body.edx-page:fullscreen .content,
:fullscreen body.edx-page .content { height: var(--edx-vh, 100vh); }

/* =========================================================
   2) ROOT GRID — topbar / progress / body / timeline
   ========================================================= */
body.edx-page .edx {
  height: 100%;
  /* containing block for the in-app colour popover (E8), which openColorPop()
     positions in #edxRoot-relative px */
  position: relative;
  display: grid;
  grid-template-rows: auto auto minmax(0, 1fr) auto;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  overflow: hidden;
}

/* (P7/0a) ONEST EVERYWHERE. Nothing "resets" the font — form controls simply
   fall back to the UA control font (Arial / system-ui on Windows) unless told
   otherwise, and app.css has NO blanket `button { font-family: inherit }`
   (it repeats the declaration per component). That is why the media-tile
   filename, the swatches and the preset tiles rendered in Arial. One rule for
   the whole editor page; individual components also carry `font: inherit`. */
body.edx-page button,
body.edx-page input,
body.edx-page select,
body.edx-page textarea { font-family: inherit; }

/* KRİTİK: satırları AÇIKÇA ata. renderProg gizliyken (display:none) grid'den
   düşüyor ve otomatik yerleşim body'yi 2. satıra (auto → içeriğe şişer), 1fr'yi
   de timeline'a kaydırıyordu → body taşması + ölü alan. Açık atama bunu keser. */
body.edx-page .edx-top       { grid-row: 1; }
body.edx-page .edx-top__prog { grid-row: 2; }
body.edx-page .edx-body      { grid-row: 3; }
body.edx-page .edx-tl        { grid-row: 4; }

/* =========================================================
   3) TOP BAR
   ========================================================= */
body.edx-page .edx-top {
  display: grid;
  grid-template-columns: 1fr auto;   /* aspect group moved to the stage corner */
  align-items: center;
  gap: 14px;
  padding: 10px 16px;
  border-bottom: var(--edx-line);
  background: linear-gradient(180deg, rgba(255,255,255,.015), transparent);
}
body.edx-page .edx-top__left { display: flex; align-items: center; gap: 10px; min-width: 0; }
body.edx-page .edx-top__center { display: flex; justify-content: center; }
body.edx-page .edx-top__right { display: flex; align-items: center; justify-content: flex-end; gap: 8px; min-width: 0; }

body.edx-page .edx-top__title {
  margin: 0;
  font-size: 15px;
  font-weight: 500;
  letter-spacing: .01em;
  color: var(--text-dim);
  white-space: nowrap;
}
body.edx-page .edx-top__phase {
  font-size: 11px; font-weight: 500;
  padding: 3px 9px; border-radius: 999px;
  background: var(--edx-accent-soft); color: var(--violet-light);
  letter-spacing: .02em;
}

/* segmented control (aspect + tabs + anim/pos groups) */
body.edx-page .edx-seg {
  display: inline-flex; align-items: center; gap: 2px;
  padding: 3px; border-radius: 10px;
  background: var(--bg-soft); border: var(--edx-line);
}
body.edx-page .edx-seg button {
  appearance: none; border: 0; background: transparent;
  color: var(--text-dim); font: inherit; font-size: 12px; font-weight: 500;
  padding: 5px 11px; border-radius: 7px; cursor: pointer; white-space: nowrap;
  line-height: 1.1; transition: background .15s ease, color .15s ease;
}
body.edx-page .edx-seg button:hover { color: var(--text); background: rgba(255,255,255,.05); }
body.edx-page .edx-seg button.is-active {
  color: #fff; background: var(--violet); box-shadow: 0 1px 6px rgba(105,101,219,.35);
}
body.edx-page .edx-seg button:disabled,
body.edx-page .edx-seg button.is-disabled { opacity: .35; pointer-events: none; }
body.edx-page .edx-seg--sm button { padding: 4px 9px; font-size: 11.5px; }
body.edx-page .edx-seg--wrap { flex-wrap: wrap; }
/* (v8) background-box SHAPE row — "Block" (one rectangle) vs "Per line" (one per
   line, hugging it). Sits inside #txtBoxGeo with the radius/opacity sliders. */
body.edx-page #txtBoxMode { margin: 2px 0 9px; width: max-content; }

/* focus / fullscreen button (now lives in the top bar, NOT over the preview) */
body.edx-page .edx-focusbtn {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 12px; border-radius: 9px;
  background: var(--bg-soft); border: var(--edx-line);
  color: var(--text-dim); font: inherit; font-size: 12px; font-weight: 500;
  cursor: pointer; white-space: nowrap;
  transition: background .14s ease, color .14s ease, border-color .14s ease, transform .1s ease;
}
body.edx-page .edx-focusbtn svg { width: 15px; height: 15px; }
body.edx-page .edx-focusbtn:hover { color: var(--text); border-color: var(--edx-accent-line); }
body.edx-page .edx-focusbtn:active { transform: translateY(1px); }
body.edx-page .edx-focusbtn[aria-pressed="true"] { color: var(--violet-light); border-color: var(--edx-accent-line); background: var(--edx-accent-soft); }

/* Export / Download — reuse app.css .btn; keep the shared Free badge */
body.edx-page .edx-top__right .btn { white-space: nowrap; }

/* Export — sleek bordered pill (not a loud purple bar); small violet accent.
   Markup keeps .btn.btn--primary for JS safety, so override those here. */
body.edx-page #edxExport.btn.btn--primary {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 13px; border-radius: 9px; box-shadow: none;
  background: var(--bg-soft); border: 1px solid var(--edx-accent-line);
  color: var(--violet-light); font-size: 12px; font-weight: 500;
  transition: background .14s ease, border-color .14s ease, color .14s ease, transform .1s ease;
}
body.edx-page #edxExport.btn.btn--primary:hover {
  background: var(--edx-accent-soft); border-color: var(--violet); color: #fff;
}
body.edx-page #edxExport.btn.btn--primary:active { transform: translateY(1px); }
body.edx-page #edxExport svg { color: var(--violet-light); }
body.edx-page #edxExport .cap__free {
  background: var(--edx-accent-soft); color: var(--violet-light); margin-left: 5px;
}
body.edx-page .cap__free {
  font-size: 9.5px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  padding: 2px 6px; border-radius: 999px; margin-left: 7px;
  background: rgba(255,255,255,.18); color: #fff;
}
body.edx-page .cap__msg { margin: 0; font-size: 12px; color: var(--text-dim); white-space: nowrap; }

/* thin progress bar under the top bar */
body.edx-page .edx-top__prog {
  display: flex; align-items: center; gap: 12px;
  padding: 7px 16px; border-bottom: var(--edx-line);
  background: var(--bg-soft);
}
body.edx-page .edx-top__prog .cap__bar {
  flex: 1 1 auto; height: 4px; border-radius: 999px; overflow: hidden;
  background: rgba(255,255,255,.08);
}
body.edx-page .edx-top__prog .cap__bar > i {
  display: block; height: 100%; width: 0%; border-radius: inherit;
  background: var(--grad-violet); transition: width .3s ease;
}
body.edx-page .edx-top__prog p { margin: 0; font-size: 11.5px; color: var(--text-dim); white-space: nowrap; font-variant-numeric: tabular-nums; }

/* =========================================================
   4) BODY GRID — left / center / right
   ========================================================= */
body.edx-page .edx-body {
  display: grid;
  /* left | vres | center | vres | right — widths driven by JS-persisted vars.
     Center keeps a hard min so a drag can never collapse the stage. */
  grid-template-columns: var(--edx-lw) auto minmax(360px, 1fr) auto var(--edx-rw);
  min-height: 0;
  overflow: hidden;
}
body.edx-page .edx-left,
body.edx-page .edx-right {
  display: flex; flex-direction: column;
  min-height: 0; min-width: 0; overflow-x: hidden;
  padding: var(--edx-pad);
  gap: 10px;
}
/* (R2) BOTH side panels now keep their tab row fixed and scroll their BODY —
   .edx-left itself must not scroll or the library tab row would scroll away. */
body.edx-page .edx-left { border-right: var(--edx-line); background: var(--bg-soft); overflow: hidden; }
body.edx-page .edx-right { border-left: var(--edx-line); background: var(--bg-soft); overflow: hidden; }

/* ---- vertical pane resizers (CapCut-style drag handles) ---- */
body.edx-page .edx-vres {
  position: relative; align-self: stretch; width: 5px; min-width: 5px;
  cursor: col-resize; background: transparent; z-index: 6;
  touch-action: none; user-select: none;
  transition: background .14s ease;
}
body.edx-page .edx-vres::before {
  content: ""; position: absolute; top: 0; bottom: 0; left: 50%; width: 1px;
  transform: translateX(-50%); background: var(--border);
  transition: background .14s ease, box-shadow .14s ease;
}
body.edx-page .edx-vres:hover::before,
body.edx-page .edx-vres.is-drag::before,
body.edx-page .edx-vres:focus-visible::before {
  background: var(--violet); box-shadow: 0 0 6px rgba(105,101,219,.6);
}
body.edx-page .edx-vres:focus-visible { outline: none; }
/* initResizer() adds this to <body> for the duration of a pane/timeline drag; it
   had no rule, so dragging a gutter selected the surrounding UI text and the
   cursor flickered back to the default over every element it passed. */
body.edx-page.edx-resizing,
body.edx-page.edx-resizing * { user-select: none; }
/* slim scrollbars inside panels */
body.edx-page .edx-left::-webkit-scrollbar,
body.edx-page .edx-right__body::-webkit-scrollbar,
body.edx-page .edx-tl__scroll::-webkit-scrollbar { width: 9px; height: 9px; }
body.edx-page .edx-left::-webkit-scrollbar-thumb,
body.edx-page .edx-right__body::-webkit-scrollbar-thumb,
body.edx-page .edx-tl__scroll::-webkit-scrollbar-thumb { background: rgba(255,255,255,.1); border-radius: 999px; border: 2px solid transparent; background-clip: padding-box; }
body.edx-page .edx-left::-webkit-scrollbar-thumb:hover,
body.edx-page .edx-right__body::-webkit-scrollbar-thumb:hover,
body.edx-page .edx-tl__scroll::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,.2); background-clip: padding-box; }

/* panel titles — thin, never bold */
body.edx-page .edx-h {
  margin: 0; font-size: 12px; font-weight: 600; letter-spacing: .02em;
  color: var(--text-dim); text-transform: none;
}

/* =========================================================
   5) LEFT — media / library
   ========================================================= */
body.edx-page .edx-left__head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
body.edx-page .edx-count { font-size: 11px; color: var(--text-dim); font-variant-numeric: tabular-nums; }

body.edx-page .edx-drop {
  display: flex; flex-direction: column; align-items: center; gap: 8px; text-align: center;
  padding: 18px 14px; border-radius: 12px; cursor: pointer;
  border: 1.5px dashed var(--border-lt); background: rgba(255,255,255,.015);
  transition: border-color .15s ease, background .15s ease;
}
body.edx-page .edx-drop:hover,
body.edx-page .edx-drop.is-drag { border-color: var(--edx-accent-line); background: var(--edx-accent-soft); }
body.edx-page .edx-drop__icon { width: 34px; height: 34px; display: grid; place-items: center; color: var(--violet-light); }
body.edx-page .edx-drop__icon svg { width: 26px; height: 26px; }
body.edx-page .edx-drop__txt { display: flex; flex-direction: column; gap: 3px; }
body.edx-page .edx-drop__txt strong { font-size: 12.5px; font-weight: 600; color: var(--text); }
body.edx-page .edx-drop__txt small { font-size: 10.5px; color: var(--text-dim); line-height: 1.35; }

/* (V2) the hint strip exists ONLY while the bin is empty — the engine flips
   [hidden] on it; .edx-drop is display:flex so it needs an explicit rule */
body.edx-page .edx-drop[hidden] { display: none !important; }

/* slim drop zone — Import button is now the primary action; this is a hint strip */
body.edx-page .edx-drop--slim {
  flex-direction: row; padding: 9px 12px; border-radius: 10px; min-width: 0;
}
body.edx-page .edx-drop--slim .edx-drop__txt {
  flex-direction: row; gap: 0; font-size: 11px; color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ---- Import dropdown (compact header button + menu) ---- */
body.edx-page .edx-import { position: relative; flex: 0 0 auto; }
body.edx-page .edx-import__btn { padding: 6px 10px; }
body.edx-page .edx-import__btn .edx-import__chev { width: 12px; height: 12px; opacity: .8; }
body.edx-page .edx-import__btn[aria-expanded="true"] {
  color: var(--violet-light); border-color: var(--edx-accent-line); background: var(--edx-accent-soft);
}
body.edx-page .edx-import__menu {
  position: absolute; top: calc(100% + 5px); right: 0; z-index: 30;
  min-width: 190px; padding: 5px;
  background: var(--bg-card); border: 1px solid var(--border-lt); border-radius: 11px;
  box-shadow: var(--shadow);
}
body.edx-page .edx-import__menu[hidden] { display: none; }
body.edx-page .edx-import__item {
  display: flex; align-items: center; gap: 9px; width: 100%; text-align: left;
  border: 0; background: transparent; cursor: pointer;
  color: var(--text); font: inherit; font-size: 12.5px; font-weight: 500;
  padding: 8px 9px; border-radius: 8px;
  transition: background .13s ease, color .13s ease;
}
body.edx-page .edx-import__item svg { flex: 0 0 auto; color: var(--text-dim); }
body.edx-page .edx-import__item:hover { background: var(--edx-accent-soft); color: var(--violet-light); }
body.edx-page .edx-import__item:hover svg { color: var(--violet-light); }

/* (F) BOTH menu rows must be pixel-identical. `#fromRenders` used to carry
   app.css `.rpk-open { width:100%; margin-top:8px; justify-content:center }`
   which pushed its icon+label to the centre. The class is gone from the markup;
   these are the defensive overrides so ANY inherited .btn/.rpk-open styling
   (and the left action-button `width:100%` rule below) can never re-offset it. */
body.edx-page .edx-import .edx-import__menu .edx-import__item,
body.edx-page .edx-import .edx-import__menu .btn,
body.edx-page .edx-import .edx-import__menu .rpk-open {
  width: 100%; min-width: 0; margin: 0; box-shadow: none;
  display: flex; align-items: center; justify-content: flex-start; gap: 9px;
  text-align: left; border: 0; background: transparent;
  color: var(--text); font: inherit; font-size: 12.5px; font-weight: 500;
  line-height: 1.2; height: auto; min-height: 34px; padding: 8px 9px; border-radius: 8px;
}
body.edx-page .edx-import__menu .edx-import__item + .edx-import__item { margin-top: 2px; }
body.edx-page .edx-import__ico { flex: 0 0 auto; width: 15px; height: 15px; }
body.edx-page .edx-import__lbl { flex: 1 1 auto; min-width: 0; text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* =========================================================
   5b) MEDIA TILES (D) — premium card: thumb on top, name, status line.
   DOM (engine, mediaTile()):
     button.edx-tile > span.edx-tile__thumb
                         [ video | ♪ ]
                         span.edx-tile__dur                 (video, bottom-RIGHT)
                         span.edx-tile__kind                (image, bottom-RIGHT)
                         span.edx-tile__act.edx-tile__rm    (hover, top-RIGHT)
                     > span.edx-tile__name
                     > span.edx-tile__state.edx-tile__state--*   (NOT .is-* — the
                       global .is-uploading utility used to hijack this span; see
                       the .edx-tile__load block below)
   ========================================================= */
body.edx-page .edx-bin { min-height: 0; }
/* 2 per row, compact */
body.edx-page .edx-binlist {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px; align-content: start;
}

body.edx-page .edx-tile {
  position: relative; display: flex; flex-direction: column; gap: 5px;
  min-width: 0; padding: 5px 5px 7px; border-radius: 12px; cursor: grab;
  background: var(--bg-card); border: var(--edx-line);
  text-align: left; color: var(--text); appearance: none; font: inherit;
  transition: border-color .14s ease, background .14s ease, transform .06s ease, box-shadow .14s ease;
}
body.edx-page .edx-tile:hover {
  border-color: var(--edx-accent-line);
  background: linear-gradient(180deg, var(--edx-accent-soft), transparent 62%), var(--bg-card);
  box-shadow: 0 4px 14px rgba(0,0,0,.28);
}
body.edx-page .edx-tile:active { cursor: grabbing; transform: scale(.985); }
body.edx-page .edx-tile.is-drag { opacity: .5; }
body.edx-page .edx-tile:focus-visible { outline: none; border-color: var(--violet); box-shadow: 0 0 0 2px var(--edx-accent-soft); }

/* --- thumbnail (P7) LETTERBOX, never crop ---
   Same behaviour as the Posts page (.appost__thumb): the media is CONTAINed on
   a black field, so a 9:16 clip shows with black side bars and a 16:9 clip fits
   edge to edge. `cover` used to zoom into vertical clips and hide the subject. */
body.edx-page .edx-tile__thumb {
  position: relative; display: grid; place-items: center;
  /* Kutu HER ZAMAN 16:9. İçerik `contain` ile sığar: 9:16 klip yanlarda siyah
     bantla TAM görünür, 16:9 klip kutuyu birebir doldurur. Kırpma YOK.
     (Posts'taki .pcard__thumb ile aynı davranış; orada kutu sabit yükseklikle
     16:9'a yakın oluyor, burada oranı doğrudan sabitliyoruz.) */
  width: 100%; aspect-ratio: 16 / 9;
  border-radius: 9px; overflow: hidden;
  background: #000;
  color: var(--violet-light); font-size: 20px; line-height: 1;
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.05);
}
body.edx-page .edx-tile__thumb img,
body.edx-page .edx-tile__thumb video {
  width: 100%; height: 100%; display: block;
  object-fit: contain;                     /* NEVER crop — 9:16 gets side bars */
  background: #000; border-radius: inherit; pointer-events: none;
}
body.edx-page .edx-tile__thumb--audio,
body.edx-page .edx-tile[data-kind="audio"] .edx-tile__thumb {
  background:
    radial-gradient(80% 120% at 50% 0%, rgba(105,101,219,.32), transparent 70%),
    var(--bg-elev);
  font-size: 22px;
}

/* (IMG) KIND BADGE — bottom-RIGHT of the thumb: the SAME slot the video tiles
   spend on their duration pill, so both kinds read on one corner line. A still
   has no length, so that slot is free. Same glass treatment as the pill. */
body.edx-page .edx-tile__kind {
  position: absolute; right: 5px; left: auto; bottom: 5px; z-index: 2;
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; border-radius: 6px; pointer-events: none;
  background: rgba(8,8,10,.72); backdrop-filter: blur(4px);
  color: var(--violet-light);
}
body.edx-page .edx-tile__kind svg { width: 12px; height: 12px; display: block; overflow: visible; }

/* duration pill — bottom-right, inside the thumb */
body.edx-page .edx-tile__dur {
  position: absolute; right: 5px; bottom: 5px; z-index: 2;
  padding: 2px 6px; border-radius: 999px; pointer-events: none;
  background: rgba(8,8,10,.72); backdrop-filter: blur(4px);
  color: #fff; font-size: 10px; font-weight: 500; line-height: 1.35;
  font-variant-numeric: tabular-nums; letter-spacing: .01em;
}

/* hover-only corner actions — ✕ sits TOP-RIGHT (it used to be top-left, which
   put it on the opposite corner from every other close affordance in the app) */
body.edx-page .edx-tile__act {
  position: absolute; top: 5px; z-index: 3;
  width: 22px; height: 22px; border-radius: 7px; border: 0; padding: 0; cursor: pointer;
  display: grid; place-items: center;
  font-size: 12px; font-weight: 500; line-height: 1;
  background: rgba(8,8,10,.74); backdrop-filter: blur(4px); color: #fff;
  opacity: 0; transform: scale(.86); pointer-events: none;
  transition: opacity .13s ease, transform .13s ease, background .13s ease, color .13s ease;
}
body.edx-page .edx-tile__add { right: 5px; font-size: 13px; }
body.edx-page .edx-tile__rm  { right: 5px; left: auto; }
body.edx-page .edx-tile:hover .edx-tile__act,
body.edx-page .edx-tile:focus-within .edx-tile__act { opacity: 1; transform: none; pointer-events: auto; }
body.edx-page .edx-tile__add:hover { background: var(--violet); color: #fff; }
body.edx-page .edx-tile__rm:hover { background: rgba(224,49,49,.9); color: #fff; }
@media (hover: none) {
  body.edx-page .edx-tile__act { opacity: 1; transform: none; pointer-events: auto; }
}

/* --- (V1) LOADING STATE — Repurposer (.batchcell) pattern ---
   A dim veil + centred spinner live INSIDE the thumbnail, never next to the
   label. The old code put the GLOBAL utility class `is-uploading` on the 10px
   status span, and app.css turns that into `position:relative` + a full-size
   dim ::before + a 22px spinner ::after — i.e. a 22px spinner centred on a 12px
   text line, right on top of the "Uploading…" label and the orange status dot.
   The modifier is namespaced now (.edx-tile__state--*) and the global rule is
   neutralised below for anything inside a tile. */
body.edx-page .edx-tile__load {
  position: absolute; inset: 0; z-index: 4;
  display: grid; place-items: center;
  background: rgba(14,15,26,.62); border-radius: inherit; pointer-events: none;
}
body.edx-page .edx-tile__spin {
  width: 22px; height: 22px; border-radius: 50%;
  border: 2.5px solid rgba(224,223,255,.28); border-top-color: var(--violet-light);
  animation: edxSpin .8s linear infinite;
}
@keyframes edxSpin { to { transform: rotate(360deg); } }
/* defensive: the global app.css spinner/veil must never fire inside a tile */
body.edx-page .edx-tile.is-uploading::before,
body.edx-page .edx-tile.is-uploading::after,
body.edx-page .edx-tile .is-uploading::before,
body.edx-page .edx-tile .is-uploading::after { content: none !important; display: none !important; }

/* --- (P7) name + status: ONE row, filename left (truncates), status right ---
   The status is a proper PILL with a glossy dot from the same family as the
   calendar / connections dots (.pdot in app.css) — the old 10px bare text +
   5px flat dot read as an afterthought. `font: inherit` keeps Onest. */
body.edx-page .edx-tile__meta {
  display: flex; align-items: center; gap: 6px; min-width: 0; padding: 0 3px;
}
body.edx-page .edx-tile__name {
  flex: 1 1 auto; display: block; min-width: 0; padding: 0;
  font: inherit; font-size: 11.5px; font-weight: 500; line-height: 1.3; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
body.edx-page .edx-tile__state {
  /* the tiles are 2-up in a 200–460px pane, so the pill must stay COMPACT or it
     eats the filename it is supposed to sit beside. Never shrinks (flex 0 0),
     never wider than half the row. */
  flex: 0 0 auto; max-width: 50%;
  display: inline-flex; align-items: center; gap: 5px; min-width: 0;
  padding: 2px 7px 2px 5px; border-radius: 999px;
  background: rgba(255,255,255,.045); border: 1px solid var(--border);
  font: inherit; font-size: 10px; font-weight: 500; line-height: 1.5;
  color: var(--text-mute); white-space: nowrap;
}
body.edx-page .edx-tile__state > span {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
body.edx-page .edx-tile__state::before {
  content: ""; flex: 0 0 auto; width: 7px; height: 7px; border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #d9d9e0 0%, #8a8a95 55%, #4a4a52 100%);
  box-shadow: inset 0 -1px 2px rgba(0,0,0,.4);
}
/* status dot — engine writes .edx-tile__state--* ; [data-status] kept as a fallback */
body.edx-page .edx-tile__state--ready::before,
body.edx-page .edx-tile[data-status="ready"] .edx-tile__state::before {
  background: radial-gradient(circle at 35% 30%, #b9ffd0 0%, #34d26b 55%, #17743a 100%);
  box-shadow: 0 0 9px rgba(52,210,107,.55), inset 0 -1px 2px rgba(0,0,0,.4);
  animation: none;
}
body.edx-page .edx-tile__state--uploading::before,
body.edx-page .edx-tile__state--transcribing::before,
body.edx-page .edx-tile[data-status="uploading"] .edx-tile__state::before,
body.edx-page .edx-tile[data-status="transcribing"] .edx-tile__state::before {
  background: radial-gradient(circle at 35% 30%, #ffe0ad 0%, #f08c00 55%, #7d4900 100%);
  box-shadow: 0 0 9px rgba(240,140,0,.5), inset 0 -1px 2px rgba(0,0,0,.4);
  animation: edxPulse 1s ease-in-out infinite;
}
body.edx-page .edx-tile__state--error::before,
body.edx-page .edx-tile[data-status="error"] .edx-tile__state::before {
  background: radial-gradient(circle at 35% 30%, #ffd0d6 0%, #f0506e 55%, #8d1f34 100%);
  box-shadow: 0 0 9px rgba(240,80,110,.5), inset 0 -1px 2px rgba(0,0,0,.4);
  animation: none;
}
body.edx-page .edx-tile__state--error,
body.edx-page .edx-tile[data-status="error"] .edx-tile__state { color: #ff9b9b; border-color: rgba(224,49,49,.4); }
body.edx-page .edx-tile__state:empty { display: none; }
/* busy tiles stay usable (the engine patches the clip id when the upload lands) */
body.edx-page .edx-tile.is-busy .edx-tile__act { z-index: 5; }
/* legacy node — duration now lives in .edx-tile__dur */
body.edx-page .edx-tile__len { display: none; }
@keyframes edxPulse { 0%,100% { opacity: 1; } 50% { opacity: .35; } }

/* drag ghost that follows the cursor while a media tile is dragged onto the
   timeline. It had NO rule at all — the engine appended an unstyled, statically
   positioned <div> to <body>, so the drag had zero visual feedback. */
body.edx-page .edx-dragghost {
  position: fixed; z-index: 400; pointer-events: none;
  transform: translate(10px, 10px);
  max-width: 220px; padding: 5px 10px; border-radius: 8px;
  background: var(--bg-card); border: 1px solid var(--edx-accent-line);
  box-shadow: 0 8px 22px rgba(0,0,0,.45);
  color: var(--text); font-size: 11.5px; font-weight: 500; line-height: 1.2;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* (v8) EDGE-DRAG LABEL — an edge drag RATE STRETCHES: clip.speed is recomputed
   so the whole (frozen) source window fits the new length, the opposite edge
   stays pinned, and there is no modifier. Text blocks and stills have no source
   to re-time, so their grips set the plain length instead. The label shows the
   number the drag is writing. */
body.edx-page .edx-dragghost--mode {
  font-variant-numeric: tabular-nums;
  border-color: var(--violet); color: var(--text);
}
body.edx-page.is-ratedrag,
body.edx-page.is-ratedrag * { cursor: col-resize !important; }

/* left action buttons full width */
body.edx-page .edx-left .btn { width: 100%; justify-content: center; }
body.edx-page .btn--block { width: 100%; justify-content: center; }

/* =========================================================
   5c) (R2) LIBRARY TABS — Media · Text · Captions (CapCut left panel)
   The pane no longer scrolls as a whole; the tab BODY owns the scroll so the
   tab row stays pinned. Same visual language as the right-hand tab row.
   ========================================================= */
body.edx-page .edx-left__body {
  flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden;
  display: flex; flex-direction: column; gap: 10px; padding-right: 4px;
}
body.edx-page .edx-left__body::-webkit-scrollbar { width: 9px; }
body.edx-page .edx-left__body::-webkit-scrollbar-thumb { background: rgba(255,255,255,.1); border-radius: 999px; border: 2px solid transparent; background-clip: padding-box; }
body.edx-page .edx-left__body::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,.2); background-clip: padding-box; }

body.edx-page .edx-lefttabs { display: flex; gap: 2px; flex: 0 0 auto; border-bottom: var(--edx-line); }
body.edx-page .edx-lefttabs button {
  position: relative; flex: 0 0 auto; appearance: none; border: 0; background: transparent; cursor: pointer;
  color: var(--text-dim); font: inherit; font-size: 12.5px; font-weight: 500; padding: 9px 10px 8px; line-height: 1;
  transition: color .14s ease;
}
body.edx-page .edx-lefttabs button::after {
  content: ""; position: absolute; left: 8px; right: 8px; bottom: -1px; height: 2px;
  border-radius: 2px 2px 0 0; background: transparent; transition: background .14s ease;
}
body.edx-page .edx-lefttabs button:hover { color: var(--text); }
body.edx-page .edx-lefttabs button.is-active { color: var(--text); }
body.edx-page .edx-lefttabs button.is-active::after { background: var(--violet); }

body.edx-page .edx-lpane { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
body.edx-page .edx-lpane[hidden] { display: none; }

/* text preset library (engine builds the tiles) */
body.edx-page .edx-presets { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 8px; }
body.edx-page .edx-preset {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
  min-height: 62px; padding: 10px 8px; border-radius: 11px; cursor: pointer; min-width: 0;
  background: var(--bg-card); border: var(--edx-line); color: var(--text); font: inherit;
  transition: border-color .14s ease, background .14s ease, transform .06s ease;
}
body.edx-page .edx-preset:hover { border-color: var(--edx-accent-line); background: linear-gradient(180deg, var(--edx-accent-soft), transparent 62%), var(--bg-card); }
body.edx-page .edx-preset:active { transform: scale(.985); }
body.edx-page .edx-preset__prev {
  max-width: 100%; font-size: 20px; font-weight: 800; line-height: 1.1;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
body.edx-page .edx-preset__lbl { font-size: 10.5px; font-weight: 500; color: var(--text-dim); }

/* native select, on-brand */
body.edx-page .edx-select {
  width: 100%; appearance: none; -webkit-appearance: none;
  background: var(--bg-card); border: var(--edx-line); border-radius: 9px;
  color: var(--text); font: inherit; font-size: 12.5px; padding: 8px 11px; cursor: pointer;
}
body.edx-page .edx-select:focus { outline: none; border-color: var(--edx-accent-line); }

/* legacy prep progress (force-hidden by JS) reuse look */
body.edx-page .capprog { display: flex; align-items: center; gap: 10px; }
body.edx-page .capprog .cap__bar { flex: 1; height: 4px; border-radius: 999px; overflow: hidden; background: rgba(255,255,255,.08); }
body.edx-page .capprog .cap__bar > i { display: block; height: 100%; width: 0; background: var(--grad-violet); }

/* =========================================================
   6) CENTER — preview stage / compositor
   ========================================================= */
/* (R3) the stage is a COLUMN now: preview area on top, player bar underneath.
   Nothing may float over the frame any more — the guides / aspect dropdowns and
   the play button all moved into .edx-playbar. */
body.edx-page .edx-stage {
  position: relative;
  display: flex; flex-direction: column; align-items: stretch; justify-content: flex-start;
  min-width: 0; min-height: 0; overflow: hidden;
  padding: 0;
  background:
    radial-gradient(120% 90% at 50% 0%, rgba(105,101,219,.06), transparent 60%),
    var(--bg);
}
body.edx-page.edx-focus .edx-stage { padding: 0; }
body.edx-page .edx-stage__view {
  /* (Z1) `position: relative` anchors the zoom HUD; `overflow: hidden` was
     already here and is what clips the magnified frame. Neither creates a
     stacking context (z-index stays auto), so the playbar's fly-up menus
     (.edx-guidedd, z-index 20) still paint over the preview. */
  position: relative;
  flex: 1 1 auto; min-height: 0; min-width: 0;
  display: flex; align-items: center; justify-content: center;
  padding: 10px; overflow: hidden;
  /* NOTE: no `touch-action` override here on purpose. A trackpad pinch arrives as
     ctrl+wheel and is handled (and preventDefault()ed) in JS, which is the actual
     "the whole site zooms" complaint; forcing `touch-action: none` would only
     take page scrolling away from touch users without giving them anything. */
}
body.edx-page .edx-stage__view.is-panning { cursor: grabbing; }

/* (Z1) PREVIEW ZOOM HUD — floats over the backdrop, bottom-right of the stage
   view. Hidden at Fit; the engine unhides it the moment the view scale moves. */
body.edx-page .edx-zoomhud {
  position: absolute; right: 12px; bottom: 12px; z-index: 6;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 3px 3px 3px 10px; border-radius: 999px;
  background: rgba(16,16,20,.78); border: 1px solid rgba(255,255,255,.10);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  box-shadow: 0 6px 20px rgba(0,0,0,.35);
}
body.edx-page .edx-zoomhud[hidden] { display: none; }
body.edx-page .edx-zoomhud__val {
  font-size: 11px; font-weight: 400; color: var(--text);
  font-variant-numeric: tabular-nums; letter-spacing: .01em; white-space: nowrap;
}
body.edx-page .edx-zoomhud__fit {
  appearance: none; border: 0; cursor: pointer; font: inherit;
  font-size: 11px; font-weight: 500; line-height: 1.1; color: #fff;
  background: var(--violet); padding: 4px 10px; border-radius: 999px;
  transition: filter .15s ease;
}
body.edx-page .edx-zoomhud__fit:hover { filter: brightness(1.1); }

/* ---- CapCut player bar ---- */
body.edx-page .edx-playbar {
  flex: 0 0 auto; display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 8px;
  padding: 6px 10px; border-top: var(--edx-line); background: var(--bg-soft); min-width: 0;
}
body.edx-page .edx-playbar__l { display: flex; align-items: center; gap: 8px; min-width: 0; }
body.edx-page .edx-playbar__c { display: flex; align-items: center; gap: 2px; justify-self: center; }
body.edx-page .edx-playbar__r { display: flex; align-items: center; gap: 6px; justify-content: flex-end; min-width: 0; }
body.edx-page .edx-playbar__time { font-variant-numeric: tabular-nums; font-size: 12px; font-weight: 500; color: var(--text-dim); white-space: nowrap; }
body.edx-page .edx-playbar .tlicon--play { color: var(--text); }
body.edx-page .edx-playbar .tlicon--play svg { width: 18px; height: 18px; }

/* the two dropdowns now live IN the bar — kill the absolute corner positioning
   and fly both menus UP so they never leave the viewport. */
body.edx-page .edx-playbar .edx-guidedd,
body.edx-page .edx-playbar .edx-aspectdd { position: relative; left: auto; right: auto; top: auto; bottom: auto; z-index: 20; }
body.edx-page .edx-playbar .edx-guidedd .edx-guidemenu { left: auto; right: 0; top: auto; bottom: calc(100% + 5px); }

/* fullscreen preview — the STAGE goes fullscreen so the bar comes along */
body.edx-page .edx-stage:fullscreen { background: #000; }
body.edx-page .edx-stage:fullscreen .edx-playbar { background: rgba(16,16,20,.92); border-top-color: rgba(255,255,255,.08); }
body.edx-page.edx-prevfs .edx-stage__view { padding: 6px; }

/* =========================================================
   (G) PLATFORM SAFE-ZONE GUIDES — compact corner dropdown (#prevModes),
   mirrors the aspect dropdown. Engine keeps toggling [hidden] on the wrapper
   so it only exists at 9:16. Options: No guides / TikTok / Instagram Reels /
   YouTube Shorts. The guide boxes themselves are .uiov__box / .uiov__line
   inside #uiOverlay, positioned inline by buildOverlay() exactly like
   reaction.js — app.css owns their base look and is NOT touched; everything
   below is scoped to body.edx-page only.
   ========================================================= */
body.edx-page .edx-guidedd { position: absolute; left: 16px; top: 16px; z-index: 6; }
body.edx-page .edx-guidedd[hidden] { display: none; }
/* the guide menu drops DOWN-LEFT (the aspect one flies UP-RIGHT) */
body.edx-page .edx-guidedd .edx-guidemenu {
  right: auto; bottom: auto; left: 0; top: calc(100% + 5px); min-width: 176px;
}
body.edx-page .edx-guidedd .edx-cornerbtn { font-variant-numeric: normal; }

/* overlay itself — above the layers, below the transform box */
body.edx-page .uiov[hidden] { display: none; }
body.edx-page .uiov > * { pointer-events: none; }
/* scoped polish only — never restyled globally */
body.edx-page .uiov__box { border-radius: 7px; }
body.edx-page .uiov__lab { letter-spacing: .04em; }

/* legacy 3-button pill row (kept for safety; markup now uses .edx-guidedd) */
body.edx-page .edx-guide {
  position: absolute; top: 16px; left: 16px; z-index: 6;
  display: inline-flex; gap: 2px; padding: 3px; border-radius: 9px;
  background: rgba(18,18,18,.72); backdrop-filter: blur(6px); border: var(--edx-line);
}
body.edx-page .edx-guide[hidden] { display: none; }
body.edx-page .edx-guide button {
  border: 0; background: transparent; color: var(--text-dim);
  font: inherit; font-size: 11px; font-weight: 500; padding: 3px 9px; border-radius: 6px; cursor: pointer;
  transition: background .14s ease, color .14s ease;
}
body.edx-page .edx-guide button:hover { color: var(--text); }
body.edx-page .edx-guide button.is-active { color: var(--violet-light); background: var(--edx-accent-soft); }

/* ---- compact corner dropdown (aspect ratio) in the player's bottom-right ---- */
body.edx-page .edx-aspectdd { position: absolute; right: 16px; bottom: 16px; z-index: 6; }
body.edx-page .edx-cornerbtn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 9px; border-radius: 8px; cursor: pointer;
  background: rgba(18,18,18,.72); backdrop-filter: blur(6px); border: var(--edx-line);
  color: var(--text); font: inherit; font-size: 11.5px; font-weight: 500; white-space: nowrap;
  font-variant-numeric: tabular-nums;
  transition: border-color .14s ease, color .14s ease;
}
body.edx-page .edx-cornerbtn svg { opacity: .8; transition: transform .16s ease; }
body.edx-page .edx-cornerbtn:hover { border-color: var(--edx-accent-line); }
body.edx-page .edx-cornerbtn[aria-expanded="true"] { border-color: var(--edx-accent-line); color: var(--violet-light); }
body.edx-page .edx-cornerbtn[aria-expanded="true"] svg { transform: rotate(180deg); }
body.edx-page .edx-cornermenu {
  position: absolute; right: 0; bottom: calc(100% + 5px); z-index: 30;
  min-width: 168px; padding: 5px;
  background: var(--bg-card); border: 1px solid var(--border-lt); border-radius: 11px;
  box-shadow: var(--shadow);
}
body.edx-page .edx-cornermenu[hidden] { display: none; }
body.edx-page .edx-cornermenu button {
  display: block; width: 100%; text-align: left; border: 0; background: transparent; cursor: pointer;
  color: var(--text); font: inherit; font-size: 12.5px; font-weight: 500;
  padding: 8px 9px; border-radius: 8px; white-space: nowrap;
  transition: background .13s ease, color .13s ease;
}
body.edx-page .edx-cornermenu button:hover { background: var(--edx-accent-soft); color: var(--violet-light); }
body.edx-page .edx-cornermenu button.is-active { background: var(--edx-accent-soft); color: var(--violet-light); }

/* (P8) GUIDES DROPDOWN — brand LOGO on the trigger, logo + full label in the menu.
   These rules MUST come after `.edx-cornermenu button { display:block }` above:
   identical specificity, so source order decides which `display` wins. */
body.edx-page .edx-guidedd__ico {
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; flex: 0 0 auto;
}
body.edx-page .edx-guidedd__ico svg { width: 16px; height: 16px; display: block; }
/* the trigger is icon-only now — tighten it so it reads as a control, not a chip */
body.edx-page .edx-cornerbtn--ico { padding: 5px 7px; gap: 4px; }
body.edx-page .edx-guidemenu button { display: flex; align-items: center; gap: 9px; }
body.edx-page .edx-guidemenu__ico {
  flex: 0 0 auto; display: grid; place-items: center;
  width: 16px; height: 16px; color: var(--text-dim);
}
body.edx-page .edx-guidemenu__ico svg { width: 15px; height: 15px; display: block; }
body.edx-page .edx-guidemenu button:hover .edx-guidemenu__ico,
body.edx-page .edx-guidemenu button.is-active .edx-guidemenu__ico { color: var(--violet-light); }

/* the aspect frame = compositor root (sized by JS via sizeStage → --edx-frame-w/h).
   sizeStage() computes the LARGEST box of the current aspect that fits BOTH the
   stage width and height, so the preview always fits its allotted space for
   9:16, 1:1 AND 16:9. max-* clamp is a safety net if JS hasn't run yet. */
body.edx-page .edx-frame {
  position: relative;
  width: var(--edx-frame-w); height: var(--edx-frame-h);
  max-width: 100%; max-height: 100%;
  border-radius: 12px; overflow: hidden;
  background: #000;
  /* (Z1) the preview zoom scales the frame about its own centre. No `transform`
     is declared here — caption.v7.js writes one ONLY while the view is off Fit
     and clears it back to "" at 100 %, so at Fit the frame has no transform at
     all and every client rect reads exactly as it did before the zoom existed. */
  transform-origin: 50% 50%;
  box-shadow: 0 18px 60px rgba(0,0,0,.5), 0 0 0 1px var(--border);
  /* self-contained z stack: nothing inside the preview (layers, transform box,
     play button) may out-paint the stage's corner controls (z 6) */
  isolation: isolate;
}
/* (B5) `isolation: isolate` is LOAD-BEARING, not decoration. The compositor gives
   each layer a stacking z of (tracks - ti) * 100 + z + 1 — i.e. 101 and up. With
   `.edx-comp` at z-index:auto it creates NO stacking context, so those layer
   z-indexes competed directly with the frame's overlays in the root stacking
   context and painted OVER them: the transform box (z 8), the play button (z 9)
   and the safe-zone overlay (z 7) all ended up behind the video, which made them
   invisible AND unhittable (elementFromPoint returned the <video>) — preview drag
   / scale / rotate could never start. Isolating the comp keeps the layer stack
   internal, so the overlays sit above it again. */
body.edx-page .edx-comp { position: absolute; inset: 0; overflow: hidden; background: #000; isolation: isolate; }

/* layers */
body.edx-page .edx-layer { position: absolute; inset: 0; transform-origin: center center; will-change: transform, opacity; }
/* BASE PLACEMENT = CONTAIN (fit) — this was the "changing the ratio destroys my
   clip" bug. `object-fit: cover` centre-CROPPED the source into the frame, so
   switching 9:16 → 4:3 threw away the sides for good. The base is CONTAIN now:
   at transform.scale = 100 the WHOLE source frame is visible, centred, with
   black bars where the aspects differ — exactly what composite.py's
   `scale={cw}:{ch}` contain base renders.
   The engine (applyTransform) also sizes the element to the CONTAINED box in px,
   so the layer's border box IS the picture: the transform box, hit testing and
   `object-fit` all agree. `contain` stays as the safety net for the frames
   before the source dimensions are known (element still frame-sized then). */
body.edx-page .edx-layer--video,
body.edx-page .edx-layer--image {
  inset: auto; left: 50%; top: 50%;      /* JS keeps both at 50% and centres via translate(-50%,-50%) */
  transform: translate(-50%, -50%);      /* default so a layer JS has not touched yet is still centred */
  width: 100%; height: 100%;
  object-fit: contain; display: block;
}
body.edx-page .edx-layer[hidden] { display: none; }
/* (B6) TEXT LAYER GEOMETRY — this was the "text deforms when you drag it" bug.
   `.edx-layer` sets `inset:0`; the engine only overrode `left`/`top` inline, so
   `right:0`/`bottom:0` survived and the box stretched from (xP%,yP%) all the way
   to the frame's bottom-right corner — its width/height changed on every move,
   which is exactly what the transform box was measuring. The box is real now:
   auto inset, max-content width capped at 88%, centred by translate(-50%,-50%). */
body.edx-page .edx-layer--text {
  inset: auto;
  left: 50%; top: 80%;                 /* JS overrides both from text.xP / text.yP */
  /* (item 8) THE ENGINE OWNS THE WIDTH. ensureText() writes width/max-width/
     min-width inline from text.wrapW, because the wrap column is a free 10–100 %
     parameter now: it must be able to sit NARROWER than the text (forcing breaks,
     inside a word if need be) and WIDER than it (the box extends past the glyphs).
     These three values are only the pre-JS default. */
  width: max-content; max-width: 88%; min-width: 1.5em; min-height: 1em;
  display: flex; align-items: center; justify-content: center; text-align: center;
  padding: 0;
  pointer-events: auto; cursor: move;
  transform-origin: center center;
  /* line-height 1 == the RENDERER's line pitch. libass scales glyphs so that
     ascender+descender equals Fontsize and steps exactly one Fontsize per \N
     (measured: 96 px steps at Fontsize 96), so the old 1.12 made multi-line
     preview text sit looser than the exported frame — visible now that each line
     can carry its own background box. */
  font-weight: 700; line-height: 1; color: #fff;
  text-shadow: 0 2px 6px rgba(0,0,0,.55);
  word-break: break-word; white-space: pre-wrap;
  /* (PX4) BREAK WHERE THE RENDERER BREAKS.
     ass_text.py decides every line break itself (`_wrap`, then `\q2` so libass
     does not re-break) and it measures with Pillow's BASIC layout engine, which
     applies NO kerning and NO ligature substitution. Chrome's default layout
     does both, so at the same nominal size Chrome measured a line 0.29–0.75 %
     NARROWER than the renderer did, and roughly one wrap column in six broke on
     a different word — a whole word jumping between lines between preview and
     export. Measured after turning these off: 6 of 7 reference strings land
     within 0.05 % of Pillow, the seventh within 0.30 %.
     THE TRADE-OFF, stated plainly: libass DOES kern when it finally draws, so
     the preview's glyphs sit up to ~0.6 % of a line width looser than the
     exported ones — a fraction of a pixel per glyph, and exactly the width the
     renderer itself assumes when it sizes the background box. Correct BREAKS
     beat sub-pixel tracking.
     (GAP1, 2026-07) The other half of the mismatch has since been closed on the
     RENDERER side: ass_text._advance no longer measures at an integer ppem with
     hinting on (which quantised every glyph advance and hurt most at small
     sizes) but at a fixed ppem 4096, rescaled linearly — the unhinted,
     fractional-size behaviour Chrome has. Re-measured against real Chrome with
     these very rules: whole-string advance error 0.33 % -> 0.012 %, and the
     share of wrap columns that broke on a different word 1.12 % -> 0.13 %. The
     shaper is still Pillow BASIC (no kern, no liga), which is exactly what the
     three declarations below ask Chrome for. */
  font-kerning: none;
  font-variant-ligatures: none;
  font-feature-settings: "kern" 0, "liga" 0, "clig" 0, "calt" 0;
}
/* only interactive while editing — never steal clicks from the render preview */
body.edx-page .edx-layer--text { pointer-events: none; }
body.edx-page .edx.is-editing .edx-layer--text { pointer-events: auto; }

/* (v8) the drawn string lives in ONE inline span. Inline is what makes the
   "Per line" background work: with box-decoration-break:clone every wrapped line
   gets its OWN rectangle hugging that line's width — the DOM mirror of the
   per-line \p1 drawings ass_text.py emits for text.box.mode === "line".
   The span is transparent and unpadded in "Block" mode, where the rectangle
   stays on the layer itself, so the two modes share one geometry. */
body.edx-page .edx-layer--text .edx-txt__box {
  max-width: 100%;
  background: transparent; padding: 0; border-radius: 0;
  text-align: inherit; word-break: inherit; white-space: inherit;
}
body.edx-page .edx-layer--text .edx-txt__in {
  display: inline;         /* MUST stay inline — box-decoration-break needs it */
  background: transparent; padding: 0; border-radius: 0;
  color: inherit; font: inherit; letter-spacing: inherit; white-space: inherit;
  /* (PX4) `font: inherit` is a SHORTHAND and it RESETS font-kerning /
     font-variant-ligatures / font-feature-settings to their initial values — so
     the layer's no-kerning rule above would stop at this span, which is the node
     the glyphs actually live in. Restate them AFTER the shorthand. */
  font-kerning: none;
  font-variant-ligatures: none;
  font-feature-settings: "kern" 0, "liga" 0, "clig" 0, "calt" 0;
}

/* transform box over the selected layer */
/* z-index 10 = ABOVE .playbtn (9). At scale 100 the box covers the whole frame,
   so a z of 8 left the 62px play button punching a dead hole in the middle of the
   drag surface. The box swallows that click instead and the engine toggles
   playback itself when the pointer did not move (see the #edxTbox pointerup). */
/* (D2) WHITE by default, VIOLET only while an edge/centre is snapped. The box was
   always violet, so "snapped" and "free" looked identical; the engine toggles
   .is-snapped from snapPreviewDrag(). */
body.edx-page .edx-tbox {
  position: absolute; z-index: 10; cursor: move;
  border: 1px solid #fff; box-shadow: 0 0 0 1px rgba(0,0,0,.35);
  touch-action: none;                 /* pointer drag must not scroll the page */
  transition: border-color .1s ease;
}
body.edx-page .edx-tbox[hidden] { display: none; }
body.edx-page .edx-tbox__h,
body.edx-page .edx-tbox__rot { touch-action: none; }
/* text rotation IS render-true now (ASS \frz) — the knob stays for every kind */
body.edx-page .edx-tbox__h {
  position: absolute; width: 9px; height: 9px; background: #fff;
  border: 1px solid rgba(0,0,0,.45); border-radius: 2px;
}
body.edx-page .edx-tbox.is-snapped { border-color: var(--violet); }
body.edx-page .edx-tbox.is-snapped .edx-tbox__h,
body.edx-page .edx-tbox.is-snapped .edx-tbox__rot { border-color: var(--violet); background: var(--violet-light); }
body.edx-page .edx-tbox.is-snapped .edx-tbox__rot::before { background: var(--violet); }

/* (D1) SNAP GUIDE LINES — drawn inside .edx-frame at the snapped line.
   z-index 35 sits between .uiov (30) and .edx-tbox (40) in the frame's ladder. */
body.edx-page .edx-guideline {
  position: absolute; z-index: 35; pointer-events: none; display: block;
  background: var(--violet); box-shadow: 0 0 6px rgba(105,101,219,.8);
}
body.edx-page .edx-guideline--v { top: 0; bottom: 0; width: 1px; }
body.edx-page .edx-guideline--h { left: 0; right: 0; height: 1px; }
body.edx-page .edx-guideline[hidden] { display: none; }

/* (D3) inline text editing on the preview */
body.edx-page .edx-layer--text.is-editing {
  outline: 1px dashed var(--violet); outline-offset: 2px;
  cursor: text; user-select: text; pointer-events: auto;
}

/* video FX overlay (dip / flash / vignette / RGB split) — see syncVfxOverlay() */
body.edx-page .edx-vfx { position: absolute; inset: 0; display: none; pointer-events: none; }
body.edx-page .edx-tbox__h[data-h="nw"] { top: -5px; left: -5px; cursor: nwse-resize; }
body.edx-page .edx-tbox__h[data-h="n"]  { top: -5px; left: 50%; transform: translateX(-50%); cursor: ns-resize; }
body.edx-page .edx-tbox__h[data-h="ne"] { top: -5px; right: -5px; cursor: nesw-resize; }
body.edx-page .edx-tbox__h[data-h="e"]  { top: 50%; right: -5px; transform: translateY(-50%); cursor: ew-resize; }
body.edx-page .edx-tbox__h[data-h="se"] { bottom: -5px; right: -5px; cursor: nwse-resize; }
body.edx-page .edx-tbox__h[data-h="s"]  { bottom: -5px; left: 50%; transform: translateX(-50%); cursor: ns-resize; }
body.edx-page .edx-tbox__h[data-h="sw"] { bottom: -5px; left: -5px; cursor: nesw-resize; }
body.edx-page .edx-tbox__h[data-h="w"]  { top: 50%; left: -5px; transform: translateY(-50%); cursor: ew-resize; }
body.edx-page .edx-tbox__rot {
  position: absolute; top: -22px; left: 50%; transform: translateX(-50%);
  width: 11px; height: 11px; border-radius: 50%; background: #fff; border: 1px solid rgba(0,0,0,.45); cursor: grab;
}
body.edx-page .edx-tbox__rot::before { content: ""; position: absolute; top: 11px; left: 50%; transform: translateX(-50%); width: 1px; height: 11px; background: #fff; }

/* (A1) TEXT SIDE GRIPS — CapCut's slim vertical bars.
   On a TEXT layer the left/right grips do something the corners do not: they
   change the WRAP COLUMN (text.wrapW) and leave the font size alone. They are
   shaped like it — a thin pill rather than a square corner nub — so the two
   gestures are told apart before the drag starts. The visible bar is 4px wide,
   but ::before spreads an INVISIBLE 16px-wide hit area around it so it stays
   easy to grab (and `background:transparent` keeps the pad from painting).
   `data-kind` is set by updateTbox() from the selected clip's kind, so video and
   image layers keep the square handles — they have no text to re-wrap. */
body.edx-page .edx-tbox[data-kind="text"] .edx-tbox__h[data-h="w"],
body.edx-page .edx-tbox[data-kind="text"] .edx-tbox__h[data-h="e"] {
  width: 4px; height: 26px; min-height: 26px; border-radius: 999px;
  background: #fff; border: 1px solid rgba(0,0,0,.45);
  cursor: ew-resize;
}
body.edx-page .edx-tbox[data-kind="text"] .edx-tbox__h[data-h="w"] { left: -3px; }
body.edx-page .edx-tbox[data-kind="text"] .edx-tbox__h[data-h="e"] { right: -3px; }
body.edx-page .edx-tbox[data-kind="text"] .edx-tbox__h[data-h="w"]::before,
body.edx-page .edx-tbox[data-kind="text"] .edx-tbox__h[data-h="e"]::before {
  content: ""; position: absolute; top: -8px; bottom: -8px; left: -7px; right: -7px;
  background: transparent;
}
/* the block is centred on text.xP, so a side drag widens BOTH sides — the top and
   bottom grips would read as "stretch vertically", which text cannot do. They
   stay on uniform scale, so keep them visually identical to the corners. */
body.edx-page .edx-tbox[data-kind="text"] .edx-tbox__h[data-h="w"]:hover,
body.edx-page .edx-tbox[data-kind="text"] .edx-tbox__h[data-h="e"]:hover { background: var(--violet-light); }

/* =========================================================
   (R4) ONE AUTHORITATIVE STACKING LADDER INSIDE .edx-frame
   .edx-frame is `isolation: isolate`, so this ladder is self-contained:
     .edx-comp          1   own stacking context — layer z 101+ stay INSIDE it
     .phoneframe__empty 5
     .uiov (guides)    30   ← must ALWAYS paint over every video/text layer
     .edx-tbox         40   ← above the guides so the handles stay grabbable
   Never give .edx-comp a z-index above 1 and never drop its `isolation`.
   ========================================================= */
body.edx-page .uiov { position: absolute; inset: 0; z-index: 30; pointer-events: none; }
body.edx-page .edx-comp { z-index: 1; }
body.edx-page .edx-tbox { z-index: 40; }
body.edx-page .phoneframe__empty { z-index: 5; }
/* the guides must stay readable on a bright video frame */
body.edx-page .uiov__box { border-radius: 7px; box-shadow: 0 0 0 1px rgba(0,0,0,.35); }
body.edx-page .uiov__line { filter: drop-shadow(0 1px 1px rgba(0,0,0,.6)); }

/* shared overlay + play button + empty state */
body.edx-page .playbtn {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 9;
  width: 62px; height: 62px; border-radius: 50%; border: 0; cursor: pointer;
  background: rgba(18,18,18,.55); backdrop-filter: blur(6px); color: #fff;
  font-size: 22px; display: grid; place-items: center;
  transition: background .15s ease, transform .1s ease;
}
body.edx-page .playbtn:hover { background: rgba(105,101,219,.85); }
body.edx-page .playbtn:active { transform: translate(-50%, -50%) scale(.94); }
/* (R3) RETIRED — nothing may overlap the video frame. The node stays in the DOM
   so the engine's ref keeps working; clicking the preview still toggles play. */
body.edx-page .playbtn { display: none !important; }
body.edx-page .capsample { display: none !important; }
body.edx-page .phoneframe__empty {
  position: absolute; inset: 0; z-index: 4; display: grid; place-items: center;
  color: var(--text-dim); font-size: 13px; font-weight: 500; letter-spacing: .01em;
  background: repeating-linear-gradient(45deg, rgba(255,255,255,.015) 0 12px, transparent 12px 24px);
}

/* =========================================================
   7) RIGHT — properties tabs & panes
   ========================================================= */
/* tab bar wrapper — chevrons flank the horizontally-scrolling underline row */
body.edx-page .edx-tabs {
  position: relative; display: flex; align-items: stretch; gap: 2px;
  flex: 0 0 auto; min-width: 0;
  border-bottom: var(--edx-line); margin-bottom: 8px;
}
body.edx-page .edx-right__tabs {
  display: flex; flex-wrap: nowrap; gap: 2px; min-width: 0;
  flex: 1 1 auto; overflow-x: auto; overflow-y: hidden;
  scrollbar-width: none; scroll-behavior: smooth;
}
body.edx-page .edx-right__tabs::-webkit-scrollbar { height: 0; display: none; }
/* Posts-page style: minimal text tab with an active violet underline (no fill) */
body.edx-page .edx-right__tabs button {
  position: relative; flex: 0 0 auto; white-space: nowrap;
  appearance: none; border: 0; background: transparent; cursor: pointer;
  color: var(--text-dim); font: inherit; font-size: 12.5px; font-weight: 500;
  padding: 9px 10px 8px; line-height: 1;
  transition: color .14s ease;
}
body.edx-page .edx-right__tabs button::after {
  content: ""; position: absolute; left: 8px; right: 8px; bottom: -1px; height: 2px;
  border-radius: 2px 2px 0 0; background: transparent; transition: background .14s ease;
}
body.edx-page .edx-right__tabs button:hover { color: var(--text); }
body.edx-page .edx-right__tabs button.is-active { color: var(--text); }
body.edx-page .edx-right__tabs button.is-active::after { background: var(--violet); }

/* (C) CONTEXT-SENSITIVE PANEL — the engine flips `hidden` on every
   [data-kinds] node (tab buttons AND groups inside the panes) to match the
   current selection kind: none|video|text|audio. Tabs are HIDDEN, never
   disabled, so the visible row reads exactly like CapCut's:
     video → Video · Speed · Adjust
     text  → Text · Animation
     audio → Audio · Speed
     none  → Captions
   These rules must beat the generic button rules above, hence the extra class. */
body.edx-page .edx-tabs .edx-right__tabs button[hidden] { display: none; }
body.edx-page .edx-right__body [data-kinds][hidden] { display: none; }
/* when a single tab is left the underline row should still read as a header */
body.edx-page .edx-right__tabs:empty { min-height: 0; }

/* scroll chevrons — CapCut affordance; JS toggles [hidden] on overflow */
body.edx-page .edx-tabs__chev {
  flex: 0 0 auto; display: grid; place-items: center; width: 22px;
  border: 0; background: var(--bg-soft); color: var(--text-dim); cursor: pointer;
  transition: color .14s ease;
}
body.edx-page .edx-tabs__chev:hover { color: var(--text); }
body.edx-page .edx-tabs__chev[hidden] { display: none; }
body.edx-page .edx-tabs__chev svg { width: 14px; height: 14px; }

/* right-panel body scrolls vertically; min-height:0 lets the flex child shrink.
   (V3) The 12px --edx-pad lives on .edx-right, but the SCROLLING element is this
   one and it had no horizontal padding of its own — its 9px scrollbar is drawn
   inside its content box, so full-width sliders and the right-aligned
   .edx-field__val ran straight into the border. scrollbar-gutter keeps the
   10px gutter stable whether or not the scrollbar is showing. */
/* (E6) padding-left was 2px while a SELECTED swatch paints a 3.5px ring OUTSIDE
   its border box — with `overflow-x: hidden` the first (white) swatch had its
   ring, and part of the circle itself, clipped away by this scroll container.
   6px clears the ring on both ends; the swatch row adds its own 4px too. */
body.edx-page .edx-right__body {
  display: flex; flex-direction: column; min-height: 0;
  flex: 1 1 auto; overflow-y: auto; overflow-x: hidden;
  padding-right: 10px; padding-left: 6px;
  scrollbar-gutter: stable;
}
/* keep the tab underline aligned with the padded body */
body.edx-page .edx-right .edx-tabs { padding-right: 10px; padding-left: 6px; }
body.edx-page .edx-pane { display: flex; flex-direction: column; gap: 12px; padding: 4px 0 6px; }
body.edx-page .edx-pane[hidden] { display: none; }

/* --- CapCut-style pane header + grouped sub-sections (C) --- */
body.edx-page .edx-pane__title {
  margin: 0 0 2px; padding-bottom: 8px; border-bottom: var(--edx-line);
  font-size: 13px; font-weight: 600; letter-spacing: .01em; color: var(--text);
}
body.edx-page .edx-sub {
  display: flex; align-items: center; gap: 8px;
  margin: 2px 0 -2px;
  font-size: 10.5px; font-weight: 600; letter-spacing: .07em; text-transform: uppercase;
  color: var(--text-dim); white-space: nowrap;
}
body.edx-page .edx-sub::after {
  content: ""; flex: 1 1 auto; height: 1px; min-width: 8px;
  background: var(--border); opacity: .8;
}
body.edx-page .edx-group { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
body.edx-page .edx-group[hidden] { display: none; }
/* sub-headings sitting directly in a pane keep the same rhythm as grouped ones */
body.edx-page .edx-pane > .edx-sub { margin-top: 4px; }

/* --- (R7) CapCut sub-header pattern INSIDE a pane (Text settings / animation) --- */
body.edx-page .edx-subtabs {
  display: inline-flex; gap: 2px; padding: 3px; border-radius: 10px; align-self: stretch;
  background: var(--bg-soft); border: var(--edx-line);
}
body.edx-page .edx-subtabs button {
  flex: 1 1 auto; appearance: none; border: 0; background: transparent; cursor: pointer;
  color: var(--text-dim); font: inherit; font-size: 12px; font-weight: 500;
  padding: 6px 10px; border-radius: 7px; line-height: 1.1; white-space: nowrap;
  transition: background .15s ease, color .15s ease;
}
body.edx-page .edx-subtabs button:hover { color: var(--text); background: rgba(255,255,255,.05); }
body.edx-page .edx-subtabs button.is-active { color: #fff; background: var(--violet); box-shadow: 0 1px 6px rgba(105,101,219,.35); }
body.edx-page .edx-subpane { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
body.edx-page .edx-subpane[hidden] { display: none; }
/* (E2) compact sub-tabs (background mode, repurpose strength) */
body.edx-page .edx-subtabs--sm { align-self: flex-start; padding: 2px; }
body.edx-page .edx-subtabs--sm button { padding: 4px 16px; font-size: 11.5px; }
/* (Z3) IN / OUT above an animation grid — CapCut's two-up segmented control:
   full pane width, split EXACTLY in half. `flex: 1 1 auto` only shares the FREE
   space, so "In" stayed narrower than "Out"; a zero basis makes both segments
   the same width whatever the labels are. Used by BOTH animation panes
   (#vanDir, #txtAnimDir) so video and text can never drift apart again — the
   text one used to carry `--sm`, which shrank it to its content. */
body.edx-page .edx-subtabs--half { align-self: stretch; }
body.edx-page .edx-subtabs--half button { flex: 1 1 0; min-width: 0; padding: 6px 8px; text-align: center; }
body.edx-page .edx-anidir { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
body.edx-page .edx-anidir[hidden] { display: none; }

/* --- (R1) EMPTY STATE — nothing on the timeline at all --- */
body.edx-page .edx-pane--empty { flex: 1 1 auto; }
body.edx-page .edx-empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center;
  gap: 8px; margin: auto 0; padding: 26px 14px; border-radius: 12px;
  border: 1px dashed var(--border-lt); background: rgba(255,255,255,.014);
}
body.edx-page .edx-empty__ico { display: grid; place-items: center; width: 42px; height: 42px; border-radius: 12px; color: var(--violet-light); background: var(--edx-accent-soft); }
body.edx-page .edx-empty__t { margin: 0; font-size: 13px; font-weight: 600; color: var(--text); }
body.edx-page .edx-empty__p { margin: 0; font-size: 12px; color: var(--text-mute); line-height: 1.45; }
body.edx-page .edx-empty__hint { margin: 0; font-size: 10.5px; color: var(--text-dim); line-height: 1.4; }
/* the whole tab row disappears in the empty state */
body.edx-page .edx-tabs[hidden] { display: none; }

/* generic field + range */
body.edx-page .edx-field { display: flex; flex-direction: column; gap: 6px; }
body.edx-page .edx-field > label {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  font-size: 12px; font-weight: 500; color: var(--text-mute);
}
body.edx-page .edx-field__val { font-size: 11px; font-weight: 400; color: var(--text-dim); font-variant-numeric: tabular-nums; }
body.edx-page .edx-field--row { flex-direction: row; align-items: center; justify-content: space-between; }

/* (P9) CapCut TRACK FILL — violet to the LEFT of the thumb on EVERY range in the
   editor (size, outline, opacity, scale, speed, durations, volume, fades, adjust).
   `--v` (0–100%) is written by the engine's paintRange() on every `input` event
   AND on every programmatic setVal(), so the fill matches the value after load,
   after a drag and after selecting another clip.
   The selector is intentionally page-wide (not `.edx-pane`) so any range added
   outside a properties pane picks the same treatment up automatically.
   Firefox note: ::-moz-range-track MUST be transparent or it would paint over
   the gradient that lives on the input's own background. */
body.edx-page input[type="range"] {
  -webkit-appearance: none; appearance: none; width: 100%; height: 4px; margin: 6px 0;
  border-radius: 999px; cursor: pointer;
  background-color: rgba(255,255,255,.10);
  /* Dolgu --v0 (sıfır noktası) ile --v (değer) ARASINI boyar. Tek yönlü
     alanlarda --v0 = 0% → eskisi gibi soldan dolar. Çift yönlü alanlarda
     (Position X/Y, Rotate, Temp/Tint) --v0 ortadaki sıfırdır → dolgu ortadan
     değere doğru uzar, 0'da hiç görünmez. */
  background-image: linear-gradient(to right,
    transparent 0 min(var(--v0, 0%), var(--v, 0%)),
    var(--violet) min(var(--v0, 0%), var(--v, 0%)) max(var(--v0, 0%), var(--v, 0%)),
    transparent max(var(--v0, 0%), var(--v, 0%)) 100%);
  background-repeat: no-repeat;
}
body.edx-page input[type="range"]:disabled { opacity: .45; cursor: default; }
body.edx-page input[type="range"]::-webkit-slider-runnable-track { height: 4px; border-radius: 999px; background: transparent; }
body.edx-page input[type="range"]::-moz-range-track { height: 4px; border-radius: 999px; background: transparent; }
body.edx-page input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none; width: 14px; height: 14px; margin-top: -5px;
  border-radius: 50%; background: #fff; border: 3px solid var(--violet);
  cursor: grab; box-shadow: 0 1px 4px rgba(0,0,0,.4); transition: transform .08s ease;
}
body.edx-page input[type="range"]::-webkit-slider-thumb:active { transform: scale(1.12); cursor: grabbing; }
body.edx-page input[type="range"]::-moz-range-thumb {
  width: 14px; height: 14px; border-radius: 50%; background: #fff;
  border: 3px solid var(--violet); cursor: grab; box-shadow: 0 1px 4px rgba(0,0,0,.4);
}
body.edx-page input[type="range"]:focus-visible { outline: none; }
body.edx-page input[type="range"]:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 4px var(--edx-accent-soft); }

/* textarea */
body.edx-page .edx-textarea {
  width: 100%; resize: vertical; min-height: 46px;
  background: var(--bg-card); border: var(--edx-line); border-radius: 9px;
  color: var(--text); font: inherit; font-size: 12.5px; padding: 8px 10px;
  transition: border-color .14s ease;
}
body.edx-page .edx-textarea:focus { outline: none; border-color: var(--edx-accent-line); }

/* (P9) checkbox row — CUSTOM violet check, not the raw browser box.
   Covers #txtBox ("Background box") and #spdReverse ("Reverse clip"). */
body.edx-page .edx-check {
  display: inline-flex; align-items: center; gap: 9px;
  font: inherit; font-size: 12px; color: var(--text-mute); cursor: pointer;
}
body.edx-page .edx-check input[type="checkbox"] {
  appearance: none; -webkit-appearance: none; margin: 0;
  width: 16px; height: 16px; flex: 0 0 auto; border-radius: 5px;
  border: 1.5px solid var(--border-lt); background: var(--bg-card);
  display: inline-grid; place-items: center; cursor: pointer;
  transition: background .14s ease, border-color .14s ease;
}
body.edx-page .edx-check input[type="checkbox"]::after {
  content: ""; width: 9px; height: 5px; margin-top: -2px;
  border-left: 2px solid #fff; border-bottom: 2px solid #fff;
  transform: rotate(-45deg) scale(.5); opacity: 0;
  transition: opacity .12s ease, transform .12s ease;
}
body.edx-page .edx-check input[type="checkbox"]:hover { border-color: var(--edx-accent-line); }
body.edx-page .edx-check input[type="checkbox"]:checked { background: var(--violet); border-color: var(--violet); }
body.edx-page .edx-check input[type="checkbox"]:checked::after { opacity: 1; transform: rotate(-45deg) scale(1); }
body.edx-page .edx-check input[type="checkbox"]:disabled { opacity: .45; cursor: default; }
body.edx-page .edx-check input[type="checkbox"]:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--edx-accent-soft); }
/* `.edx-field > label` is `display:flex; justify-content:space-between` (it is
   the slider caption pattern: name left, value right). A checkbox label is a
   DIRECT child of .edx-field too, so it inherited that rule and the box drifted
   to the far left while its text sat pinned to the right edge. Higher
   specificity (…> label.edx-check) puts the pair back together. */
body.edx-page .edx-field > label.edx-check {
  display: inline-flex; justify-content: flex-start; align-self: flex-start; gap: 9px;
}

/* button rows */
body.edx-page .edx-btnrow { display: flex; flex-wrap: wrap; gap: 6px; }
body.edx-page .edx-btnrow .minibtn { flex: 1 1 auto; }

/* small note */
body.edx-page .edx-note { margin: 0; font-size: 10.5px; line-height: 1.4; color: var(--text-dim); }
body.edx-page .edx-note b { color: var(--text-mute); font-weight: 600; }

/* (RP) YELLOW WARNING BOX — the SAME amber the export dialog note uses
   (.edxx__note), scaled down for the right-hand property panel. One colour for
   "read this before you do it" across the editor. */
body.edx-page .edx-warn {
  padding: 9px 11px;
  background: rgba(250, 176, 5, .10); border: 1px solid rgba(250, 176, 5, .32);
  border-radius: 9px; font-size: 11px; font-weight: 300; line-height: 1.5;
  color: var(--text);
}
body.edx-page .edx-warn b { font-weight: 500; color: var(--text); }
/* (BG) same box, one notch smaller — used for the honest "the preview cannot
   run the cut-out" line under the Remove background toggle. */
body.edx-page .edx-warn--sm { margin-top: 8px; padding: 7px 9px; font-size: 10.5px; }
body.edx-page .edx-warn--sm[hidden] { display: none; }

/* (RP, item 5) LIVE REPURPOSE READ-OUT — the exact numbers the export applies,
   fetched from /api/caption/repurpose-params. A definition list so the label and
   the value line up without a table; tabular figures so the digits do not dance
   while the playhead moves. */
body.edx-page .edx-rpvals {
  margin-top: 12px; padding: 9px 11px;
  background: var(--edx-accent-soft, rgba(105, 101, 219, .08));
  border: 1px solid var(--edx-accent-line, rgba(105, 101, 219, .28));
  border-radius: 9px;
}
body.edx-page .edx-rpvals[hidden] { display: none; }
body.edx-page .edx-rpvals .edx-sub { margin: 0 0 6px; }
body.edx-page .edx-rpvals__list {
  display: grid; grid-template-columns: auto 1fr; gap: 3px 10px; margin: 0;
  font-size: 11px; font-weight: 300; line-height: 1.5;
}
body.edx-page .edx-rpvals__list dt { color: var(--text-dim); }
body.edx-page .edx-rpvals__list dd {
  margin: 0; color: var(--text); text-align: right;
  font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1;
}
body.edx-page .edx-rpvals .edx-note { margin: 7px 0 0; }

/* color / swatch rows */
body.edx-page .edx-colorrow { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* (P9) ROUND, evenly-sized swatches with a clear selected ring.
   STACKING FIX: every swatch used to be `z-index: auto`, so the selected ring
   (a 3.5px box-shadow, painted OUTSIDE the border box) was covered by the NEXT
   sibling in DOM order — that is the "white swatch looks half-hidden" bug.
   Giving them an explicit z (1 base / 2 selected / 3 hover+focus) fixes it. */
/* (E6) 4px of self-padding so the 3.5px selected ring of the FIRST and LAST
   swatch has somewhere to paint even when the row sits against a clipping edge. */
body.edx-page .edx-swatches { display: inline-flex; gap: 7px; flex-wrap: wrap; padding: 4px; }
body.edx-page .edx-swatches button {
  position: relative; z-index: 1;
  width: 22px; height: 22px; border-radius: 50%; cursor: pointer; padding: 0; flex: 0 0 auto;
  background: var(--sw, #fff); border: 1px solid rgba(255,255,255,.22);
  display: grid; place-items: center;
  font: inherit; font-size: 11px; font-weight: 600; color: var(--text);
  transition: transform .08s ease, box-shadow .14s ease;
}
body.edx-page .edx-swatches button:hover { transform: scale(1.10); z-index: 3; }
body.edx-page .edx-swatches button.is-active {
  z-index: 2;
  box-shadow: 0 0 0 2px var(--bg-soft), 0 0 0 3.5px var(--violet);
}
body.edx-page .edx-swatches button:focus-visible {
  outline: none; z-index: 3;
  box-shadow: 0 0 0 2px var(--bg-soft), 0 0 0 3.5px var(--violet-light);
}
body.edx-page .edx-swatches .swatch--none {
  background: var(--bg-card);
  background-image: linear-gradient(45deg, transparent 44%, var(--red) 44%, var(--red) 56%, transparent 56%);
}
body.edx-page .edx-swatches .swatch--auto { background: var(--bg-elev); color: var(--text); }

/* (P9) CUSTOM COLOUR PICKER — a real <input type="color"> dressed as a round
   swatch with a rainbow ring, sitting right next to the fixed swatches. Used for
   the fill colour (#capColor), the outline colour (#capOlColor) and the
   background-box colour (#txtBoxColor). It was previously a 26px grey rounded
   square with the native control bleeding out of it, which read as decoration. */
/* (E7) FULL RAINBOW RING. The conic gradient was already here, but the native
   <input type=color> filled 100 % of the button and Chrome painted its own
   black/white split swatch straight over it — which is exactly the "split circle"
   the owner reported. The input is inset by 5px now, so the rainbow ring is always
   visible around the current colour. */
body.edx-page .edx-colorpick {
  position: relative; flex: 0 0 auto;
  width: 28px; height: 28px; padding: 0; border-radius: 50%;
  border: 0; cursor: pointer; overflow: hidden;
  background: conic-gradient(#ff2f2f, #ffd24d, #6bff8f, #4dd9ff, #6965db, #ff6bd6, #ff2f2f);
  box-shadow: inset 0 0 0 1px rgba(0,0,0,.45);
}
body.edx-page .edx-colorpick::after {
  content: ""; position: absolute; inset: 4px; border-radius: 50%; pointer-events: none;
  box-shadow: inset 0 0 0 1.5px rgba(0,0,0,.55);
}
body.edx-page .edx-colorpick input[type="color"] {
  position: absolute; z-index: 1; inset: 5px;
  display: block; width: auto; height: auto;
  border: 0; padding: 0; border-radius: 50%; background: transparent; cursor: pointer;
  box-shadow: 0 0 0 1px rgba(0,0,0,.55);
  -webkit-appearance: none; appearance: none;
}
/* (E8) when the in-app popover owns the trigger, the native input is a pure value
   carrier — never a click target (its OS dialog would fight the popover). */
body.edx-page .edx-colorpick--pop input[type="color"] { pointer-events: none; }
body.edx-page .edx-colorpick:hover { filter: brightness(1.12); }
body.edx-page .edx-colorpick:focus-within { box-shadow: inset 0 0 0 1px rgba(0,0,0,.45), 0 0 0 2px var(--violet); }
body.edx-page .edx-colorpick input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
body.edx-page .edx-colorpick input[type="color"]::-webkit-color-swatch { border: 0; border-radius: 50%; }
body.edx-page .edx-colorpick input[type="color"]::-moz-color-swatch { border: 0; border-radius: 50%; }
body.edx-page .edx-colorpick--sm { width: 22px; height: 22px; }

/* (E8) IN-APP COLOUR POPOVER — hue strip + saturation/value square + hex field.
   Appended to #edxRoot (which is `position: relative`) and positioned in px by
   openColorPop(); z-index clears the right panel and the timeline. */
body.edx-page .edx-cpop {
  position: absolute; z-index: 90; width: 190px; padding: 10px;
  display: flex; flex-direction: column; gap: 8px;
  border-radius: 12px; border: var(--edx-line); background: var(--bg-elev);
  box-shadow: 0 14px 34px rgba(0,0,0,.55);
}
body.edx-page .edx-cpop canvas { display: block; width: 100%; border-radius: 6px; cursor: crosshair; touch-action: none; }
body.edx-page .edx-cpop__sv { height: 120px; }
body.edx-page .edx-cpop__hue { height: 12px; }
body.edx-page .edx-cpop__row { display: flex; align-items: center; gap: 8px; }
body.edx-page .edx-cpop__prev {
  flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,.55), 0 0 0 1px rgba(255,255,255,.16);
}
body.edx-page .edx-cpop__hex {
  flex: 1 1 auto; min-width: 0; height: 26px; padding: 0 8px;
  border-radius: 7px; border: var(--edx-line); background: var(--bg-soft);
  color: var(--text); font: inherit; font-size: 11.5px; letter-spacing: .04em; text-transform: lowercase;
}
body.edx-page .edx-cpop__hex:focus { outline: none; border-color: var(--violet); }

/* (E9) background-box geometry sits on its own row inside the colour row */
body.edx-page .edx-field--sub {
  flex: 1 1 100%; display: flex; flex-direction: column; gap: 4px; margin-top: 2px;
}
body.edx-page .edx-colorrow.is-off .edx-field--sub input { pointer-events: none; }

/* (P9) the background-box colour row, grouped directly UNDER its checkbox.
   The engine adds/removes `.is-off` from #txtBoxRow to mirror text.box.on. */
body.edx-page .edx-colorrow--sub { margin-top: 2px; padding-left: 25px; }
body.edx-page .edx-colorrow--sub.is-off { opacity: .4; pointer-events: none; }

/* (v8) PATTERN ROWS — Bold/Italic/Underline, Case, Alignment.
   Compact CapCut-style icon rows: the label sits on its own line and the segment
   fills the width, so three rows stack without ever wrapping a control. */
body.edx-page .edx-field--pat { gap: 5px; }
body.edx-page .edx-field--pat .edx-seg { width: 100%; }
body.edx-page .edx-seg--ico { display: flex; }
body.edx-page .edx-seg--ico button {
  flex: 1 1 0; min-width: 0; padding: 6px 4px; min-height: 28px;
  display: inline-flex; align-items: center; justify-content: center;
}
/* min-height keeps the glyph rows and the SVG row exactly the same height */
body.edx-page .edx-seg--ico svg { width: 15px; height: 15px; display: block; }
/* letterform "icons" — the glyph itself shows what the toggle does */
body.edx-page .edx-ptn {
  font-size: 12.5px; line-height: 1; letter-spacing: .01em;
  font-family: var(--font, inherit);
}
body.edx-page .edx-ptn--b { font-weight: 800; }
body.edx-page .edx-ptn--i { font-style: italic; font-weight: 600; }
body.edx-page .edx-ptn--u { text-decoration: underline; text-underline-offset: 2px; }

/* font picker */
body.edx-page .edx-fontwrap { position: relative; }
body.edx-page .edx-fontbtn {
  width: 100%; display: flex; align-items: center; justify-content: space-between;
  background: var(--bg-card); border: var(--edx-line); border-radius: 9px;
  color: var(--text); font: inherit; font-size: 12.5px; padding: 8px 11px; cursor: pointer;
  transition: border-color .14s ease;
}
body.edx-page .edx-fontbtn:hover { border-color: var(--edx-accent-line); }
body.edx-page .edx-fontbtn::after { content: "▾"; color: var(--text-dim); font-size: 11px; }
body.edx-page .edx-fontpanel {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 20;
  max-height: 240px; overflow-y: auto;
  background: var(--bg-card); border: 1px solid var(--border-lt); border-radius: 10px;
  box-shadow: var(--shadow); padding: 5px;
}
body.edx-page .edx-fontpanel[hidden] { display: none; }
body.edx-page .edx-fontpanel button,
body.edx-page .edx-fontpanel .edx-fontopt {
  display: block; width: 100%; text-align: left; border: 0; background: transparent;
  color: var(--text); font-size: 13px; padding: 7px 9px; border-radius: 7px; cursor: pointer;
}
body.edx-page .edx-fontpanel button:hover,
body.edx-page .edx-fontpanel .edx-fontopt:hover { background: var(--edx-accent-soft); }
body.edx-page .edx-fontpanel button.is-active { background: var(--edx-accent-soft); color: var(--violet-light); }
/* (v8) keyboard TYPE-AHEAD cursor — the row the next Enter would pick. Distinct
   from .is-active (the font already applied) so both can show at once. */
body.edx-page .edx-fontpanel button.is-cursor {
  background: var(--edx-accent-soft);
  box-shadow: inset 0 0 0 1px var(--violet);
  color: var(--violet-light);
}

/* captions pane: selbar + chunks */
body.edx-page .edx-selbar {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
  padding: 8px; border-radius: 10px; background: var(--edx-accent-soft); border: 1px solid var(--edx-accent-line);
}
body.edx-page .edx-selbar__n { font-size: 11.5px; font-weight: 600; color: var(--violet-light); margin-right: auto; }
body.edx-page .edx-caphead { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
body.edx-page .edx-chunks { display: flex; flex-direction: column; gap: 6px; }
body.edx-page .edx-chunk {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 9px; border-radius: 9px; cursor: pointer; min-width: 0;
  background: var(--bg-card); border: var(--edx-line); font: inherit; color: var(--text);
  transition: border-color .14s ease, background .14s ease;
}
body.edx-page .edx-chunk:hover { border-color: var(--edx-accent-line); }
body.edx-page .edx-chunk.is-selected { border-color: var(--violet); background: var(--edx-accent-soft); }
body.edx-page .edx-chunk.is-active { box-shadow: inset 3px 0 0 var(--violet); }
/* chunk text label — grows and truncates; the time stays compact.
   (The engine used to emit .edx-chunk__t / .edx-chunk__tx while the CSS targeted
   .edx-chunk__time / .edx-chunk__text, so the `> span:first-child` fallback gave
   flex:1 to the TIME and long captions blew the row out. Names match now.) */
body.edx-page .edx-chunk__text {
  flex: 1 1 auto; min-width: 0; font-size: 12px; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
body.edx-page .edx-chunk__time { flex: 0 0 auto; font-size: 10.5px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
body.edx-page .edx-chunk.is-sel { border-color: var(--violet); background: var(--edx-accent-soft); }
/* renderChunks() emits this node when no text clip exists — it had no rule and
   rendered as raw unstyled body text inside the pane */
body.edx-page .edx-chunks__empty {
  padding: 12px 10px; border-radius: 9px; text-align: center;
  border: 1px dashed var(--border-lt); background: rgba(255,255,255,.014);
  color: var(--text-dim); font-size: 11.5px; line-height: 1.45;
}

/* limits note pinned at bottom of right panel */
body.edx-page .limits-note { margin: 8px 0 0; font-size: 10.5px; color: var(--text-dim); line-height: 1.4; }

/* =========================================================
   8) TIMELINE — free multi-track
   ========================================================= */
body.edx-page .edx-tl {
  position: relative;
  display: flex; flex-direction: column; min-height: 0;
  border-top: var(--edx-line); background: var(--bg-soft);
  height: var(--edx-tlh);          /* user-resizable via .edx-hres (JS-persisted) */
  /* (B) hard rails so a stale/oversized stored --edx-tlh can never open a void:
     the same [140px … 62vh] window the engine clamps to. */
  min-height: 140px; max-height: 62vh;
  flex: 0 0 auto; overflow: hidden;
}

/* horizontal timeline resizer — sits on the TOP edge of the timeline */
body.edx-page .edx-hres {
  position: absolute; top: -3px; left: 0; right: 0; height: 7px; z-index: 8;
  cursor: row-resize; background: transparent;
  touch-action: none; user-select: none;
}
body.edx-page .edx-hres::before {
  content: ""; position: absolute; left: 0; right: 0; top: 3px; height: 1px;
  background: transparent; transition: background .14s ease, box-shadow .14s ease;
}
body.edx-page .edx-hres:hover::before,
body.edx-page .edx-hres.is-drag::before,
body.edx-page .edx-hres:focus-visible::before {
  background: var(--violet); box-shadow: 0 0 6px rgba(105,101,219,.6);
}
body.edx-page .edx-hres:focus-visible { outline: none; }

/* toolbar */
body.edx-page .edx-tl__bar {
  display: flex; align-items: center; gap: 4px;
  padding: 7px 12px; border-bottom: var(--edx-line);
  background: linear-gradient(180deg, rgba(255,255,255,.02), transparent);
}
body.edx-page .edx-tl__time {
  font-variant-numeric: tabular-nums; font-weight: 500; font-size: 12.5px;
  color: var(--text); letter-spacing: .01em; min-width: 94px;
}

/* transparent flat icon buttons (shared .tlicon reused) */
body.edx-page .tlicon {
  display: inline-flex; align-items: center; gap: 6px;
  background: transparent; border: 0; border-radius: 8px;
  color: var(--text-dim); font: inherit; font-size: 12.5px; font-weight: 500;
  padding: 6px 8px; cursor: pointer; white-space: nowrap; line-height: 1;
  transition: background .13s ease, color .13s ease;
}
body.edx-page .tlicon svg { width: 15px; height: 15px; flex: 0 0 auto; }
body.edx-page .tlicon:hover:not(:disabled) { background: rgba(255,255,255,.07); color: var(--text); }
body.edx-page .tlicon:active:not(:disabled) { background: rgba(255,255,255,.12); }
body.edx-page .tlicon:disabled { opacity: .34; cursor: default; }
body.edx-page .tlicon.is-danger:hover:not(:disabled) { background: rgba(224,49,49,.15); color: #ff8f8f; }
body.edx-page .tlicon.is-on { color: var(--violet-light); background: var(--edx-accent-soft); }

body.edx-page .tlsep { width: 1px; height: 18px; background: var(--border); margin: 0 4px; flex: 0 0 auto; }

body.edx-page .tlzoom { display: inline-flex; align-items: center; gap: 1px; }
body.edx-page .tlzoom .tlicon { padding: 6px 8px; font-size: 15px; }
body.edx-page .tlzoom__val { min-width: 34px; text-align: center; font-size: 11.5px; font-weight: 500; color: var(--text-dim); font-variant-numeric: tabular-nums; }

body.edx-page .cardhint-inline { margin-left: auto; color: var(--text-dim); font-size: 11px; white-space: nowrap; }
@media (max-width: 1200px) { body.edx-page .cardhint-inline { display: none; } }

/* scroll area + inner canvas — inner fills the panel so there's no black void.
   (B) the scroll area paints the panel colour itself, so whatever is left under
   the last track is --bg-soft, never a black hole. */
body.edx-page .edx-tl__scroll {
  position: relative; flex: 1 1 auto; min-height: 0; overflow: auto;
  background: var(--bg-soft);
}
body.edx-page .edx-tl__inner { position: relative; min-width: 100%; min-height: 100%; background: transparent; }

/* (B) EMPTY STATE — was `position:absolute; top:22px; bottom:0` with a hatched
   fill, i.e. a full-height void covering every lane. It is now an IN-FLOW,
   compact dashed drop strip of ~1 track height that sits directly under the
   last (starter) track, after the rail. No hatching, no void. */
body.edx-page .edx-tl__empty {
  position: relative; z-index: 1;
  display: flex; align-items: center; justify-content: center;
  box-sizing: border-box;
  margin: 8px 0 10px var(--edx-railw);
  width: 420px; max-width: calc(100% - var(--edx-railw) - 12px);
  min-height: calc(var(--edx-track-h) - 8px);
  padding: 8px 14px; border-radius: 10px;
  border: 1.5px dashed var(--border-lt); background: rgba(255,255,255,.014);
  color: var(--text-dim); font-size: 11.5px; font-weight: 500; letter-spacing: .01em;
  text-align: center; pointer-events: none;
  transition: border-color .15s ease, background .15s ease, color .15s ease;
}
body.edx-page .edx-tl__empty[hidden] { display: none; }
/* light up while something is being dragged over the timeline */
body.edx-page .edx-tl.is-drag .edx-tl__empty,
body.edx-page .edx-tl__scroll.is-drag .edx-tl__empty,
body.edx-page .edx-tl__empty.is-drag {
  border-color: var(--edx-accent-line); background: var(--edx-accent-soft); color: var(--violet-light);
}

/* ruler */
/* (V4) the ruler is also the scrub surface — min-width:100% so clicking the
   empty area to the RIGHT of the content still seeks (it used to do nothing) */
/* (P3) the ruler spans the FULL scrollable width at every zoom — buildRuler()
   sets an explicit px width from tlWidths().fullW, so a 7 s project no longer
   makes the scale stop at 7 s. The old `min-width` floor is gone: JS owns the
   width and must stay the single source of truth. */
body.edx-page .edx-tl__ruler {
  position: sticky; top: 0; z-index: 5; height: 22px;
  margin-left: var(--edx-railw);
  border-bottom: var(--edx-line); background: var(--bg-soft);
  color: var(--text-dim); font-size: 10px; font-variant-numeric: tabular-nums;
  cursor: ew-resize;
}
/* (P3) CapCut tick scale: tall MAJOR ticks carry the label, short MINOR ticks
   subdivide them (density picked by the engine from the current zoom), and a
   MID tick marks the halfway point of a 10-way split. */
body.edx-page .edx-tl__ruler .edx-tick { position: absolute; top: 0; bottom: 0; }
body.edx-page .edx-tl__ruler .edx-tick::before {
  content: ""; position: absolute; left: 0; bottom: 0; width: 1px; height: 7px;
  background: var(--border-lt);
}
/* (C2) LABEL SITS ON ITS OWN TICK. It used to be nudged `left: 4px` while the
   tick line itself is `::before { left: 0; width: 1px }` — a constant +4px drift
   at EVERY zoom, which is exactly the "labels are horizontally offset" report.
   The label is now centred over the 1px line. t = 0 is the single exception: a
   centred "0s" would be half-clipped by the rail, so buildRuler() tags it
   .edx-tick--t0 and it stays left-aligned. (buildRuler appends minors BEFORE
   majors, so a :first-of-type selector would target the wrong node — the
   explicit class is required.) */
body.edx-page .edx-tl__ruler .edx-tick > span {
  position: absolute; left: 0; top: 3px; transform: translateX(-50%);
  white-space: nowrap; pointer-events: none;
}
body.edx-page .edx-tl__ruler .edx-tick--t0 > span { transform: none; left: 2px; }
/* unlabelled subdivisions between two labelled ticks */
body.edx-page .edx-tl__ruler .edx-tick--minor::before { height: 3px; opacity: .5; }
body.edx-page .edx-tl__ruler .edx-tick--mid::before   { height: 5px; opacity: .75; }

/* tracks */
body.edx-page .edx-tl__tracks { position: relative; display: flex; flex-direction: column; }
body.edx-page .edx-track {
  position: relative; display: flex; align-items: stretch;
  min-height: var(--edx-track-h); border-bottom: var(--edx-line);
}
/* (A) RAIL — must hold 4 controls (kind glyph + mute + lock + eye) INSIDE
   var(--edx-railw). It used to overflow a 64px rail, so the ruler / playhead /
   dropline (all anchored at left:var(--edx-railw)) painted on top of the lock
   and eye icons. Fixed budget at 116px:
     10 (pad-l) + 16 (glyph) + 4 + 20 + 4 + 20 + 4 + 20 + 8 (pad-r) = 106px. */
/* z-index 8 = ABOVE the playhead needle (7), the dropline (6) and the ruler (5).
   The rail is STICKY: as soon as the timeline is zoomed and scrolled right it
   slides over lane content, and the needle — which is a sibling of .edx-tl__tracks
   inside the same (non-isolated) stacking context — used to paint straight over
   the lock / eye / volume / kind controls. Nothing in the lanes may cover the
   rail, so the rail wins and the needle passes BEHIND it. The ruler never
   overlaps the rail (it starts after it via margin-left), so it is unaffected. */
body.edx-page .edx-track__rail {
  position: sticky; left: 0; z-index: 8;
  flex: 0 0 var(--edx-railw); width: var(--edx-railw); max-width: var(--edx-railw);
  box-sizing: border-box; overflow: hidden;
  display: flex; align-items: center; justify-content: flex-start; gap: 4px;
  padding: 0 8px 0 10px;              /* left gap so the icons breathe */
  background: var(--bg-card); border-right: var(--edx-line);
  color: var(--text-dim);
}
body.edx-page .edx-track__kind {
  display: grid; place-items: center; flex: 0 0 16px; width: 16px; margin-right: 2px;
}
body.edx-page .edx-track__kind svg { width: 14px; height: 14px; display: block; }
body.edx-page .edx-track__rail button {
  width: 20px; height: 20px; min-width: 20px; padding: 0;
  border: 0; border-radius: 5px; background: transparent; cursor: pointer;
  color: var(--text-dim); display: grid; place-items: center; flex: 0 0 20px;
  transition: background .13s, color .13s;
}
/* (I) rail SVG icons ship without width/height attrs — size them here */
body.edx-page .edx-track__rail button svg,
body.edx-page .edx-track__rail button svg[viewBox] {
  width: 14px; height: 14px; display: block; flex: 0 0 auto; pointer-events: none;
}
/* (P6) the new MUTE / VIDEO artwork fills its 24×24 box edge to edge (video
   spans x 0.25→23.5, mute x 1.0→22.5). `overflow: visible` guarantees a stray
   half-pixel can never clip a glyph at 14px. */
body.edx-page .edx-track__kind svg,
body.edx-page .edx-track__rail button svg { overflow: visible; }
body.edx-page .edx-track__rail button:hover { background: rgba(255,255,255,.08); color: var(--text); }
body.edx-page .edx-track__rail button.is-on { color: var(--violet-light); }
body.edx-page .edx-track[data-kind="video"] .edx-track__kind { color: var(--violet-light); }
body.edx-page .edx-track[data-kind="text"]  .edx-track__kind { color: var(--cyan); }
body.edx-page .edx-track[data-kind="audio"] .edx-track__kind { color: var(--green); }
/* (IMG) A VIDEO lane that currently holds only stills. `data-kind` stays "video"
   (that IS the track kind, and drop resolution matches on it) — the engine adds
   .edx-track--img purely so the rail glyph and the lane read as "images". */
body.edx-page .edx-track.edx-track--img .edx-track__kind { color: var(--orange); }

body.edx-page .edx-track__lane {
  position: relative; flex: 1 1 auto; min-width: 0;
  background: rgba(0,0,0,.14);
}
body.edx-page .edx-track[data-kind="video"] .edx-track__lane { background: rgba(105,101,219,.045); }
body.edx-page .edx-track[data-kind="text"]  .edx-track__lane { background: rgba(21,170,191,.04); }
body.edx-page .edx-track[data-kind="audio"] .edx-track__lane { background: rgba(47,158,68,.04); }
body.edx-page .edx-track.edx-track--img .edx-track__lane { background: rgba(240,140,0,.045); }
body.edx-page .edx-track.is-drop-target .edx-track__lane { background: var(--edx-accent-soft); box-shadow: inset 0 0 0 1px var(--edx-accent-line); }
/* the engine now actually sets .is-hidden / .is-locked on the row */
body.edx-page .edx-track.is-hidden .edx-track__lane { opacity: .38; }
body.edx-page .edx-track.is-hidden .edx-clip { filter: grayscale(.7); }
body.edx-page .edx-track.is-locked .edx-track__lane { background-image: repeating-linear-gradient(45deg, rgba(255,255,255,.03) 0 6px, transparent 6px 12px); }
body.edx-page .edx-track.is-locked .edx-clip { cursor: default; }
body.edx-page .edx-track.is-locked .edx-clip__grip { display: none; }

/* clip block */
body.edx-page .edx-clip {
  position: absolute; top: 4px; bottom: 4px; left: 0; width: 60px;
  border-radius: 6px; overflow: hidden; cursor: grab;
  border: 1px solid var(--edx-accent-line); background: var(--bg-elev);
  display: flex; align-items: flex-end;
  transition: box-shadow .12s ease;
}
body.edx-page .edx-clip:active { cursor: grabbing; }
body.edx-page .edx-clip.is-selected { box-shadow: 0 0 0 1.5px #fff, 0 0 10px rgba(105,101,219,.55); z-index: 3; }
body.edx-page .edx-clip[data-kind="text"] { border-color: rgba(21,170,191,.6); background: rgba(21,170,191,.14); }
body.edx-page .edx-clip[data-kind="audio"] { border-color: rgba(47,158,68,.6); background: rgba(47,158,68,.1); align-items: center; }
/* (IMG) still-image clip — its own amber identity so a photo is never mistaken
   for a video block on a shared video lane. The thumb layer paints the picture
   itself (engine, renderClip()), so the tint sits on the border only. */
body.edx-page .edx-clip[data-kind="image"] { border-color: rgba(240,140,0,.65); background: rgba(240,140,0,.12); }
body.edx-page .edx-clip[data-kind="image"].is-selected { box-shadow: 0 0 0 1.5px #fff, 0 0 10px rgba(240,140,0,.5); }

body.edx-page .edx-clip__thumbs {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background-repeat: repeat-x; background-size: auto 100%; background-position: left center;
  opacity: .9;
}
body.edx-page .edx-clip[data-kind="audio"] .edx-clip__thumbs { background-repeat: no-repeat; background-size: 100% 100%; }
body.edx-page .edx-clip__label {
  position: relative; z-index: 1; max-width: 100%;
  padding: 2px 6px; font-size: 11px; font-weight: 500; color: #fff;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  text-shadow: 0 1px 3px rgba(0,0,0,.85);
}
body.edx-page .edx-clip[data-kind="text"] .edx-clip__label,
body.edx-page .edx-clip[data-kind="audio"] .edx-clip__label { color: var(--text); text-shadow: none; }

/* trim grips */
body.edx-page .edx-clip__grip {
  position: absolute; top: 0; bottom: 0; width: 9px; z-index: 2; cursor: ew-resize;
  background: rgba(0,0,0,.001); opacity: 0; transition: opacity .12s ease;
}
body.edx-page .edx-clip__grip::before {
  content: ""; position: absolute; top: 50%; transform: translateY(-50%);
  width: 3px; height: 40%; border-radius: 999px; background: #fff;
}
body.edx-page .edx-clip__grip--l { left: 0; }
body.edx-page .edx-clip__grip--l::before { left: 3px; }
body.edx-page .edx-clip__grip--r { right: 0; }
body.edx-page .edx-clip__grip--r::before { right: 3px; }
body.edx-page .edx-clip:hover .edx-clip__grip,
body.edx-page .edx-clip.is-selected .edx-clip__grip { opacity: .85; }
/* (R5) a 1 s caption at zoom 1× is ~12px wide — its grips must SHRINK, not
   vanish, so every clip kind stays trimmable from both edges at any zoom.
   The engine adds .is-narrow in renderClip() and hit-tests the band by
   geometry (clamp(width*0.30, 3, 9) px), so the visual matches the target. */
body.edx-page .edx-clip.is-narrow .edx-clip__grip { width: 4px; }
body.edx-page .edx-clip.is-narrow .edx-clip__grip::before { width: 2px; height: 55%; }
body.edx-page .edx-clip.is-narrow .edx-clip__grip--l::before { left: 1px; }
body.edx-page .edx-clip.is-narrow .edx-clip__grip--r::before { right: 1px; }
body.edx-page .edx-clip.is-selected .edx-clip__grip { opacity: 1; }

/* (C4) VOLUME FADE RAMPS — a diagonal wedge at the head/tail whose WIDTH is
   exactly fadeIn/fadeOut seconds at the current zoom (renderClip sets it in px).
   The dark triangle reads as "the level ramps up here", like CapCut, and the
   hairline along its hypotenuse makes it legible on a bright filmstrip.
   Replaces the dead .edx-clip__fade circles, which nothing ever rendered. */
body.edx-page .edx-clip__ramp {
  position: absolute; top: 0; bottom: 0; z-index: 1; pointer-events: none;
  background: rgba(0,0,0,.55);
}
body.edx-page .edx-clip__ramp--in  { left: 0;  clip-path: polygon(0 0, 100% 0, 0 100%); }
body.edx-page .edx-clip__ramp--out { right: 0; clip-path: polygon(100% 0, 100% 100%, 0 0); }
body.edx-page .edx-clip__ramp::after {
  content: ""; position: absolute; inset: 0; opacity: .9;
  background: linear-gradient(to right, rgba(255,255,255,.30), rgba(255,255,255,0));
}
body.edx-page .edx-clip__ramp--out::after {
  background: linear-gradient(to left, rgba(255,255,255,.30), rgba(255,255,255,0));
}

/* (BG) BACKGROUND-REMOVAL BADGE — the ONLY claim the timeline makes about the
   cut-out. The filmstrip still shows the clip whole (there is no browser-side
   matting model and a faked cut-out would not match the export), so the badge
   states plainly WHEN it happens. Sits above the ramps, never eats a click. */
body.edx-page .edx-clip__bg {
  position: absolute; left: 4px; bottom: 4px; z-index: 3; pointer-events: none;
  display: inline-flex; align-items: center; gap: 4px; max-width: calc(100% - 8px);
  padding: 2px 6px 2px 4px; border-radius: 999px;
  background: rgba(12,12,16,.82); border: 1px solid var(--edx-accent-line);
  color: var(--violet-light); font-size: 9.5px; font-weight: 600; letter-spacing: .01em;
  line-height: 1.3; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
body.edx-page .edx-clip__bg svg { flex: 0 0 auto; width: 10px; height: 10px; }
/* narrow clips: keep the mark, drop the words (the tooltip still explains) */
body.edx-page .edx-clip.is-narrow .edx-clip__bg span { display: none; }
body.edx-page .edx-clip.is-narrow .edx-clip__bg { padding: 2px 4px; }

/* dropline — "create a new track here" indicator */
body.edx-page .edx-tl__dropline {
  position: absolute; left: var(--edx-railw); right: 0; height: 2px; z-index: 6; pointer-events: none;
  background: var(--violet); box-shadow: 0 0 8px rgba(105,101,219,.7);
}
body.edx-page .edx-tl__dropline::before {
  content: ""; position: absolute; left: -6px; top: 50%; transform: translateY(-50%);
  width: 8px; height: 8px; border-radius: 50%; background: var(--violet);
}
body.edx-page .edx-tl__dropline[hidden] { display: none; }

/* thin needle playhead — 1px, with a tiny grab head */
body.edx-page .edx-tl__playhead {
  position: absolute; top: 0; bottom: 0; left: var(--edx-railw); width: 1px; z-index: 7;
  background: var(--violet); box-shadow: 0 0 4px rgba(105,101,219,.5); pointer-events: none;
}
body.edx-page .edx-tl__playhead::before {
  content: ""; position: absolute; top: 0; left: 50%; transform: translate(-50%, -2px) rotate(45deg);
  width: 8px; height: 8px; background: var(--violet); border-radius: 2px; pointer-events: auto; cursor: ew-resize;
}
/* (P4) SLIM GRAB COLUMN over the needle's WHOLE height — the playhead used to be
   draggable only by its 8px diamond in the ruler strip. This child is the only
   pointer-events:auto part of the (otherwise pointer-events:none) needle, so the
   existing pointerdown/pointermove/pointerup wiring on #tlPlayhead just receives
   the bubbled events and setPointerCapture keeps the drag on the needle. 9px and
   no wider: the column sits above the clips, so anything fatter would start
   stealing the trim grips of a clip whose edge is under the playhead. */
body.edx-page .edx-tl__ph-hit {
  position: absolute; top: 0; bottom: 0; left: 50%;
  transform: translateX(-50%);
  width: 9px; background: transparent;
  pointer-events: auto; cursor: ew-resize; touch-action: none;
}

/* =========================================================
   9) SHARED minibtn (reused) — keep it lightweight in the editor
   ========================================================= */
body.edx-page .minibtn {
  display: inline-flex; align-items: center; gap: 5px;
  background: var(--bg-card); border: var(--edx-line); border-radius: 8px;
  color: var(--text-mute); font: inherit; font-size: 11.5px; font-weight: 500;
  padding: 6px 10px; cursor: pointer; white-space: nowrap; line-height: 1;
  transition: border-color .14s ease, color .14s ease, background .14s ease;
}
body.edx-page .minibtn svg { width: 12px; height: 12px; }
body.edx-page .minibtn:hover { border-color: var(--edx-accent-line); color: var(--text); }
body.edx-page .minibtn--danger { color: #ff8f8f; }
body.edx-page .minibtn--danger:hover { border-color: rgba(224,49,49,.6); background: rgba(224,49,49,.12); color: #ffb0b0; }

/* =========================================================
   9b) SAFETY MIN-WIDTHS — resizing can never break the layout
   ========================================================= */
body.edx-page .edx-left  { min-width: 200px; }   /* matches JS left clamp floor  */
body.edx-page .edx-right { min-width: 240px; }   /* matches JS right clamp floor */
body.edx-page .edx-stage { min-width: 360px; }   /* center never collapses       */
body.edx-page .edx-tl__bar { min-width: 0; overflow: hidden; }
body.edx-page .edx-clip { min-width: 12px; }
body.edx-page .edx-track { min-width: 0; }
@media (max-width: 900px) {
  body.edx-page .edx-left,
  body.edx-page .edx-right,
  body.edx-page .edx-stage { min-width: 0; }
}

/* =========================================================
   10) RESPONSIVE — stack on narrow widths
   ========================================================= */
@media (max-width: 900px) {
  body.edx-page { overflow: visible; }
  body.edx-page .content { height: auto; overflow: visible; }
  body.edx-page .edx { height: auto; min-height: calc(100vh - 64px); grid-template-rows: auto auto auto auto; }
  body.edx-page .edx-top { grid-template-columns: 1fr auto; grid-auto-rows: auto; }
  /* stack the panes; resizers are meaningless on a single column */
  body.edx-page .edx-body { display: flex; flex-direction: column; }
  body.edx-page .edx-vres { display: none; }
  body.edx-page .edx-left,
  body.edx-page .edx-right { width: auto; border: 0; border-top: var(--edx-line); max-height: none; overflow: visible; }
  body.edx-page .edx-right__body { overflow: visible; }
  body.edx-page .edx-left { overflow: visible; }
  body.edx-page .edx-left__body { overflow: visible; }
  body.edx-page .edx-stage { min-height: 46vh; }
  body.edx-page .edx-stage__view { min-height: 46vh; }
  body.edx-page .edx-playbar { flex-wrap: wrap; }
  body.edx-page .edx-tl { height: 320px; }
  body.edx-page .edx-hres { display: none; }
}
@media (max-width: 720px) {
  body.edx-page .tlicon span { display: none; }
  body.edx-page .edx-top__title { display: none; }
}

/* =========================================================
   11b) FINAL POLISH — overlap / overflow guards for 1280→1920 (J)
   ========================================================= */

/* (B) the timeline is the LAST grid row and the grid fills .content exactly,
   so nothing can sit below it. Belt & braces: the root grid never overflows. */
body.edx-page .edx { max-height: 100%; }
body.edx-page .edx-tl:last-child { margin-bottom: 0; }

/* (H) snapping is ON by default — the markup ships `.is-on` + aria-pressed=true
   and the engine must not clear it on init. Reflect BOTH signals visually so
   the button reads "on" on the very first paint. */
body.edx-page .tlicon[aria-pressed="true"] { color: var(--violet-light); background: var(--edx-accent-soft); }
body.edx-page .tlicon[aria-pressed="true"]:hover:not(:disabled) { background: rgba(105,101,219,.24); color: var(--violet-light); }

/* (A) nothing in the timeline may paint over the rail: the rail is sticky at
   z-index 8 — ABOVE the playhead (7), the dropline (6) and the ruler (5) — and
   the ruler / playhead / dropline all start AFTER the rail via the same token.
   The inset keeps them off the rail at scrollLeft 0; the z-index is what keeps
   the needle behind the rail once the timeline is scrolled. Re-assert the
   anchors here so a future edit can't silently drop one. */
body.edx-page .edx-tl__ruler   { margin-left: var(--edx-railw); }
body.edx-page .edx-tl__playhead { left: var(--edx-railw); }
body.edx-page .edx-tl__dropline { left: var(--edx-railw); }
/* the rail stays opaque under the sticky ruler corner */
body.edx-page .edx-track__rail { background-clip: padding-box; }

/* right panel: long labels must wrap instead of pushing a horizontal scrollbar */
body.edx-page .edx-field > label { min-width: 0; }
body.edx-page .edx-field > label > span:first-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
body.edx-page .edx-pane,
body.edx-page .edx-group,
body.edx-page .edx-field { max-width: 100%; }
body.edx-page .edx-seg--wrap { max-width: 100%; }

/* left panel: two tiles per row down to the 200px pane floor */
@media (max-width: 1360px) {
  body.edx-page .edx-binlist { gap: 7px; }
  body.edx-page .edx-tile__name { font-size: 11px; }
}
/* the bin stays 2-up at every width — the tiles scale, the grid doesn't reflow */
body.edx-page .edx-binlist { grid-template-columns: repeat(2, minmax(0, 1fr)); }

/* =========================================================
   (P10) ANIMATION PRESET TILES — CapCut grid with live CSS previews.
   The container ids (#animIn / #animOut / #vanIn / #vanOut) and every option's
   `button[data-val]` + `.is-active` contract are UNCHANGED, so bindSeg(),
   bindVSeg() and segActive() keep driving them and the export payload
   (text.anim.in/out, clip.anim.in/out) is untouched — this is a re-skin only.
   WHICH animation a tile plays is resolved by (grid direction × data-val) → the
   --edxa custom property, which the shared hover/selected rule consumes.
   Only the hovered + selected tiles animate (≤2 per grid), and `will-change` is
   scoped to the tiny preview element — never put `animation` on the tile itself.
   ========================================================= */
/* Kutular SABİT ve KÜÇÜK: eskiden repeat(3,1fr) idi → panel genişledikçe tile
   156px'e kadar şişip içindeki 34px'lik işareti kaybediyordu. Artık sabit 72px
   sütunlar (stage ≈60px, içerik ≈40px) — CapCut'taki gibi minimal, panel ne
   kadar genişlerse genişlesin satır başına daha çok tile sığar. */
body.edx-page .edx-anigrid {
  display: grid; grid-template-columns: repeat(auto-fill, 72px);
  justify-content: start; gap: 6px; max-width: 100%;
}
body.edx-page .edx-anitile {
  display: flex; flex-direction: column; gap: 5px; min-width: 0;
  padding: 5px 5px 6px; border-radius: 10px; cursor: pointer; appearance: none;
  background: var(--bg-card); border: var(--edx-line); color: var(--text-dim);
  font: inherit; text-align: center;
  transition: border-color .14s ease, background .14s ease, color .14s ease, transform .06s ease;
}
body.edx-page .edx-anitile:hover { border-color: var(--edx-accent-line); color: var(--text); }
body.edx-page .edx-anitile:active { transform: scale(.98); }
body.edx-page .edx-anitile.is-active {
  border-color: var(--violet); color: var(--violet-light);
  background: linear-gradient(180deg, var(--edx-accent-soft), transparent 70%), var(--bg-card);
  box-shadow: 0 0 0 1px var(--edx-accent-line);
}
body.edx-page .edx-anitile:focus-visible { outline: none; border-color: var(--violet); }
/* (item 10b) A PRESET THE RENDERER CANNOT HONOUR — the mask budget is spent
   (2 per project) or the source is longer than the zoom limit (120 s). The tile
   stays readable so the user can see WHAT is unavailable; #vanCapNote and the
   tile's own title say WHY. Mirrors the reverse checkbox's disabled state. */
body.edx-page .edx-anitile.is-capped {
  opacity: .38; cursor: not-allowed; filter: grayscale(1);
}
body.edx-page .edx-anitile.is-capped:hover { border-color: var(--border); color: var(--text-dim); }
body.edx-page .edx-anitile.is-capped:active { transform: none; }
body.edx-page .edx-anitile.is-capped .edx-anitile__m { animation: none !important; }
/* (E2) SQUARE stage (1:1) with bigger preview content inside */
body.edx-page .edx-anitile__stage {
  position: relative; display: grid; place-items: center; overflow: hidden;
  aspect-ratio: 1 / 1; height: auto; border-radius: 8px;
  background: linear-gradient(180deg, #17171b, #0f0f13);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.04);
  perspective: 320px;
}
body.edx-page .edx-anitile__m {
  display: inline-block; max-width: 94%;
  font: inherit; font-size: 15px; font-weight: 800; letter-spacing: .02em; color: #fff;
  white-space: nowrap; will-change: transform, opacity;
  transform-origin: center center;
}
body.edx-page .edx-anitile__m--img { width: 40px; height: 40px; object-fit: contain; }
/* (E4) the text mark is the product name — it needs to fit a square tile */
body.edx-page .edx-anigrid--txt .edx-anitile__m { font-size: 11px; letter-spacing: 0; }
body.edx-page .edx-anitile__lbl {
  font-size: 9.5px; font-weight: 500; line-height: 1.2;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* group headings so 50+ tiles stay browsable (E2) */
body.edx-page .edx-anigroups { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
body.edx-page .edx-anigroup__h {
  margin: 4px 0 -2px; font-size: 10px; font-weight: 600; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text-dim);
}

/* (E2) "None" gets a real crossed-out / no-symbol indicator instead of a still mark */
body.edx-page .edx-anitile[data-val="none"] .edx-anitile__m { display: none; }
body.edx-page .edx-anitile[data-val="none"] .edx-anitile__stage::after {
  content: ""; position: absolute; inset: 26%; border-radius: 50%;
  border: 2px solid var(--text-dim);
  background: linear-gradient(45deg, transparent calc(50% - 1px), var(--text-dim) calc(50% - 1px),
              var(--text-dim) calc(50% + 1px), transparent calc(50% + 1px));
}
body.edx-page .edx-anitile[data-val="none"].is-active .edx-anitile__stage::after { border-color: var(--violet-light); }

/* play the preview on HOVER and while SELECTED. The keyframe NAME is written
   INLINE by buildAnimGrid() (`--edxa`), so a preset can never inherit another
   preset's motion — a missing keyframe shows a still tile, never the wrong one. */
body.edx-page .edx-anitile:hover .edx-anitile__m,
body.edx-page .edx-anitile.is-active .edx-anitile__m {
  animation: var(--edxa, none) 1.9s cubic-bezier(.22,.61,.36,1) infinite both;
}
body.edx-page .edx-anitile[data-val="none"] { --edxa: none; }
body.edx-page .edx-anitile[data-val^="typewriter"] .edx-anitile__m,
body.edx-page .edx-anitile[data-val="letter-fade"] .edx-anitile__m,
body.edx-page .edx-anitile[data-val="word-by-word"] .edx-anitile__m { overflow: hidden; }

/* =========================================================
   (A2) KEYFRAME CATALOGUE — one @keyframes per preset per direction.
   Naming: edxaT_<id>_in|out (text, libass) · edxaV_<id>_in|out (video, ffmpeg).
   Hyphens in an id become underscores. Timeline shape: 0 % = the animation's
   START state, 45–85 % = settled/neutral, 100 % = back to the start so the loop
   reads. OUT variants invert it: 0–15 % neutral, 60–85 % = the END state.
   Every recipe mirrors the renderer's tags/filters, not a generic fade.
   ========================================================= */

/* ---- TEXT · Basic ---- */
@keyframes edxaT_fade_in        { 0%{opacity:0} 45%,85%{opacity:1} 100%{opacity:0} }
@keyframes edxaT_fade_out       { 0%,15%{opacity:1} 60%,85%{opacity:0} 100%{opacity:1} }
@keyframes edxaT_fade_up_in     { 0%{opacity:0;transform:translateY(45%)} 45%,85%{opacity:1;transform:translateY(0)} 100%{opacity:0;transform:translateY(45%)} }
@keyframes edxaT_fade_up_out    { 0%,15%{opacity:1;transform:translateY(0)} 60%,85%{opacity:0;transform:translateY(-45%)} 100%{opacity:1;transform:translateY(0)} }
@keyframes edxaT_fade_down_in   { 0%{opacity:0;transform:translateY(-45%)} 45%,85%{opacity:1;transform:translateY(0)} 100%{opacity:0;transform:translateY(-45%)} }
@keyframes edxaT_fade_down_out  { 0%,15%{opacity:1;transform:translateY(0)} 60%,85%{opacity:0;transform:translateY(45%)} 100%{opacity:1;transform:translateY(0)} }
@keyframes edxaT_fade_scale_in  { 0%{opacity:0;transform:scale(.88)} 45%,85%{opacity:1;transform:scale(1)} 100%{opacity:0;transform:scale(.88)} }
@keyframes edxaT_fade_scale_out { 0%,15%{opacity:1;transform:scale(1)} 60%,85%{opacity:0;transform:scale(.88)} 100%{opacity:1;transform:scale(1)} }
@keyframes edxaT_blur_in        { 0%{opacity:0;filter:blur(6px)} 45%,85%{opacity:1;filter:blur(0)} 100%{opacity:0;filter:blur(6px)} }
@keyframes edxaT_blur_out       { 0%,15%{opacity:1;filter:blur(0)} 60%,85%{opacity:0;filter:blur(6px)} 100%{opacity:1;filter:blur(0)} }

/* ---- TEXT · Motion ---- */
@keyframes edxaT_slide_left_in    { 0%{transform:translateX(-160%)} 45%,85%{transform:translateX(0)} 100%{transform:translateX(-160%)} }
@keyframes edxaT_slide_left_out   { 0%,15%{transform:translateX(0)} 60%,85%{transform:translateX(-160%)} 100%{transform:translateX(0)} }
@keyframes edxaT_slide_right_in   { 0%{transform:translateX(160%)} 45%,85%{transform:translateX(0)} 100%{transform:translateX(160%)} }
@keyframes edxaT_slide_right_out  { 0%,15%{transform:translateX(0)} 60%,85%{transform:translateX(160%)} 100%{transform:translateX(0)} }
@keyframes edxaT_slide_up_in      { 0%{transform:translateY(150%)} 45%,85%{transform:translateY(0)} 100%{transform:translateY(150%)} }
@keyframes edxaT_slide_up_out     { 0%,15%{transform:translateY(0)} 60%,85%{transform:translateY(-150%)} 100%{transform:translateY(0)} }
@keyframes edxaT_slide_down_in    { 0%{transform:translateY(-150%)} 45%,85%{transform:translateY(0)} 100%{transform:translateY(-150%)} }
@keyframes edxaT_slide_down_out   { 0%,15%{transform:translateY(0)} 60%,85%{transform:translateY(150%)} 100%{transform:translateY(0)} }
@keyframes edxaT_rise_in          { 0%{transform:translateY(60%)} 45%,85%{transform:translateY(0)} 100%{transform:translateY(60%)} }
@keyframes edxaT_rise_out         { 0%,15%{transform:translateY(0)} 60%,85%{transform:translateY(-60%)} 100%{transform:translateY(0)} }
@keyframes edxaT_drop_in          { 0%{transform:translateY(-60%)} 45%,85%{transform:translateY(0)} 100%{transform:translateY(-60%)} }
@keyframes edxaT_drop_out         { 0%,15%{transform:translateY(0)} 60%,85%{transform:translateY(60%)} 100%{transform:translateY(0)} }
@keyframes edxaT_drift_in         { 0%{opacity:0;transform:translateX(-22%)} 45%,85%{opacity:1;transform:translateX(0)} 100%{opacity:0;transform:translateX(-22%)} }
@keyframes edxaT_drift_out        { 0%,15%{opacity:1;transform:translateX(0)} 60%,85%{opacity:0;transform:translateX(22%)} 100%{opacity:1;transform:translateX(0)} }
@keyframes edxaT_float_in         { 0%{opacity:0;transform:translateY(16%)} 45%,85%{opacity:1;transform:translateY(0)} 100%{opacity:0;transform:translateY(16%)} }
@keyframes edxaT_float_out        { 0%,15%{opacity:1;transform:translateY(0)} 60%,85%{opacity:0;transform:translateY(-16%)} 100%{opacity:1;transform:translateY(0)} }
@keyframes edxaT_slam_in          { 0%{transform:translateY(-160%) scale(1,1)} 26%{transform:translateY(0) scale(1,1)} 34%{transform:translateY(0) scale(1.18,.8)} 45%,85%{transform:translateY(0) scale(1,1)} 100%{transform:translateY(-160%) scale(1,1)} }
@keyframes edxaT_slam_out         { 0%,15%{transform:translateY(0) scale(1,1)} 26%{transform:translateY(0) scale(1.18,.8)} 60%,85%{transform:translateY(160%) scale(1,1)} 100%{transform:translateY(0) scale(1,1)} }
@keyframes edxaT_whip_in          { 0%{transform:translateX(-170%) skewX(-26deg);filter:blur(4px)} 45%,85%{transform:translateX(0) skewX(0);filter:blur(0)} 100%{transform:translateX(-170%) skewX(-26deg);filter:blur(4px)} }
@keyframes edxaT_whip_out         { 0%,15%{transform:translateX(0) skewX(0);filter:blur(0)} 60%,85%{transform:translateX(170%) skewX(26deg);filter:blur(4px)} 100%{transform:translateX(0) skewX(0);filter:blur(0)} }

/* ---- TEXT · Zoom & scale ---- */
@keyframes edxaT_pop_in         { 0%{opacity:0;transform:scale(.45)} 12%{opacity:1} 45%,85%{opacity:1;transform:scale(1)} 100%{opacity:0;transform:scale(.45)} }
@keyframes edxaT_pop_out        { 0%,15%{opacity:1;transform:scale(1)} 60%,85%{opacity:0;transform:scale(.45)} 100%{opacity:1;transform:scale(1)} }
@keyframes edxaT_pop_bounce_in  { 0%{opacity:0;transform:scale(.40)} 27%{opacity:1;transform:scale(1.12)} 36%{transform:scale(.94)} 45%,85%{transform:scale(1)} 100%{opacity:0;transform:scale(.40)} }
@keyframes edxaT_pop_bounce_out { 0%,15%{opacity:1;transform:scale(1)} 28%{transform:scale(1.10)} 60%,85%{opacity:0;transform:scale(.35)} 100%{opacity:1;transform:scale(1)} }
@keyframes edxaT_bounce_in      { 0%{transform:translateY(-70%) scale(1,1)} 25%{transform:translateY(0) scale(1,1)} 31%{transform:translateY(0) scale(1.16,.78)} 38%{transform:translateY(0) scale(.96,1.08)} 45%,85%{transform:translateY(0) scale(1,1)} 100%{transform:translateY(-70%) scale(1,1)} }
@keyframes edxaT_bounce_out     { 0%,15%{transform:translateY(0) scale(1,1)} 24%{transform:translateY(0) scale(1.16,.78)} 60%,85%{transform:translateY(-70%) scale(1,1)} 100%{transform:translateY(0) scale(1,1)} }
@keyframes edxaT_elastic_in     { 0%{transform:scale(1.30)} 16%{transform:scale(.92)} 29%{transform:scale(1.06)} 38%{transform:scale(.97)} 45%,85%{transform:scale(1)} 100%{transform:scale(1.30)} }
@keyframes edxaT_elastic_out    { 0%,15%{transform:scale(1)} 26%{transform:scale(.97)} 38%{transform:scale(1.06)} 60%,85%{opacity:0;transform:scale(1.30)} 100%{opacity:1;transform:scale(1)} }
@keyframes edxaT_spring_in      { 0%{transform:scale(.60)} 25%{transform:scale(1.08)} 45%,85%{transform:scale(1)} 100%{transform:scale(.60)} }
@keyframes edxaT_spring_out     { 0%,15%{transform:scale(1)} 32%{transform:scale(1.08)} 60%,85%{opacity:0;transform:scale(.60)} 100%{opacity:1;transform:scale(1)} }
@keyframes edxaT_zoom_in_in     { 0%{opacity:0;transform:scale(1.60)} 45%,85%{opacity:1;transform:scale(1)} 100%{opacity:0;transform:scale(1.60)} }
@keyframes edxaT_zoom_in_out    { 0%,15%{opacity:1;transform:scale(1)} 60%,85%{opacity:0;transform:scale(1.60)} 100%{opacity:1;transform:scale(1)} }
@keyframes edxaT_zoom_tiny_in   { 0%{transform:scale(.10)} 45%,85%{transform:scale(1)} 100%{transform:scale(.10)} }
@keyframes edxaT_zoom_tiny_out  { 0%,15%{transform:scale(1)} 60%,85%{transform:scale(.10)} 100%{transform:scale(1)} }
@keyframes edxaT_punch_in       { 0%{transform:scale(1.30)} 18%{transform:scale(.96)} 45%,85%{transform:scale(1)} 100%{transform:scale(1.30)} }
@keyframes edxaT_punch_out      { 0%,15%{transform:scale(1)} 28%{transform:scale(1.04)} 60%,85%{opacity:0;transform:scale(1.30)} 100%{opacity:1;transform:scale(1)} }
@keyframes edxaT_squash_in      { 0%{transform:scale(1.40,.60)} 22%{transform:scale(.92,1.12)} 45%,85%{transform:scale(1,1)} 100%{transform:scale(1.40,.60)} }
@keyframes edxaT_squash_out     { 0%,15%{transform:scale(1,1)} 38%{transform:scale(.92,1.12)} 60%,85%{transform:scale(1.40,.60)} 100%{transform:scale(1,1)} }
@keyframes edxaT_stretch_in     { 0%{transform:scale(.60,1.40)} 22%{transform:scale(1.12,.92)} 45%,85%{transform:scale(1,1)} 100%{transform:scale(.60,1.40)} }
@keyframes edxaT_stretch_out    { 0%,15%{transform:scale(1,1)} 38%{transform:scale(1.12,.92)} 60%,85%{transform:scale(.60,1.40)} 100%{transform:scale(1,1)} }

/* ---- TEXT · Unfold ---- */
@keyframes edxaT_expand_in    { 0%{transform:scaleY(0)} 45%,85%{transform:scaleY(1)} 100%{transform:scaleY(0)} }
@keyframes edxaT_expand_out   { 0%,15%{transform:scaleY(1)} 60%,85%{transform:scaleY(0)} 100%{transform:scaleY(1)} }
@keyframes edxaT_contract_in  { 0%{transform:scaleX(2)} 45%,85%{transform:scaleX(1)} 100%{transform:scaleX(2)} }
@keyframes edxaT_contract_out { 0%,15%{opacity:1;transform:scaleX(1)} 60%,85%{opacity:0;transform:scaleX(2)} 100%{opacity:1;transform:scaleX(1)} }
@keyframes edxaT_flip_x_in    { 0%{transform:scaleX(0)} 45%,85%{transform:scaleX(1)} 100%{transform:scaleX(0)} }
@keyframes edxaT_flip_x_out   { 0%,15%{transform:scaleX(1)} 60%,85%{transform:scaleX(0)} 100%{transform:scaleX(1)} }
@keyframes edxaT_flip_y_in    { 0%{transform:scaleY(0) translateY(-14%)} 45%,85%{transform:scaleY(1) translateY(0)} 100%{transform:scaleY(0) translateY(-14%)} }
@keyframes edxaT_flip_y_out   { 0%,15%{transform:scaleY(1) translateY(0)} 60%,85%{transform:scaleY(0) translateY(-14%)} 100%{transform:scaleY(1) translateY(0)} }

/* ---- TEXT · Rotate & 3D (\frz / \frx / \fry) ---- */
@keyframes edxaT_spin_in        { 0%{transform:rotate(-180deg) scale(.40)} 45%,85%{transform:rotate(0) scale(1)} 100%{transform:rotate(-180deg) scale(.40)} }
@keyframes edxaT_spin_out       { 0%,15%{opacity:1;transform:rotate(0) scale(1)} 60%,85%{opacity:0;transform:rotate(180deg) scale(.40)} 100%{opacity:1;transform:rotate(0) scale(1)} }
@keyframes edxaT_rotate_in_in   { 0%{opacity:0;transform:rotate(-18deg)} 45%,85%{opacity:1;transform:rotate(0)} 100%{opacity:0;transform:rotate(-18deg)} }
@keyframes edxaT_rotate_in_out  { 0%,15%{opacity:1;transform:rotate(0)} 60%,85%{opacity:0;transform:rotate(18deg)} 100%{opacity:1;transform:rotate(0)} }
@keyframes edxaT_swing_in       { 0%{transform:rotate(22deg)} 20%{transform:rotate(-10deg)} 34%{transform:rotate(5deg)} 45%,85%{transform:rotate(0)} 100%{transform:rotate(22deg)} }
@keyframes edxaT_swing_out      { 0%,15%{transform:rotate(0)} 30%{transform:rotate(-5deg)} 45%{transform:rotate(10deg)} 60%,85%{transform:rotate(-22deg)} 100%{transform:rotate(0)} }
@keyframes edxaT_roll_in        { 0%{transform:translateX(-120%) rotate(-360deg)} 45%,85%{transform:translateX(0) rotate(0)} 100%{transform:translateX(-120%) rotate(-360deg)} }
@keyframes edxaT_roll_out       { 0%,15%{transform:translateX(0) rotate(0)} 60%,85%{transform:translateX(120%) rotate(360deg)} 100%{transform:translateX(0) rotate(0)} }
@keyframes edxaT_unfold_3d_in   { 0%{transform:rotateY(-85deg)} 45%,85%{transform:rotateY(0)} 100%{transform:rotateY(-85deg)} }
@keyframes edxaT_unfold_3d_out  { 0%,15%{transform:rotateY(0)} 60%,85%{transform:rotateY(85deg)} 100%{transform:rotateY(0)} }
@keyframes edxaT_tilt_3d_in     { 0%{transform:rotateX(-70deg)} 45%,85%{transform:rotateX(0)} 100%{transform:rotateX(-70deg)} }
@keyframes edxaT_tilt_3d_out    { 0%,15%{transform:rotateX(0)} 60%,85%{transform:rotateX(70deg)} 100%{transform:rotateX(0)} }

/* ---- TEXT · Reveal (per-character / per-word · \k \kf \1a \bord) ---- */
@keyframes edxaT_typewriter_in     { 0%{clip-path:inset(0 100% 0 0)} 45%,85%{clip-path:inset(0 0 0 0)} 100%{clip-path:inset(0 100% 0 0)} }
@keyframes edxaT_typewriter_out_out{ 0%,15%{clip-path:inset(0 0 0 0)} 60%,85%{clip-path:inset(0 100% 0 0)} 100%{clip-path:inset(0 0 0 0)} }
@keyframes edxaT_letter_fade_in    { 0%{clip-path:inset(0 100% 0 0);opacity:.35} 45%,85%{clip-path:inset(0 0 0 0);opacity:1} 100%{clip-path:inset(0 100% 0 0);opacity:.35} }
/* letter-hide peels from the LEFT (typewriter-out peels from the right) */
@keyframes edxaT_letter_hide_out   { 0%,15%{clip-path:inset(0 0 0 0)} 60%,85%{clip-path:inset(0 0 0 100%)} 100%{clip-path:inset(0 0 0 0)} }
@keyframes edxaT_word_by_word_in   { 0%{clip-path:inset(0 100% 0 0)} 15%{clip-path:inset(0 66% 0 0)} 30%{clip-path:inset(0 33% 0 0)} 45%,85%{clip-path:inset(0 0 0 0)} 100%{clip-path:inset(0 100% 0 0)} }
@keyframes edxaT_karaoke_in        { 0%{color:#8a8a94} 45%,85%{color:#fff} 100%{color:#8a8a94} }
@keyframes edxaT_colour_sweep_in   { 0%{color:#8a8a94;clip-path:inset(0 0 0 0)} 45%,85%{color:var(--violet-light);clip-path:inset(0 0 0 0)} 100%{color:#8a8a94} }
@keyframes edxaT_fill_reveal_in    { 0%{-webkit-text-fill-color:transparent;-webkit-text-stroke:1px #fff} 45%,85%{-webkit-text-fill-color:#fff;-webkit-text-stroke:1px #fff} 100%{-webkit-text-fill-color:transparent;-webkit-text-stroke:1px #fff} }
@keyframes edxaT_fill_hide_out     { 0%,15%{-webkit-text-fill-color:#fff;-webkit-text-stroke:1px #fff} 60%,85%{-webkit-text-fill-color:transparent;-webkit-text-stroke:1px #fff} 100%{-webkit-text-fill-color:#fff} }
@keyframes edxaT_outline_grow_in   { 0%{-webkit-text-stroke:0 var(--violet)} 45%,85%{-webkit-text-stroke:2px var(--violet)} 100%{-webkit-text-stroke:0 var(--violet)} }
@keyframes edxaT_outline_shrink_out{ 0%,15%{-webkit-text-stroke:2px var(--violet)} 60%,85%{-webkit-text-stroke:0 var(--violet)} 100%{-webkit-text-stroke:2px var(--violet)} }

/* ---- TEXT · Style ---- */
@keyframes edxaT_shadow_drop_in    { 0%{text-shadow:0 0 0 rgba(0,0,0,0)} 45%,85%{text-shadow:0 3px 0 rgba(0,0,0,.85)} 100%{text-shadow:0 0 0 rgba(0,0,0,0)} }
@keyframes edxaT_shadow_lift_out   { 0%,15%{text-shadow:0 3px 0 rgba(0,0,0,.85)} 60%,85%{text-shadow:0 0 0 rgba(0,0,0,0)} 100%{text-shadow:0 3px 0 rgba(0,0,0,.85)} }
@keyframes edxaT_glow_pulse_in     { 0%,100%{text-shadow:0 0 0 var(--violet)} 45%{text-shadow:0 0 9px var(--violet-light),0 0 16px var(--violet)} 85%{text-shadow:0 0 0 var(--violet)} }
@keyframes edxaT_glow_pulse_out    { 0%,100%{text-shadow:0 0 0 var(--violet)} 50%{text-shadow:0 0 9px var(--violet-light),0 0 16px var(--violet)} }
@keyframes edxaT_tracking_in_in    { 0%{letter-spacing:.34em} 45%,85%{letter-spacing:0} 100%{letter-spacing:.34em} }
@keyframes edxaT_tracking_out_out  { 0%,15%{opacity:1;letter-spacing:0} 60%,85%{opacity:0;letter-spacing:.34em} 100%{opacity:1;letter-spacing:0} }

/* ---- TEXT · Fun (chained \t windows → real oscillation) ---- */
@keyframes edxaT_shake_in    { 0%,100%{transform:rotate(0)} 8%{transform:rotate(6deg)} 16%{transform:rotate(-6deg)} 24%{transform:rotate(6deg)} 32%{transform:rotate(-6deg)} 40%{transform:rotate(3deg)} 45%,85%{transform:rotate(0)} }
@keyframes edxaT_shake_out   { 0%,15%{transform:rotate(0)} 30%{transform:rotate(6deg)} 45%{transform:rotate(-6deg)} 60%{transform:rotate(6deg)} 75%{transform:rotate(-6deg)} 100%{transform:rotate(0)} }
@keyframes edxaT_jitter_in   { 0%{transform:scale(1.04,.96) rotate(3deg)} 10%{transform:scale(.97,1.03) rotate(-3deg)} 20%{transform:scale(1.04,.96) rotate(3deg)} 30%{transform:scale(.97,1.03) rotate(-3deg)} 45%,85%{transform:scale(1,1) rotate(0)} 100%{transform:scale(1.04,.96) rotate(3deg)} }
@keyframes edxaT_jitter_out  { 0%,15%{transform:scale(1,1) rotate(0)} 35%{transform:scale(1.04,.96) rotate(3deg)} 55%{transform:scale(.97,1.03) rotate(-3deg)} 75%{transform:scale(1.04,.96) rotate(3deg)} 100%{transform:scale(1,1) rotate(0)} }
@keyframes edxaT_wobble_in   { 0%{transform:scale(1.10,.92)} 12%{transform:scale(.92,1.06)} 24%{transform:scale(1.10,.92)} 36%{transform:scale(.92,1.06)} 45%,85%{transform:scale(1,1)} 100%{transform:scale(1.10,.92)} }
@keyframes edxaT_wobble_out  { 0%,15%{transform:scale(1,1)} 35%{transform:scale(1.10,.92)} 55%{transform:scale(.92,1.06)} 75%{transform:scale(1.10,.92)} 100%{transform:scale(1,1)} }
@keyframes edxaT_flicker_in  { 0%,10%,20%,30%{opacity:0} 5%,15%,25%{opacity:1} 45%,85%{opacity:1} 100%{opacity:0} }
@keyframes edxaT_flicker_out { 0%,15%{opacity:1} 30%,50%,70%{opacity:0} 40%,60%{opacity:1} 85%{opacity:0} 100%{opacity:1} }

/* ---- VIDEO · Basic ---- */
@keyframes edxaV_fade_in        { 0%{opacity:0} 45%,85%{opacity:1} 100%{opacity:0} }
@keyframes edxaV_fade_out       { 0%,15%{opacity:1} 60%,85%{opacity:0} 100%{opacity:1} }
@keyframes edxaV_dip_black_in   { 0%{filter:brightness(.08)} 45%,85%{filter:brightness(1)} 100%{filter:brightness(.08)} }
@keyframes edxaV_dip_black_out  { 0%,15%{filter:brightness(1)} 60%,85%{filter:brightness(.08)} 100%{filter:brightness(1)} }
@keyframes edxaV_dip_white_in   { 0%{filter:brightness(5) saturate(0)} 45%,85%{filter:brightness(1) saturate(1)} 100%{filter:brightness(5) saturate(0)} }
@keyframes edxaV_dip_white_out  { 0%,15%{filter:brightness(1) saturate(1)} 60%,85%{filter:brightness(5) saturate(0)} 100%{filter:brightness(1) saturate(1)} }

/* ---- VIDEO · Motion (overlay x/y) ---- */
@keyframes edxaV_slide_l_in   { 0%{transform:translateX(-140%)} 45%,85%{transform:translateX(0)} 100%{transform:translateX(-140%)} }
@keyframes edxaV_slide_l_out  { 0%,15%{transform:translateX(0)} 60%,85%{transform:translateX(-140%)} 100%{transform:translateX(0)} }
@keyframes edxaV_slide_r_in   { 0%{transform:translateX(140%)} 45%,85%{transform:translateX(0)} 100%{transform:translateX(140%)} }
@keyframes edxaV_slide_r_out  { 0%,15%{transform:translateX(0)} 60%,85%{transform:translateX(140%)} 100%{transform:translateX(0)} }
@keyframes edxaV_slide_t_in   { 0%{transform:translateY(-140%)} 45%,85%{transform:translateY(0)} 100%{transform:translateY(-140%)} }
@keyframes edxaV_slide_t_out  { 0%,15%{transform:translateY(0)} 60%,85%{transform:translateY(-140%)} 100%{transform:translateY(0)} }
@keyframes edxaV_slide_b_in   { 0%{transform:translateY(140%)} 45%,85%{transform:translateY(0)} 100%{transform:translateY(140%)} }
@keyframes edxaV_slide_b_out  { 0%,15%{transform:translateY(0)} 60%,85%{transform:translateY(140%)} 100%{transform:translateY(0)} }
@keyframes edxaV_bounce_in    { 0%{transform:translateY(-120%)} 20%{transform:translateY(0)} 27%{transform:translateY(-38%)} 34%{transform:translateY(0)} 39%{transform:translateY(-14%)} 45%,85%{transform:translateY(0)} 100%{transform:translateY(-120%)} }
@keyframes edxaV_bounce_out   { 0%,15%{transform:translateY(0)} 30%{transform:translateY(-38%)} 42%{transform:translateY(0)} 55%{transform:translateY(-70%)} 85%{transform:translateY(-120%)} 100%{transform:translateY(0)} }
@keyframes edxaV_elastic_in   { 0%{transform:translateX(120%)} 18%{transform:translateX(-34%)} 28%{transform:translateX(16%)} 36%{transform:translateX(-7%)} 45%,85%{transform:translateX(0)} 100%{transform:translateX(120%)} }
@keyframes edxaV_elastic_out  { 0%,15%{transform:translateX(0)} 30%{transform:translateX(-7%)} 45%{transform:translateX(16%)} 60%{transform:translateX(-34%)} 85%{transform:translateX(120%)} 100%{transform:translateX(0)} }
@keyframes edxaV_slam_in      { 0%{transform:translateY(-150%) scale(1.2)} 30%{transform:translateY(0) scale(1)} 45%,85%{transform:translateY(0) scale(1)} 100%{transform:translateY(-150%) scale(1.2)} }
@keyframes edxaV_slam_out     { 0%,15%{transform:translateY(0) scale(1)} 60%,85%{transform:translateY(150%) scale(1.2)} 100%{transform:translateY(0) scale(1)} }
@keyframes edxaV_drift_in     { 0%{transform:translateX(-26%)} 45%,85%{transform:translateX(0)} 100%{transform:translateX(-26%)} }
@keyframes edxaV_drift_out    { 0%,15%{transform:translateX(0)} 60%,85%{transform:translateX(26%)} 100%{transform:translateX(0)} }
@keyframes edxaV_whip_pan_in  { 0%{transform:translateX(-150%);filter:blur(5px)} 45%,85%{transform:translateX(0);filter:blur(0)} 100%{transform:translateX(-150%);filter:blur(5px)} }
@keyframes edxaV_whip_pan_out { 0%,15%{transform:translateX(0);filter:blur(0)} 60%,85%{transform:translateX(150%);filter:blur(5px)} 100%{transform:translateX(0);filter:blur(0)} }

/* ---- VIDEO · Zoom & pan (zoompan / crop ramp) ---- */
@keyframes edxaV_zoom_in           { 0%{transform:scale(1.35)} 45%,85%{transform:scale(1)} 100%{transform:scale(1.35)} }
@keyframes edxaV_zoom_out          { 0%,15%{transform:scale(1)} 60%,85%{transform:scale(1.35)} 100%{transform:scale(1)} }
@keyframes edxaV_zoom_in_in        { 0%{transform:scale(1.45)} 45%,85%{transform:scale(1)} 100%{transform:scale(1.45)} }
@keyframes edxaV_zoom_in_out       { 0%,15%{transform:scale(1)} 60%,85%{transform:scale(1.45)} 100%{transform:scale(1)} }
@keyframes edxaV_zoom_out_in       { 0%{transform:scale(.7)} 45%,85%{transform:scale(1)} 100%{transform:scale(.7)} }
@keyframes edxaV_zoom_out_out      { 0%,15%{transform:scale(1)} 60%,85%{transform:scale(.7)} 100%{transform:scale(1)} }
@keyframes edxaV_push_in_slow_in   { 0%{transform:scale(1)} 85%{transform:scale(1.28)} 100%{transform:scale(1)} }
@keyframes edxaV_push_in_slow_out  { 0%{transform:scale(1)} 85%{transform:scale(1.28)} 100%{transform:scale(1)} }
@keyframes edxaV_pull_out_slow_in  { 0%{transform:scale(1.28)} 85%{transform:scale(1)} 100%{transform:scale(1.28)} }
@keyframes edxaV_pull_out_slow_out { 0%{transform:scale(1.28)} 85%{transform:scale(1)} 100%{transform:scale(1.28)} }
@keyframes edxaV_punch_in_in       { 0%{transform:scale(1.6)} 18%{transform:scale(1)} 45%,85%{transform:scale(1)} 100%{transform:scale(1.6)} }
@keyframes edxaV_punch_in_out      { 0%,15%{transform:scale(1)} 70%{transform:scale(1)} 85%{transform:scale(1.6)} 100%{transform:scale(1)} }
@keyframes edxaV_pulse_in          { 0%,100%{transform:scale(1)} 25%{transform:scale(1.16)} 50%{transform:scale(1)} 75%{transform:scale(1.16)} }
@keyframes edxaV_pulse_out         { 0%,100%{transform:scale(1)} 30%{transform:scale(1.16)} 60%{transform:scale(1)} 85%{transform:scale(1.16)} }
@keyframes edxaV_ken_burns_in_in   { 0%{transform:scale(1) translate(0,0)} 85%{transform:scale(1.24) translate(-8%,-6%)} 100%{transform:scale(1) translate(0,0)} }
@keyframes edxaV_ken_burns_in_out  { 0%{transform:scale(1) translate(0,0)} 85%{transform:scale(1.24) translate(-8%,-6%)} 100%{transform:scale(1) translate(0,0)} }
@keyframes edxaV_ken_burns_out_in  { 0%{transform:scale(1.24) translate(8%,6%)} 85%{transform:scale(1) translate(0,0)} 100%{transform:scale(1.24) translate(8%,6%)} }
@keyframes edxaV_ken_burns_out_out { 0%{transform:scale(1.24) translate(8%,6%)} 85%{transform:scale(1) translate(0,0)} 100%{transform:scale(1.24) translate(8%,6%)} }
@keyframes edxaV_pan_l_in          { 0%{transform:scale(1.3) translateX(16%)} 85%{transform:scale(1.3) translateX(-16%)} 100%{transform:scale(1.3) translateX(16%)} }
@keyframes edxaV_pan_l_out         { 0%{transform:scale(1.3) translateX(16%)} 85%{transform:scale(1.3) translateX(-16%)} 100%{transform:scale(1.3) translateX(16%)} }
@keyframes edxaV_pan_r_in          { 0%{transform:scale(1.3) translateX(-16%)} 85%{transform:scale(1.3) translateX(16%)} 100%{transform:scale(1.3) translateX(-16%)} }
@keyframes edxaV_pan_r_out         { 0%{transform:scale(1.3) translateX(-16%)} 85%{transform:scale(1.3) translateX(16%)} 100%{transform:scale(1.3) translateX(-16%)} }
@keyframes edxaV_pan_u_in          { 0%{transform:scale(1.3) translateY(16%)} 85%{transform:scale(1.3) translateY(-16%)} 100%{transform:scale(1.3) translateY(16%)} }
@keyframes edxaV_pan_u_out         { 0%{transform:scale(1.3) translateY(16%)} 85%{transform:scale(1.3) translateY(-16%)} 100%{transform:scale(1.3) translateY(16%)} }
@keyframes edxaV_pan_d_in          { 0%{transform:scale(1.3) translateY(-16%)} 85%{transform:scale(1.3) translateY(16%)} 100%{transform:scale(1.3) translateY(-16%)} }
@keyframes edxaV_pan_d_out         { 0%{transform:scale(1.3) translateY(-16%)} 85%{transform:scale(1.3) translateY(16%)} 100%{transform:scale(1.3) translateY(-16%)} }

/* ---- VIDEO · Rotate ---- */
@keyframes edxaV_spin_in    { 0%{transform:rotate(-69deg) scale(.7)} 45%,85%{transform:rotate(0) scale(1)} 100%{transform:rotate(-69deg) scale(.7)} }
@keyframes edxaV_spin_out   { 0%,15%{transform:rotate(0) scale(1)} 60%,85%{transform:rotate(69deg) scale(.7)} 100%{transform:rotate(0) scale(1)} }
@keyframes edxaV_swing_in   { 0%{transform:rotate(14deg)} 12%{transform:rotate(-11deg)} 24%{transform:rotate(7deg)} 34%{transform:rotate(-3deg)} 45%,85%{transform:rotate(0)} 100%{transform:rotate(14deg)} }
@keyframes edxaV_swing_out  { 0%,15%{transform:rotate(0)} 32%{transform:rotate(-3deg)} 48%{transform:rotate(7deg)} 64%{transform:rotate(-11deg)} 85%{transform:rotate(14deg)} 100%{transform:rotate(0)} }
@keyframes edxaV_tilt_in    { 0%{transform:rotate(8deg)} 45%,85%{transform:rotate(0)} 100%{transform:rotate(8deg)} }
@keyframes edxaV_tilt_out   { 0%,15%{transform:rotate(0)} 60%,85%{transform:rotate(8deg)} 100%{transform:rotate(0)} }
@keyframes edxaV_roll_in    { 0%{transform:translateX(-110%) rotate(-360deg)} 45%,85%{transform:translateX(0) rotate(0)} 100%{transform:translateX(-110%) rotate(-360deg)} }
@keyframes edxaV_roll_out   { 0%,15%{transform:translateX(0) rotate(0)} 60%,85%{transform:translateX(110%) rotate(360deg)} 100%{transform:translateX(0) rotate(0)} }

/* ---- VIDEO · Blur (stepped gblur) ---- */
@keyframes edxaV_blur_in        { 0%{filter:blur(7px)} 45%,85%{filter:blur(0)} 100%{filter:blur(7px)} }
@keyframes edxaV_blur_out       { 0%,15%{filter:blur(0)} 60%,85%{filter:blur(7px)} 100%{filter:blur(0)} }
@keyframes edxaV_zoom_blur_in   { 0%{filter:blur(6px);transform:scale(1.3)} 45%,85%{filter:blur(0);transform:scale(1)} 100%{filter:blur(6px);transform:scale(1.3)} }
@keyframes edxaV_zoom_blur_out  { 0%,15%{filter:blur(0);transform:scale(1)} 60%,85%{filter:blur(6px);transform:scale(1.3)} 100%{filter:blur(0);transform:scale(1)} }

/* ---- VIDEO · Light & colour (eq / hue / vignette) ---- */
@keyframes edxaV_flash_white_in   { 0%{filter:brightness(2.6)} 45%,85%{filter:brightness(1)} 100%{filter:brightness(2.6)} }
@keyframes edxaV_flash_white_out  { 0%,15%{filter:brightness(1)} 60%,85%{filter:brightness(2.6)} 100%{filter:brightness(1)} }
@keyframes edxaV_flash_black_in   { 0%{filter:brightness(.12)} 45%,85%{filter:brightness(1)} 100%{filter:brightness(.12)} }
@keyframes edxaV_flash_black_out  { 0%,15%{filter:brightness(1)} 60%,85%{filter:brightness(.12)} 100%{filter:brightness(1)} }
@keyframes edxaV_exposure_in      { 0%{filter:brightness(.5)} 45%,85%{filter:brightness(1)} 100%{filter:brightness(.5)} }
@keyframes edxaV_exposure_out     { 0%,15%{filter:brightness(1)} 60%,85%{filter:brightness(.5)} 100%{filter:brightness(1)} }
@keyframes edxaV_desaturate_in    { 0%{filter:saturate(0)} 45%,85%{filter:saturate(1)} 100%{filter:saturate(0)} }
@keyframes edxaV_desaturate_out   { 0%,15%{filter:saturate(1)} 60%,85%{filter:saturate(0)} 100%{filter:saturate(1)} }
@keyframes edxaV_saturate_pop_in  { 0%{filter:saturate(2.6)} 45%,85%{filter:saturate(1)} 100%{filter:saturate(2.6)} }
@keyframes edxaV_saturate_pop_out { 0%,15%{filter:saturate(1)} 60%,85%{filter:saturate(2.6)} 100%{filter:saturate(1)} }
@keyframes edxaV_hue_cycle_in     { 0%{filter:hue-rotate(360deg)} 45%,85%{filter:hue-rotate(0)} 100%{filter:hue-rotate(360deg)} }
@keyframes edxaV_hue_cycle_out    { 0%,15%{filter:hue-rotate(0)} 60%,85%{filter:hue-rotate(360deg)} 100%{filter:hue-rotate(0)} }
@keyframes edxaV_vignette_in      { 0%{box-shadow:inset 0 0 22px 14px #000} 45%,85%{box-shadow:inset 0 0 0 0 rgba(0,0,0,0)} 100%{box-shadow:inset 0 0 22px 14px #000} }
@keyframes edxaV_vignette_out     { 0%,15%{box-shadow:inset 0 0 0 0 rgba(0,0,0,0)} 60%,85%{box-shadow:inset 0 0 22px 14px #000} 100%{box-shadow:inset 0 0 0 0 rgba(0,0,0,0)} }

/* ---- VIDEO · Mask wipes (geq alpha, timeline-gated) ---- */
@keyframes edxaV_wipe_l_in      { 0%{clip-path:inset(0 100% 0 0)} 45%,85%{clip-path:inset(0 0 0 0)} 100%{clip-path:inset(0 100% 0 0)} }
@keyframes edxaV_wipe_l_out     { 0%,15%{clip-path:inset(0 0 0 0)} 60%,85%{clip-path:inset(0 0 0 100%)} 100%{clip-path:inset(0 0 0 0)} }
@keyframes edxaV_wipe_r_in      { 0%{clip-path:inset(0 0 0 100%)} 45%,85%{clip-path:inset(0 0 0 0)} 100%{clip-path:inset(0 0 0 100%)} }
@keyframes edxaV_wipe_r_out     { 0%,15%{clip-path:inset(0 0 0 0)} 60%,85%{clip-path:inset(0 100% 0 0)} 100%{clip-path:inset(0 0 0 0)} }
@keyframes edxaV_wipe_u_in      { 0%{clip-path:inset(100% 0 0 0)} 45%,85%{clip-path:inset(0 0 0 0)} 100%{clip-path:inset(100% 0 0 0)} }
@keyframes edxaV_wipe_u_out     { 0%,15%{clip-path:inset(0 0 0 0)} 60%,85%{clip-path:inset(0 0 100% 0)} 100%{clip-path:inset(0 0 0 0)} }
@keyframes edxaV_wipe_d_in      { 0%{clip-path:inset(0 0 100% 0)} 45%,85%{clip-path:inset(0 0 0 0)} 100%{clip-path:inset(0 0 100% 0)} }
@keyframes edxaV_wipe_d_out     { 0%,15%{clip-path:inset(0 0 0 0)} 60%,85%{clip-path:inset(100% 0 0 0)} 100%{clip-path:inset(0 0 0 0)} }
@keyframes edxaV_iris_in        { 0%{clip-path:circle(0 at 50% 50%)} 45%,85%{clip-path:circle(75% at 50% 50%)} 100%{clip-path:circle(0 at 50% 50%)} }
@keyframes edxaV_iris_out       { 0%,15%{clip-path:circle(75% at 50% 50%)} 60%,85%{clip-path:circle(0 at 50% 50%)} 100%{clip-path:circle(75% at 50% 50%)} }
@keyframes edxaV_blinds_in      { 0%{-webkit-mask-image:repeating-linear-gradient(to bottom,#000 0 0,transparent 0 16.6%);mask-image:repeating-linear-gradient(to bottom,#000 0 0,transparent 0 16.6%)} 45%,85%{-webkit-mask-image:repeating-linear-gradient(to bottom,#000 0 16.6%,transparent 16.6% 16.6%);mask-image:repeating-linear-gradient(to bottom,#000 0 16.6%,transparent 16.6% 16.6%)} 100%{-webkit-mask-image:repeating-linear-gradient(to bottom,#000 0 0,transparent 0 16.6%);mask-image:repeating-linear-gradient(to bottom,#000 0 0,transparent 0 16.6%)} }
@keyframes edxaV_blinds_out     { 0%,15%{-webkit-mask-image:repeating-linear-gradient(to bottom,#000 0 16.6%,transparent 16.6% 16.6%);mask-image:repeating-linear-gradient(to bottom,#000 0 16.6%,transparent 16.6% 16.6%)} 60%,85%{-webkit-mask-image:repeating-linear-gradient(to bottom,#000 0 0,transparent 0 16.6%);mask-image:repeating-linear-gradient(to bottom,#000 0 0,transparent 0 16.6%)} 100%{-webkit-mask-image:repeating-linear-gradient(to bottom,#000 0 16.6%,transparent 16.6% 16.6%);mask-image:repeating-linear-gradient(to bottom,#000 0 16.6%,transparent 16.6% 16.6%)} }
@keyframes edxaV_circle_wipe_in  { 0%{clip-path:circle(0 at 0% 0%)} 45%,85%{clip-path:circle(160% at 0% 0%)} 100%{clip-path:circle(0 at 0% 0%)} }
@keyframes edxaV_circle_wipe_out { 0%,15%{clip-path:circle(160% at 0% 0%)} 60%,85%{clip-path:circle(0 at 0% 0%)} 100%{clip-path:circle(160% at 0% 0%)} }

/* ---- VIDEO · Fun (stepped rgbashift / chromashift / neighbor scale) ---- */
@keyframes edxaV_shake_in         { 0%,100%{transform:translate(0,0)} 8%{transform:translate(-9%,5%)} 16%{transform:translate(8%,-6%)} 24%{transform:translate(-6%,-4%)} 32%{transform:translate(5%,5%)} 45%,85%{transform:translate(0,0)} }
@keyframes edxaV_shake_out        { 0%,15%{transform:translate(0,0)} 30%{transform:translate(-9%,5%)} 45%{transform:translate(8%,-6%)} 60%{transform:translate(-6%,-4%)} 75%{transform:translate(5%,5%)} 100%{transform:translate(0,0)} }
@keyframes edxaV_jitter_in        { 0%,100%{transform:translate(0,0)} 5%{transform:translate(-4%,3%)} 10%{transform:translate(4%,-3%)} 15%{transform:translate(-3%,-2%)} 20%{transform:translate(3%,2%)} 45%,85%{transform:translate(0,0)} }
@keyframes edxaV_jitter_out       { 0%,15%{transform:translate(0,0)} 35%{transform:translate(-4%,3%)} 50%{transform:translate(4%,-3%)} 65%{transform:translate(-3%,-2%)} 80%{transform:translate(3%,2%)} 100%{transform:translate(0,0)} }
@keyframes edxaV_glitch_in        { 0%{transform:translateX(-8%);filter:hue-rotate(40deg) saturate(2.2)} 10%{transform:translateX(8%);filter:hue-rotate(-40deg) saturate(2.2)} 20%{transform:translateX(-5%)} 30%{transform:translateX(4%)} 45%,85%{transform:translateX(0);filter:none} 100%{transform:translateX(-8%);filter:hue-rotate(40deg) saturate(2.2)} }
@keyframes edxaV_glitch_out       { 0%,15%{transform:translateX(0);filter:none} 35%{transform:translateX(-8%);filter:hue-rotate(40deg) saturate(2.2)} 55%{transform:translateX(8%);filter:hue-rotate(-40deg) saturate(2.2)} 75%{transform:translateX(-5%)} 100%{transform:translateX(0);filter:none} }
@keyframes edxaV_rgb_split_in     { 0%{filter:drop-shadow(4px 0 rgba(255,0,0,.95)) drop-shadow(-4px 0 rgba(0,255,255,.95))} 45%,85%{filter:drop-shadow(0 0 rgba(255,0,0,0)) drop-shadow(0 0 rgba(0,255,255,0))} 100%{filter:drop-shadow(4px 0 rgba(255,0,0,.95)) drop-shadow(-4px 0 rgba(0,255,255,.95))} }
@keyframes edxaV_rgb_split_out    { 0%,15%{filter:drop-shadow(0 0 rgba(255,0,0,0)) drop-shadow(0 0 rgba(0,255,255,0))} 60%,85%{filter:drop-shadow(4px 0 rgba(255,0,0,.95)) drop-shadow(-4px 0 rgba(0,255,255,.95))} 100%{filter:drop-shadow(0 0 rgba(255,0,0,0)) drop-shadow(0 0 rgba(0,255,255,0))} }
@keyframes edxaV_chroma_shift_in  { 0%{filter:drop-shadow(3px 0 rgba(0,120,255,.9)) hue-rotate(12deg)} 45%,85%{filter:drop-shadow(0 0 rgba(0,120,255,0)) hue-rotate(0)} 100%{filter:drop-shadow(3px 0 rgba(0,120,255,.9)) hue-rotate(12deg)} }
@keyframes edxaV_chroma_shift_out { 0%,15%{filter:drop-shadow(0 0 rgba(0,120,255,0)) hue-rotate(0)} 60%,85%{filter:drop-shadow(3px 0 rgba(0,120,255,.9)) hue-rotate(12deg)} 100%{filter:drop-shadow(0 0 rgba(0,120,255,0)) hue-rotate(0)} }
@keyframes edxaV_pixelate_in      { 0%{filter:blur(6px) contrast(1.4)} 20%{filter:blur(4px) contrast(1.3)} 32%{filter:blur(2px) contrast(1.15)} 45%,85%{filter:blur(0) contrast(1)} 100%{filter:blur(6px) contrast(1.4)} }
@keyframes edxaV_pixelate_out     { 0%,15%{filter:blur(0) contrast(1)} 40%{filter:blur(2px) contrast(1.15)} 60%{filter:blur(4px) contrast(1.3)} 85%{filter:blur(6px) contrast(1.4)} 100%{filter:blur(0) contrast(1)} }

/* =========================================================
   11) MOTION — respect reduced-motion
   ========================================================= */
@media (prefers-reduced-motion: reduce) {
  body.edx-page *,
  body.edx-page *::before,
  body.edx-page *::after { transition: none !important; animation: none !important; }
}

/* =========================================================
   12) EXPORT DIALOG (#edxExportModal) — CapCut-style modal
   Lives INSIDE #edxRoot (see caption.html) so the Fullscreen API element
   (<html> in focus mode) still contains it: opening/closing the dialog never
   calls requestFullscreen/exitFullscreen and never drops focus mode.
   `.edx` is position:relative + overflow:hidden → inset:0 covers exactly the
   editor, and position:absolute keeps it OUT of the .edx grid rows.
   ========================================================= */
body.edx-page .edxx {
  position: absolute; inset: 0; z-index: 120;
  display: flex; align-items: center; justify-content: center;
  padding: 20px;
}
body.edx-page .edxx[hidden] { display: none; }
body.edx-page .edxx__scrim {
  position: absolute; inset: 0;
  background: rgba(8, 8, 14, .58);
  backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px);
}
body.edx-page .edxx__card {
  position: relative;
  width: min(520px, 100%); max-height: 100%;
  display: flex; flex-direction: column;
  background: var(--bg-card); color: var(--text);
  border: 1px solid var(--border-lt); border-radius: 16px;
  box-shadow: 0 24px 64px rgba(0, 0, 0, .45);
  overflow: hidden;
  animation: edxxPop .16s ease-out;
}
@keyframes edxxPop { from { opacity: 0; transform: translateY(8px) scale(.985); } to { opacity: 1; transform: none; } }

body.edx-page .edxx__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; padding: 16px 18px 12px;
  border-bottom: 1px solid var(--border);
}
body.edx-page .edxx__title { margin: 0; font-size: 16px; font-weight: 500; letter-spacing: .2px; }
body.edx-page .edxx__x {
  display: grid; place-items: center; width: 28px; height: 28px;
  background: none; border: 0; border-radius: 8px;
  color: var(--text-mute); cursor: pointer; transition: background .14s, color .14s;
}
body.edx-page .edxx__x:hover { background: var(--bg-elev); color: var(--text); }
body.edx-page .edxx__x svg { width: 15px; height: 15px; }

/* flex:1 1 auto + min-height:0 → in a SHORT editor (small window / focus mode on
   a laptop) the body scrolls and the header + action row stay visible; without
   min-height:0 a column flex item refuses to shrink and the Export button would
   be cut off past the card's max-height. */
body.edx-page .edxx__body { flex: 1 1 auto; min-height: 0; padding: 14px 18px 4px; overflow-y: auto; scrollbar-width: thin; }
body.edx-page .edxx__row {
  display: grid; grid-template-columns: 96px minmax(0, 1fr);
  align-items: start; gap: 12px; margin-bottom: 14px;
}
body.edx-page .edxx__lbl {
  font-size: 12.5px; font-weight: 400; color: var(--text-mute);
  padding-top: 8px; letter-spacing: .2px;
}
body.edx-page .edxx__ctl { min-width: 0; }
body.edx-page .edxx__input {
  width: 100%; height: 34px; padding: 0 11px;
  background: var(--bg-soft); color: var(--text);
  border: 1px solid var(--border); border-radius: 9px;
  font-family: var(--font); font-size: 13px; font-weight: 300;
  outline: none; transition: border-color .14s, box-shadow .14s;
}
body.edx-page .edxx__input:focus {
  border-color: var(--edx-accent-line);
  box-shadow: 0 0 0 3px var(--edx-accent-soft);
}
body.edx-page .edxx__hint {
  margin: 6px 0 0; font-size: 11.5px; font-weight: 300;
  color: var(--text-dim); line-height: 1.45;
}

/* segmented control (resolution / frame rate) */
body.edx-page .edxx__seg { display: flex; gap: 6px; }
body.edx-page .edxx__seg button {
  flex: 1 1 0; min-width: 0;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  height: 34px; padding: 0 8px;
  background: var(--bg-soft); color: var(--text-mute);
  border: 1px solid var(--border); border-radius: 9px;
  font-family: var(--font); font-size: 12.5px; cursor: pointer;
  transition: background .14s, border-color .14s, color .14s;
}
body.edx-page .edxx__seg button b { font-weight: 400; }
body.edx-page .edxx__seg button svg { width: 14px; height: 14px; opacity: .75; }
body.edx-page .edxx__seg button:hover { border-color: var(--border-lt); color: var(--text); }
body.edx-page .edxx__seg button.is-on {
  background: var(--edx-accent-soft); border-color: var(--edx-accent-line);
  color: var(--violet-light);
}
body.edx-page .edxx__seg button.is-on svg { opacity: 1; }

/* destination options */
body.edx-page .edxx__dest { display: flex; flex-direction: column; gap: 6px; }
body.edx-page .edxx__dopt {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 9px 11px; text-align: left;
  background: var(--bg-soft); color: var(--text);
  border: 1px solid var(--border); border-radius: 10px;
  font-family: var(--font); cursor: pointer;
  transition: background .14s, border-color .14s;
}
body.edx-page .edxx__dopt:hover { border-color: var(--border-lt); }
body.edx-page .edxx__dopt.is-on { background: var(--edx-accent-soft); border-color: var(--edx-accent-line); }
body.edx-page .edxx__dico { display: grid; place-items: center; width: 20px; color: var(--text-mute); }
body.edx-page .edxx__dopt.is-on .edxx__dico { color: var(--violet-light); }
body.edx-page .edxx__dico svg { width: 16px; height: 16px; }
body.edx-page .edxx__dtxt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
body.edx-page .edxx__dtxt b { font-size: 12.5px; font-weight: 400; }
body.edx-page .edxx__dtxt i { font-size: 11px; font-weight: 300; font-style: normal; color: var(--text-dim); }

/* folder picker row (window.ugcFolders) */
body.edx-page .edxx__fld {
  display: flex; align-items: center; gap: 8px; margin-top: 8px;
  position: relative;              /* ugcFolders.pickMenu anchors its panel here */
}
body.edx-page .edxx__fld[hidden] { display: none; }
body.edx-page .edxx__fldlbl { font-size: 11.5px; font-weight: 300; color: var(--text-dim); }
body.edx-page .edxx__fldbtn {
  display: inline-flex; align-items: center; gap: 7px;
  height: 30px; padding: 0 10px;
  background: var(--bg-soft); color: var(--text);
  border: 1px solid var(--border); border-radius: 8px;
  font-family: var(--font); font-size: 12px; font-weight: 300; cursor: pointer;
  transition: border-color .14s;
}
body.edx-page .edxx__fldbtn:hover { border-color: var(--border-lt); }
body.edx-page .edxx__fldbtn svg { width: 14px; height: 14px; }

body.edx-page .edxx__note {
  margin: 0 0 12px; padding: 9px 11px;
  background: rgba(250, 176, 5, .10); border: 1px solid rgba(250, 176, 5, .32);
  border-radius: 9px; font-size: 11.5px; font-weight: 300; line-height: 1.5;
  color: var(--text);
}
body.edx-page .edxx__note[hidden] { display: none; }
body.edx-page .edxx__sum {
  display: flex; flex-wrap: wrap; gap: 6px 16px;
  padding: 10px 12px; margin-bottom: 12px;
  background: var(--bg-soft); border: 1px solid var(--border); border-radius: 10px;
  font-size: 11.5px; font-weight: 300; color: var(--text-mute);
}
body.edx-page .edxx__sum span { white-space: nowrap; }
body.edx-page .edxx__sum b { font-weight: 400; color: var(--text); }

body.edx-page .edxx__acts {
  display: flex; justify-content: flex-end; gap: 8px;
  padding: 12px 18px 16px; border-top: 1px solid var(--border);
}
body.edx-page .edxx__btn {
  display: inline-flex; align-items: center; gap: 7px;
  height: 34px; padding: 0 16px;
  background: var(--bg-soft); color: var(--text-mute);
  border: 1px solid var(--border); border-radius: 9px;
  font-family: var(--font); font-size: 13px; font-weight: 300; cursor: pointer;
  transition: background .14s, color .14s, border-color .14s, transform .1s;
}
body.edx-page .edxx__btn:hover { color: var(--text); border-color: var(--border-lt); }
body.edx-page .edxx__btn--go {
  background: var(--violet); border-color: var(--violet); color: #fff; font-weight: 400;
}
body.edx-page .edxx__btn--go:hover { background: #7b77e3; border-color: #7b77e3; color: #fff; }
body.edx-page .edxx__btn--go:active { transform: translateY(1px); }
body.edx-page .edxx__btn--go svg { width: 14px; height: 14px; }
body.edx-page .edxx__btn:disabled { opacity: .55; cursor: default; transform: none; }

/* FOLDER PICKER INSIDE THE EXPORT DIALOG.
   The folders module (folders.js `pickMenu`) appends its panel into the button's
   parent with the shared .pchan__panel look from app.css. That parent lives
   inside .edxx__body, which is `overflow-y: auto` — so an absolutely positioned
   panel was CLIPPED by it and the dialog grew a scrollbar instead of the menu
   floating over the card. `position: fixed` takes it out of every overflow
   ancestor (nothing between it and the viewport establishes a containing block:
   .edxx__card's pop animation has no fill-mode, so no transform survives it),
   and the engine (caption.v7.js `floatPickMenu`) writes left/top from the
   button's viewport rect, flipping the panel UPWARD when the room below is
   smaller than its height. z-index 200 clears .edxx itself (120). */
body.edx-page .edxx .fldpick {
  position: fixed; z-index: 200;
  right: auto; bottom: auto; left: 0; top: 0;
  max-height: min(320px, 60vh); overflow-y: auto; scrollbar-width: thin;
}

@media (max-width: 560px) {
  body.edx-page .edxx { padding: 10px; }
  body.edx-page .edxx__row { grid-template-columns: 1fr; gap: 6px; }
  body.edx-page .edxx__lbl { padding-top: 0; }
}

/* ============================================================ 13" PLAYBAR ===
   VERIFIER FIX — #edxPrevFwd and #prevModesBtn overlapped 97% at 1280 (and 45%
   at 1440), i.e. the "next edit point" button was completely unclickable.

   Cause (measured, not guessed): .edx-playbar is `1fr auto 1fr`, so the two
   outer tracks always take the SAME width. At 1280 the stage — and therefore
   the bar — is 362px, giving each outer track 113px, but .edx-playbar__r holds
   three controls (guides dd 48 + aspect dd 60 + fullscreen 31 + 2 gaps = 151px).
   `min-width:0` on __r stops the TRACK inflating (good: no document overflow),
   so instead the CONTENT spills 38px to the LEFT — straight over the centred
   transport group. Symmetric centring simply costs more than the bar has:
   it needs 2*151 + 100 + gaps = 418px of bar.

   Fix: trim the right group's intrinsic width below its 113px share at <=1440.
   Every control stays present, clickable and labelled — only the two dropdown
   CARETS go (both triggers keep aria-haspopup + title, and both still open).
   Nothing here applies at >=1441px, so 1920/2560 are untouched. */
@media (max-width: 1440px) {
  body.edx-page .edx-playbar { gap: 6px; }
  body.edx-page .edx-playbar__r { gap: 4px; }
  body.edx-page .edx-playbar .edx-cornerbtn { padding: 5px 6px; gap: 4px; }
  body.edx-page .edx-playbar .edx-cornerbtn--ico { padding: 5px 5px; }
  /* direct-child caret only — the brand logo lives inside .edx-guidedd__ico */
  body.edx-page .edx-playbar .edx-cornerbtn > svg { display: none; }
}


/* ==========================================================================
   (M) MOBILE EDITOR — CapCut-style shell.  ADDITIVE ONLY.

   ACTIVATION CONTRACT — read before editing:
   Every rule below is gated on `body.edx-page.edxm-on`. The `edxm-on` class is
   added by the EDXM module in caption.v7.js ONLY while
   matchMedia("(max-width: 900px)") matches. On a desktop-width viewport the
   class is absent, so NOT ONE of these rules can match and the desktop editor
   keeps exactly the layout it had before. That is the whole isolation story —
   no rule here may ever be written without the .edxm-on gate.

   The mobile UI drives the SAME DOM and the SAME window.EDX state as desktop:
   #edxRight / #edxLeft are re-positioned into bottom sheets rather than
   duplicated, and the tool rows mirror the engine's own #edxTabs gating. There
   is no second render path and no second export payload.
   ========================================================================== */

/* THE INERT DEFAULT — the mobile chrome is injected into #edxRoot on every
   viewport, so it must be display:none until .edxm-on turns it on. These class
   names appear nowhere in the desktop design, so this rule can only ever match
   nodes this feature created. Without it the injected nodes would paint on
   desktop, which is the one thing that must not happen.
   .edxm-grab BELONGS IN THIS LIST AND WAS MISSING FROM IT. A zero-height div is
   not a zero-cost div: #edxLeft and #edxRight are `display:flex; flex-direction:
   column; gap:10px`, so the drag handle — painting nothing, measuring 0px tall —
   still counted as a flex ITEM and bought one extra 10px gap. Every desktop
   panel therefore started 10px lower and ended 10px shorter the moment the
   mobile shell had ever been built. Measured: #edxLeft's tab row y 65 -> 75,
   its body 724.91 -> 714.91. `display:none` removes the item, not just the ink.
   .edxm-fsx BELONGS IN THIS LIST TOO, for the same reason and then some: it is
   appended INSIDE #edxStage — the desktop preview — so a missing rule here
   would paint an "Exit" pill on top of the desktop player. */
body.edx-page .edxm-trans,
body.edx-page .edxm-dock,
body.edx-page .edxm-scrim,
body.edx-page .edxm-grab,
body.edx-page .edxm-fsx,
body.edx-page .edxm-needle,
body.edx-page .edxm-shead { display: none; }

body.edx-page.edxm-on {
  /* mobile-only geometry tokens */
  --edxm-trans-h: 48px;
  --edxm-dock-h: 64px;
  --edxm-tl-h: 138px;
  --edxm-sheeth: 0px;              /* set by JS while a sheet is open */
  --edxm-kb: 0px;                  /* visualViewport keyboard inset */
  --edxm-safe-t: env(safe-area-inset-top, 0px);
  --edxm-safe-b: env(safe-area-inset-bottom, 0px);
  --edxm-tap: 44px;                /* minimum touch target */

  /* ---- ONE ICON SCALE FOR THE WHOLE PHONE UI ----
     Three sizes, no fourth. A touch TARGET is 44px; the GLYPH inside it is 22px
     — the two are not the same number, and conflating them is exactly what went
     wrong before: .edxm-tbtn, .edxm-shead__b and .edxm-dock__back had no svg
     rule, so their icons inflated to the full 44px button. That class of bug is
     structurally impossible now: the catch-all below sizes every SVG on every
     mobile surface, and the exceptions are one line each. */
  --edxm-ico: 22px;                /* transport · dock · sheet header · top bar */
  --edxm-ico-sm: 16px;             /* inside a pill (Export, quality) */
  --edxm-ico-xs: 15px;             /* the timeline rail's mute / lock */

  /* THE LEAD-IN, NOT A RAIL.
     RAILW in caption.v7.js is read back from this very custom property by
     refreshRailW(), and the engine anchors the ruler margin, the playhead, the
     dropline, #tlInner's width and xToTime() to it. On mobile the module
     OVERWRITES this inline on #edxRoot with half the timeline viewport, which is
     what puts t = 0 under a centred playhead (CapCut's read). The value here is
     only the pre-measurement fallback — it must never be 0, because
     refreshRailW() guards with `v > 0` and would silently leave RAILW at the
     desktop 116px while the CSS moved. The rail's own controls are pinned back
     to the left edge below. */
  --edx-railw: 160px;
  --edx-track-h: 42px;
  --edx-pad: 10px;

  overflow: hidden;
  overscroll-behavior: none;
  /* iOS: stop the document rubber-banding under the fixed sheets */
  position: fixed;
  inset: 0;
  width: 100%;
}

/* --- THE APP HEADER STAYS, EXCEPT IN FULLSCREEN. The editor used to launch
       chromeless on every phone — no appbar, no way back into the app — which
       is not how any other page behaves. The app chrome now stands down for
       exactly one reason: the user asked for a fullscreen preview, and that
       state carries its own always-visible way out (.edxm-fsx, and the
       transport button that turned it on). `.edx-focus` is kept in the selector
       purely as a belt-and-braces: mobile has no control that can enter focus
       mode any more and apply() turns it off on the way in, so the flag should
       never be set here — if it somehow is, the layout still agrees with the
       engine's --edx-vh instead of leaving a 64px band of nothing. --- */
body.edx-page.edxm-on.edxm-fs .appbar,
body.edx-page.edxm-on.edxm-fs .sidebar,
body.edx-page.edxm-on.edxm-fs .sidebar-backdrop,
body.edx-page.edxm-on.edxm-fs .sidebar__scrim,
body.edx-page.edxm-on.edx-focus .appbar,
body.edx-page.edxm-on.edx-focus .sidebar,
body.edx-page.edxm-on.edx-focus .sidebar-backdrop,
body.edx-page.edxm-on.edx-focus .sidebar__scrim { display: none !important; }

/* --edx-vh is the engine's measurement of `innerHeight - appbar.bottom`
   (syncViewportVar), re-run on resize AND inside setFocus(), so ONE expression
   covers both modes: full viewport in focus, viewport-minus-header outside it.
   No hard-coded 64px anywhere. */
body.edx-page.edxm-on .layout,
body.edx-page.edxm-on .content {
  display: block;
  height: var(--edx-vh, 100dvh);
  min-height: 0;
  margin: 0;
  padding: 0;
  max-width: none;
  overflow: hidden;
}

/* --- the shell: a flex column, so the mobile children injected into #edxRoot
       cannot disturb the desktop grid-template-rows contract on .edx --- */
body.edx-page.edxm-on .edx {
  display: flex;
  flex-direction: column;
  height: var(--edx-vh, 100dvh);
  max-height: var(--edx-vh, 100dvh);
  overflow: hidden;
  /* THE SPACE THE SHEET WILL OCCUPY, RESERVED THE INSTANT IT OPENS.
     There is deliberately NO transition on this padding, and that is a fix, not
     an omission. `padding-bottom` was being transitioned while its value came
     from a custom property that JS rewrites (measureSheet writes --edxm-sheeth
     on this very element). Chrome then kept the property at its BEFORE-change
     computed value — measured 0px at t = 0, 100, 300, 700, 1500 and 3000ms after
     the sheet opened, with --edxm-sheeth resolving to 406px the whole time. The
     shell therefore reserved nothing and the sheet slid up OVER the video: at
     360x780 the preview ran to y=732 while the sheet started at y=374, so 358px
     of the picture — the thing the user is editing — sat behind it.
     It is also the wrong animation to want. sizeStage() re-letterboxes the
     canvas in one step on the resize this module dispatches, so a 260ms padding
     crawl underneath an instant canvas jump could never agree with itself. The
     sheet's own transform and the scrim's fade still animate; the space they
     move into is simply correct from the first frame. */
  padding-bottom: var(--edxm-sheeth);
}

/* desktop-only chrome that has a mobile replacement */
body.edx-page.edxm-on .edx-top,
body.edx-page.edxm-on .edx-vres,
body.edx-page.edxm-on .edx-hres,
body.edx-page.edxm-on .edx-tl__bar,
body.edx-page.edxm-on .edx-playbar,
body.edx-page.edxm-on .cardhint-inline { display: none !important; }

/* THE CATCH-ALL. Every SVG on a mobile surface gets the icon size unless a rule
   below says otherwise. `flex: 0 0 auto` stops a glyph inside a flex row from
   being stretched or squashed by its neighbours — which is how the old quality
   pill's chevron reached 0px wide. */
body.edx-page.edxm-on .edxm-fsx svg,
body.edx-page.edxm-on .edxm-trans svg,
body.edx-page.edxm-on .edxm-dock svg,
body.edx-page.edxm-on .edxm-shead svg {
  width: var(--edxm-ico); height: var(--edxm-ico);
  flex: 0 0 auto;
}

/* ---------------- top bar: THERE ISN'T ONE ----------------
   The mobile top strip is gone. It held a focus-mode toggle (a desktop idea
   that on a phone only ever produced a chromeless editor nobody could get out
   of) and a "Video editor" caption repeating the page you just opened. Export
   moved down into the transport row. The app header above #edxRoot is the way
   back into the app, exactly as on every other page — so the editor now spends
   ~54px more of the viewport on the thing being edited. */

/* ---------------- preview ---------------- */
body.edx-page.edxm-on .edx-body {
  order: 2;
  display: block;
  flex: 1 1 auto;
  min-height: 0;
  position: relative;
  background: #000;
}
/* The older `@media (max-width: 900px)` stacking block gives .edx-stage and
   .edx-stage__view `min-height: 46vh`. That floor is taller than the stage
   becomes once a sheet reserves the bottom of the shell, so the view (and the
   letterboxed frame inside it) overflowed and the preview was visibly cut off
   along its bottom edge. min-height:0 hands height control back to the flex
   row; sizeStage() then letterboxes into whatever is actually left. */
body.edx-page.edxm-on .edx-stage {
  height: 100%;
  width: 100%;
  min-height: 0;
  min-width: 0;
  padding: 0;
  /* (M-BG) ARKA PLANI EZMİYORUZ — masaüstündeki `radial-gradient + var(--bg)`
     (#121212) aynen geçerli. Burada `background:#000` vardı ve TUVALİ GÖRÜNMEZ
     YAPIYORDU: .edx-frame de #000, tek ayırt edici işaret 1px'lik kenarlığı;
     sahne de siyah olunca oran değiştirmenin ne yaptığı anlaşılmıyordu, çünkü
     videonun kendi siyahı da aynı renge karışıyor. Masaüstünde tuval #000,
     sahne #121212 olduğu için sınır zaten belliydi — mobil artık onunla aynı.
     Tam ekranda siyah kalmaya devam ediyor: onu ayrı bir kural veriyor
     (`.edx-stage:fullscreen { background:#000 }`), o yüzden burayı kaldırmak
     tam ekran görünümünü bozmuyor. */
}
body.edx-page.edxm-on .edx-stage__view {
  height: 100%;
  min-height: 0;
  padding: 6px 0;
}

/* ---------------- transport row ---------------- */
body.edx-page.edxm-on .edxm-trans {
  display: flex;
  order: 3;
  flex: 0 0 auto;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  height: var(--edxm-trans-h);
  padding: 0 8px;
  background: var(--bg);
  border-top: var(--edx-line);
  border-bottom: var(--edx-line);
}
body.edx-page.edxm-on .edxm-trans__mid { display: flex; align-items: center; gap: 8px; }
body.edx-page.edxm-on .edxm-trans__end { display: flex; align-items: center; gap: 2px; }
body.edx-page.edxm-on .edxm-time {
  font: 500 11.5px/1 var(--font);
  font-variant-numeric: tabular-nums;
  color: var(--text-mute); letter-spacing: .02em;
}
body.edx-page.edxm-on .edxm-tbtn {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: var(--edxm-tap); height: var(--edxm-tap);
  border: 0; background: none; color: var(--text-mute);
  border-radius: var(--r-sm); cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
body.edx-page.edxm-on .edxm-tbtn:active:not(:disabled) { background: var(--bg-elev); color: var(--text); }
body.edx-page.edxm-on .edxm-tbtn:disabled { opacity: .32; }
/* play is the one control in the row with primacy — it gets 2px and full text
   colour, the rest stay muted. That is the whole hierarchy. */
body.edx-page.edxm-on .edxm-tbtn--play { color: var(--text); }
body.edx-page.edxm-on .edxm-tbtn--play svg { width: 24px; height: 24px; }
/* the fullscreen button LIGHTS UP while fullscreen is on, and its glyph flips to
   the inward arrows — so the control that got you in is visibly the control
   that gets you out, without adding a second button to the row */
body.edx-page.edxm-on .edxm-tbtn--fs.is-on { color: var(--violet-light); background: var(--edx-accent-soft, rgba(105,101,219,.16)); }

/* EXPORT LIVES HERE NOW.
   It is the only violet thing in the row, right-most, after the fullscreen
   toggle — the far corner is where every phone editor puts "get it out".
   Sized to survive 360px: the row's fixed costs are undo+redo (90), play+time
   (~110) and fullscreen (44), which at 360 leaves ~86px, so the pill is built
   from 12.5px type, a 15px glyph and 9/11px padding rather than the top bar's
   roomier 13px/16px/12/14. `flex: 0 0 auto` so it can never be squeezed into an
   ellipsis by the middle cluster. */
body.edx-page.edxm-on .edxm-exp {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; gap: 5px;
  min-height: 34px; padding: 0 11px 0 9px;
  margin-left: 2px;
  font: 600 12.5px/1 var(--font); letter-spacing: -.01em;
  color: #fff; background: var(--violet);
  border: 0; border-radius: 999px; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
body.edx-page.edxm-on .edxm-exp svg { width: 15px; height: 15px; }
body.edx-page.edxm-on .edxm-exp:active { background: var(--violet-dark); }
body.edx-page.edxm-on .edxm-exp[hidden] { display: none; }
/* 44px MINIMUM TOUCH TARGET. The pill is 34px because a 44px-tall pill reads as
   a tablet button; the FINGER target and the PAINTED target are not the same
   rectangle. An out-of-flow ::before pays the missing 5px above and below
   (34 + 10 = 44 = --edxm-tap) inside the 48px row, so the hit area meets the
   minimum while the pill keeps its exact size, radius and colour. Verified by
   hit-testing the band edges with elementFromPoint, not by reading the box. */
body.edx-page.edxm-on .edxm-exp { position: relative; }
body.edx-page.edxm-on .edxm-exp::before {
  content: "";
  position: absolute;
  inset: calc((var(--edxm-tap) - 34px) / -2) -2px;
  border-radius: inherit;
}

/* ---------------- timeline ---------------- */
body.edx-page.edxm-on .edx-tl {
  order: 4;
  flex: 0 0 auto;
  height: var(--edxm-tl-h);
  min-height: var(--edxm-tl-h);
  max-height: var(--edxm-tl-h);
  background: var(--bg);
  border-top: 0;
}
body.edx-page.edxm-on .edx-tl__scroll {
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  touch-action: pan-x pan-y;
}
/* FINGER DRAG / TRIM / SCRUB.
   `touch-action: pan-x pan-y` on the scroller is right for the empty areas, but
   a clip inherits it — so the browser claimed every horizontal drag as a scroll
   and fired pointercancel, killing the engine's move/trim session before it
   started. Opting the interactive nodes out hands those gestures back to the
   pointer handlers, while blank lane space still scrolls the strip.

   (M2) THE RULER AND THE NEEDLE ARE NO LONGER ON THAT LIST, AND THAT IS THE FIX.
   They were opted out so the engine's scrub handlers could own them — which is
   precisely why the ruler, the most obvious place on the whole strip to put a
   thumb, could not be dragged: `touch-action: none` tells the browser not to
   scroll, and the scrub handler then jumped the playhead to wherever the finger
   landed. The needle does not move any more (see .edxm-needle) and the engine's
   scrub is gated off on this shell, so both are ordinary scrollable surface. */
body.edx-page.edxm-on .edx-clip,
body.edx-page.edxm-on .edx-clip * { touch-action: none; }
/* the engine's in-content needle is replaced by .edxm-needle below; its 9px
   grab column would otherwise sit dead centre and block the pan */
body.edx-page.edxm-on .edx-tl__playhead,
body.edx-page.edxm-on .edx-tl__ph-hit { display: none; }

/* (M2) THE LAST RULER LABEL WAS THE LAST BIT OF DEAD SPACE.
   #tlInner is sized to the exact pixel by tlWidths() — RAILW + duration·pps +
   TAILW — and the whole "t = duration is the end and there is nothing past it"
   guarantee is maxScroll === duration·pps, i.e. scrollWidth === innerW. But
   scrollWidth is the union of the DESCENDANTS' overflow, and a ruler label is a
   `<span>` centred on its tick with translateX(-50%): whenever the last major
   tick happens to land near the ruler's right edge, half that label — 6-12px,
   0.13-0.25s at the phone zoom — hangs past the end and the strip could be
   dragged into it. MEASURED: a 10 s project at 390x844 gave scrollWidth 876 vs
   innerW 870 (label right edge 876.27), while a 15 s one was exact, so it came
   and went with the tick spacing, which is precisely the kind of "sometimes
   there is a bit of slack at the end" the centred-needle model must not have.
   Clipping the ruler's own overflow takes those labels out of #tlInner's
   scrollable area. It is the RULER that clips, not #tlInner, so nothing else in
   the strip is touched; the only pixels lost are the outer half of a label that
   is already past the last frame. Desktop is unaffected — it is not on this
   selector, and its 40px tail means overflow there was never load-bearing. */
body.edx-page.edxm-on .edx-tl__ruler { overflow: hidden; }

/* ---------- (M2) THE NEEDLE THAT CANNOT MOVE ----------
   A child of the timeline PANEL, not of the scrolling content, at `left: 50%`.
   There is no JS anywhere that writes to it: "the playhead stays fixed at the
   horizontal centre, permanently" is a CSS fact here, not a per-frame
   correction that can fall a frame behind a momentum scroll. Everything else —
   the time readout, the preview, the ruler under it — is derived from
   scrollLeft, which the browser owns. */
body.edx-page.edxm-on .edxm-needle {
  display: block;
  position: absolute;
  top: 0; bottom: 0; left: 50%;
  width: 2px; margin-left: -1px;
  z-index: 9;
  pointer-events: none;                 /* it is a readout, never a target */
  background: var(--violet);
  box-shadow: 0 0 6px rgba(105, 101, 219, .55);
}
body.edx-page.edxm-on .edxm-needle__head {
  position: absolute; top: 0; left: 50%;
  width: 11px; height: 11px; margin: -1px 0 0 -5.5px;
  border-radius: 3px;
  background: var(--violet);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .45);
}

/* ---------- the track head RIDES THE STRIP ----------
   IT WAS PINNED, AND THAT WAS THE BUG. The rail kept the desktop's
   `position: sticky; left: 0`, so the mute and lock buttons sat welded to the
   left edge of the screen while the timeline scrolled underneath them — a fixed
   rail with a moving track, which is not what any phone editor does and not
   what the owner asked for twice. In CapCut those controls are simply the FIRST
   thing on the track: they sit in the lead-in, immediately before the first
   clip, and they slide away to the left as you scrub forward.
   That is now literally what they are. The rail is back to its full
   `--edx-railw` box (the CapCut lead-in = half the timeline viewport, so the
   lane still begins exactly where the ruler, the needle, the dropline and
   xToTime() all agree it does), it is `position: relative` instead of sticky so
   it scrolls with everything else, and its two buttons are pushed to the END of
   that box — i.e. hard up against t = 0, which is the horizontal centre of the
   screen. At playhead 0 the clip starts mid-screen and its controls sit just to
   its left; scrub right and they leave with the track.
   `relative` rather than `static` only so the desktop z-index 8 still applies
   and the needle passes behind the buttons instead of through them. */
body.edx-page.edxm-on .edx-track__rail {
  position: relative;
  flex: 0 0 var(--edx-railw);
  width: var(--edx-railw);
  max-width: var(--edx-railw);
  margin-right: 0;
  justify-content: flex-end;
  gap: 6px;
  padding: 0 8px 0 6px;
  /* it is LEAD-IN, not a control column: no card fill and no divider, or the
     left half of every phone timeline would read as a solid panel with a rule
     down the middle of the screen */
  background: transparent;
  border-right: 0;
  overflow: hidden;
}
/* Only AUDIO and LOCK survive on a phone. The kind glyph is decoration, and the
   EYE (hide) is a desktop compositing nicety that on a phone is
   indistinguishable from "my clip disappeared" — Delete is the mobile answer.
   Child order is fixed by buildTracks(): kind, mute, lock, hide. */
body.edx-page.edxm-on .edx-track__rail > * { display: none; }
body.edx-page.edxm-on .edx-track__rail button:nth-of-type(1),
body.edx-page.edxm-on .edx-track__rail button:nth-of-type(2) { display: grid; }
/* a text lane has no audio to mute */
body.edx-page.edxm-on .edx-track[data-kind="text"] .edx-track__rail button:nth-of-type(1) { display: none; }
/* they used to read against the rail's card fill; standing on the open timeline
   they need their own chip, or two thin glyphs float on the background */
body.edx-page.edxm-on .edx-track__rail button {
  width: 26px; height: 26px; min-width: 26px; flex: 0 0 26px; border-radius: 7px;
  background: var(--bg-card);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .05);
}
body.edx-page.edxm-on .edx-track__rail button.is-on { background: var(--edx-accent-soft, rgba(105,101,219,.16)); }
body.edx-page.edxm-on .edx-track__rail button svg,
body.edx-page.edxm-on .edx-track__rail button svg[viewBox] { width: var(--edxm-ico-xs); height: var(--edxm-ico-xs); }

/* THE LEAD-OUT IS NO LONGER A CSS GUESS.
   It used to be `::after { left: 100%; width: 50vw }` — half a VIEWPORT of
   overflow bolted on after the engine's own flat 40px desktop tail. The last
   frame did become reachable, but the strip then ran 40px PAST it: 0.83s of
   dead space beyond the end at the mobile zoom, and more on a short project
   where the engine's 4-second floor set the width. The lead-out is now
   `--edx-tailw`, written by the shell and consumed by the engine's tlWidths()
   (see caption.v7.js), so the strip ends EXACTLY on the last frame. Nothing
   here may add width to #tlInner or that exactness is gone. */

/* the empty-state strip belongs under the needle, and its desktop wording asks
   for a drag-and-drop a phone cannot do */
body.edx-page.edxm-on .edx-tl__empty {
  margin-left: var(--edx-railw);
  width: 210px; max-width: 62vw;
  transform: translateX(-50%);
  font-size: 0;
}
body.edx-page.edxm-on .edx-tl__empty::after {
  content: "Tap Media to add your first clip";
  font-size: 11px; font-weight: 500; letter-spacing: .01em;
}

body.edx-page.edxm-on .edxm-tool > span { white-space: nowrap; }

/* ---------------- bottom dock ---------------- */
/* THE ROW IS CENTRED, NOT LEFT-PACKED.
   The back arrow used to be a 44px flex item, so the scroller began 44px in and
   the whole row read as shoved to the right. It is absolute now: the scroll area
   spans the full dock and the tools sit dead centre. `safe center` keeps the
   FIRST item reachable when the row does overflow — plain `center` would push it
   past scrollLeft 0 and make it unscrollable-to. */
body.edx-page.edxm-on .edxm-dock {
  display: flex;
  position: relative;
  order: 6;
  flex: 0 0 auto;
  align-items: stretch;
  height: calc(var(--edxm-dock-h) + var(--edxm-safe-b));
  padding-bottom: var(--edxm-safe-b);
  background: var(--bg-soft);
  border-top: var(--edx-line);
}
body.edx-page.edxm-on .edxm-dock__back {
  display: none;
  position: absolute;
  left: 0; top: 0; bottom: var(--edxm-safe-b);
  z-index: 2;
  align-items: center; justify-content: center;
  width: 44px;
  border: 0; border-right: var(--edx-line);
  background: var(--bg-soft); color: var(--text-mute);
  cursor: pointer; -webkit-tap-highlight-color: transparent;
}
body.edx-page.edxm-on .edxm-dock.is-sub .edxm-dock__back { display: flex; }
body.edx-page.edxm-on .edxm-dock__scroll {
  flex: 1 1 auto;
  display: flex; align-items: stretch; gap: 2px;
  justify-content: center;
  justify-content: safe center;
  overflow-x: auto; overflow-y: hidden;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  padding: 0 6px;
}
/* symmetric gutters so "centred" stays true once the back arrow is showing */
body.edx-page.edxm-on .edxm-dock.is-sub .edxm-dock__scroll { padding: 0 48px; }
body.edx-page.edxm-on .edxm-dock__scroll::-webkit-scrollbar { display: none; }

body.edx-page.edxm-on .edxm-tool {
  flex: 0 0 auto;
  display: inline-flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 4px;
  min-width: 56px; min-height: var(--edxm-tap);
  padding: 6px 6px;
  border: 0; background: none;
  color: var(--text-mute);
  font: 500 10.5px/1.1 var(--font); letter-spacing: .01em;
  cursor: pointer; -webkit-tap-highlight-color: transparent;
}
body.edx-page.edxm-on .edxm-tool:active { color: var(--text); }
body.edx-page.edxm-on .edxm-tool.is-active { color: var(--violet-light); }
body.edx-page.edxm-on .edxm-tool.is-danger { color: #e8635f; }
body.edx-page.edxm-on .edxm-tool:disabled { opacity: .3; }

/* ---------------- bottom sheets (#edxRight / #edxLeft) ----------------
   These are the DESKTOP panels, re-positioned. Nothing is cloned, so every
   control inside them is the same node bound to the same engine handler. */
body.edx-page.edxm-on .edx-right,
body.edx-page.edxm-on .edx-left {
  position: fixed;
  left: 0; right: 0;
  /* THE KEYBOARD SITS UNDER THE SHEET, NOT INSIDE IT. Anchoring at bottom:0 and
     paying for the keyboard with padding-bottom collapsed the scrollable body
     to ~14px — the sheet was still "open" but had no usable content area. The
     whole sheet rides above the keyboard instead, and its height is capped so
     the preview + transport keep a usable strip on screen. */
  bottom: var(--edxm-kb);
  width: auto;
  height: min(var(--edxm-sheet-max, 52dvh),
              calc(100dvh - var(--edxm-kb) - 240px));
  max-height: 72dvh;
  z-index: 60;
  margin: 0;
  padding: 0 0 var(--edxm-safe-b);
  /* A SHEET IS A SURFACE THAT ROSE, NOT A BOX THAT APPEARED. It sits a step
     above the shell (bg-card over bg), carries a 1px highlight along its top
     edge where the light would catch, and drops a long soft shadow. Those three
     things together are most of what "premium" means on a phone. */
  background: var(--bg-card);
  border: 0;
  border-radius: 22px 22px 0 0;
  box-shadow: 0 -1px 0 rgba(255, 255, 255, .07), 0 -24px 60px rgba(0, 0, 0, .62);
  transform: translateY(102%);
  transition: transform .32s cubic-bezier(.22, 1, .36, 1);
  will-change: transform;
  visibility: hidden;
}
body.edx-page.edxm-on.edxm-sheet-right .edx-right,
body.edx-page.edxm-on.edxm-sheet-left .edx-left { transform: none; visibility: visible; }

body.edx-page.edxm-on .edx-right__body,
body.edx-page.edxm-on .edx-left__body {
  flex: 1 1 auto; min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  padding: 2px 16px 18px;
}
/* the desktop body reserves a scrollbar gutter; a phone has no scrollbar, and
   the 6/10px asymmetry it leaves behind reads as a crooked sheet */
body.edx-page.edxm-on .edx-right__body { scrollbar-gutter: auto; padding-left: 16px; padding-right: 16px; }
/* room to breathe between sections — the single biggest "cheap vs premium" tell */
body.edx-page.edxm-on .edx-pane { gap: 18px; padding: 10px 0 4px; }
body.edx-page.edxm-on .edx-group { gap: 14px; }
body.edx-page.edxm-on .edx-sub {
  margin: 2px 0 0;
  font-size: 10.5px; font-weight: 600; letter-spacing: .09em; text-transform: uppercase;
  color: var(--text-dim);
}
body.edx-page.edxm-on .edx-note { font-size: 11px; line-height: 1.5; }

/* sheet header — injected as the first child of each panel, inert on desktop */
body.edx-page.edxm-on .edxm-shead {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 6px;
  height: 50px;
  padding: 0 8px 0 6px;
  border-bottom: var(--edx-line);
}
body.edx-page.edxm-on .edxm-shead__t {
  flex: 1 1 auto; min-width: 0;
  font: 600 15px/1 var(--font);
  letter-spacing: -.01em;
  color: var(--text);
  text-align: center;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
body.edx-page.edxm-on .edxm-shead__b {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: var(--edxm-tap); min-height: var(--edxm-tap);
  border: 0; background: none; color: var(--text-mute);
  border-radius: 999px; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color .14s ease, background .14s ease;
}
/* the ✓ is the affirmative end of the row, so it is the only violet thing in it */
body.edx-page.edxm-on .edxm-shead__b--ok { color: var(--violet); }
body.edx-page.edxm-on .edxm-shead__b--ok svg { width: 20px; height: 20px; }
body.edx-page.edxm-on .edxm-shead__b:active { background: var(--bg-elev); color: var(--text); }
body.edx-page.edxm-on .edxm-shead__b--ok:active { background: var(--edx-accent-soft, rgba(105,101,219,.16)); color: var(--violet); }

/* THE GRAB HANDLE. Injected above the header and wired to a real
   pull-down-to-dismiss drag (grabDrag). 22px of height is the invisible part —
   a 4px bar you cannot catch is decoration, not an affordance. */
body.edx-page.edxm-on .edxm-grab {
  flex: 0 0 auto;
  width: 100%; height: 22px;
  display: flex; align-items: center; justify-content: center;
  touch-action: none;
  cursor: grab;
}
body.edx-page.edxm-on .edxm-grab::before {
  content: ""; width: 36px; height: 4px; border-radius: 2px;
  background: var(--border-lt);
  transition: background .14s ease, width .14s ease;
}
body.edx-page.edxm-on .edxm-grab:active::before { background: var(--text-dim); width: 44px; }

/* ==========================================================================
   THE TAB STRIP IS A SECOND NAVIGATION, AND THE DOCK ALREADY IS THE FIRST.

   Tapping "Style" on a text clip opened a sheet whose first row was
   "Text settings | Text animation"; tapping "Video" on a video clip opened one
   headed "Video | Audio | Speed | Animation | Adjust | Auto captions". The dock
   underneath ALREADY has one item per pane — the pills were the same choice
   offered twice, one of them 50px of a 439px sheet. They are gone on mobile:
   a dock item opens its pane directly, and switching means closing the sheet
   and tapping another dock item.

   IT IS HIDDEN, NOT DELETED, AND THAT IS LOAD-BEARING. The dock does not decide
   for itself which tools apply to the selection — it READS the engine's own
   #edxTabs button visibility (visibleTabButtons()), which syncKindUI() writes.
   That is the whole reason the dock can never disagree with the engine about,
   say, "a still image has no Audio tab". `display: none` on the wrapper leaves
   every button's `hidden` / inline `display` exactly as the engine set it, and
   .click() still works on a button inside a display:none subtree — so openTab()
   keeps switching panes through the engine's own handler. Removing the nodes,
   or setting `hidden` on the wrapper, would break both.
   (visibleTabButtons() guards on `edxTabsWrap.hidden` and the wrapper's INLINE
   style, neither of which a stylesheet can set — checked before relying on it.)

   The sheet's height does NOT move: it is min(52dvh, 100dvh - kb - 240px),
   a pure function of the viewport, so the 50px the strip gave up goes to
   .edx-right__body and nothing else changes. Measured before/after at all seven
   test widths. */
body.edx-page.edxm-on .edx-right .edx-tabs { display: none; }
body.edx-page.edxm-on .edx-tabs__chev { display: none !important; }

/* PRESET TILES. 72px columns are a desktop density; on a 360px sheet that is a
   grid of stamps. Fluid columns with a 84px floor give three or four per row at
   every test width and let the animated mark actually read. */
body.edx-page.edxm-on .edx-anigrid {
  grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
  gap: 8px;
}
body.edx-page.edxm-on .edx-anitile { padding: 6px 6px 8px; border-radius: 14px; gap: 6px; }
body.edx-page.edxm-on .edx-anitile__stage { border-radius: 11px; }
body.edx-page.edxm-on .edx-anitile__lbl { font-size: 10.5px; }
body.edx-page.edxm-on .edx-anigroup__h { font-size: 10.5px; letter-spacing: .09em; }

/* segmented controls + buttons: phone-sized, still thin */
body.edx-page.edxm-on .edx-seg button { min-height: 34px; padding: 0 14px; font-size: 12.5px; }
body.edx-page.edxm-on .edx-seg--sm button { min-height: 32px; padding: 0 12px; font-size: 12px; }
body.edx-page.edxm-on .edx-subtabs button { min-height: 34px; font-size: 12.5px; }
body.edx-page.edxm-on .edx-btnrow { gap: 8px; }
body.edx-page.edxm-on .edx-btnrow .minibtn { min-height: 40px; border-radius: 12px; font-size: 12.5px; }

/* scrim */
body.edx-page.edxm-on .edxm-scrim {
  position: fixed; inset: 0;
  z-index: 55;
  background: rgba(0, 0, 0, .38);
  opacity: 0; pointer-events: none;
  transition: opacity .26s ease;
}
body.edx-page.edxm-on.edxm-sheet-right .edxm-scrim,
body.edx-page.edxm-on.edxm-sheet-left .edxm-scrim { opacity: 1; pointer-events: auto; }

/* While a sheet is open the timeline + dock give up their space to it, so the
   preview only loses the difference — it is never covered (CapCut's rule).
   .edxm-trans deliberately STAYS: picking an animation or a font is useless if
   you cannot hit play to see it, so the transport rides directly under the
   shrunken preview. */
body.edx-page.edxm-on.edxm-sheeton .edx-tl,
body.edx-page.edxm-on.edxm-sheeton .edxm-dock { display: none !important; }

/* The left library's TEXT tab is dead weight on mobile: Text is a dock tool that
   drops a block straight onto the timeline, so the preset list would be a second
   route to the same action inside a sheet the phone no longer opens. */
body.edx-page.edxm-on .edx-lefttabs button[data-ltab="text"] { display: none; }

/* ==========================================================================
   (RULER) THE MOBILE NUMERIC CONTROL.

   INERT BY DEFAULT — .edxm-ruler is injected into the DESKTOP panels the first
   time the phone shell activates, so like every other .edxm-* node it must not
   exist visually until .edxm-on turns it on. A viewport that never went narrow
   never gets one at all (buildChrome only runs on the first mobile match).
   ========================================================================== */
body.edx-page .edxm-ruler { display: none; }

/* The native slider is hidden ONLY when a ruler actually took it over
   (data-edxm-ruler is written by makeRuler after the ruler is in the DOM), so a
   control that failed to get one still shows its slider rather than nothing. */
body.edx-page.edxm-on .edx-right input[type="range"][data-edxm-ruler],
body.edx-page.edxm-on .edx-left input[type="range"][data-edxm-ruler] { display: none; }

body.edx-page.edxm-on .edxm-ruler {
  display: block;
  position: relative;
  height: 46px;
  margin-top: 2px;
  overflow: hidden;
  border-radius: 12px;
  background: var(--bg-soft);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .045);
  /* THE PAGE MUST NOT SCROLL WHILE A VALUE IS BEING DRAGGED. Without this the
     browser claims the horizontal pan itself, fires pointercancel, and the
     gesture dies half way through — the same failure the timeline clips had. */
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
  -webkit-user-select: none; user-select: none;
  cursor: ew-resize;
}
/* the tape ENDS at min and max — there is deliberately nothing to drag past the
   limit, which is what makes the limit legible instead of merely enforced */
body.edx-page.edxm-on .edxm-ruler__tape {
  position: absolute;
  top: 0; left: 0; bottom: 0;
  will-change: transform;
  /* two tiled layers, both hanging from the baseline: a short notch every unit
     and a long one every fifth */
  background-image:
    linear-gradient(90deg, var(--border-lt) 0 1.5px, transparent 1.5px),
    linear-gradient(90deg, var(--text-dim) 0 1.5px, transparent 1.5px);
  background-size:
    var(--edxm-rpx, 8px) 12px,
    calc(var(--edxm-rpx, 8px) * 5) 20px;
  background-position: 0 26px, 0 18px;
  background-repeat: repeat-x, repeat-x;
}
/* origin marker for the controls that go negative (position, rotation, temp) */
body.edx-page.edxm-on .edxm-ruler__zero {
  position: absolute;
  top: 12px; bottom: 6px;
  width: 1.5px; margin-left: -0.75px;
  border-radius: 1px;
  background: rgba(105, 101, 219, .55);
}
/* THE FIXED INDICATOR. Everything else moves; this never does. */
body.edx-page.edxm-on .edxm-ruler__needle {
  position: absolute;
  left: 50%; top: 8px; bottom: 6px;
  width: 2px; margin-left: -1px;
  border-radius: 2px;
  background: var(--violet);
  box-shadow: 0 0 10px rgba(105, 101, 219, .55);
  pointer-events: none;
}
body.edx-page.edxm-on .edxm-ruler__needle::before {
  content: "";
  position: absolute;
  top: -7px; left: 50%;
  width: 0; height: 0; margin-left: -4px;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid var(--violet);
}
/* the tape fades out at both ends instead of being sliced off by the corner
   radius — the difference between "a window onto a scale" and "a cropped box" */
body.edx-page.edxm-on .edxm-ruler::after {
  content: "";
  position: absolute; inset: 0;
  pointer-events: none;
  background: linear-gradient(90deg,
    var(--bg-soft) 0, rgba(0, 0, 0, 0) 15%,
    rgba(0, 0, 0, 0) 85%, var(--bg-soft) 100%);
}
body.edx-page.edxm-on .edxm-ruler:active { background: var(--bg-elev); }
body.edx-page.edxm-on .edxm-ruler:active::after {
  background: linear-gradient(90deg,
    var(--bg-elev) 0, rgba(0, 0, 0, 0) 15%,
    rgba(0, 0, 0, 0) 85%, var(--bg-elev) 100%);
}

/* THE VALUE IS THE HEADLINE. The label row above each ruler is the readout —
   name on the left, live number on the right in violet, tabular so it cannot
   jitter as digits change. It is the engine's OWN .edx-field__val span, updated
   by bindRange, so it can never disagree with what will be rendered. */
body.edx-page.edxm-on .edx-field { gap: 4px; }
body.edx-page.edxm-on .edx-field > label { font-size: 12.5px; color: var(--text-mute); }
body.edx-page.edxm-on .edx-field__val {
  font-size: 13px; font-weight: 600;
  color: var(--violet-light);
  letter-spacing: -.01em;
}

/* touch ergonomics inside the reused desktop panes */
body.edx-page.edxm-on .edx-right select,
body.edx-page.edxm-on .edx-right input[type="text"],
body.edx-page.edxm-on .edx-right input[type="number"],
body.edx-page.edxm-on .edx-right textarea {
  min-height: 40px;
  font-size: 16px;            /* iOS: anything under 16px focuses with a zoom-in */
}
body.edx-page.edxm-on .edx-right .minibtn,
body.edx-page.edxm-on .edx-right .tlicon { min-height: 36px; min-width: 36px; }

/* ==========================================================================
   (FS) FULLSCREEN PREVIEW — A STATE OF THIS SHELL, NOT OF THE BROWSER.

   It used to call requestFullscreen() on #edxStage. The browser then paints
   ONLY that element, and every mobile control lives outside it (.edxm-trans is
   a sibling; the desktop .edx-playbar that IS inside is display:none here —
   measured: 16 buttons inside #edxStage, every one of them 0px wide). So
   fullscreen was a picture with no control on it, and because nothing cleared
   `.edxm-fs` when fullscreen ended, pressing Escape dropped you back into an
   editor with no top bar, no timeline and no dock. Two ways to be stuck.

   Now: the timeline, the dock and the APP HEADER stand down and the stage takes
   the room. Everything is still in the document, so it is reversible by
   definition — and the two things that remain on screen are the transport row
   (you came here to watch it, you need play) and an Exit control that cannot be
   missed. Escape and the system back gesture do the same job (see toggleFs).
   ========================================================================== */
body.edx-page.edxm-on.edxm-fs .edx-tl,
body.edx-page.edxm-on.edxm-fs .edxm-dock { display: none !important; }
/* Export is a "finish the project" action; it has no business over a preview
   the user is watching, and taking it out gives the row its centre back. */
body.edx-page.edxm-on.edxm-fs .edxm-exp { display: none; }

/* THE EXIT CONTROL, ON THE PICTURE.
   Inside #edxStage on purpose — that is the element native fullscreen would
   promote, so this button is the one control that survives every fullscreen
   path, including ones this module never asks for. Top-right, out of the way of
   the frame's own content, with a 44px target and enough contrast to read over
   any frame: a phone-editor exit has to be findable on a white video. */
body.edx-page.edxm-on.edxm-fs .edxm-fsx {
  display: inline-flex;
  position: absolute;
  top: calc(10px + var(--edxm-safe-t));
  right: 10px;
  z-index: 12;
  align-items: center; gap: 6px;
  min-height: var(--edxm-tap); padding: 0 14px 0 11px;
  font: 600 12.5px/1 var(--font); letter-spacing: -.01em;
  color: #fff;
  background: rgba(10, 10, 14, .62);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  border: 0; border-radius: 999px;
  box-shadow: 0 2px 14px rgba(0, 0, 0, .45), inset 0 0 0 1px rgba(255, 255, 255, .14);
  cursor: pointer; -webkit-tap-highlight-color: transparent;
}
body.edx-page.edxm-on.edxm-fs .edxm-fsx svg { width: 17px; height: 17px; }
body.edx-page.edxm-on.edxm-fs .edxm-fsx:active { background: rgba(10, 10, 14, .8); }

@media (prefers-reduced-motion: reduce) {
  body.edx-page.edxm-on .edx,
  body.edx-page.edxm-on .edx-right,
  body.edx-page.edxm-on .edx-left,
  body.edx-page.edxm-on .edxm-scrim { transition: none; }
}
