/* Qawlak — قولك component layer (U1).
   Loads after qawlak.css. Owns cross-page components that page-specific CSS in qawlak.css
   doesn't cover yet: icons, toasts, skeletons, tooltips, chips, template cards, timeline
   blocks, sidebar-nav icon slots, meters, dialog polish. Uses the semantic --color-* tokens
   from tokens.css so any component here follows the OS dark-mode preference automatically;
   qawlak.css's page backgrounds are still hardcoded to the light palette (see UI.md "Known gaps"
   and TECH-PLAN "P1c — UX/UI" for the full-site retheme follow-up).
   Logical properties throughout (RTL-safe); :focus-visible everywhere; reduced-motion respected. */

/* ── Icons ──────────────────────────────────────────────────────────────── */
.qk-icon {
  width: 1em; height: 1em; flex: none;
  display: inline-block; vertical-align: -0.15em;
  fill: none; stroke: currentColor;
}
.qk-icon--sm { width: 14px; height: 14px; }
.qk-icon--md { width: 18px; height: 18px; }
.qk-icon--lg { width: 24px; height: 24px; }

/* ── Toast (#notice) ───────────────────────────────────────────────────── */
#notice:not(:empty) {
  display: flex; align-items: flex-start; gap: var(--space-2, 8px);
  animation: qk-toast-in var(--duration-base, 220ms) var(--ease-standard, ease) both;
}
#notice:not(:empty)::before {
  content: ''; flex: none; width: 8px; height: 8px; margin-block-start: var(--space-1); border-radius: 50%;
  background: var(--lime-500, #C8E87A);
}
@keyframes qk-toast-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ── Tooltip (CSS-only, [data-tooltip]) ───────────────────────────────── */
[data-tooltip] { position: relative; }
[data-tooltip]::after {
  content: attr(data-tooltip);
  position: absolute; inset-block-end: calc(100% + 8px); inset-inline-start: 50%;
  transform: translateX(-50%) translateY(4px);
  background: var(--forest-900, #0B1F19); color: var(--ivory-50, #F5F1E7);
  font-size: 12px; line-height: 1.4; white-space: nowrap; padding: var(--space-2) var(--space-3); border-radius: 6px;
  opacity: 0; pointer-events: none; z-index: 30;
  transition: opacity var(--duration-fast, 120ms) ease, transform var(--duration-fast, 120ms) ease;
}
html[dir="rtl"] [data-tooltip]::after { transform: translateX(50%) translateY(4px); }
[data-tooltip]:hover::after, [data-tooltip]:focus-visible::after {
  opacity: 1; transform: translateX(-50%) translateY(0);
}
html[dir="rtl"] [data-tooltip]:hover::after, html[dir="rtl"] [data-tooltip]:focus-visible::after {
  transform: translateX(50%) translateY(0);
}

/* ── Status chips ──────────────────────────────────────────────────────── */
.chip {
  display: inline-flex; align-items: center; gap: var(--space-2);
  font-size: 12px; font-weight: 600; line-height: 1;
  padding: var(--space-1) var(--space-3); border-radius: var(--radius-pill, 999px);
  background: var(--forest-100, #DCE9E2); color: var(--forest-700, #163D32);
}
.chip::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }
.chip--busy { background: color-mix(in srgb, var(--color-warning) 18%, var(--color-surface)); color: var(--color-warning); }
.chip--busy::before { animation: qk-pulse 1.2s ease-in-out infinite; }
.chip--error { background: color-mix(in srgb, var(--color-danger) 18%, var(--color-surface)); color: var(--color-danger); }
.chip--ready { background: var(--color-highlight); color: var(--color-highlight-text); }
@keyframes qk-pulse { 0%,100%{opacity:1} 50%{opacity:.35} }
/* Opt-in per-state colouring once an owner adds data-status to #status (see TECH-PLAN) */
.badge[data-status="ready"] { background: var(--color-highlight); color: var(--color-highlight-text); }
/* UX 1.2: in the studio "ready" is a state, not the action -- neutral, so Export keeps the only lime. */
:is([data-surface="studio"], .app-shell) .badge[data-status="ready"] { background: var(--studio-raise-2); color: var(--studio-fg-2); }
.badge[data-status="error"] { background: color-mix(in srgb, var(--color-danger) 18%, var(--color-surface)); color: var(--color-danger); }
.badge[data-status="queued"],
.badge[data-status="transcribing"],
.badge[data-status="rendering"],
.badge[data-status="translating"],
.badge[data-status="enhancing"],
.badge[data-status="rewriting"],
.badge[data-status="clipping"] { background: color-mix(in srgb, var(--color-warning) 18%, var(--color-surface)); color: var(--color-warning); }

/* ── Skeleton loaders ──────────────────────────────────────────────────── */
.skeleton {
  position: relative; overflow: hidden; background: var(--color-border); border-radius: var(--radius-sm, 6px);
}
.skeleton::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, var(--studio-sheen-lit), transparent);
  animation: qk-shimmer 1.4s ease-in-out infinite;
}
.skeleton-text { height: 12px; margin-block: var(--space-2); width: 100%; }
.skeleton-text.w-60 { width: 60%; } .skeleton-text.w-40 { width: 40%; }
.skeleton-thumb { aspect-ratio: 16/9; width: 100%; }
@keyframes qk-shimmer { from { transform: translateX(-100%); } to { transform: translateX(100%); } }

/* ── Template gallery cards (C4/K4, live Arabic preview per template) ──── */
#caption-template-gallery.template-gallery {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(90px, 1fr)); gap: var(--space-3);
}
#caption-template-gallery .template-card {
  border: 1px solid var(--color-border); border-radius: var(--radius-md, 12px);
  background: var(--forest-900, #0B1F19); color: var(--ivory-50, #F5F1E7);
  cursor: pointer; overflow: hidden;
  transition: transform var(--duration-fast, 120ms) var(--ease-standard, ease),
              border-color var(--duration-fast, 120ms) var(--ease-standard, ease),
              box-shadow var(--duration-fast, 120ms) var(--ease-standard, ease);
}
#caption-template-gallery .template-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-raised, 0 8px 24px rgba(11,31,25,.1)); }
#caption-template-gallery .template-card:focus-visible { outline: 3px solid var(--color-focus, #2A6B58); outline-offset: 2px; }
#caption-template-gallery .template-card.selected { border-color: var(--lime-500, #C8E87A); box-shadow: 0 0 0 2px var(--lime-500, #C8E87A) inset; }
#caption-template-gallery .template-card-sample {
  display: flex; align-items: center; justify-content: center; text-align: center;
  min-height: 64px; padding: var(--space-3) var(--space-2); font-family: var(--font-arabic); font-size: 13px; line-height: 1.6;
  text-wrap: balance;   /* D4-24: a five-word Desi sample on a 115 px phone card breaks into even lines, never one orphaned word */
  background: linear-gradient(180deg, var(--studio-raise, #151D19), var(--studio-bg, #070A09));
}
#caption-template-gallery .template-card.selected .template-card-sample { background: linear-gradient(180deg, var(--studio-raise-2, #1F2A25), var(--studio-panel, #0D1311)); }
#caption-template-gallery .template-card-name {
  font-size: 12px; font-weight: 600; text-align: center; padding: var(--space-2) var(--space-2);
  background: var(--studio-scrim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#caption-template-gallery .template-card.selected::after {
  content: ''; display: none;
}

/* ── Bottom timeline (K5 — word blocks) ───────────────────────────────── */
#cue-strip button {
  transition: background var(--duration-fast, 120ms) ease;
}
#cue-strip button:hover { filter: brightness(1.08); }
#cue-strip button:focus-visible { outline: 2px solid var(--color-focus, #2A6B58); outline-offset: 1px; z-index: 1; }

/* ── Sidebar nav icon slots ────────────────────────────────────────────── */
/* Base .button/button already sets display:inline-flex + gap:8px (qawlak.css), so icons
   just need alignment/opacity, not extra margin. */
.sidebar button > .qk-icon,
.sidebar-nav a > .qk-icon,
.kebab-menu button > .qk-icon {
  opacity: .85;
}
.search-field { position: relative; display: flex; align-items: center; }
.search-field > .qk-icon {
  position: absolute; inset-inline-start: var(--space-3); opacity: .6; pointer-events: none;
}
.search-field > input { padding-inline-start: 32px; width: 100%; }

/* ── Safe-zone guide — default 6% margin; studio-preview-tools.js sets a real
   per-platform inset (from GET /api/presets) via inline style once a preset
   is picked in #preview-safezone. ─────────────────────────────────────────── */
