/* =========================================================================
   app.css - layout and components.

   Load order:  glass.css -> theme.css -> app.css
     glass.css  the material: three optical layers driven by --lg-*
     theme.css  tokens, the animated scene, the baked --lg-* presets
     app.css    this file: layout, typography, components, responsive

   This file is written against the HTML that tools/build.js actually emits
   (read the built pages under dist/, not a spec). Class names are the
   templates' own semantic names, not a vocabulary invented here.
   Where ui.js renders its own markup at runtime, its class names are added
   to the same selector lists rather than given parallel rules.

   =========================================================================
   TWO HARD RULES FOR ANY CSS TOUCHING A .lg ELEMENT
   =========================================================================
   1. Never write ::before or ::after on a .lg element. glass.css owns both
      (::before is the refracted, blurred, tinted backdrop; ::after is the
      specular rim). Redefining either deletes an optical layer. Add a child
      element, or use the material's own highlight control: --lg-spec with
      --lg-spec-x / --lg-spec-y. Adding a *property* to those pseudo-elements
      is fine (see .dropzone::after, which only adds an outline); redeclaring
      `content` is what breaks them.
   2. Never set border-radius on a .lg element. Set --lg-radius, so the rim
      mask and the refraction lens map agree with the visible corner.

   =========================================================================
   MARKUP CONTRACT (as built)
   =========================================================================
   document
     <html lang data-accent="indigo|teal|violet|gunmetal|midnight"
                data-appearance="dark|light">          (build / theme.js)
     <body data-mode="home|slow|speed|local|error"
           data-appearance data-interactive
           data-reduce-transparency data-increase-contrast>   (theme.js)
     <svg id="lg-filters"> is the FIRST element in body and carries the id
       itself: glass.js appends <filter> nodes into it, and a <filter> inside
       a <div> never applies. It is positioned off-screen inline; this file
       does not restyle it.
     a.skip-link[href="#main"]

   header.site-header.lg.glass-bar
     nav.site-nav
       a.brand > span.brand-name + span.brand-tagline
       ul.nav-links > li > a  (aria-current="page" marks the active one)
       div.nav-toggles
         a.lang-toggle.lg.glass-pill.lg--live[data-lang-toggle][lang][hreflang]
           span.flag (contains a flag emoji; see the .flag rule) + span.lang-code
         button.theme-toggle.lg.glass-pill.lg--live[data-theme-toggle][aria-pressed]
           svg.icon.icon-sun + svg.icon.icon-moon

   main#main   <- the page container: max-width, gutters and vertical rhythm
     article.hero | article.tool | article.native   (one per page)
     section.howto, section.faq, nav.crosslinks     (shared tail)

   home      h1, p.lede, p.hero-subhead,
             div.cards > a.card.lg.glass-card.lg--live > h2 + p + span.card-cta
             section.angle > h2 + p + p.hero-note
             section.trust > h2 + ul.trust-list > li.lg.glass-card
   tool      h1, p.lede, p.tool-subtitle
             section.workbench
               div.dropzone.lg.glass-panel.lg--live[data-dropzone]
                 p.dz-idle, p.dz-hint,
                 label.dz-pick.lg.glass-cta.lg--live[for=file-input],
                 input#file-input.visually-hidden[data-file-input],
                 p.dz-formats
               div.warnings.lg.glass-panel[data-warnings][hidden]
               section.probe.lg.glass-panel[data-probe-panel][hidden]
                 h3 + dl.probe-grid > div > dt + dd[data-probe-field]
               form.options.lg.glass-panel[data-options]
                 h3
                 fieldset.opt.opt-factor|.opt-mode|.opt-audio|.opt-quality
                   legend
                   label.chip.lg.glass-pill.lg--live
                     input[type=radio][name][value]
                     span.chip-label + span.chip-caption
                   p.opt-note                     (audio group only)
                 details.opt.opt-advanced > summary
                   label.check > input[type=checkbox]
                     + span.check-label + span.check-caption
               section.plan.lg.glass-panel[data-plan][hidden]
                 h3 + dl.plan-grid > div > dt + dd[data-plan-field]
               div.actions
                 button.lg.glass-cta.lg--live[data-start]
                 button.lg.glass-pill.lg--live[data-cancel][hidden]
               section.progress.lg.glass-panel[data-progress][hidden]
                 h3, progress[data-progress-bar][max][value],
                 p.progress-phase, p.progress-counter,
                 p.progress-eta, p.progress-keep
               section.result.lg.glass-panel[data-result][hidden]
                 h3, p.result-summary, video.result-preview,
                 p.result-actions > a.lg.glass-cta.lg--live[data-result-download]
                                  + button.lg.glass-pill.lg--live[data-result-again]
             section.angle > h2 + p + p.angle-links > a, a
   local     h1, p.lede, p.native-intro, p.native-varshint
             div.os-switch[data-native-tabs] > button.lg.glass-pill[data-os][aria-pressed]
             p.native-copyhint
             section.native-section > h2 + p
               div.cmd[data-os-panel][data-os-active?]
                 h3.cmd-os
                 div.cmd-line(.cmd-vars)
                   pre.lg.glass-code > code[data-native]
                   button.lg.glass-pill.lg--live[data-copy]
                 p.cmd-note
             section.angle > h2 + p + p.native-note + p.angle-links
   shared    section.howto > h2 + p.howto-intro
                 + ol.howto-steps > li.howto-step.lg.glass-card > h3 + p
             section.faq > h2 + div.faq-list
                 > article.faq-item.lg.glass-card > h3 + p
             nav.crosslinks > ul > li > a + span
   footer.site-footer
     p.footer-privacy
     nav.mirrors > h2 + ul > li > a + span
     p.footer-made, p.footer-rights > small, p.footer-build > small > time

   i18n hooks (owned by i18n.js, styled nowhere):
     [data-i18n="key"]  [data-i18n-attr="attr:key"]  [data-native="path"]

   ui.js runtime classes, rendered inside the panels above and folded into
   the same rules: .cvs-facts(__row|__key|__val) .cvs-seg(__item)
     .cvs-switch .cvs-field(__label|__caption|__note) .cvs-panel__title
     .cvs-progress__(phase|pct|track|bar|meta|engine|fallback|keep)
     .cvs-result__(video|summary|name|actions) .cvs-btn(--primary|--ghost)
     .cvs-warnings__(list|item[data-warning]) .cvs-error(__text)
   State classes: .is-dragover .is-busy .is-locked .is-selected .is-disabled
     .is-on .is-copied .is-hidden
   ========================================================================= */


/* -------------------------------------------------------------------------
   Reset
   ------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 104px;    /* clears the sticky header on anchor jumps */
}

body {
  margin: 0;
  min-height: 100svh;
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--text-1);
  background: transparent;      /* the scene lives on <html>, see theme.css */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
  /* NOTE: never add isolation / transform / filter / opacity to <body>. Any
     of them makes it a stacking context and buries the scene layers, which
     sit at negative z-index in the root stacking context. */
}

h1, h2, h3, h4, p, figure, blockquote, dl, dd, ul, ol, fieldset, legend, pre {
  margin: 0;
  padding: 0;
}
ul, ol { list-style: none; }
fieldset { border: 0; min-width: 0; }
img, svg, video, canvas { display: block; max-width: 100%; }
button, input, select, textarea { font: inherit; color: inherit; }
button { background: none; border: 0; padding: 0; cursor: pointer; }
a { color: inherit; text-decoration: none; }

::selection { background: var(--selection-bg); color: var(--text-1); }

/* ui.js hides panels with the attribute AND the class; several of them are
   flex or grid containers, which would otherwise beat plain [hidden]. */
[hidden] { display: none !important; }
.is-hidden { display: none !important; }

.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* -------------------------------------------------------------------------
   Focus.
   One ring for the whole site, drawn outside the box: an inset ring would be
   swallowed by the specular rim on glass. Modern engines follow the
   element's own border-radius, so nothing here restates a radius.
   ------------------------------------------------------------------------- */
:focus { outline: none; }
:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 3px;
}

.skip-link {
  position: fixed;
  top: var(--sp-3);
  left: 50%;
  translate: -50% -200%;
  z-index: var(--z-modal);
  padding: var(--sp-3) var(--sp-5);
  border-radius: var(--r-pill);
  background: var(--accent);
  color: var(--accent-ink);
  font-weight: 650;
  transition: translate var(--dur-2) var(--ease-out);
}
.skip-link:focus-visible { translate: -50% 0; }

/* -------------------------------------------------------------------------
   The page container and the vertical rhythm.
   The templates emit no wrapper div, so <main> itself is the container: it
   owns the max width, the gutters and the spacing between page sections.
   Every section-level block is a flex column with a gap, which keeps the
   rhythm consistent and sidesteps margin collapsing entirely.
   ------------------------------------------------------------------------- */
main {
  display: flex;
  flex-direction: column;
  gap: clamp(56px, 8vw, 104px);
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
  padding-block: clamp(24px, 5vw, 64px) clamp(64px, 9vw, 120px);
}

.hero,
.tool,
.native,
.workbench,
.angle,
.trust,
.howto,
.faq,
.native-section,
.crosslinks {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
  min-width: 0;
}
/* the tool stack breathes a little more: each panel is its own step */
.workbench { gap: clamp(16px, 2.4vw, 24px); }
.hero, .tool, .native { gap: clamp(20px, 3vw, 32px); }

/* -------------------------------------------------------------------------
   Typography.
   Headings have no classes in the templates, so they are styled by element
   inside <main> and inside the glass cards that contain them.
   ------------------------------------------------------------------------- */
main h1 {
  font-size: var(--fs-display);
  line-height: var(--lh-tight);
  letter-spacing: var(--tracking-tight);
  font-weight: 760;
  max-width: 18ch;
  text-wrap: balance;
}
main h2 {
  font-size: var(--fs-h2);
  line-height: var(--lh-snug);
  letter-spacing: var(--tracking-tight);
  font-weight: 720;
  max-width: 26ch;
  text-wrap: balance;
}
main h3 {
  font-size: var(--fs-h3);
  line-height: var(--lh-snug);
  font-weight: 680;
  letter-spacing: -0.012em;
}
main p { max-width: 72ch; text-wrap: pretty; }

/* The panel headings inside the workbench are labels, not titles: they are
   small, uppercase and quiet so the controls under them lead. */
.probe > h3,
.options > h3,
.plan > h3,
.progress > h3,
.result > h3,
.cvs-panel__title {
  font-size: var(--fs-xs);
  font-weight: 730;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-3);
  max-width: none;
}

/* The opening paragraph of every page. */
.lede {
  font-size: var(--fs-lead);
  color: var(--text-2);
  max-width: 64ch;
}
/* Secondary standfirsts, all the same weight of voice. */
.hero-subhead,
.tool-subtitle,
.howto-intro,
.native-intro {
  font-size: var(--fs-lead);
  color: var(--text-2);
  max-width: 62ch;
}
/* Two full sentences defining INPUT / OUTPUT / F / FPS for the 89 command
   blocks below it. It is body prose, not a caption: it needs three lines at
   360px and two on desktop, so it gets body size and a normal measure. */
.native-varshint {
  color: var(--text-2);
  max-width: 70ch;
  padding: var(--sp-4) var(--sp-5);
  border-radius: var(--r-md);
  background: rgba(var(--accent-rgb), .08);
  box-shadow: inset 0 0 0 1px rgba(var(--accent-rgb), .18);
}
/* Asides: quieter than body copy, never the first thing read. */
.hero-note,
.native-note,
.native-copyhint,
.opt-note,
.cmd-note,
.progress-keep,
.cvs-field__note {
  font-size: var(--fs-sm);
  color: var(--text-3);
  max-width: 68ch;
}

/* Prose links are the one documented exception to the 44px target rule:
   they are inline runs inside a sentence, where a forced block height would
   break the line box. Every navigational and control link is 44px. */
main a:not([class]) {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
  text-decoration-color: rgba(var(--accent-rgb), .45);
  transition: text-decoration-color var(--dur-2) var(--ease-out);
}
main a:not([class]):hover { text-decoration-color: var(--accent); }

/* -------------------------------------------------------------------------
   Header.
   The glass is on <header> itself, so the sticky element and the material
   are the same box. It is inset from the viewport rather than full bleed:
   a zero-inset sticky panel with a backdrop-filter reads as a hard band
   cutting the page, which is the opposite of a floating functional layer.
   ------------------------------------------------------------------------- */
.site-header {
  position: sticky;
  top: var(--sp-3);
  z-index: var(--z-header);
  width: calc(100% - var(--gutter) * 2);
  max-width: var(--container);
  margin: var(--sp-3) auto var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
}

.site-nav {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  min-height: 48px;
}

.brand {
  display: flex;
  flex-direction: column;
  gap: 1px;
  justify-content: center;
  min-width: 0;
  min-height: 44px;
  padding: var(--sp-2) var(--sp-3);
  margin-left: calc(var(--sp-1) * -1);
  border-radius: var(--r-md);
  transition: color var(--dur-2) var(--ease-out);
}
.brand-name {
  font-size: 1rem;
  font-weight: 720;
  letter-spacing: -0.015em;
  white-space: nowrap;
  transition: color var(--dur-2) var(--ease-out);
}
.brand:hover .brand-name { color: var(--accent); }
/* The tagline is a full sentence: it is useful markup for search engines and
   too long for the bar, so it is shown only where there is real room. */
.brand-tagline {
  display: none;
  font-size: var(--fs-xs);
  color: var(--text-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 34ch;
}

.nav-links {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  margin-inline: auto;
  min-width: 0;
}
.nav-links a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 var(--sp-4);
  border-radius: var(--r-pill);
  font-size: var(--fs-sm);
  font-weight: 550;
  color: var(--text-2);
  white-space: nowrap;
  transition: color var(--dur-2) var(--ease-out),
              background-color var(--dur-2) var(--ease-out);
}
.nav-links a:hover { color: var(--text-1); background: rgba(var(--accent-rgb), .12); }
.nav-links a[aria-current="page"] {
  color: var(--text-1);
  background: rgba(var(--accent-rgb), .18);
  box-shadow: inset 0 0 0 1px rgba(var(--accent-rgb), .35);
}

.nav-toggles {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-left: auto;
  flex: none;
}

/* --- language pill -------------------------------------------------------
   The span holds a flag emoji, which Windows refuses to render as a flag
   (it shows the two regional-indicator letters instead). So the glyph is
   collapsed to zero font-size and a real SVG flag is painted behind it: the
   emoji stays in the DOM for assistive tech and copy-paste, and every
   platform sees the same picture. Which flag is chosen from the toggle's
   own lang attribute, which is the language the link leads TO. */