.safe-zone-guide {
  position: absolute; inset: 6%; border: 1px dashed var(--studio-line-input);
  border-radius: 6px; pointer-events: none;
}
/* Res toggle + safe-zone/opacity picker for the preview live in studio-features.js
   (K8, appended into .playback-tools) — this pass started building the same thing
   independently and deleted it on discovering theirs already worked end to end
   against the same .safe-zone-guide element; no CSS needed here beyond the base
   .safe-zone-guide rule above, which their renderSafeZoneGuide() drives directly.
   .playback-tools itself needed a wrap fix once those 3 extra controls landed
   after the auto-margined Speed label (they were overflowing/overlapping). */
.playback-tools { flex-wrap: wrap; row-gap: var(--space-2); }
.playback-tools label { margin-inline-start: 0; } /* was auto; wrong once more controls follow it */
.playback-tools > button[aria-pressed] { border-radius: var(--radius-pill, 999px); padding: var(--space-2) var(--space-3); order: 4; }
.playback-tools > button[aria-pressed="true"] { background: var(--forest-700, #163D32); color: var(--ivory-50, #F5F1E7); }
.playback-tools > select[aria-label*="أمان"] { order: 5; }
.playback-tools > input[type="range"][aria-label*="شفافية"] { order: 6; width: 70px; }

/* ── Editor shell + far rail (U5) ────────────────────────────────────────
   .editor-shell wraps the existing 3-column .editor-grid without touching
   its own grid-template-columns / responsive rules in qawlak.css — the rail
   is a flex sibling, so qawlak.css's @media breakpoints for .editor-grid
   keep working untouched. */
/* workspace.js inserts #sample-note/#busy-note via $('.editor-grid').before(...),
   which lands them inside whatever .editor-grid's parent now is (.editor-shell) —
   force them onto their own full-width flex line so they still read as banners
   above the grid+rail row, not squeezed flex items beside it. */
.editor-shell { display: flex; flex-wrap: wrap; align-items: stretch; gap: var(--space-3); }
.editor-shell > .editor-grid { flex: 1; min-width: 0; }
.editor-shell > #sample-note, .editor-shell > #busy-note { flex-basis: 100%; }
.studio-rail {
  flex: none; width: 64px; display: flex; flex-direction: column; align-items: stretch; gap: 4px;
  background: var(--forest-900, #0B1F19); border-radius: var(--radius-md, 12px); padding: var(--space-3) var(--space-2);
}
.studio-rail-tabs { display: flex; flex-direction: column; gap: 4px; }
.studio-rail-btn {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  background: transparent; border: 0; border-radius: 8px; color: var(--studio-fg-2, #B7C6BF);
  padding: var(--space-3) var(--space-1); font-size: 12px; line-height: 1.3; min-height: 52px;
}
.studio-rail-btn:hover { background: var(--studio-fill-hover); color: var(--studio-fg, #fff); }
.studio-rail-btn[aria-selected="true"] { background: var(--studio-accent-soft); color: var(--studio-accent, #C8E87A); }
.studio-rail-btn[aria-disabled="true"] { opacity: .5; }
.studio-rail-btn small { font-size: 12px; opacity: .8; }
@media (max-width: 1200px) { .studio-rail { display: none; } } /* narrow layout: rail's jumps aren't worth the squeeze */

/* ── Dialog / sheet polish ─────────────────────────────────────────────── */
dialog { overscroll-behavior: contain; }
dialog[open] { animation: qk-dialog-in var(--duration-base, 220ms) var(--ease-standard, ease) both; }
@keyframes qk-dialog-in { from { opacity: 0; transform: translateY(10px) scale(.98); } to { opacity: 1; transform: none; } }

/* ── Meters ────────────────────────────────────────────────────────────── */
.qk-meter { display: grid; gap: 4px; }
.qk-meter-track { height: 6px; border-radius: var(--radius-pill, 999px); background: var(--color-border); overflow: hidden; }
.qk-meter-fill { height: 100%; background: var(--forest-700, #163D32); border-radius: inherit; }
.qk-meter-label { display: flex; justify-content: space-between; font-size: 12px; color: var(--color-text-muted); }

/* ── Real template thumbnails (U6) — replaces the 5 fake CSS caption
   mockups on the landing page with actual cropped frames from the export
   engine's own template-contact-sheet test output (tests/test_captions.py),
   compressed to static/img/. Real proof, not a drawing of the feature. ─── */
.caption-sample--real { padding: 0; overflow: hidden; background: var(--studio-stage, #000); }
.caption-sample--real img { width: 100%; height: 100%; aspect-ratio: 480/106; object-fit: cover; display: block; }

/* ── Kalakar-grade property groups (U10) ─────────────────────────────────
   prop-groups.js reorganizes the *existing* style-panel inputs (built by
   creator-tools.js/inspector-tabs.js) into these <details> — ids/names are
   untouched, only their DOM position + presentation change. */
.prop-group { border: 1px solid var(--color-border); border-radius: var(--radius-md, 12px); margin-block-end: var(--space-3); overflow: hidden; }
.prop-group summary {
  display: flex; align-items: center; gap: 8px; list-style: none; cursor: pointer;
  padding: var(--space-3) var(--space-3); font-size: 12px; font-weight: 600; background: var(--color-surface-raised);
  margin-bottom: 0;
}
.prop-group summary::-webkit-details-marker { display: none; }
.prop-group summary::marker { content: ''; }
.prop-group summary::before {
  content: ''; width: 8px; height: 8px; flex: none;
  border-inline-end: 2px solid var(--forest-700, #163D32); border-block-end: 2px solid var(--forest-700, #163D32);
  transform: rotate(-45deg); transition: transform var(--duration-fast, 120ms) ease;
}
.prop-group[open] summary::before { transform: rotate(45deg); }
@media (prefers-reduced-motion: reduce) { .prop-group summary::before { transition: none; } }
.prop-group-reset {
  margin-inline-start: auto; font-size: 12px; padding: var(--space-1) var(--space-2); border-radius: var(--radius-pill, 999px);
  background: transparent; border: 1px solid var(--color-border-strong); color: var(--color-text-muted);
}
.prop-group-body { padding: 12px; display: grid; gap: var(--space-3); }
.prop-group-body label { margin: 0; }
.slider-number-label { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 8px; }
.slider-number-label input[type="range"] { grid-column: 1/-1; }
.slider-number-label input[type="number"] { width: 70px; justify-self: end; }

/* ── Style tab grouped sections (K4/U2 — a lighter stand-in for Kalakar's
   Text·Templates·Transitions·Audio sub-tabs; those need a JS tab controller,
   see TECH-PLAN UX requests) ──────────────────────────────────────────── */
.style-section { margin-block-end: var(--space-5); }
.style-section:last-child { margin-block-end: 0; }
.style-section-title {
  display: flex; align-items: center; gap: 8px;
  font-size: 12px; font-weight: 600; color: var(--color-text-muted);
  text-transform: uppercase; letter-spacing: .03em;
  margin: 0 0 var(--space-3); padding-block-end: 8px; border-block-end: 1px solid var(--color-border);
}
html[lang="ar"] .style-section-title, [dir="rtl"] .style-section-title { letter-spacing: 0; text-transform: none; }
.style-section-title .qk-icon { color: var(--forest-600, #1E5243); }
.inspector-pane h3 { display: flex; align-items: center; gap: var(--space-3); }

/* ── Empty / loading / error states ───────────────────────────────────── */
.empty, .empty-projects { border: 1px dashed var(--color-border-strong); }
.busy-note .qk-icon, .sample-note .qk-icon { margin-inline-end: 8px; }
/* .quality is set by JS (word-editor.js/app.js) on a per-segment warning line; give it a
   pill treatment so it reads as a warning at a glance without needing a JS-added icon. */
.segment .quality:not(:empty) {
  display: inline-flex; align-items: center; gap: var(--space-2);   /* 12 Oct: no !important -- studio.css hides the pill on closed rows, the open row shows it */
  background: color-mix(in srgb, var(--color-warning) 18%, var(--color-surface)); padding: var(--space-1) var(--space-2); border-radius: var(--radius-pill, 999px);
}

/* ── Caption-tools row (per-segment split/merge/word-timing buttons) ───── */
.caption-tools button { border-radius: var(--radius-pill, 999px); }

/* ── Motion & focus ────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  #notice:not(:empty), dialog[open], .skeleton::after, .chip--busy::before { animation: none !important; }
}
:focus-visible { outline: 3px solid var(--color-focus, #2A6B58); outline-offset: 2px; }

/* ── Mobile studio layout (U8, <=560px only) ─────────────────────────────
   Bottom-sheet inspector: collapsed to a peeking handle bar, expands over
   the rest of the screen on tap (mobile-sheet.js toggles .sheet-open).
   Sticky transport: playback controls stay reachable while scrolling the
   (now much taller, single-column) preview/transcript stack. */
@media (max-width: 560px) {
  .sheet-handle {
    display: flex; align-items: center; justify-content: center; gap: 8px;
    width: 100%; border: 0; border-radius: 0; background: var(--color-surface, #fff);
    padding: var(--space-3); font-size: 12px; font-weight: 600;
  }
  .sheet-handle-bar { width: 36px; height: 4px; border-radius: 2px; background: var(--color-border-strong); flex: none; }
  .inspector {
    position: fixed; inset-inline: 0; bottom: 0; z-index: 25;
    max-height: 85vh; overflow: auto; overscroll-behavior: contain;
    border-radius: 16px 16px 0 0; box-shadow: var(--studio-float);
    transform: translateY(calc(100% - 46px));
    transition: transform var(--duration-base, 220ms) var(--ease-standard, ease);
  }
  .inspector.sheet-open { transform: translateY(0); }
  @media (prefers-reduced-motion: reduce) { .inspector { transition: none; } }
  /* qawlak.css's .editor-grid carries overflow:hidden (to clip its desktop
     rounded-card corners) — that disables position:sticky on any descendant,
     because the nearest ancestor with a non-visible overflow becomes the
     (non-scrolling) containing block for stickiness. Full-bleed the grid at
     this breakpoint instead (a common, reasonable mobile treatment anyway)
     so sticky actually has the viewport to stick within. */
  .editor-grid { overflow: visible; border-radius: 0; border-inline: 0; }
  /* Sticky within just .preview-column (its own containing block) is nearly
     invisible since that column is short — the real win is keeping the video
     + transport reachable while scrolling the much longer caption list below,
     so the whole column sticks, not just the controls row. */
  .preview-column { position: sticky; top: 0; z-index: 5; background: var(--color-bg, #F5F1E7); }
}

/* ── Hit targets on small screens (U4) ────────────────────────────────── */
@media (max-width: 560px) {
  button, .button, a.button, select, input[type="checkbox"] + label, .language-toggle {
    min-height: 44px;
  }
  #cue-strip button { min-height: 27px; } /* timeline blocks stay compact; not a tap target on touch scrub */
  .kebab > summary { width: 34px; height: 34px; }
}

/* ── State illustrations (U11) ─────────────────────────────────────────
   Inline SVG brand illustrations (speech-bubble Q motif, forest/lime) for
   empty/error states. Owners wire these in with two classes: `.state-illo`
   (base size/layout) + a modifier naming the state — no markup beyond that,
   see static/UI.md for the exact class list. ───────────────────────────── */
.state-illo { display: block; width: 96px; height: 96px; margin: 0 auto 12px; background-repeat: no-repeat; background-position: center; background-size: contain; opacity: 0.9; }
.state-illo--empty-projects { background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2096%2096%22%3E%3Crect%20x%3D%224%22%20y%3D%224%22%20width%3D%2288%22%20height%3D%2288%22%20rx%3D%2224%22%20fill%3D%22none%22%20stroke%3D%22%23163D32%22%20stroke-width%3D%224%22%2F%3E%3Cpath%20d%3D%22M62%2062%20L80%2080%20L66%2068%20Z%22%20fill%3D%22%23C8E87A%22%2F%3E%3Cline%20x1%3D%2234%22%20y1%3D%2248%22%20x2%3D%2262%22%20y2%3D%2248%22%20stroke%3D%22%23C8E87A%22%20stroke-width%3D%225%22%20stroke-linecap%3D%22round%22%2F%3E%3Cline%20x1%3D%2248%22%20y1%3D%2234%22%20x2%3D%2248%22%20y2%3D%2262%22%20stroke%3D%22%23C8E87A%22%20stroke-width%3D%225%22%20stroke-linecap%3D%22round%22%2F%3E%3C%2Fsvg%3E"); }
.state-illo--empty-queue { background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2096%2096%22%3E%3Crect%20x%3D%224%22%20y%3D%224%22%20width%3D%2288%22%20height%3D%2288%22%20rx%3D%2224%22%20fill%3D%22none%22%20stroke%3D%22%23163D32%22%20stroke-width%3D%224%22%2F%3E%3Cpath%20d%3D%22M62%2062%20L80%2080%20L66%2068%20Z%22%20fill%3D%22%23C8E87A%22%2F%3E%3Cline%20x1%3D%2230%22%20y1%3D%2240%22%20x2%3D%2266%22%20y2%3D%2240%22%20stroke%3D%22%23163D32%22%20stroke-width%3D%224%22%20stroke-linecap%3D%22round%22%20opacity%3D%220.55%22%2F%3E%3Cline%20x1%3D%2230%22%20y1%3D%2250%22%20x2%3D%2266%22%20y2%3D%2250%22%20stroke%3D%22%23163D32%22%20stroke-width%3D%224%22%20stroke-linecap%3D%22round%22%20opacity%3D%220.35%22%2F%3E%3Cline%20x1%3D%2230%22%20y1%3D%2260%22%20x2%3D%2252%22%20y2%3D%2260%22%20stroke%3D%22%23163D32%22%20stroke-width%3D%224%22%20stroke-linecap%3D%22round%22%20opacity%3D%220.2%22%2F%3E%3C%2Fsvg%3E"); }
.state-illo--no-captions { background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2096%2096%22%3E%3Crect%20x%3D%224%22%20y%3D%224%22%20width%3D%2288%22%20height%3D%2288%22%20rx%3D%2224%22%20fill%3D%22none%22%20stroke%3D%22%23163D32%22%20stroke-width%3D%224%22%2F%3E%3Cpath%20d%3D%22M62%2062%20L80%2080%20L66%2068%20Z%22%20fill%3D%22%23C8E87A%22%2F%3E%3Ccircle%20cx%3D%2236%22%20cy%3D%2250%22%20r%3D%223.5%22%20fill%3D%22%23C8E87A%22%2F%3E%3Ccircle%20cx%3D%2248%22%20cy%3D%2250%22%20r%3D%223.5%22%20fill%3D%22%23C8E87A%22%2F%3E%3Ccircle%20cx%3D%2260%22%20cy%3D%2250%22%20r%3D%223.5%22%20fill%3D%22%23C8E87A%22%2F%3E%3C%2Fsvg%3E"); }
.state-illo--upload-error { background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2096%2096%22%3E%3Crect%20x%3D%224%22%20y%3D%224%22%20width%3D%2288%22%20height%3D%2288%22%20rx%3D%2224%22%20fill%3D%22none%22%20stroke%3D%22%23163D32%22%20stroke-width%3D%224%22%2F%3E%3Cpath%20d%3D%22M62%2062%20L80%2080%20L66%2068%20Z%22%20fill%3D%22%23C8E87A%22%2F%3E%3Cpath%20d%3D%22M48%2060V36M38%2046l10-10%2010%2010%22%20fill%3D%22none%22%20stroke%3D%22%23C8E87A%22%20stroke-width%3D%225%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3Cline%20x1%3D%2230%22%20y1%3D%2266%22%20x2%3D%2266%22%20y2%3D%2266%22%20stroke%3D%22%23B3261E%22%20stroke-width%3D%224%22%20stroke-linecap%3D%22round%22%2F%3E%3C%2Fsvg%3E"); }
.state-illo--job-failed { background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2096%2096%22%3E%3Crect%20x%3D%224%22%20y%3D%224%22%20width%3D%2288%22%20height%3D%2288%22%20rx%3D%2224%22%20fill%3D%22none%22%20stroke%3D%22%23163D32%22%20stroke-width%3D%224%22%2F%3E%3Cpath%20d%3D%22M62%2062%20L80%2080%20L66%2068%20Z%22%20fill%3D%22%23C8E87A%22%2F%3E%3Cline%20x1%3D%2238%22%20y1%3D%2238%22%20x2%3D%2258%22%20y2%3D%2258%22%20stroke%3D%22%23B3261E%22%20stroke-width%3D%225%22%20stroke-linecap%3D%22round%22%2F%3E%3Cline%20x1%3D%2258%22%20y1%3D%2238%22%20x2%3D%2238%22%20y2%3D%2258%22%20stroke%3D%22%23B3261E%22%20stroke-width%3D%225%22%20stroke-linecap%3D%22round%22%2F%3E%3C%2Fsvg%3E"); }
.state-illo--review-expired { background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2096%2096%22%3E%3Crect%20x%3D%224%22%20y%3D%224%22%20width%3D%2288%22%20height%3D%2288%22%20rx%3D%2224%22%20fill%3D%22none%22%20stroke%3D%22%23163D32%22%20stroke-width%3D%224%22%2F%3E%3Cpath%20d%3D%22M62%2062%20L80%2080%20L66%2068%20Z%22%20fill%3D%22%23C8E87A%22%2F%3E%3Ccircle%20cx%3D%2246%22%20cy%3D%2248%22%20r%3D%2214%22%20fill%3D%22none%22%20stroke%3D%22%23163D32%22%20stroke-width%3D%224%22%2F%3E%3Cpath%20d%3D%22M46%2040v8l6%204%22%20fill%3D%22none%22%20stroke%3D%22%23163D32%22%20stroke-width%3D%224%22%20stroke-linecap%3D%22round%22%2F%3E%3Cline%20x1%3D%2234%22%20y1%3D%2262%22%20x2%3D%2260%22%20y2%3D%2236%22%20stroke%3D%22%23B3261E%22%20stroke-width%3D%224%22%20stroke-linecap%3D%22round%22%2F%3E%3C%2Fsvg%3E"); }
.state-illo--404 { background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2096%2096%22%3E%3Crect%20x%3D%224%22%20y%3D%224%22%20width%3D%2288%22%20height%3D%2288%22%20rx%3D%2224%22%20fill%3D%22none%22%20stroke%3D%22%23163D32%22%20stroke-width%3D%224%22%2F%3E%3Cpath%20d%3D%22M62%2062%20L80%2080%20L66%2068%20Z%22%20fill%3D%22%23C8E87A%22%2F%3E%3Ctext%20x%3D%2248%22%20y%3D%2260%22%20font-family%3D%22Arial%2Csans-serif%22%20font-size%3D%2240%22%20font-weight%3D%22700%22%20fill%3D%22%23163D32%22%20text-anchor%3D%22middle%22%3E%D8%9F%3C%2Ftext%3E%3C%2Fsvg%3E"); }
@media (prefers-reduced-motion: no-preference) { .state-illo { animation: qk-illo-in .3s ease both; } }
@keyframes qk-illo-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 0.9; transform: none; } }
/* ══════════════════════════════════════════════════════════════════════════
   ANIMATED CONTROL LAYER (visual-direction pass, 10 Sep)

   Every control in the studio, with real states. No library: see
   static/vendor/LICENSES.md for the eight that were evaluated and what native
   feature replaced each. Everything here is transform/opacity plus one
   registered custom property, so it composites at 60fps.

   Budget, enforced by the tokens: press 90ms, state change 160ms, anything
   that moves across the screen 240ms. Nothing on a control is longer.

   The one structural device: KASHIDA. In Arabic script a kashida is the
   stroke that elongates a joined letter to justify a line. Here the active
   state of anything -- rail item, tab, selected row -- is drawn as that stroke
   growing along the inline-start edge, and a section rule opens with a short
   accent segment the way a kashida opens a word. It is the only decorative
   idea in this layer; everything else stays quiet so it reads.
   ══════════════════════════════════════════════════════════════════════════ */

@property --qk-angle {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}

/* ── 1. The hero control ──────────────────────────────────────────────────
   One button, four states, driven entirely by [data-state]. The loading state
   is part of the button -- there is no separate spinner to look at while the
   thing you pressed sits there dead.

     <button class="qk-cta" data-state="idle">توليد التفريغ</button>
     data-state: idle -> loading -> done   (JS sets the attribute, nothing else)
*/
.qk-cta {
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--studio-gap, 8px);
  min-height: 40px;
  padding-inline: var(--space-5);
  border: 0;
  border-radius: var(--radius-pill, 999px);
  background: var(--studio-accent, #C8E87A);
  color: var(--studio-accent-ink, #0B1F19);
  font: 700 var(--studio-text-body, 14px)/1 var(--studio-font, inherit);
  cursor: pointer;
  overflow: hidden;
  transition:
    transform var(--studio-dur-press, 90ms) var(--studio-ease-out, ease),
    box-shadow var(--studio-dur-state, 160ms) var(--studio-ease-out, ease),
    background var(--studio-dur-state, 160ms) var(--studio-ease-out, ease);
  box-shadow: 0 0 0 0 var(--studio-accent-ring, rgba(200, 232, 122, .25));
}

/* Rest reads as lit, not flat: a 1px inner top highlight, the way a physical
   key catches light. Costs one pseudo-element and no paint on hover. */
.qk-cta::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: inset 0 1px 0 var(--studio-sheen);
  pointer-events: none;
  z-index: 2;
}

/* Hover: light crosses the face. A translating pseudo-element, so it is one
   composited layer -- not a background-position animation, which repaints. */
.qk-cta::after {
  content: '';
  position: absolute;
  inset-block: 0;
  inset-inline-start: -60%;
  width: 55%;
  z-index: 1;
  background: linear-gradient(100deg, transparent, var(--studio-sheen-lit), transparent);
  transform: translateX(0);
  opacity: 0;
  pointer-events: none;
}
.qk-cta:hover {
  transform: translateY(-1px);
  box-shadow: 0 0 0 4px var(--studio-accent-ring, rgba(200, 232, 122, .25));
}
.qk-cta:hover::after {
  opacity: 1;
  animation: qk-shine 240ms var(--studio-ease-out, ease) 1;
}
.qk-cta:active { transform: scale(.97) translateY(0); }
.qk-cta:focus-visible {
  outline: 2px solid var(--studio-accent, #C8E87A);
  outline-offset: 3px;
}
/* D2-12: disabled is an outline in the muted text colour (>= 4.5:1 on either surface),
   not 45% lime -- which read at 1.1:1 on the ivory review page. */
.qk-cta[disabled] {
  background: transparent;
  color: var(--color-text-muted, #4E5C56);
  box-shadow: inset 0 0 0 1.5px currentColor;
  opacity: 1;   /* the generic button:disabled fade would take the outline back down to a ghost */
  cursor: not-allowed;
  transform: none;
}
.qk-cta[disabled]::before, .qk-cta[disabled]::after { display: none; }

@keyframes qk-shine { from { transform: translateX(0); } to { transform: translateX(320%); } }

/* Loading: the button becomes the progress indicator. A conic gradient rotates
   in the border via a registered custom property -- native @property is what
   makes an <angle> animatable at all -- and the label dims rather than
   disappearing, so the button keeps saying what it is doing. */
.qk-cta[data-state="loading"] {
  background: color-mix(in srgb, var(--studio-accent, #C8E87A) 22%, var(--studio-raise, #151D19));
  color: var(--studio-fg, #F5F1E7);
  cursor: progress;
  transform: none;
}
.qk-cta[data-state="loading"]::after {
  opacity: 1;
  inset: 0;
  width: auto;
  background: conic-gradient(from var(--qk-angle),
    transparent 0deg 250deg,
    var(--studio-accent, #C8E87A) 310deg,
    transparent 360deg);
  -webkit-mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
  mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
  padding: 1.5px;
  border-radius: inherit;
  animation: qk-orbit 1.1s linear infinite;
}
@keyframes qk-orbit { to { --qk-angle: 360deg; } }

/* Done: one settle. Fill returns, a check scales in, and it is over in 400ms.
   No confetti, no toast that repeats what the button already said. */
.qk-cta[data-state="done"] {
  background: var(--studio-accent, #C8E87A);
  color: var(--studio-accent-ink, #0B1F19);
  animation: qk-settle 240ms var(--studio-ease-out, ease) 1;
}
.qk-cta[data-state="done"] .qk-icon { animation: qk-check-in 200ms var(--studio-ease, ease) 1 both; }
/* UX 1.2 -- the quiet CTA: same four states, no lime at rest. Lime is scarce (DESIGN:
   "if you reach for it twice on one screen, one of them is wrong"), so the editor's
   secondary steps (generate, translate, save) sit on the raise-2 rung with an alpha
   hairline and only the loading arc stays lime -- that is progress, not decoration.
   fg on raise-2: 14.1:1. */
.qk-cta--quiet,
.qk-cta--quiet[data-state="done"] {
  background: var(--studio-raise-2, #1F2A25);
  color: var(--studio-fg, #F5F1E7);
  box-shadow: inset 0 0 0 1px var(--studio-line-input, rgba(245,241,231,.14));
}
.qk-cta--quiet:hover { box-shadow: inset 0 0 0 1px var(--studio-line-input, rgba(245,241,231,.14)), 0 0 0 3px var(--studio-line-strong, rgba(245,241,231,.10)); }
.qk-cta--quiet::before { box-shadow: none; }
.qk-cta--quiet[disabled] { background: transparent; color: var(--color-text-muted, #4E5C56); box-shadow: inset 0 0 0 1.5px currentColor; }
@keyframes qk-settle {
  0%   { box-shadow: 0 0 0 0 var(--studio-accent-ring, rgba(200, 232, 122, .25)); }
  45%  { box-shadow: 0 0 0 10px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}
@keyframes qk-check-in {
  from { transform: scale(.4) rotate(-12deg); opacity: 0; }
  to   { transform: none; opacity: 1; }
}

/* ── ONE BUTTON SYSTEM ─────────────────────────────────────────────────────
   qawlak.css used to carry a second, parallel button implementation: its own
   box model, skin, hover, primary variant and disabled state. It is folded in
   here, declarations unchanged, with the legacy names joined to this layer's
   selector lists. `button` and `.button` are now ALIASES of the layer, not a
   system beside it — there is one implementation to retune, and the public
   pages keep the exact pixels they had (proved by scripts/button_parity.py).

   The brand pill stays the DEFAULT skin for a bare <button>, which is what
   makes this a zero-diff fold: roughly seventy contextual rules across the app
   set only size and padding on top of it and keep working untouched. `.qk-btn`
   sits later in this file and overrides the skin for the compact editor look,
   exactly as it did when the base lived in qawlak.css.

   No `--brand` modifier was needed: the brand treatment survives the fold as
   the default, so a modifier would have been a name for the status quo. */

button, .button, .qk-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  cursor: pointer; text-decoration: none; font-family: inherit;
}
button, .button {display:inline-flex;align-items:center;justify-content:center;gap:8px;border:1px solid var(--color-border);border-radius:999px;padding:var(--space-2) var(--space-4);background:var(--color-surface);color:var(--color-action);font-size:12px;font-weight:600;line-height:1.6;text-decoration:none;transition:background .15s}
button:hover, .button:hover {background:var(--color-surface-raised);text-decoration:none}
button:disabled, .button:disabled, .button[aria-disabled="true"] {opacity:.45;cursor:not-allowed}

/* Filled variant — what `.primary` meant, on the same tokens it used. Reading
   --studio-accent here instead looked equivalent and was not: it repainted the
   landing's active preset-pack chip from forest to lime, which the parity check
   caught. A fold is allowed to change nothing, so the alias reads --color-action.
   (That chip being forest-on-forest is a real legibility bug -- filed, not
   smuggled in here.) */
button.primary, .button.primary, .qk-btn--primary {
  background: var(--color-action); border-color: var(--color-action);
  color: var(--color-action-text);
}
button.primary:hover, .button.primary:hover, .qk-btn--primary:hover {background:var(--color-action-hover)}

/* Accent-filled variant — the lime nav button. Was `.site-nav .button`, a skin
   defined outside the layer; it is a named variant now and the nav aliases it. */
.qk-btn--accent {background:var(--color-highlight);color:var(--color-highlight-text);border-color:var(--color-highlight)}
/* Loop 12, round 6: the nav button is the quiet outline pill, so the page's own action (the landing's upload CTA,
   pricing's plan button) is the one lime thing on screen, as in the studio. Nav pills never wrap; on a phone the
   Theme pill shows its icon only (its aria-label names it), so logo + Theme + language + studio fit 390 px. */
.site-nav .button {background:transparent;color:var(--ivory-50);border-color:var(--studio-line-input)}
.site-nav .button:hover {background:rgba(245,241,231,.08)}
.nav-actions > * {white-space:nowrap}
@media (max-width:560px) {
  .nav-inner {padding-inline:16px; gap:var(--space-3)}
  .site-nav [data-theme-toggle] [data-theme-label] {display:none}
  .site-nav [data-theme-toggle] {min-width:44px; min-height:44px; justify-content:center; padding-inline:var(--space-3)}
}

/* A pressed pill in a group is the filled variant, not a third look. */
.language-pills button[aria-pressed="true"] {
  background: var(--color-action); border-color: var(--color-action);
  color: var(--color-action-text);
}

/* ── 2. The rest of the button family ────────────────────────────────────
   Deliberately quieter than the hero. Only one control on a screen gets light
   crossing it. */
.qk-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--studio-gap-sm, 4px);
  min-height: 28px;
  padding-inline: 12px;
  border: 1px solid var(--studio-line-input, rgba(245, 241, 231, .14));
  border-radius: var(--studio-radius, 6px);
  background: transparent;
  color: var(--studio-fg, #F5F1E7);
  font: 500 var(--studio-text-value, 13px)/1 var(--studio-font, inherit);
  cursor: pointer;
  transition:
    background var(--studio-dur-state, 160ms) var(--studio-ease-out, ease),
    border-color var(--studio-dur-state, 160ms) var(--studio-ease-out, ease),
    transform var(--studio-dur-press, 90ms) var(--studio-ease-out, ease);
}
.qk-btn:hover { background: var(--studio-raise-2, #1F2A25); border-color: var(--studio-line-strong); }
.qk-btn:active { transform: scale(.97); }
.qk-btn--ghost { border-color: transparent; color: var(--studio-fg-2, rgba(245, 241, 231, .72)); }
.qk-btn--ghost:hover { color: var(--studio-fg, #F5F1E7); }

/* Destructive feels different on purpose: it does not lift, it recoils. */
.qk-btn--danger { color: var(--studio-danger, #FF7A6B); border-color: color-mix(in srgb, var(--studio-danger, #FF7A6B) 35%, transparent); }
.qk-btn--danger:hover { background: color-mix(in srgb, var(--studio-danger, #FF7A6B) 14%, transparent); }
.qk-btn--danger:active { transform: scale(.94); }

/* Icon buttons: rail and toolbar. Toggled-on must never look like hover. */
.qk-iconbtn {
  display: inline-grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border: 0;
  border-radius: var(--studio-radius, 6px);
  background: transparent;
  color: var(--studio-fg-2, rgba(245, 241, 231, .72));
  cursor: pointer;
  transition:
    background var(--studio-dur-state, 160ms) var(--studio-ease-out, ease),
    color var(--studio-dur-state, 160ms) var(--studio-ease-out, ease),
    transform var(--studio-dur-press, 90ms) var(--studio-ease-out, ease);
}
.qk-iconbtn:hover { background: var(--studio-fill-hover); color: var(--studio-fg, #F5F1E7); }
.qk-iconbtn:active { transform: scale(.92); }
.qk-iconbtn[aria-pressed="true"],
.qk-iconbtn[aria-current="true"] {
  background: var(--studio-accent-soft, rgba(200, 232, 122, .18));
  color: var(--studio-accent, #C8E87A);
}
/* Direction-of-travel icons mirror in RTL. Media symbols never do. */
/* UX 4.3: directional icons (back/next, chevrons, arrows, log-out, undo/redo) mirror in RTL wherever they sit;
   media, time, upload/download, search and check never carry the class. */
[dir="rtl"] .qk-icon--mirror { transform: scaleX(-1); }

/* ── Four more variants, lifted from what six screens already do ───────────
   Each carries SKIN AND STATE ONLY. Every screen keeps its own geometry rules
   (padding, size, flex, font-size), which is exactly what the BUTTON audit rule
   permits -- so adopting one of these is a class swap plus a deletion, never a
   redesign. Values come from the rules they replace, not from fresh taste. */

/* 1. List row — sidebar entries, kebab menus. Start-aligned because that is what
   makes it a row rather than a button. */
.qk-btn--list {
  justify-content: start;
  border: 0;
  background: transparent;
  color: var(--studio-fg-2);
  border-radius: var(--studio-radius);
}
.qk-btn--list:hover { background: var(--studio-fill-hover); color: var(--studio-fg); }
.qk-btn--list.active,
.qk-btn--list[aria-current="true"] {
  background: var(--studio-accent-soft);
  color: var(--studio-accent);
}

/* 2. Underline tab — inspector tabs, settings sub-tabs. The pill-shaped tab group
   (.qawlak-style-tabs) is NOT a third shape: it is .qk-btn with .qk-btn--primary
   on the selected one, so it needs no variant of its own. */
.qk-tab {
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  color: var(--studio-fg-2);
}
.qk-tab:hover { color: var(--studio-fg); background: transparent; }
.qk-tab[aria-selected="true"] {
  color: var(--studio-accent);
  border-bottom-color: var(--studio-accent);
  font-weight: 600;
}

/* 3. Solid danger — a destructive action that IS the point of its row, as opposed
   to .qk-btn--danger, which is destructive but secondary.
   The hover mixes toward --studio-fg rather than toward black, because black was
   measurably wrong: shell.css's `color-mix(danger 78%, black)` puts dark ink on
   #C75F53 at 4.24:1 on the studio surface, which fails AA. Mixing toward the
   foreground darkens on the ivory ground and lightens on the near-black one, and
   clears 6.9:1 on both. */
.qk-btn--danger-solid {
  background: var(--studio-danger);
  border-color: var(--studio-danger);
  color: var(--studio-danger-ink);
}
.qk-btn--danger-solid:hover {
  background: color-mix(in srgb, var(--studio-danger) 85%, var(--studio-fg));
  color: var(--studio-danger-ink);
}

/* 4a. Chip — timeline word blocks, speed and mode toggles. Two states with two
   meanings: [aria-pressed] is a toggle and gets the soft treatment the layer
   already uses for a toggled-on icon button; .current means "the one playing"
   and gets the solid accent, which is what the timeline needs to be scannable. */
.qk-btn--chip {
  border-radius: var(--studio-radius-input);
  background: var(--studio-raise-2);
  border-color: var(--studio-line-input);
  color: var(--studio-fg);
}
.qk-btn--chip[aria-pressed="true"] {
  background: var(--studio-accent-soft);
  color: var(--studio-accent);
}
.qk-btn--chip.current {
  background: var(--studio-accent);
  border-color: var(--studio-accent);
  color: var(--studio-accent-ink);
}

/* 4b. Overlay icon button — sits on a poster or a video frame, so it carries its
   own scrim instead of relying on whatever is behind it. Size stays with the
   screen; only the skin is here. */
.qk-iconbtn--overlay {
  background: var(--studio-scrim-strong);
  /* NOT --studio-fg: this button carries its own dark scrim, so on the ivory
     surface --studio-fg is ink and the icon disappears (measured 1.69:1). The
     scrim is fixed, so the ink on it is fixed too. */
  color: var(--ivory-50);
  border-radius: var(--studio-radius);
}
.qk-iconbtn--overlay:hover { background: var(--studio-accent); color: var(--studio-accent-ink); }
.qk-iconbtn--overlay.qk-btn--danger { border-color: transparent; }
.qk-iconbtn--overlay.qk-btn--danger:hover {
  background: var(--studio-danger);
  color: var(--studio-danger-ink);
}

/* Kashida: the active rail item grows its stroke from nothing. */
.qk-rail-item { position: relative; }
.qk-rail-item::before {
  content: '';
  position: absolute;
  inset-inline-start: 0;
  top: 50%;
  width: 2px;
  height: 20px;
  border-radius: 0 2px 2px 0;
  background: var(--studio-accent, #C8E87A);
  transform: translateY(-50%) scaleY(0);
  transition: transform var(--studio-dur-move, 240ms) var(--studio-ease, ease);
}
[dir="rtl"] .qk-rail-item::before { border-radius: 2px 0 0 2px; }
.qk-rail-item[aria-current="true"]::before { transform: translateY(-50%) scaleY(1); }

/* A section rule that opens with a kashida segment rather than running edge to
   edge. Two backgrounds, no extra element. */
.qk-rule {
  height: 1px;
  border: 0;
  margin: var(--studio-pad, 12px) 0 var(--studio-gap, 8px);
  background:
    linear-gradient(to right, var(--studio-accent, #C8E87A) 0 20px, transparent 20px) 0 0 / 100% 100% no-repeat,
    var(--studio-line, rgba(245, 241, 231, .06));
}
[dir="rtl"] .qk-rule {
  background:
    linear-gradient(to left, var(--studio-accent, #C8E87A) 0 20px, transparent 20px) 0 0 / 100% 100% no-repeat,
    var(--studio-line, rgba(245, 241, 231, .06));
}

/* ── 3. Toggle chips with a sliding indicator ────────────────────────────
   Dialect, language, word/line, safe zone. Pure CSS: the thumb is one absolutely
   positioned element and :has() reads which radio is checked, so there is no
   JS and no per-item background transition to flicker.

     <div class="qk-seg" style="--qk-n:4">
       <span class="qk-seg-thumb"></span>
       <label><input type="radio" name="dialect" checked><span>خليجي</span></label>
       ... up to six
     </div>

   The concentric-radius rule matters here: container 7px minus 2px padding
   gives items 5px. Getting that wrong is the tell of a hand-rolled control. */
.qk-seg {
  --qk-n: 3;
  --qk-i: 0;
  --qk-dir: 1;
  position: relative;
  display: inline-flex;
  padding: 2px;
  border-radius: 7px;
  background: var(--studio-fill);
  height: var(--studio-row, 28px);
  box-sizing: border-box;
}
.qk-seg:dir(rtl) { --qk-dir: -1; }   /* D2-10: computed direction -- the timeline is ltr inside an rtl page */
.qk-seg-thumb {
  position: absolute;
  inset-block: 2px;
  inset-inline-start: 2px;
  width: calc((100% - 4px) / var(--qk-n));
  border-radius: 5px;
  background: var(--studio-line-strong);
  box-shadow: inset 0 0 0 1px var(--studio-fill-hover);
  transform: translateX(calc(var(--qk-i) * var(--qk-dir) * 100%));
  transition: transform var(--studio-dur-move, 240ms) var(--studio-ease, ease);
  pointer-events: none;
}
.qk-seg:has(label:nth-of-type(1) input:checked) { --qk-i: 0; }
.qk-seg:has(label:nth-of-type(2) input:checked) { --qk-i: 1; }
.qk-seg:has(label:nth-of-type(3) input:checked) { --qk-i: 2; }
.qk-seg:has(label:nth-of-type(4) input:checked) { --qk-i: 3; }
.qk-seg:has(label:nth-of-type(5) input:checked) { --qk-i: 4; }
.qk-seg:has(label:nth-of-type(6) input:checked) { --qk-i: 5; }
.qk-seg label {
  position: relative;
  z-index: 1;
  flex: 1 1 0;
  display: grid;
  place-items: center;
  padding-inline: var(--space-3);
  border-radius: 5px;
  color: var(--studio-fg-3, rgba(245, 241, 231, .58));
  font: 500 var(--studio-text-label, 12px)/1 var(--studio-font, inherit);
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--studio-dur-state, 160ms) var(--studio-ease-out, ease), transform var(--studio-dur-press, 90ms) var(--studio-ease-out, ease);
}
.qk-seg label:active { transform: scale(.97); }   /* Loop 13: a chip answers the press like every button (press = 90ms, .97) */
.qk-seg label:hover { color: var(--studio-fg-2, rgba(245, 241, 231, .72)); }
.qk-seg label:has(input:checked) { color: var(--studio-fg, #F5F1E7); }
.qk-seg input { position: absolute; opacity: 0; inset: 0; margin: 0; cursor: pointer; }
.qk-seg label:has(input:focus-visible) { outline: 2px solid var(--studio-accent, #C8E87A); outline-offset: 1px; }

/* ── 4. Template gallery cards ───────────────────────────────────────────
   The card lifts, a beam runs round its border, and the sample line performs
   the preset's own motion -- so the creator sees what they are choosing before
   they choose it. Nothing here plays unless the pointer is on the card, so a
   gallery of 26 presets costs nothing at rest.

   Cards are 4:3, not 16:9: two lines of Arabic at a legible size need the
   vertical room, and the sample text is real Arabic, never "Aa". */
#caption-template-gallery .template-card {
  position: relative;
  isolation: isolate;
  transition:
    transform var(--studio-dur-state, 160ms) var(--studio-ease-out, ease),
    box-shadow var(--studio-dur-state, 160ms) var(--studio-ease-out, ease);
}
#caption-template-gallery .template-card:hover { transform: translateY(-1px); }
#caption-template-gallery .template-card:hover .template-card-sample {
  box-shadow: inset 0 0 0 1px var(--studio-line-input);
}
/* Selection is a ring, never a fill -- a filled card destroys the preview. */
#caption-template-gallery .template-card.selected .template-card-sample {
  box-shadow: var(--studio-select, inset 0 0 0 1.5px #C8E87A);
}
/* Border beam. Same @property angle trick as the CTA, only on hover.
   D2-26: masked to a 1px ring so nothing leaks through the card's transparent
   corners, and only where hover exists -- a tap left it stuck on the phone. */
@media (hover: hover) {
  #caption-template-gallery .template-card:hover::before {
    content: '';
    position: absolute;
    inset: -1px;
    z-index: -1;
    border-radius: inherit;
    padding: 1px;
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask-composite: exclude;
    background: conic-gradient(from var(--qk-angle),
      transparent 0deg 300deg,
      var(--studio-accent, #C8E87A) 350deg,
      transparent 360deg);
    animation: qk-orbit 1.6s linear infinite;
  }
}

/* The sample line performs the preset's entrance, looping with a hold. One
   keyframe per ASS transition, reusing caption-preview.css's qc-* names via a
   data-anim hook on the card; without the hook a card still gets the generic
   pop so the gallery is never static. */
@media (prefers-reduced-motion: no-preference) {
  #caption-template-gallery .template-card:hover .caption-line {
    animation: qk-sample-pop 1.6s var(--studio-ease-ass, linear) infinite;
  }
  #caption-template-gallery .template-card[data-anim="fade"]:hover  .caption-line { animation-name: qk-sample-fade; }
  #caption-template-gallery .template-card[data-anim="zoom"]:hover  .caption-line { animation-name: qk-sample-zoom; }
  #caption-template-gallery .template-card[data-anim="scale"]:hover .caption-line { animation-name: qk-sample-scale; }
  #caption-template-gallery .template-card[data-anim="hook"]:hover  .caption-line { animation-name: qk-sample-hook; }
  #caption-template-gallery .template-card[data-anim="rise"]:hover  .caption-line { animation-name: qk-sample-rise; }
  #caption-template-gallery .template-card[data-anim="none"]:hover  .caption-line { animation: none; }

  /* The word that lights up is what actually sells a caption preset, so the
     highlight walks the line on hover: five staggered steps off one keyframe. */
  #caption-template-gallery .template-card:hover .caption-line > span {
    animation: qk-word-walk 1.6s var(--studio-ease-ass, linear) infinite;
  }
  #caption-template-gallery .template-card:hover .caption-line > span:nth-child(1) { animation-delay: 0ms; }
  #caption-template-gallery .template-card:hover .caption-line > span:nth-child(2) { animation-delay: 260ms; }
  #caption-template-gallery .template-card:hover .caption-line > span:nth-child(3) { animation-delay: 520ms; }
  #caption-template-gallery .template-card:hover .caption-line > span:nth-child(4) { animation-delay: 780ms; }
  #caption-template-gallery .template-card:hover .caption-line > span:nth-child(5) { animation-delay: 1040ms; }
}
/* 15% entrance, 85% hold — the hold is what makes it read as a caption cue
   rather than a throbbing card. */
@keyframes qk-sample-pop   { 0% { transform: scale(.55); } 15%, 100% { transform: none; } }
@keyframes qk-sample-fade  { 0% { opacity: 0; }           15%, 100% { opacity: 1; } }
@keyframes qk-sample-zoom  { 0% { transform: scale(1.7); } 15%, 100% { transform: none; } }
@keyframes qk-sample-scale { 0% { transform: scale(0); }   15%, 100% { transform: none; } }
@keyframes qk-sample-hook  { 0% { transform: scale(1.35) rotate(-10deg); } 15%, 100% { transform: none; } }
@keyframes qk-sample-rise  { 0% { transform: translateY(6%); opacity: 0; } 15%, 100% { transform: none; opacity: 1; } }
@keyframes qk-word-walk {
  0%, 14%  { color: var(--qk-word-hi, #C8E87A); }
  15%, 100% { color: inherit; }
}

/* ── 5. Inputs, selects, sliders ─────────────────────────────────────────
   Borderless until hover: twenty visible field borders in an inspector make a
   form, not a tool. The focus ring GROWS rather than appearing, which is the
   difference between "the field responded" and "something flashed". */
.qk-field {
  height: 24px;
  padding-inline: var(--space-2);
  border: 1px solid transparent;
  border-radius: var(--studio-radius-input, 4px);
  background: var(--studio-fill);
  color: var(--studio-fg, #F5F1E7);
  font: 400 var(--studio-text-value, 13px)/1 var(--studio-font, inherit);
  font-variant-numeric: tabular-nums;
  box-shadow: 0 0 0 0 var(--studio-accent-ring, rgba(200, 232, 122, .25));
  transition:
    background var(--studio-dur-state, 160ms) var(--studio-ease-out, ease),
    border-color var(--studio-dur-state, 160ms) var(--studio-ease-out, ease),
    box-shadow var(--studio-dur-state, 160ms) var(--studio-ease-out, ease);
}
.qk-field:hover { background: var(--studio-fill-hover); }
.qk-field:focus {
  outline: 0;
  background: var(--studio-fill-sunk);
  border-color: var(--studio-accent, #C8E87A);
  box-shadow: 0 0 0 2px var(--studio-accent-ring, rgba(200, 232, 122, .25));
}
/* A value changed by something other than the person typing flashes once, so
   an external edit is never silent. JS adds .flashed and removes it on
   animationend. */
.qk-field.flashed { animation: qk-flash 240ms var(--studio-ease-out, ease) 1; }
@keyframes qk-flash {
  0%   { background: var(--studio-accent-soft, rgba(200, 232, 122, .18)); color: var(--studio-accent, #C8E87A); }
  100% { background: var(--studio-fill); }
}

/* Track-only slider: a 20px thumb blows a 28px row, so the thumb appears on
   row hover and the fill carries the value the rest of the time. */
.qk-slider {
  -webkit-appearance: none;
  appearance: none;
  height: 4px;
  border-radius: 2px;
  background: linear-gradient(to var(--qk-fill-dir, right),
    var(--studio-accent, #C8E87A) 0 var(--qk-fill, 50%),
    var(--studio-fill-hover) var(--qk-fill, 50%) 100%);
  cursor: pointer;
}
[dir="rtl"] .qk-slider { --qk-fill-dir: left; }
.qk-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 10px; height: 10px;
  border-radius: 50%;
  background: var(--studio-fg, #F5F1E7);
  opacity: 0;
  transition: opacity var(--studio-dur-state, 160ms) var(--studio-ease-out, ease),
              transform var(--studio-dur-press, 90ms) var(--studio-ease-out, ease);
}
.qk-slider::-moz-range-thumb {
  width: 10px; height: 10px; border: 0;
  border-radius: 50%;
  background: var(--studio-fg, #F5F1E7);
  opacity: 0;
  transition: opacity var(--studio-dur-state, 160ms) var(--studio-ease-out, ease);
}
.qk-row:hover .qk-slider::-webkit-slider-thumb,
.qk-slider:hover::-webkit-slider-thumb,
.qk-slider:focus-visible::-webkit-slider-thumb { opacity: 1; }
.qk-row:hover .qk-slider::-moz-range-thumb,
.qk-slider:hover::-moz-range-thumb { opacity: 1; }
.qk-slider:active::-webkit-slider-thumb { transform: scale(1.25); }

/* ── 6. Progress and loading, one family ─────────────────────────────────
   Upload bar, job progress and skeletons all run the same sweep at the same
   speed, so a screen with three of them going reads as one system. */
.qk-progress {
  --qk-dir: 1;
  position: relative;
  height: 4px;
  border-radius: 2px;
  background: var(--studio-fill-hover);
  overflow: hidden;
}
.qk-progress > i {
  display: block;
  height: 100%;
  width: 100%;
  border-radius: inherit;
  background: var(--studio-accent, #C8E87A);
  transform: translateX(calc(var(--qk-dir, 1) * -1 * (100% - var(--qk-value, 0%))));
  transition: transform var(--studio-dur-move, 240ms) var(--studio-ease-out, ease);
}
/* Indeterminate: same bar, no value. */
.qk-progress[data-state="indeterminate"] > i {
  width: 35%;
  transform: none;
  animation: qk-sweep 1.15s var(--studio-ease, ease) infinite;
}
[dir="rtl"] .qk-progress { --qk-dir: -1; }
@keyframes qk-sweep {
  0%   { transform: translateX(-120%); }
  100% { transform: translateX(320%); }
}
.qk-skeleton {
  border-radius: var(--studio-radius-input, 4px);
  background:
    linear-gradient(100deg, transparent 20%, var(--studio-fill-hover) 50%, transparent 80%) 0 0 / 220% 100%,
    var(--studio-fill);
  animation: qk-shimmer 1.15s linear infinite;
}
@keyframes qk-shimmer { to { background-position: -220% 0; } }

/* ── 7. Moments ──────────────────────────────────────────────────────────
   Transcription finished, export ready, upload done. Motion A «الكلمة» (13 Oct 2026,
   research/design-directions/motion): the 8-point girih star is built edge by edge
   (8 x scaleX, state cue, 60 ms apart), the word «جاهز» lands in it (move cue,
   200 ms), the tick draws as two strokes (state cue, 600 / 760 ms) -- 920 ms, transform
   and opacity only. The base styles ARE the final frame; the animations only exist
   under no-preference, so reduced motion shows the finished seal at once. Sizes are
   in cqmin, so one rule serves the 26 px studio badge and a whole project poster.
   Showing the element (hidden -> shown, or a fresh node) replays it.

     <span class="qk-moment" data-tone="gold"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><span></span><b></b><b></b></span>
*/
/* :where() = zero specificity, so a host page can place and size the seal (workspace.css .poster-seal covers the poster). */
:where(.qk-moment) { position: relative; display: inline-block; width: 64px; height: 64px; }
.qk-moment { container-type: size; color: var(--studio-accent, #C8E87A); pointer-events: none; }
.qk-moment[data-tone="gold"] { color: var(--occasion-gold, #E8C46A); }
.qk-moment > * { position: absolute; background: currentColor; border-radius: 2px; height: max(2px, 2.5cqmin); transform-origin: 0 50%; }
.qk-moment > i { left: 50%; top: 50%; width: 60cqmin; margin-top: max(-1px, -1.25cqmin); transform: rotate(var(--a, 0deg)) translate(-50%, -30cqmin) scaleX(1); }
.qk-moment > i:nth-child(2) { --a: 45deg; --d: 60ms; }  .qk-moment > i:nth-child(3) { --a: 90deg; --d: 120ms; }
.qk-moment > i:nth-child(4) { --a: 135deg; --d: 180ms; } .qk-moment > i:nth-child(5) { --a: 180deg; --d: 240ms; }
.qk-moment > i:nth-child(6) { --a: 225deg; --d: 300ms; } .qk-moment > i:nth-child(7) { --a: 270deg; --d: 360ms; }
.qk-moment > i:nth-child(8) { --a: 315deg; --d: 420ms; }
.qk-moment > span { inset: auto 0 calc(50% - 8cqmin); height: auto; background: none; transform-origin: 50% 100%; text-align: center; font: 400 22cqmin/1 var(--studio-display-font, 'Lalezar', Tahoma, sans-serif); }
.qk-moment > span::before { content: "جاهز"; }
.qk-moment > span:lang(ar) { bottom: calc(50% - 15cqmin); }   /* Lalezar's Arabic sits high in its line box */
.qk-moment > span:lang(en) { font-size: 17cqmin; }
.qk-moment > span:lang(en)::before { content: "Ready"; }
.qk-moment > b { --a: 45deg; left: calc(50% - 5.3cqmin); top: calc(50% + 18.3cqmin); width: 6.7cqmin; transform: rotate(var(--a)) scaleX(1); }
.qk-moment > b:last-child { --a: -50deg; left: calc(50% - 1.3cqmin); top: calc(50% + 23cqmin); width: 13.3cqmin; }
/* Too small for a word (the studio heading badge): star and a larger tick only. */
@container (max-width: 40px) {
  .qk-moment > span { display: none; }
  .qk-moment > b { left: calc(50% - 14cqmin); top: calc(50% + 1cqmin); width: 13cqmin; }
  .qk-moment > b:last-child { left: calc(50% - 5cqmin); top: calc(50% + 10cqmin); width: 26cqmin; }
}
@media (prefers-reduced-motion: no-preference) {
  .qk-moment > i { animation: qk-edge var(--studio-dur-state, 160ms) var(--studio-ease-out, cubic-bezier(.2,0,0,1)) var(--d, 0ms) backwards; }
  .qk-moment > span { animation: qk-land var(--studio-dur-move, 240ms) var(--studio-ease-out, cubic-bezier(.2,0,0,1)) 200ms backwards; }
  .qk-moment > b { animation: qk-stroke var(--studio-dur-state, 160ms) var(--studio-ease-out, cubic-bezier(.2,0,0,1)) 600ms backwards; }
  .qk-moment > b:last-child { animation-delay: 760ms; }
}
@keyframes qk-edge { from { transform: rotate(var(--a, 0deg)) translate(-50%, -30cqmin) scaleX(0); } }
@keyframes qk-stroke { from { transform: rotate(var(--a)) scaleX(0); } }
@keyframes qk-land { from { opacity: 0; transform: scale(.9); } }

/* Motion A, waiting: the upload's percent is one display numeral that refreshes on each real progress
   event (workspace-home.js uploadCount), and a caption line the transcription stream returns lifts into
   the list (app.js draw). The bar under them stays the one progress family. */
.upload-count { font: 400 40px/1.1 var(--studio-display-font, 'Lalezar', Tahoma, sans-serif); font-variant-numeric: tabular-nums; }
@media (prefers-reduced-motion: no-preference) {
  .segment.qk-arrive { animation: qk-arrive var(--studio-dur-move, 240ms) var(--studio-ease-out, cubic-bezier(.2,0,0,1)) backwards; }
}
@keyframes qk-arrive { from { opacity: 0; transform: translateY(.4em); } }

/* ── 8. Panel and row motion ─────────────────────────────────────────────
   Caption rows reordering after a split or merge uses the View Transitions
   API, which animates the real DOM change instead of shimming one. Rows carry
   view-transition-name; the JS owner wraps the mutation in
   document.startViewTransition() where it exists and does the plain mutation
   where it does not. */
@media (prefers-reduced-motion: no-preference) {
  ::view-transition-group(*) { animation-duration: var(--studio-dur-move, 240ms); }
  .caption-row { view-transition-name: var(--qk-vt, none); }
  .qk-panel-enter { animation: qk-panel-in var(--studio-dur-move, 240ms) var(--studio-ease-out, ease) both; }
}
@keyframes qk-panel-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }

/* Reduced motion: tokens.css already flattens durations globally. These are the
   looping decorations, which must stop outright rather than run fast. */
@media (prefers-reduced-motion: reduce) {
  .qk-cta::after,
  #caption-template-gallery .template-card:hover::before,
  .qk-progress[data-state="indeterminate"] > i,
  .qk-skeleton { animation: none !important; }
  .qk-cta:hover, #caption-template-gallery .template-card:hover { transform: none; }
}

/* Touch: hover states never fire, so the affordances that hide behind hover
   have to be visible. */
@media (hover: none) {
  .qk-slider::-webkit-slider-thumb { opacity: 1; }
  .qk-slider::-moz-range-thumb { opacity: 1; }
  /* No hover to reveal the field, so the boundary has to stand on its own.
     --studio-fg-3 is the dimmest value the layer already proves clears AA on
     every surface (6.1-8.0:1), which beats inventing another alpha. */
  .qk-field { border-color: var(--studio-fg-3); }
  .qk-cta, .qk-btn, .qk-iconbtn { min-height: var(--studio-hit, 44px); }
  .qk-iconbtn { width: var(--studio-hit, 44px); }
}

/* UX 1.7: one Latin face per English site screen. The studio controls carry the studio family
   (the studio's single family); on the site surface (auth, pricing, landing, review) they take the
   page's own face instead, so one face runs a screen (since 11 Oct 2026 both are Readex Pro; the rule stays for any future split). */
:root:not([data-surface="studio"]) :is(.qk-cta, .qk-btn):not(.app-shell *) { font-family: inherit; }

/* Loop 13: when the one lime action moves (no captions -> captions, so Export becomes the next step) the
   button swells once: transform only, one move duration out and back, never again until the lime moves. */
@media (prefers-reduced-motion: no-preference) {
  .qk-cta--next { animation: qk-next calc(var(--studio-dur-move, 240ms) * 2) var(--studio-ease-out, ease); }
}
@keyframes qk-next { 50% { transform: scale(1.06); } }
@media (prefers-reduced-motion: reduce) { .qk-seg label:active { transform: none; } }

/* Loop 13, round 9: every empty state in the studio is one typographic pattern — one sentence in fg-2 at body size,
   centred, then at most one action — with no dashed box and no illustration (the editor's empty list, the home grid,
   the Library, the template search). The public error pages keep their illustration. */
[data-surface="studio"] :is(.empty, .empty-projects, .template-empty) {
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  padding: var(--space-7) var(--space-5); border: 0; background: none; text-align: center;
  color: var(--studio-fg-2); font-size: var(--studio-text-body); line-height: 1.6;
}
[data-surface="studio"] :is(.empty, .empty-projects, .template-empty) :is(p, .hint) { margin: 0; color: inherit; font-size: inherit; }
[data-surface="studio"] :is(.empty, .empty-projects, .template-empty) .state-illo { display: none; }
[data-surface="studio"] .template-empty[hidden] { display: none; }

/* Partner marks (brand-icons, 13 Oct 2026): another company's own coloured mark, self-hosted from its brand kit
   (static/brand/partners, sources and rules in research/brand-icons.md). One height everywhere, set by the text it
   sits with; the width follows the artwork. Never recoloured, cropped into a shape or stretched. */
.partner-mark { display: inline-block; block-size: 1.15em; inline-size: auto; vertical-align: -0.2em; margin-inline-end: 0.4em; }
:has(> .partner-mark) { white-space: nowrap; }