.lang-toggle,
.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: 44px;
  min-width: 44px;
  padding: 0 var(--sp-3);
  color: var(--text-1);
  flex: none;
}
.lang-toggle { padding-inline: var(--sp-2) var(--sp-3); }

.flag {
  display: block;
  flex: none;
  width: 24px;
  height: 17px;
  font-size: 0;
  line-height: 0;
  color: transparent;
  border-radius: 3px;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  /* a hairline stops the white band of either flag bleeding into the glass */
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .3), 0 1px 2px rgba(0, 0, 0, .35);
}
.lang-toggle[lang="ru"] .flag { background-image: var(--flag-ru); }
.lang-toggle[lang="en"] .flag { background-image: var(--flag-en); }

.lang-code {
  font-size: var(--fs-xs);
  font-weight: 750;
  letter-spacing: .06em;
  color: var(--text-2);
}
.lang-toggle:hover .lang-code { color: var(--text-1); }

/* --- theme toggle --------------------------------------------------------
   Both icons live in the DOM at once and are stacked; the swap is a rotate
   plus a fade, so the control reads as a physical dial rather than a
   re-render. theme.js adds .is-swapping for the length of the change. */
.theme-toggle { position: relative; }
.theme-toggle .icon {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 20px;
  height: 20px;
  margin: -10px 0 0 -10px;
  transition:
    opacity var(--dur-3) var(--ease-glass),
    rotate var(--dur-3) var(--ease-glass),
    scale var(--dur-3) var(--ease-glass);
}
/* dark shows the moon, light shows the sun */
.icon-sun  { opacity: 0; rotate: -75deg; scale: .55; }
.icon-moon { opacity: 1; rotate: 0deg; scale: 1; }
[data-appearance="light"] .icon-sun  { opacity: 1; rotate: 0deg; scale: 1; }
[data-appearance="light"] .icon-moon { opacity: 0; rotate: 75deg; scale: .55; }

.theme-toggle.is-swapping { animation: theme-pop var(--dur-3) var(--ease-glass); }
@keyframes theme-pop {
  0%   { scale: 1; }
  38%  { scale: .88; }
  100% { scale: 1; }
}

/* -------------------------------------------------------------------------
   Buttons.
   The templates carry no .btn class: an actionable control is simply an
   <a> or <button> wearing a glass preset. glass-cta is the loud one,
   glass-pill the quiet one. .chip is deliberately excluded from this rule
   even though it also wears glass-pill, because it is a label wrapping a
   radio, not a button.
   ------------------------------------------------------------------------- */
button.glass-pill,
a.glass-pill,
button.glass-cta,
a.glass-cta,
label.glass-cta,
.cvs-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: 46px;
  padding: 0 var(--sp-6);
  font-size: var(--fs-sm);
  font-weight: 640;
  letter-spacing: -0.005em;
  color: var(--text-1);
  text-align: center;
  cursor: pointer;
  -webkit-user-select: none;
  user-select: none;
}
.cvs-btn { border-radius: var(--r-pill); }

/* The primary capsule. The accent is painted on the element itself, which
   sits *behind* .lg::before in the paint order, so the glass tint and the
   specular rim read as light catching a coloured body rather than as a
   sticker on top of it. */
.glass-cta,
.cvs-btn--primary:not(.lg) {
  color: var(--accent-ink);
  background:
    linear-gradient(165deg,
      rgba(var(--accent-rgb), .96) 0%,
      rgba(var(--accent-rgb), .70) 58%,
      rgba(var(--accent-rgb), .86) 100%);
  --lg-tint-top: rgba(255, 255, 255, .26);
  --lg-tint-bottom: rgba(255, 255, 255, .06);
  --lg-shadow-color: rgba(var(--accent-rgb), .36);
  --lg-shadow-y: 10px;
  --lg-shadow-blur: 30px;
}
.cvs-btn--primary:not(.lg) {
  box-shadow: 0 10px 30px rgba(var(--accent-rgb), .34),
              inset 0 1px 0 rgba(255, 255, 255, .3);
}
body[data-interactive="1"] .glass-cta:hover {
  --lg-shadow-blur: 44px;
  --lg-shadow-color: rgba(var(--accent-rgb), .5);
}
/* The start button is the one control that should be unmissable. */
.actions button.glass-cta,
.dz-pick {
  min-height: 54px;
  padding-inline: var(--sp-8);
  font-size: var(--fs-lead);
  font-weight: 680;
}

.cvs-btn--ghost:not(.lg) {
  color: var(--text-2);
  box-shadow: inset 0 0 0 1px var(--hairline-strong);
}
body[data-interactive="1"] .cvs-btn--ghost:not(.lg):hover {
  color: var(--text-1);
  background: rgba(255, 255, 255, .07);
}

button[disabled],
[aria-disabled="true"]:is(button, a, .cvs-btn) {
  opacity: .42;
  cursor: not-allowed;
  pointer-events: none;
  filter: saturate(.5);
}

.actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3);
  margin-top: var(--sp-2);
}

/* -------------------------------------------------------------------------
   Home: choice cards, angle, trust strip
   ------------------------------------------------------------------------- */
.cards {
  display: grid;
  /* auto-fit degrades to one column on a phone AND to a single card on the
     slow-only host without a second rule */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
  gap: clamp(16px, 2.5vw, 24px);
  margin-top: var(--sp-3);
}
.cards:has(> :only-child) {
  grid-template-columns: minmax(0, 560px);
  justify-content: center;
}

.card {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  min-height: clamp(200px, 26vw, 250px);
  padding: clamp(20px, 2.8vw, 30px);
}
.card h2 {
  font-size: var(--fs-h3);
  font-weight: 700;
  letter-spacing: -0.015em;
  max-width: none;
}
.card p {
  font-size: var(--fs-sm);
  color: var(--text-2);
  max-width: 38ch;
}
.card-cta {
  margin-top: auto;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding-top: var(--sp-2);
  font-size: var(--fs-sm);
  font-weight: 640;
  color: var(--accent);
}
.card-cta::after {
  content: "";
  width: 16px; height: 16px;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round' d='M9 5l7 7-7 7'/%3E%3C/svg%3E") center/contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round' d='M9 5l7 7-7 7'/%3E%3C/svg%3E") center/contain no-repeat;
  transition: translate var(--dur-2) var(--ease-out);
}
/* The hover light uses the material's own specular control rather than an
   extra element, because ::before and ::after are already spoken for. */
body[data-interactive="1"] .card:hover {
  --lg-spec: rgba(var(--accent-rgb), .34);
  --lg-spec-x: 78%;
  --lg-spec-y: -6%;
}
body[data-interactive="1"] .card:hover .card-cta::after { translate: 4px 0; }

.angle-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-5);
}
.angle-links a {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 44px;
  font-weight: 620;
  color: var(--accent);
}
.angle-links a::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: 50%;
  background: currentColor;
  opacity: .7;
}
.angle-links a:hover { text-decoration: underline; text-underline-offset: 4px; }

/* The trust strip: five short claims, each its own small pane. */
.trust-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
}
.trust-list li {
  flex: 1 1 220px;
  padding: var(--sp-4) var(--sp-5);
  font-size: var(--fs-sm);
  font-weight: 560;
  color: var(--text-2);
  --lg-radius: 14px;   /* deliberate: a claim chip is not a full card */
}

/* -------------------------------------------------------------------------
   Workbench: dropzone
   ------------------------------------------------------------------------- */
.dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: clamp(200px, 26vw, 280px);
  padding: clamp(24px, 4vw, 40px) var(--sp-5);
  text-align: center;
  cursor: pointer;
  transition: scale var(--dur-3) var(--ease-glass);
}
/* The dashed "drop here" line.
   .lg::after already exists (it is the specular rim); this rule only ADDS an
   outline to it and must not redeclare `content`, or the rim disappears. A
   negative outline-offset pulls the dashes inside the glass, and the outline
   follows the pseudo-element's radius so it stays concentric. */
.dropzone::after {
  outline: 2px dashed var(--hairline-strong);
  outline-offset: -12px;
  transition: outline-color var(--dur-2) var(--ease-out);
}
.dz-idle { font-size: var(--fs-h3); font-weight: 680; }
.dz-hint { color: var(--text-2); font-size: var(--fs-sm); }
.dz-pick { margin-top: var(--sp-3); }
.dz-formats {
  margin-top: var(--sp-2);
  font-size: var(--fs-xs);
  color: var(--text-3);
  letter-spacing: .02em;
}

body[data-interactive="1"] .dropzone:hover { --lg-spec: rgba(255, 255, 255, .16); }
body[data-interactive="1"] .dropzone:hover::after { outline-color: rgba(var(--accent-rgb), .5); }

.dropzone.is-dragover {
  scale: 1.012;
  --lg-tint-top: rgba(var(--accent-rgb), .28);
  --lg-tint-bottom: rgba(var(--accent-rgb), .12);
  --lg-spec: rgba(255, 255, 255, .3);
}
.dropzone.is-dragover::after { outline-color: var(--accent); outline-offset: -8px; }
.dropzone.is-invalid::after { outline-color: var(--danger); }

/* -------------------------------------------------------------------------
   Workbench: panels
   ------------------------------------------------------------------------- */
.probe,
.options,
.plan,
.progress,
.result,
.warnings {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  padding: clamp(18px, 2.6vw, 26px);
}
.options { gap: var(--sp-5); }

/* --- fact grids: probe readout and plan strip --------------------------
   Both are <dl> wrappers around div > dt + dd, which is also the shape
   ui.js renders, so the two spellings share one rule. */
.probe-grid,
.plan-grid,
.cvs-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--sp-4) var(--sp-5);
}
.probe-grid > div,
.plan-grid > div,
.cvs-facts__row {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.probe-grid dt,
.plan-grid dt,
.cvs-facts__key {
  font-size: var(--fs-xs);
  color: var(--text-3);
  letter-spacing: .03em;
}
.probe-grid dd,
.plan-grid dd,
.cvs-facts__val {
  font-size: var(--fs-body);
  font-weight: 640;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
/* An empty dd would collapse the row and make the grid jump as values
   arrive, so unfilled fields hold their line with a dash. */
.probe-grid dd:empty::before,
.plan-grid dd:empty::before {
  content: "\00B7\00B7\00B7";
  color: var(--text-3);
  font-weight: 500;
}
/* Hard rule 1: the plan has to be read before anything is encoded, so its
   numbers are the loudest thing in the stack. */
.plan-grid,
.cvs-facts--plan { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }
.plan-grid > div,
.cvs-facts--plan .cvs-facts__row {
  padding-left: var(--sp-4);
  border-left: 2px solid rgba(var(--accent-rgb), .45);
}
.plan-grid dt,
.cvs-facts--plan .cvs-facts__key { color: var(--text-2); }
.plan-grid dd,
.cvs-facts--plan .cvs-facts__val {
  font-size: clamp(1.2rem, 1.02rem + 0.85vw, 1.7rem);
  font-weight: 720;
  line-height: 1.15;
  letter-spacing: -0.02em;
}
.plan { --lg-tint-top: rgba(var(--accent-rgb), .16); }

/* -------------------------------------------------------------------------
   Options.
   Every group is a fieldset whose legend is the label. The chips are real
   <label>s wrapping real radios: keyboard behaviour, grouping and the form
   submission all come free, and the radio itself is restyled rather than
   hidden so the selected state survives even without :has().
   ------------------------------------------------------------------------- */
.opt {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}
.opt + .opt { padding-top: var(--sp-5); border-top: 1px solid var(--hairline); }
.opt legend,
.opt-advanced > summary,
.cvs-field__label,
.cvs-advanced__label {
  font-weight: 640;
  padding: 0;
  margin-bottom: var(--sp-1);
}
/* fieldset children do not participate in the fieldset's own flex layout in
   every engine, so the chips get their own wrapper behaviour via a grid on
   the fieldset content box. */
.opt-factor,
.opt-mode,
.opt-audio,
.opt-quality { display: grid; gap: var(--sp-3); }
.opt legend { grid-column: 1 / -1; }

/* --- chip --------------------------------------------------------------- */
.chip {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: 2px var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  cursor: pointer;
  /* deliberate override: glass-pill is a 999px capsule, which is wrong for a
     two-line chip carrying a caption */
  --lg-radius: 16px;
  transition: scale var(--dur-2) var(--ease-glass);
}
.chip input[type="radio"],
.check input[type="checkbox"] {
  grid-row: 1 / 3;
  appearance: none;
  -webkit-appearance: none;
  flex: none;
  width: 20px;
  height: 20px;
  margin: 2px 0 0;
  border-radius: 50%;
  background: rgba(0, 0, 0, .3);
  box-shadow: inset 0 0 0 1.5px var(--hairline-strong);
  cursor: pointer;
  transition: background-color var(--dur-2) var(--ease-out),
              box-shadow var(--dur-2) var(--ease-out);
}
.check input[type="checkbox"] { border-radius: 6px; }
.chip input[type="radio"]:checked,
.check input[type="checkbox"]:checked {
  background: var(--accent);
  box-shadow: inset 0 0 0 1.5px var(--accent), 0 0 14px rgba(var(--accent-rgb), .55);
}
/* the dot / tick, drawn with a mask so it inherits the ink colour */
.chip input[type="radio"]:checked { background-image: radial-gradient(circle, var(--accent-ink) 0 4px, transparent 4px); }
.check input[type="checkbox"]:checked {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23000' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round' d='M5 12.5l4.5 4.5L19 7'/%3E%3C/svg%3E");
  background-size: 14px 14px;
  background-position: center;
  background-repeat: no-repeat;
}
.chip input:focus-visible,
.check input:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 3px; }

.chip-label,
.check-label { font-weight: 640; line-height: 1.35; }
.chip-caption,
.check-caption {
  grid-column: 2;
  font-size: var(--fs-xs);
  color: var(--text-3);
  line-height: 1.5;
  text-wrap: pretty;
}

/* Selected chip. :has() is the clean version; the label emphasis below is
   the fallback for engines without it, so the state is never invisible. */
.chip input:checked ~ .chip-label,
.check input:checked ~ .check-label { color: var(--accent); }
.chip:has(input:checked) {
  --lg-tint-top: rgba(var(--accent-rgb), .30);
  --lg-tint-bottom: rgba(var(--accent-rgb), .14);
  --lg-rim-1: rgba(var(--accent-rgb), .9);
  --lg-spec: rgba(255, 255, 255, .22);
}
.chip:has(input:checked) .chip-caption { color: var(--text-2); }
body[data-interactive="1"] .chip:hover { --lg-spec: rgba(255, 255, 255, .16); }

/* Hard rule 8: an unavailable mode stays on screen with its reason, greyed
   and unselectable, rather than vanishing from the group. */
.chip:has(input:disabled),
.chip.is-disabled,
.chip[aria-disabled="true"] {
  opacity: .42;
  filter: grayscale(1);
  cursor: not-allowed;
}
.chip:has(input:disabled) .chip-label { text-decoration: line-through; }

.opt-note {
  grid-column: 1 / -1;
  margin-top: var(--sp-1);
}

/* --- advanced checkboxes ------------------------------------------------ */
.opt-advanced { gap: var(--sp-4); }
.opt-advanced > summary {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: 44px;
  cursor: pointer;
  list-style: none;
  color: var(--text-2);
  transition: color var(--dur-2) var(--ease-out);
}
.opt-advanced > summary::-webkit-details-marker { display: none; }
.opt-advanced > summary::after {
  content: "";
  width: 16px; height: 16px;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round' d='M6 9l6 6 6-6'/%3E%3C/svg%3E") center/contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round' d='M6 9l6 6 6-6'/%3E%3C/svg%3E") center/contain no-repeat;
  transition: rotate var(--dur-3) var(--ease-glass);
}
.opt-advanced[open] > summary { color: var(--text-1); }
.opt-advanced[open] > summary::after { rotate: 180deg; }
.opt-advanced > summary:hover { color: var(--accent); }

.check {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: 2px var(--sp-3);
  padding: var(--sp-3) 0;
  min-height: 44px;
  cursor: pointer;
}
.check + .check { border-top: 1px solid var(--hairline); }

/* --- ui.js's ARIA variants of the same two controls ---------------------
   ui.js builds buttons with role="radio"/"switch" instead of native inputs,
   which is the right call for a group whose members appear and disappear.
   The look is identical; only the state hooks differ. */
.cvs-field { display: flex; flex-direction: column; gap: var(--sp-3); min-width: 0; }
.cvs-field + .cvs-field { padding-top: var(--sp-5); border-top: 1px solid var(--hairline); }
.cvs-field--switch { flex-direction: row; align-items: center; gap: var(--sp-4); flex-wrap: wrap; }
.cvs-field__caption {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-md);
  background: rgba(var(--accent-rgb), .09);
  box-shadow: inset 0 0 0 1px rgba(var(--accent-rgb), .2);
  font-size: var(--fs-sm);
  color: var(--text-2);
  text-wrap: pretty;
}
/* when the caption is explaining why `motion` is unavailable it stops being
   a hint and becomes a warning */
.cvs-field__caption.is-unavailable,
.cvs-field__caption[data-unavailable] {
  background: var(--warn-bg);
  box-shadow: inset 0 0 0 1px var(--warn-rim);
}

.cvs-seg,
.os-switch {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  padding: 4px;
  border-radius: var(--r-pill);
  background: rgba(0, 0, 0, .24);
  box-shadow: inset 0 0 0 1px var(--hairline);
  align-self: flex-start;
  max-width: 100%;
}
[data-appearance="light"] .cvs-seg,
[data-appearance="light"] .os-switch { background: rgba(16, 24, 40, .07); }

.cvs-seg__item,
.os-switch button {
  flex: 0 1 auto;
  min-width: 72px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-5);
  font-size: var(--fs-sm);
  font-weight: 620;
  color: var(--text-2);
  text-align: center;
  white-space: nowrap;
  transition:
    background-color var(--dur-2) var(--ease-out),
    color var(--dur-2) var(--ease-out),
    box-shadow var(--dur-2) var(--ease-out);
  /* these are glass-pill capsules inside a track: the track already draws
     the container, so the item keeps the capsule radius */
  --lg-radius: 999px;
}
body[data-interactive="1"] .cvs-seg__item:hover,
body[data-interactive="1"] .os-switch button:hover {
  color: var(--text-1);
  background: rgba(255, 255, 255, .06);
}
.cvs-seg__item[aria-checked="true"],
.cvs-seg__item.is-selected,
.os-switch button[aria-pressed="true"] {
  color: var(--text-1);
  background: linear-gradient(180deg,
    rgba(var(--accent-rgb), .30) 0%,
    rgba(var(--accent-rgb), .16) 100%);
  box-shadow:
    inset 0 0 0 1px rgba(var(--accent-rgb), .48),
    inset 0 1px 0 0 rgba(255, 255, 255, .22),
    0 4px 14px rgba(var(--accent-rgb), .22);
}
/* explicit, because there is no native control here to inherit a ring from */
.cvs-seg__item:focus-visible,
.os-switch button:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.cvs-seg__item.is-disabled,
.cvs-seg__item[aria-disabled="true"] {
  opacity: .38;
  filter: grayscale(1);
  cursor: not-allowed;
  text-decoration: line-through;
}

/* ui.js's <button role="switch">: track and thumb are its own pseudo-
   elements, which is safe because .cvs-switch is never a .lg element. */
.cvs-switch {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: 44px;
  padding-left: 62px;
  font-size: var(--fs-sm);
  color: var(--text-2);
  text-align: left;
  cursor: pointer;
}
.cvs-switch::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  translate: 0 -50%;
  width: 50px; height: 30px;
  border-radius: var(--r-pill);
  background: rgba(0, 0, 0, .34);
  box-shadow: inset 0 0 0 1px var(--hairline-strong);
  transition: background-color var(--dur-2) var(--ease-out);
}
.cvs-switch::after {
  content: "";
  position: absolute;
  left: 3px;
  top: 50%;
  translate: 0 -50%;
  width: 24px; height: 24px;
  border-radius: 50%;
  background: linear-gradient(180deg, #ffffff, #d6dde9);
  box-shadow: 0 2px 6px rgba(0, 0, 0, .4);
  transition: translate var(--dur-3) var(--ease-glass);
}
[data-appearance="light"] .cvs-switch::before { background: rgba(16, 24, 40, .12); }
.cvs-switch.is-on::before,
.cvs-switch[aria-checked="true"]::before {
  background: linear-gradient(180deg, rgba(var(--accent-rgb), .95), rgba(var(--accent-rgb), .68));
  box-shadow: inset 0 0 0 1px rgba(var(--accent-rgb), .7), 0 4px 16px rgba(var(--accent-rgb), .3);
}
.cvs-switch.is-on::after,
.cvs-switch[aria-checked="true"]::after { translate: 20px -50%; }
.cvs-switch:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 3px; }

/* -------------------------------------------------------------------------
   Progress.
   The template ships a native <progress>, so it is restyled rather than
   replaced: it stays announced, it works before any script runs, and the
   indeterminate state is simply the absence of a value attribute.
   ------------------------------------------------------------------------- */
progress[data-progress-bar],
.cvs-progress__track {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  display: block;
  width: 100%;
  height: 8px;
  border: 0;
  border-radius: var(--r-pill);
  /* held in a variable so the indeterminate rule below can restate the
     background shorthand without losing the light-mode value */
  --track-bg: rgba(255, 255, 255, .1);
  background: var(--track-bg);
  box-shadow: inset 0 0 0 1px var(--hairline);
  color: var(--accent);          /* Firefox paints the value bar with this */
  overflow: hidden;
}
[data-appearance="light"] progress[data-progress-bar],
[data-appearance="light"] .cvs-progress__track { --track-bg: rgba(16, 24, 40, .1); }

progress[data-progress-bar]::-webkit-progress-bar { background: transparent; }
progress[data-progress-bar]::-webkit-progress-value {
  border-radius: var(--r-pill);
  background: linear-gradient(90deg, rgba(var(--accent-rgb), .6), var(--accent));
  box-shadow: 0 0 14px rgba(var(--accent-rgb), .5);
  transition: inline-size var(--dur-4) var(--ease-out);
}
progress[data-progress-bar]::-moz-progress-bar {
  border-radius: var(--r-pill);
  background: linear-gradient(90deg, rgba(var(--accent-rgb), .6), var(--accent));
}
/* ui.js writes an inline width percentage on its own fill element */
.cvs-progress__bar {
  display: block;
  width: 0;
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, rgba(var(--accent-rgb), .6), var(--accent));
  box-shadow: 0 0 14px rgba(var(--accent-rgb), .5);
  transition: width var(--dur-4) var(--ease-out);
}
/* "Total unknown": demux, muxing, and the ffmpeg core download, where a
   percentage would be a lie but a frozen 0 % reads as a hung tab. A sweep
   says the honest thing, that work is happening and the end is not known.

   Two spellings so neither renderer has to change element type mid-job:
   the native <progress> enters this state by dropping its `value`
   attribute, and ui.js's own track enters it with a class or a data
   attribute. Swapping elements mid-HUD would restart the aria-live region
   and jump the layout, which is worse than either bar. */
progress[data-progress-bar]:not([value]),
.cvs-progress__track.is-indeterminate,
.cvs-progress__track[data-indeterminate] {
  background:
    linear-gradient(90deg, transparent 0%, rgba(var(--accent-rgb), .85) 50%, transparent 100%)
      0 0 / 40% 100% no-repeat,
    var(--track-bg);
  animation: progress-sweep 1.5s var(--ease-in-out) infinite;
}
/* the fill would sit on top of the sweep and read as two bars */
.cvs-progress__track.is-indeterminate .cvs-progress__bar,
.cvs-progress__track[data-indeterminate] .cvs-progress__bar { display: none; }
@keyframes progress-sweep {
  0%   { background-position: -60% 0, 0 0; }
  100% { background-position: 160% 0, 0 0; }
}

.progress-phase,
.cvs-progress__phase { font-weight: 660; }
.progress-counter,
.progress-eta,
.cvs-progress__meta {
  font-size: var(--fs-sm);
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
}
.cvs-progress__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-4);
  flex-wrap: wrap;
}
.cvs-progress__pct {
  font-size: 1.15rem;
  font-weight: 720;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  color: var(--accent);
}
.cvs-progress__meta { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4); }
.cvs-progress__engine {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 11px;
  border-radius: var(--r-pill);
  font-size: var(--fs-xs);
  font-weight: 650;
  color: var(--accent);
  background: rgba(var(--accent-rgb), .14);
  box-shadow: inset 0 0 0 1px rgba(var(--accent-rgb), .34);
}
/* the fallback engine is much slower, so it should not look like the norm */
.cvs-progress__engine[data-engine="ffmpeg-wasm"] {
  color: var(--warn);
  background: var(--warn-bg);
  box-shadow: inset 0 0 0 1px var(--warn-rim);
}
.cvs-progress__fallback { font-size: var(--fs-sm); color: var(--warn); }
.cvs-progress__keep { font-size: var(--fs-sm); color: var(--text-3); }

/* -------------------------------------------------------------------------
   Result
   ------------------------------------------------------------------------- */
.result-summary,
.cvs-result__summary {
  font-size: var(--fs-sm);
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
}
.result-preview,
.cvs-result__video {
  width: 100%;
  height: auto;
  max-height: 64svh;
  border-radius: var(--r-lg);
  background: #000;
  box-shadow: inset 0 0 0 1px var(--hairline), 0 12px 34px rgba(0, 0, 0, .38);
}
.result-actions,
.cvs-result__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  max-width: none;
}
.cvs-result__name {
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  color: var(--text-1);
  overflow-wrap: anywhere;
}

/* -------------------------------------------------------------------------
   Warnings and errors.
   The template ships an empty .warnings panel; ui.js fills it. Tone is
   picked per warning code, and anything unlisted falls back to warn so a
   code added later is still legible rather than unstyled.
   ------------------------------------------------------------------------- */
.warnings:empty { display: none; }
.cvs-warnings__list { display: flex; flex-direction: column; gap: var(--sp-2); }

/* One rule, three tiers, switched by three custom properties. Adding a new
   warning code is a one-line change: put it in the tier list it belongs to,
   or leave it out and it inherits the warn tier below. */
.cvs-warnings__item {
  --tone: var(--warn);
  --tone-bg: var(--warn-bg);
  --tone-rim: var(--warn-rim);
  --tone-glyph: var(--glyph-warn);

  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
  color: var(--text-2);
  background: var(--tone-bg);
  box-shadow: inset 3px 0 0 0 var(--tone), inset 0 0 0 1px var(--tone-rim);
  text-wrap: pretty;
}
.cvs-warnings__item::before {
  content: "";
  flex: none;
  width: 16px; height: 16px;
  margin-top: 4px;
  background: var(--tone);
  -webkit-mask: var(--tone-glyph) center/contain no-repeat;
  mask: var(--tone-glyph) center/contain no-repeat;
}

/* TIER 1, warn (default): the output may not be what you expect. This is the
   only tier that makes a claim about the user's own file.
   vfr, hdr, largeFile, longOutput, hdrInterpolation, and anything unlisted.
   hdrInterpolation has no selector of its own on purpose: the default IS the
   right tone for it (HDR source, tone mapping off, so the chosen mode falls
   back to duplication and the output really will differ from the pick), and
   a selector that only restates the default would rot the first time the
   default changes. */

/* TIER 2, informational: we are telling you something. Nothing is broken,
   nothing needs a decision, and the job runs normally.
   noWebGPU and noMotionModel sit here rather than in the warn tier because
   neither is about the user's file: Motion is one of three modes and the
   other two work. noMotionModel in particular is OUR deployment gap (the
   RIFE model is not shipped yet), so today it is what every visitor sees.
   Amber on every first visit would read as "your video has a problem",
   which would be a false statement about their file and would make our
   gap look like their fault. */
.cvs-warnings__item[data-warning="rotation"],
.cvs-warnings__item[data-warning="audioDropped"],
.cvs-warnings__item[data-warning="iosBackground"],
.cvs-warnings__item[data-warning="noWebGPU"],
.cvs-warnings__item[data-warning="noMotionModel"] {
  --tone: var(--info);
  --tone-bg: var(--info-bg);
  --tone-rim: var(--info-rim);
  --tone-glyph: var(--glyph-info);
}

/* TIER 3, blocking: the job cannot run as asked. This tier gets the
   brightest body text as well as the loudest tone, because it is the only
   one that requires the user to change something. */
.cvs-warnings__item[data-warning="noWebCodecs"],
.cvs-warnings__item[data-warning="noHevc"],
.cvs-warnings__item[data-warning="memoryRisk"] {
  --tone: var(--danger);
  --tone-bg: var(--danger-bg);
  --tone-rim: var(--danger-rim);
  --tone-glyph: var(--glyph-danger);
  color: var(--text-1);
}

/* Rows entering the list.
   Now that the pre-job notices are driven by the plan prediction, any row can
   appear or vanish while the user is only toggling an option, and a row that
   materialises inside a live region is easy to read as a glitch. So the fade
   should cover every row, not just iosBackground.

   It CANNOT be put on .cvs-warnings__item directly, and this is the whole
   reason for the attribute. ui.js renderWarnings() clears the host and
   rebuilds every <li> from scratch, and recomputePlan() calls it on every
   option change. A CSS animation fires whenever an element is inserted, and
   it cannot tell a genuinely new row from an identical one that was just
   rebuilt. On the base class, changing the quality radio would re-fade every
   warning on screen: a flicker on an unrelated interaction, which is worse
   than the pop it was meant to fix.

   So the animation is gated on [data-entering], which only ui.js can know:
   diff the new key list against the previous one and set the attribute on
   the keys that were not there before. Inert until then, never wrong. */
.cvs-warnings__item[data-entering] {
  animation: warning-in var(--dur-3) var(--ease-out) both;
}
/* A batch arriving together cascades instead of landing as a block. Capped
   at 120ms: `both` holds the row at opacity 0 through its delay, and these
   rows are the ones the user needs in order to make a decision.

   The `of [data-entering]` is load-bearing, not a flourish. Plain nth-child
   counts every row in the list, so once ui.js diffs the key set the usual
   case is ONE new row among several survivors, and that row would inherit a
   delay from its position among rows that are not animating at all: a single
   new warning landing fourth would sit invisible for 120ms for no reason.
   Counting only the entering rows means a lone new row is always the first
   one and appears immediately, and a real batch still cascades.

   Degrades cleanly: an engine without `of` drops these three rules and every
   row fades together at 0ms, which is the pre-stagger behaviour. */
.cvs-warnings__item[data-entering]:nth-child(2 of [data-entering]) { animation-delay: 40ms; }
.cvs-warnings__item[data-entering]:nth-child(3 of [data-entering]) { animation-delay: 80ms; }
.cvs-warnings__item[data-entering]:nth-child(n+4 of [data-entering]) { animation-delay: 120ms; }

/* iosBackground keeps an ungated fade. It is the one row whose re-render is
   deliberate: ui.js re-renders it on visibilitychange during a job purely so
   the live region announces it again, and a re-fade is the visual half of
   that same re-announcement. Options are locked while a job runs, so the
   recomputePlan path above cannot fire here. */
.cvs-warnings__item[data-warning="iosBackground"] {
  animation: warning-in var(--dur-3) var(--ease-out);
}
@keyframes warning-in {
  from { opacity: 0; translate: 0 -4px; }
  to   { opacity: 1; translate: 0 0; }
}

.cvs-error {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-4) var(--sp-5);
  border-radius: var(--r-md);
  background: var(--danger-bg);
  box-shadow: inset 3px 0 0 0 var(--danger), inset 0 0 0 1px var(--danger-rim);
}
.cvs-error__text { color: var(--text-1); text-wrap: pretty; }
.cvs-error[data-error-code]::before {
  content: attr(data-error-code);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: .04em;
  color: var(--danger);
}

/* Panel states ui.js toggles on template-owned elements. */
[data-dropzone].is-busy,
[data-dropzone].is-locked,
[data-options].is-locked {
  opacity: .55;
  pointer-events: none;
  cursor: not-allowed;
  -webkit-user-select: none;
  user-select: none;
}

/* -------------------------------------------------------------------------
   Shared tail: how it works, FAQ, cross-links
   ------------------------------------------------------------------------- */
.howto-steps {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  gap: var(--sp-4);
  counter-reset: step;
}
.howto-step {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: clamp(18px, 2.4vw, 26px);
  counter-increment: step;
}
/* The step number is generated rather than authored, so the templates stay
   free to reorder or translate the list without renumbering anything. */
.howto-step::marker { content: none; }
.howto-step > h3 { display: flex; align-items: baseline; gap: var(--sp-3); }
.howto-step > h3::before {
  content: counter(step);
  flex: none;
  display: inline-grid;
  place-items: center;
  width: 30px; height: 30px;
  border-radius: 50%;
  font-size: var(--fs-xs);
  font-weight: 730;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
  background: rgba(var(--accent-rgb), .13);
  box-shadow: inset 0 0 0 1px rgba(var(--accent-rgb), .34);
}
.howto-step > p { font-size: var(--fs-sm); color: var(--text-2); }

.faq-list { display: flex; flex-direction: column; gap: var(--sp-3); }
.faq-item {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: clamp(18px, 2.4vw, 24px) clamp(20px, 2.6vw, 28px);
}
.faq-item > h3 { color: var(--text-1); }
.faq-item > p { color: var(--text-2); }

.crosslinks ul {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  gap: var(--sp-4);
}
.crosslinks li {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  padding: var(--sp-4) var(--sp-5);
  border-radius: var(--r-lg);
  background: rgba(255, 255, 255, .04);
  box-shadow: inset 0 0 0 1px var(--hairline);
  transition: background-color var(--dur-2) var(--ease-out),
              box-shadow var(--dur-2) var(--ease-out);
}
[data-appearance="light"] .crosslinks li { background: rgba(16, 24, 40, .04); }
.crosslinks li:hover {
  background: rgba(var(--accent-rgb), .1);
  box-shadow: inset 0 0 0 1px rgba(var(--accent-rgb), .3);
}
.crosslinks a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-weight: 640;
  color: var(--accent);
}
.crosslinks span { font-size: var(--fs-xs); color: var(--text-3); }

/* -------------------------------------------------------------------------
   /local: OS switch and command blocks.

   PERFORMANCE, and it is not a micro-optimisation: this page renders 12
   sections x 3 OS panels, 89 <pre> surfaces and 89 copy buttons. Every .lg
   costs the compositor one backdrop-filter layer, so the naive version asks
   for 199 of them on a 10 000px page and Chrome starts handing back blank
   frames. Two measures bring it back:

     a) content-visibility on each section, so only the sections near the
        viewport are laid out and painted at all. In-page find and the
        accessibility tree still see the content, and so do crawlers.
     b) the copy buttons opt out of the backdrop sampling. Adding a property
        to .lg::before is allowed (redeclaring `content` is what breaks the
        material); dropping the blur on a 44px capsule that sits on top of an
        already-tinted code surface costs nothing visually and removes 89
        layers. They keep their tint, rim and shadow, so they still read as
        the same material.
   ------------------------------------------------------------------------- */
.native-section {
  content-visibility: auto;
  /* `auto` lets the engine remember each section's real height after its
     first pass. The 460px seed is the measured median section height at
     1280px, so the scrollbar barely moves even on the very first scroll;
     a lazy guess of 900px inflated the document by 50 percent. */
  contain-intrinsic-size: auto 460px;
}
.os-switch { position: sticky; top: 84px; z-index: var(--z-raised); }

/* All three OS panels are in the HTML on purpose: with no JavaScript, and
   for crawlers, the page has to stay complete. So the inactive ones are
   hidden ONLY where a script can bring them back. In an engine without the
   `scripting` media feature every panel stays visible, which is verbose but
   never lossy: that is the right way for this to fail. */
.cmd { display: flex; flex-direction: column; gap: var(--sp-3); }
@media (scripting: enabled) {
  .cmd[data-os-panel]:not([data-os-active]) { display: none; }
}
/* Without the media feature the panels need their own heading to stay
   distinguishable, which .cmd-os already provides. */

.cmd-os {
  font-size: var(--fs-xs) !important;
  font-weight: 730;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-3);
}
.cmd-line {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: stretch;
  gap: var(--sp-2);
}
.cmd-line pre {
  overflow-x: auto;
  padding: var(--sp-4) var(--sp-5);
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  line-height: 1.7;
  color: var(--text-1);
  -webkit-overflow-scrolling: touch;
  tab-size: 2;
}
.cmd-line pre code { white-space: pre; }
.cmd-line button { align-self: stretch; min-height: 0; padding-inline: var(--sp-5); }
.cmd-line button.is-copied { color: var(--ok); }
/* see the performance note above: 89 of these, none of them big enough for a
   14px backdrop blur to be visible on top of an opaque code surface */
.cmd-line button.lg::before {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  --lg-tint-top: rgba(255, 255, 255, .13);
  --lg-tint-bottom: rgba(255, 255, 255, .07);
}
[data-appearance="light"] .cmd-line button.lg::before {
  --lg-tint-top: rgba(255, 255, 255, .7);
  --lg-tint-bottom: rgba(255, 255, 255, .5);
}
/* 30 of the 89 code blocks are variable-assignment preambles. Without a
   distinction each section reads as two unrelated commands, so a vars line
   is tied to the command below it: a shared accent rule down the left of
   both, and a quieter, tighter surface for the preamble itself. */
.cmd-vars { position: relative; margin-bottom: calc(var(--sp-3) * -1); }
.cmd-vars pre {
  --lg-tint-top: rgba(var(--accent-rgb), .13);
  --lg-tint-bottom: rgba(var(--accent-rgb), .06);
  padding-block: var(--sp-3);
  font-size: calc(var(--fs-mono) * .94);
  opacity: .92;
}
/* the rule runs from the top of the vars line to the bottom of the command
   that follows it, so the pair reads as one unit */
.cmd-vars,
.cmd-vars + .cmd-line { border-left: 2px solid rgba(var(--accent-rgb), .34); padding-left: var(--sp-4); }
.cmd-vars { border-top-left-radius: var(--r-xs); padding-top: var(--sp-3); }
.cmd-vars + .cmd-line { padding-bottom: var(--sp-3); border-bottom-left-radius: var(--r-xs); }

/* -------------------------------------------------------------------------
   Footer
   ------------------------------------------------------------------------- */
.site-footer {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  width: 100%;
  max-width: var(--container);
  margin: 0 auto;
  padding: clamp(28px, 4vw, 44px) var(--gutter) clamp(40px, 6vw, 64px);
  border-top: 1px solid var(--hairline);
  font-size: var(--fs-sm);
  color: var(--text-3);
}
.footer-privacy { color: var(--text-2); max-width: 68ch; }
.mirrors { display: flex; flex-direction: column; gap: var(--sp-2); }
.mirrors h2 {
  font-size: var(--fs-xs);
  font-weight: 730;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-3);
  max-width: none;
}
.mirrors ul {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-5);
}
.mirrors a {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 44px;
  color: var(--text-2);
  overflow-wrap: anywhere;
  transition: color var(--dur-2) var(--ease-out);
}
.mirrors a::before {
  content: "";
  flex: none;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--accent);
  opacity: .55;
}
.mirrors a:hover { color: var(--accent); }
.footer-made,
.footer-rights,
.footer-build { font-size: var(--fs-xs); }
.footer-build { opacity: .6; }

/* -------------------------------------------------------------------------
   Responsive.
   Single column is the baseline; columns are ADDED as room appears, so
   360px is the design floor rather than an afterthought.
   ------------------------------------------------------------------------- */
@media (min-width: 720px) {
  /* two chips side by side once a caption still has room to breathe */
  .opt-factor,
  .opt-mode,
  .opt-audio,
  .opt-quality { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
}

@media (min-width: 1080px) {
  .brand-tagline { display: block; }
}

@media (max-width: 900px) {
  /* The nav links move out of the bar: four links plus two toggles do not
     fit, and a hamburger for four links costs more taps than it saves.
     They stay reachable from the cross-links block and the footer. */
  .nav-links { display: none; }
  .site-nav { gap: var(--sp-3); }
}

@media (max-width: 560px) {
  .cmd-line { grid-template-columns: minmax(0, 1fr); }
  .cmd-line button { min-height: 44px; }
  .actions > * { flex: 1 1 100%; }
  .result-actions > * { flex: 1 1 100%; }
  .os-switch { position: static; }
  .trust-list li { flex-basis: 100%; }
}

@media (max-width: 400px) {
  /* 360px floor: shrink type and chrome, never the tap targets */
  .brand-name { font-size: .92rem; }
  .lang-code { display: none; }
  .site-header { padding-inline: var(--sp-2); }
  .chip { padding: var(--sp-3) var(--sp-4); }
}

/* Coarse pointers get no hover state: on touch, :hover sticks after a tap
   and leaves controls looking permanently lit. */
@media (hover: none) {
  body[data-interactive="1"] .lg--live:hover { --lg-lift: 0px; --lg-spec: rgba(255, 255, 255, 0); }
  body[data-interactive="1"] .card:hover { --lg-spec: rgba(255, 255, 255, 0); }
}

/* -------------------------------------------------------------------------
   Reduced motion and reduced transparency
   ------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  progress[data-progress-bar]:not([value]),
  .cvs-progress__track.is-indeterminate,
  .cvs-progress__track[data-indeterminate] { animation: none; background-position: 30% 0, 0 0; }
  .cvs-warnings__item[data-warning="iosBackground"],
  .cvs-warnings__item[data-entering] { animation: none; }
  .theme-toggle.is-swapping { animation: none; }
}

/* theme.js mirrors the query onto <body> so glass.js agrees with CSS; both
   selectors are kept so the page is already correct before the script runs. */
@media (prefers-reduced-transparency: reduce) {
  .cvs-seg,
  .os-switch { background: rgba(10, 14, 22, .9); }
  [data-appearance="light"] .cvs-seg,
  [data-appearance="light"] .os-switch { background: rgba(226, 232, 244, .96); }
}
body[data-reduce-transparency="1"] .cvs-seg,
body[data-reduce-transparency="1"] .os-switch { background: rgba(10, 14, 22, .9); }
body[data-reduce-transparency="1"][data-appearance="light"] .cvs-seg,
body[data-reduce-transparency="1"][data-appearance="light"] .os-switch { background: rgba(226, 232, 244, .96); }

/* -------------------------------------------------------------------------
   Print: drop the material, keep the words. The /local commands are the one
   thing anyone would actually print.
   ------------------------------------------------------------------------- */
@media print {
  html, body { background: #fff !important; color: #000; }
  html::before, html::after, body::before { display: none !important; }
  .site-header, .site-footer, .actions, .progress, .dropzone, .os-switch,
  .crosslinks, .skip-link { display: none !important; }
  .cmd { display: flex !important; }
  .lg { box-shadow: none; }
  .lg::before, .lg::after { display: none; }
  .cmd-line pre { border: 1px solid #999; }
  main { max-width: none; padding: 0; gap: 24px; }
}
