/* Unpixel — the one stylesheet behind the converter (ADR-0054, R1).
   Moved verbatim out of public/index.html's <style> on 2026-08-21; served as
   written, no build step. The home page and every future page that embeds
   the converter load this file. The two marked blocks below — QoL-8 tokens
   and QoL-8 site chrome — are ALSO carried inline by the content pages that
   do not load this file (this file styles bare main/button/select/aside/
   inputs, which would restyle a prose page): this copy is the reference,
   and test/pages.test.js holds every inline copy byte-identical to it.
   Referenced as /app.css?v=<8 hex of sha256(app.css ‖ app.js)> — the same
   test recomputes that token, so a change here that is not re-referenced
   on every page is a red build, never a stale cache. */
  * { box-sizing: border-box; margin: 0; padding: 0; }
  [hidden] { display: none !important; }

  /* ==== QoL-8 tokens — hand-synced VERBATIM on every public/*.html (test/pages.test.js pins the block) ==== */
  :root {
    /* type */
    --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, Roboto, "Helvetica Neue", Arial, sans-serif;
    --font-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    --fs-2xs: 11px; --fs-xs: 12px; --fs-sm: 12.5px; --fs-base: 14px;
    --fs-md: 15px; --fs-lg: 17px; --fs-xl: 20px; --fs-2xl: 26px; --fs-3xl: 34px;
    --lh: 1.5; --lh-tight: 1.3;
    /* spacing — 4px grid */
    --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px;
    --sp-5: 20px; --sp-6: 24px; --sp-8: 32px;
    /* layout */
    --container: 1320px;
    /* surfaces & lines */
    --bg: #f5f5f8; --bg-sunken: #ececf1; --card: #ffffff;
    --line: #e6e6ee; --line-strong: #d7d7e2;
    /* ink */
    --text: #17171f; --text-2: #464652; --muted: #6e6e7c; --faint: #9d9daa;
    /* accent — the indigo seed */
    --accent: #4f46e5; --accent-strong: #4338ca; --accent-weak: #eef0ff;
    --accent-line: #d9d6f8; --on-accent: #ffffff;
    --ring: rgba(79, 70, 229, .4);
    /* semantic */
    --ok: #16a34a; --ok-deep: #166534; --ok-weak: #ecfdf5; --ok-line: #bbf7d0;
    --warn: #b45309; --warn-weak: #fdf3e4;
    --danger: #dc2626; --danger-weak: #fdeded;
    /* radii · elevation · motion */
    --r-sm: 6px; --r-md: 9px; --r-lg: 13px; --r-xl: 18px; --r-full: 999px;
    --shadow-1: 0 1px 2px rgba(23, 23, 31, .06);
    --shadow-2: 0 1px 3px rgba(23, 23, 31, .05), 0 6px 20px rgba(23, 23, 31, .08);
    --shadow-3: 0 4px 12px rgba(23, 23, 31, .08), 0 16px 44px rgba(23, 23, 31, .18);
    --t-fast: .12s; --t-med: .22s; --ease: cubic-bezier(.2, .6, .2, 1);
  }
  /* ==== /tokens ==== */

  html, body { height: 100%; }
  body {
    font: var(--fs-base)/var(--lh) var(--font-sans);
    background: var(--bg);
    color: var(--text);
    -webkit-font-smoothing: antialiased;
    display: flex;
    flex-direction: column;
    /* App-scoped (the :root token block is pinned across pages): the boundary
       of a field you type into — 3.4:1 on the card, where --line-strong's
       hairline reads 1.4:1 (QoL-9 contrast pass; decorative hairlines keep
       --line-strong on purpose). */
    --field-line: #8a8a99;
    /* Safe-area (QoL-9): the app page declares viewport-fit=cover, so notched
       phones get the mat edge-to-edge and the content padded back in. */
    padding-left: env(safe-area-inset-left, 0px);
    padding-right: env(safe-area-inset-right, 0px);
  }
  /* ==== QoL-8 site chrome — hand-synced VERBATIM on every public/*.html (test/pages.test.js pins the block) ==== */
  /* Solid accent, not the 40%-alpha --ring: the ring blended to ~1.9:1 against
     a white card, under the 3:1 a focus indicator needs (QoL-9 a11y pass). */
  :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
  }
  ::selection { background: rgba(79, 70, 229, .22); }
  /* The header is a floating bar on the container axis (QoL-8.1, restyled
     same day): sticky, inset from the top, OPAQUE ON PURPOSE — the first
     cut used backdrop blur, which re-composites everything under the bar on
     every scroll frame and read as slow scrolling (Michael, 2026-08-08). A
     solid bar costs nothing to scroll under. */
  header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    padding: 8px var(--sp-4);
    width: min(100% - 24px, var(--container));
    margin: 10px auto 0;
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-1);
    flex: 0 0 auto;
    flex-wrap: wrap;
    position: sticky;
    top: 10px;
    z-index: 30;
  }
  .brand {
    display: flex; align-items: center; gap: 9px;
    font-weight: 700; font-size: var(--fs-md); letter-spacing: -.01em;
    color: inherit; text-decoration: none;
  }
  .brand svg { display: block; }
  .brand .sub { font-weight: 450; color: var(--muted); font-size: var(--fs-sm); letter-spacing: 0; margin-left: 2px; }
  .actions { display: flex; gap: var(--sp-2); align-items: center; }
  #siteNav { display: flex; align-items: center; gap: 2px; margin-left: auto; }
  #siteNav a {
    color: var(--text-2); text-decoration: none; font-weight: 500; font-size: 13.5px;
    padding: 6px 11px; border-radius: var(--r-full);
    transition: background var(--t-fast), color var(--t-fast);
  }
  #siteNav a:hover { color: var(--text); background: var(--bg-sunken); }
  #navToggle {
    display: none; align-items: center; justify-content: center;
    width: 36px; height: 22px; padding: 0; border: none; background: transparent;
    color: var(--muted); position: relative; cursor: pointer; font: inherit;
  }
  #navToggle:hover { color: var(--text); background: transparent; }
  #navToggle::after { content: ''; position: absolute; inset: -9px; }
  @media (max-width: 820px) {
    header { position: sticky; }
    #navToggle { display: inline-flex; margin-left: auto; }
    #siteNav {
      display: none; position: absolute; top: calc(100% + 8px); left: 0; right: 0; z-index: 40;
      flex-direction: column; align-items: stretch; gap: 2px; margin-left: 0;
      background: var(--card); border: 1px solid var(--line); border-radius: var(--r-lg);
      padding: 8px; box-shadow: var(--shadow-3);
    }
    #siteNav.open { display: flex; }
    #siteNav a { padding: 10px 12px; font-size: var(--fs-md); border-radius: var(--r-md); }
  }
  footer#siteFooter {
    background: var(--card);
    border-top: 1px solid var(--line);
    padding: var(--sp-8) var(--sp-5) 36px;
    flex: 0 0 auto;
  }
  .foot-grid {
    max-width: var(--container); margin: 0 auto;
    display: grid; grid-template-columns: 1.6fr 1fr 1fr 1fr; gap: var(--sp-6);
  }
  .foot-brand { display: flex; flex-direction: column; gap: var(--sp-2); }
  .foot-mark { display: flex; align-items: center; gap: var(--sp-2); font-weight: 700; font-size: var(--fs-base); letter-spacing: -.01em; }
  .foot-copy { color: var(--muted); font-size: var(--fs-sm); }
  .foot-col { display: flex; flex-direction: column; gap: 7px; }
  /* A styled label, not a heading (S2.1b): after the page's h1/h2 an h4
     here was the heading-order skip on every page; the columns are labelled
     <nav>s, which is the landmark assistive tech uses. */
  .foot-col .foot-h {
    font-size: var(--fs-2xs); letter-spacing: .09em; text-transform: uppercase;
    color: var(--muted); font-weight: 650; margin-bottom: 3px;
  }
  .foot-col a { color: var(--text-2); text-decoration: none; font-size: 13.5px; width: fit-content; transition: color var(--t-fast); }
  .foot-col a:hover { color: var(--accent); text-decoration: underline; }
  @media (max-width: 820px) {
    .foot-grid { grid-template-columns: 1fr 1fr; }
    .foot-brand { grid-column: 1 / -1; }
  }
  /* ==== /site chrome ==== */

  button {
    font: inherit;
    font-size: 13.5px;
    font-weight: 500;
    border: 1px solid var(--line-strong);
    background: var(--card);
    color: var(--text);
    border-radius: var(--r-md);
    padding: 7px 14px;
    cursor: pointer;
    transition: background var(--t-fast), border-color var(--t-fast), color var(--t-fast),
      box-shadow var(--t-fast), opacity var(--t-fast), transform var(--t-fast);
  }
  button:hover:not(:disabled) { background: var(--bg-sunken); transform: translateY(-1px); box-shadow: var(--shadow-1); }
  button:active:not(:disabled) { transform: translateY(0); box-shadow: none; }
  button:disabled { opacity: .45; cursor: default; }
  button.primary {
    background: linear-gradient(180deg, #6159f2, var(--accent)); border-color: var(--accent);
    color: var(--on-accent); font-weight: 600; box-shadow: var(--shadow-1);
  }
  button.primary:hover:not(:disabled) {
    background: linear-gradient(180deg, #574ee8, var(--accent-strong)); border-color: var(--accent-strong);
    box-shadow: 0 3px 10px rgba(79, 70, 229, .3);
  }
  button.small { padding: 4px 10px; font-size: var(--fs-xs); border-radius: var(--r-sm); }
  /* The header action's two labels (QoL-9): full text on wide screens, the
     short word under 600px — one row of header at every width. One button
     since S2.6 (Copy SVG moved to the Download screen); it wears .primary
     only while the readout's own Download SVG is out of view — app.js
     toggles the class, so nothing here needs a state. */
  .actions button { display: inline-flex; align-items: center; gap: 7px; }
  .actions .btnTxtShort { display: none; }

  /* The workspace is a framed app on a canvas (QoL-8.1): main is the mat,
     #appFrame the one elevated container, capped at the chrome's container
     width so header, frame and footer share an axis. */
  main {
    flex: 1; display: flex; min-height: 0; padding: var(--sp-4) var(--sp-5);
    background: radial-gradient(rgba(23, 23, 31, .06) 1px, transparent 1.2px) 0 0 / 22px 22px, var(--bg);
  }
  #appFrame {
    flex: 1; display: flex; min-width: 0; min-height: 0;
    width: 100%; max-width: var(--container); margin-inline: auto;
    background: var(--card); border: 1px solid var(--line);
    border-radius: var(--r-xl); box-shadow: var(--shadow-2); overflow: hidden;
  }

  /* ---------- Sidebar ---------- */
  aside {
    width: 300px;
    flex: 0 0 300px;
    background: var(--card);
    border-right: 1px solid var(--line);
    overflow-y: auto;
    padding: var(--sp-4);
    display: flex;
    flex-direction: column;
    gap: var(--sp-4);
    scrollbar-width: thin;
  }
  aside::-webkit-scrollbar, #layerList::-webkit-scrollbar, #dlScreen::-webkit-scrollbar { width: 8px; }
  aside::-webkit-scrollbar-thumb, #layerList::-webkit-scrollbar-thumb, #dlScreen::-webkit-scrollbar-thumb {
    background: var(--line-strong); border-radius: var(--r-full); border: 2px solid transparent; background-clip: content-box;
  }
  aside::-webkit-scrollbar-track, #layerList::-webkit-scrollbar-track, #dlScreen::-webkit-scrollbar-track { background: transparent; }

  /* Pre-upload, the sidebar shows only what means something before an image
     exists (QoL-8): the three steps, not the control stack. */
  #introPanel { display: flex; flex-direction: column; gap: var(--sp-4); }
  #introPanel h2 {
    font-size: var(--fs-2xs); letter-spacing: .09em; text-transform: uppercase;
    color: var(--muted); font-weight: 650;
  }
  .introSteps { list-style: none; display: flex; flex-direction: column; gap: var(--sp-3); counter-reset: step; }
  .introSteps li {
    position: relative; padding-left: 36px; counter-increment: step;
    font-size: 13.5px; color: var(--text-2); line-height: 1.5; min-height: 24px;
  }
  .introSteps li::before {
    content: counter(step);
    position: absolute; left: 0; top: 0; width: 24px; height: 24px;
    display: flex; align-items: center; justify-content: center;
    background: var(--accent-weak); color: var(--accent);
    border-radius: var(--r-full); font-size: var(--fs-xs); font-weight: 650;
    font-variant-numeric: tabular-nums;
  }
  .introSteps b { color: var(--text); font-weight: 600; }

  #traceControls { display: flex; flex-direction: column; gap: var(--sp-4); }

  details.group { border-bottom: 1px solid var(--line); padding-bottom: var(--sp-4); }
  details.group:last-child { border-bottom: none; padding-bottom: 0; }
  .group summary {
    list-style: none; cursor: pointer; display: flex; align-items: center;
    user-select: none; -webkit-user-select: none;
  }
  .group summary::-webkit-details-marker { display: none; }
  .group summary h3 {
    font-size: var(--fs-2xs); letter-spacing: .09em; text-transform: uppercase;
    color: var(--muted); font-weight: 650;
  }
  .group summary h3 .hint { text-transform: none; letter-spacing: 0; font-weight: 500; margin-left: 4px; }
  .group summary::after {
    content: ''; width: 7px; height: 7px; flex: none;
    /* --muted, not --faint: the disclosure chevron is a state indicator and
       faint reads 2.7:1 on the card (QoL-9 contrast pass; 3:1 is the bar). */
    border-right: 1.5px solid var(--muted); border-bottom: 1.5px solid var(--muted);
    transform: rotate(45deg); margin-left: auto; margin-bottom: 2px;
    transition: transform var(--t-fast);
  }
  .group:not([open]) summary::after { transform: rotate(-45deg); margin-bottom: -2px; }
  .groupBody { display: flex; flex-direction: column; gap: var(--sp-3); padding-top: var(--sp-3); }
  details[open] > .groupBody { animation: fadeSlide var(--t-med) var(--ease); }
  @keyframes fadeSlide { from { opacity: 0; transform: translateY(-4px); } }

  .ctl { display: flex; flex-direction: column; gap: 6px; }
  .ctl-top { display: flex; align-items: baseline; gap: 6px; }
  .ctl-top label { font-weight: 500; font-size: 13.5px; }
  .ctl-top output {
    margin-left: auto; color: var(--muted); font-size: var(--fs-xs);
    font-variant-numeric: tabular-nums;
  }

  /* On-demand hints (QoL-8): the explanation is a click away, never a wall. */
  .why {
    width: 16px; height: 16px; padding: 0; border: none; border-radius: var(--r-full);
    background: transparent; color: var(--muted); font-size: var(--fs-2xs); font-weight: 600;
    line-height: 1; display: inline-flex; align-items: center; justify-content: center;
    flex: none; cursor: pointer; position: relative; align-self: center;
    transition: background var(--t-fast), color var(--t-fast);
  }
  .why:hover { background: var(--bg-sunken); color: var(--text-2); }
  .why[aria-expanded="true"] { background: var(--accent-weak); color: var(--accent); }
  .why::after { content: ''; position: absolute; inset: -6px; }
  .hint { color: var(--muted); font-size: var(--fs-xs); line-height: 1.55; }
  .hint.ondemand { background: var(--bg); border-radius: var(--r-sm); padding: 8px 10px; }

  /* Sliders: filled track + ringed thumb. --fill is set by updateOutputs()
     so the track reads the value at a glance; disabled tracks go flat. */
  input[type=range] {
    -webkit-appearance: none; appearance: none;
    width: 100%; height: 22px; background: transparent; cursor: pointer;
  }
  input[type=range]::-webkit-slider-runnable-track {
    height: 4px; border-radius: var(--r-full);
    background: linear-gradient(to right, var(--accent) var(--fill, 0%), var(--bg-sunken) var(--fill, 0%));
  }
  input[type=range]::-webkit-slider-thumb {
    -webkit-appearance: none; width: 16px; height: 16px; margin-top: -6px;
    border-radius: var(--r-full); background: var(--card);
    border: 2px solid var(--accent); box-shadow: var(--shadow-1);
    transition: transform var(--t-fast), box-shadow var(--t-fast);
  }
  input[type=range]:hover:not(:disabled)::-webkit-slider-thumb { transform: scale(1.15); }
  input[type=range]:active:not(:disabled)::-webkit-slider-thumb { transform: scale(1.25); box-shadow: 0 0 0 5px var(--accent-weak); }
  input[type=range]::-moz-range-track { height: 4px; border-radius: var(--r-full); background: var(--bg-sunken); }
  input[type=range]::-moz-range-progress { height: 4px; border-radius: var(--r-full); background: var(--accent); }
  input[type=range]::-moz-range-thumb {
    width: 12px; height: 12px; border-radius: var(--r-full); background: var(--card);
    border: 2px solid var(--accent); box-shadow: var(--shadow-1);
  }
  input[type=range]:disabled { opacity: .45; cursor: default; }
  input[type=range]:disabled::-webkit-slider-runnable-track { background: var(--bg-sunken); }
  input[type=range]:disabled::-webkit-slider-thumb { border-color: var(--faint); transform: none; }
  .row { display: flex; align-items: center; gap: var(--sp-2); }
  .row label { font-weight: 500; font-size: 13.5px; }
  input[type=checkbox] { accent-color: var(--accent); width: 15px; height: 15px; }
  input[type=color] {
    width: 34px; height: 26px; padding: 1px 2px;
    border: 1px solid var(--line-strong); border-radius: var(--r-sm); background: var(--card); cursor: pointer;
  }
  select {
    font: inherit; font-size: 13px; padding: 5px 8px; border: 1px solid var(--field-line);
    border-radius: var(--r-sm); background: var(--card); color: var(--text);
  }

  .seg { display: flex; background: var(--bg-sunken); border-radius: 10px; padding: 3px; gap: 2px; }
  .seg button {
    flex: 1; border: none; background: transparent; border-radius: 8px;
    padding: 6px 8px; font-size: 13px; font-weight: 500; color: var(--text-2); box-shadow: none;
  }
  .seg button:hover:not(:disabled) { background: rgba(255, 255, 255, .65); color: var(--text); }
  .seg button.on { background: var(--card); color: var(--text); font-weight: 600; box-shadow: var(--shadow-1); }

  .sidebar-foot { margin-top: auto; color: var(--muted); font-size: var(--fs-xs); line-height: 1.55; padding-top: var(--sp-2); }

  /* ---------- Color-mode layer panel ---------- */
  #layerList {
    display: flex; flex-direction: column; max-height: 340px; overflow-y: auto;
    margin: 0 -5px; padding: 0 5px; scrollbar-width: thin;
  }
  .layer-row { display: flex; align-items: center; gap: 7px; padding: 3px 4px; border-radius: var(--r-sm); }
  .layer-row:hover { background: var(--bg); }
  .layer-row .eye {
    width: 26px; height: 24px; padding: 0; flex: none;
    display: flex; align-items: center; justify-content: center;
    font-size: 13px; line-height: 1; border: none; background: transparent;
    border-radius: var(--r-sm); color: var(--text-2);
  }
  .layer-row .eye:hover:not(:disabled) { background: var(--bg-sunken); }
  .layer-row input[type=color] { width: 26px; height: 22px; flex: none; border-radius: 5px; }
  .layer-row .lbl {
    flex: 1; min-width: 0; font-size: var(--fs-xs); font-variant-numeric: tabular-nums;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--text-2);
  }
  .layer-row .lbl code { font-family: var(--font-mono); font-size: 11px; color: var(--text); }
  .layer-row select { max-width: 64px; font-size: 11px; padding: 3px 4px; flex: none; border-radius: 5px; color: var(--muted); }
  .layer-row.off .lbl { opacity: .45; text-decoration: line-through; }

  /* ---------- Stage ---------- */
  #stage {
    flex: 1; min-width: 0; display: flex; flex-direction: column;
    padding: var(--sp-4); gap: var(--sp-3);
    background: var(--bg);
  }

  /* The drop zone is the landing moment (QoL-8): the paid-first pitch, worn
     deliberately — same substance the old paragraph carried. */
  #empty {
    flex: 1;
    border: 1.5px dashed var(--line-strong);
    border-radius: var(--r-xl);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    color: var(--muted);
    cursor: pointer;
    text-align: center;
    padding: var(--sp-6);
    transition: border-color var(--t-med), background var(--t-med);
  }
  #empty:hover { border-color: var(--accent); box-shadow: var(--shadow-1); }
  #empty:hover .icon svg { transform: translateY(-2px) scale(1.04); }
  /* h1 on the home page; a landing page demotes the same line to h2 because
     its hero carries the page's one h1 (S2.1b, ADR-0057) — same look. */
  #empty h1, #empty h2 { color: var(--text); font-size: var(--fs-2xl); font-weight: 650; letter-spacing: -.015em; line-height: var(--lh-tight); }
  #empty .icon { color: var(--accent); margin-bottom: var(--sp-1); }
  #empty .icon svg { animation: floaty 5s ease-in-out infinite alternate; transition: transform var(--t-med); }
  @keyframes floaty { from { transform: translateY(2px); } to { transform: translateY(-3px); } }
  #empty .dropSub { font-size: var(--fs-base); }
  .proofRow { display: flex; gap: var(--sp-2); flex-wrap: wrap; justify-content: center; margin-top: var(--sp-3); max-width: 560px; }
  .proof {
    display: inline-flex; align-items: center; gap: 6px;
    background: var(--card); border: 1px solid var(--line); border-radius: var(--r-full);
    padding: 5px 12px; font-size: var(--fs-sm); color: var(--text-2); box-shadow: var(--shadow-1);
    transition: transform var(--t-fast), box-shadow var(--t-fast), border-color var(--t-fast);
  }
  .proof:hover { transform: translateY(-1px); box-shadow: var(--shadow-2); border-color: var(--accent-line); }
  .proof::before { content: '✓'; color: var(--accent); font-weight: 700; }
  /* The proof line (S2.6): the scoreboard's numbers under the four claims,
     one sentence, quiet type with the figures a shade darker. Phones hide
     it (below) — the first screen there is the drop zone and the samples. */
  .proofLine { margin: var(--sp-2) 0 0; max-width: 560px; font-size: var(--fs-sm); line-height: 1.5; color: var(--muted); }
  .proofLine b { color: var(--text-2); font-weight: 600; }
  #empty .paidNote { font-size: var(--fs-sm); }
  /* Two copies of the drop zone's copy, one shown (S2.5): .onPhone under the
     phone breakpoint, .onDesk everywhere else — display:none keeps the hidden
     copy out of the accessibility tree too. */
  #empty .onPhone { display: none; }
  .pickRow { display: flex; gap: var(--sp-2); justify-content: center; flex-wrap: wrap; margin-top: 8px; }
  /* The samples (S2.5): three of our own corpus files, a thumbnail and a verb
     each — the click a visitor with no file at hand was missing. */
  .sampleRow {
    display: flex; flex-wrap: wrap; gap: var(--sp-2); justify-content: center; align-items: flex-start;
    margin-top: var(--sp-2);
  }
  .sampleLead { flex: 1 1 100%; font-size: var(--fs-sm); color: var(--muted); }
  .sample {
    display: inline-flex; flex-direction: column; align-items: center; gap: 5px;
    padding: 5px 10px 6px; min-width: 88px;
    font-size: var(--fs-sm); font-weight: 500; color: var(--text-2);
    border-color: var(--line);
  }
  .sample img { width: 40px; height: 40px; border-radius: var(--r-md); object-fit: cover; background: var(--card); border: 1px solid var(--line); }
  .sample:hover:not(:disabled) { color: var(--accent); border-color: var(--accent-line); }

  #panes {
    flex: 1;
    min-height: 0;
    display: grid;
    grid-template-columns: 1fr 1.25fr;
    gap: var(--sp-4);
  }
  .pane {
    display: flex; flex-direction: column; min-height: 0;
    background: var(--card); border: 1px solid var(--line); border-radius: var(--r-lg);
    overflow: hidden; box-shadow: var(--shadow-1);
  }
  .pane.result { border-color: var(--line-strong); box-shadow: var(--shadow-2); }
  .paneHead {
    display: flex; justify-content: space-between; align-items: baseline; gap: 10px;
    padding: 10px 14px; border-bottom: 1px solid var(--line);
    font-weight: 600; font-size: 13px; flex: 0 0 auto;
  }
  .pane:not(.result) .paneHead > span:first-child { color: var(--text-2); font-weight: 500; }
  .pane.result .paneHead > span:first-child { display: inline-flex; align-items: center; gap: 7px; }
  .pane.result .paneHead > span:first-child::before {
    content: ''; width: 7px; height: 7px; border-radius: var(--r-full);
    background: var(--accent); flex: none;
  }
  .paneHead .meta { color: var(--muted); font-weight: 400; font-size: var(--fs-xs); text-align: right; font-variant-numeric: tabular-nums; }

  /* The result's readout (QoL-8): nodes · size · time is the quality argument,
     not metadata — it gets a designed strip under the preview. Since S2.6 the
     strip has two ends: the numbers, and the next step — the plan in small
     type and Download SVG — pushed to the far edge; on a narrow pane the two
     wrap onto their own rows. */
  .paneFoot {
    display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3) var(--sp-5); flex-wrap: wrap;
    padding: 7px 14px 8px; border-top: 1px solid var(--line);
    background: linear-gradient(to bottom, rgba(79, 70, 229, .04), rgba(79, 70, 229, .012));
    flex: 0 0 auto;
  }
  .paneFoot .stats { display: flex; align-items: baseline; gap: var(--sp-5); flex-wrap: wrap; min-width: 0; }
  .resultCta { display: flex; align-items: center; gap: 10px; margin-left: auto; }
  .resultCta .planNote { font-size: var(--fs-2xs); color: var(--muted); white-space: nowrap; }
  #resultDownloadBtn { padding: 7px 16px; }
  .stat { display: flex; align-items: baseline; gap: 5px; white-space: nowrap; }
  .stat b { font-size: var(--fs-md); font-weight: 650; font-variant-numeric: tabular-nums; letter-spacing: -.01em; }
  .stat span { font-size: var(--fs-2xs); color: var(--muted); text-transform: uppercase; letter-spacing: .05em; }
  /* Fresh traces reveal their numbers left to right — one beat, staggered. */
  .paneFoot.fresh .stat { animation: settle .45s var(--ease) backwards; }
  .paneFoot.fresh .stat:nth-child(2) { animation-delay: .05s; }
  .paneFoot.fresh .stat:nth-child(3) { animation-delay: .1s; }
  .paneFoot.fresh .stat:nth-child(4) { animation-delay: .15s; }
  .paneFoot.fresh .stat:nth-child(5) { animation-delay: .2s; }
  @keyframes settle { from { opacity: 0; transform: translateY(5px); } }

  .canvas { flex: 1; position: relative; min-height: 220px; }
  .canvas.checker { background: repeating-conic-gradient(#ebebf0 0% 25%, #f8f8fa 0% 50%) 0 0 / 20px 20px; }
  .canvas.light { background: #ffffff; }
  .canvas.dark { background: #17171d; }
  .fit { position: absolute; inset: 14px; }
  .fit img { width: 100%; height: 100%; display: block; }
  .fit img { object-fit: contain; }

  /* The preview pane holds a server-rendered PNG since S2 (the SVG never
     reaches this page). Two rules keep it from reading softer than the inline
     SVG it replaced: the raster is asked for at pane size × devicePixelRatio,
     and it is displayed at the CSS width the server hands back — which is never
     wider than the raster's own pixels, so nothing is ever upscaled. */
  #previewFit { position: absolute; inset: 14px; display: flex; align-items: center; justify-content: center; overflow: hidden; }
  #previewImg { display: block; max-width: 100%; max-height: 100%; object-fit: contain; transition: opacity .1s; will-change: transform; }
  /* The optimistic ghost: while the server renders the crisp view, the current
     raster is CSS-scaled to roughly where that view will land. Animated for a
     zoom step (a click deserves a beat of motion), instant for a drag. */
  #previewImg.ghost-anim { transition: transform .18s var(--ease), opacity .1s; }
  #previewFit.zoomed { cursor: grab; }
  #previewFit.zoomed.dragging { cursor: grabbing; }
  #previewFit.empty #previewImg { display: none; }

  #zoomBar { display: flex; align-items: center; gap: var(--sp-2); }
  #zoomBar .seg { width: auto; }
  #zoomBar .seg button { padding: 5px 10px; font-size: var(--fs-xs); }
  /* Two ids so the width survives #bottomBar .seg's fixed 240px below. */
  #bottomBar #viewSeg { width: auto; }
  #viewSeg button { flex: 0 0 auto; }
  #zoomNote { color: var(--muted); font-size: 11.5px; }

  /* Busy indicators: a bar across the pane's top edge — indeterminate sweep
     while the server works, a real fill while a known-length response body
     downloads — and a pill that says what is actually happening. The pill
     waits 250ms before showing so a warm ~100ms render never flashes it. */
  #busyBar {
    position: absolute; top: 0; left: 0; right: 0; height: 3px; z-index: 6;
    background: rgba(79, 70, 229, .14); overflow: hidden;
  }
  #busyBar .fill { height: 100%; width: 0%; background: var(--accent); transition: width .12s linear; }
  #busyBar.indet .fill { width: 34%; animation: busysweep 1.05s ease-in-out infinite; }
  @keyframes busysweep { from { transform: translateX(-110%); } to { transform: translateX(400%); } }
  #busyPill {
    position: absolute; left: 50%; top: 12px; transform: translateX(-50%); z-index: 6;
    display: flex; align-items: center; gap: 8px; max-width: calc(100% - 24px);
    background: rgba(28, 28, 36, .93); color: #fff; padding: 7px 14px; border-radius: var(--r-full);
    font-size: var(--fs-sm); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    box-shadow: var(--shadow-3); pointer-events: none;
  }
  #busyPill .ring {
    width: 12px; height: 12px; border-radius: 50%; flex: none;
    border: 2px solid rgba(255, 255, 255, .3); border-top-color: #fff;
    animation: spin .7s linear infinite;
  }
  @keyframes spin { to { transform: rotate(360deg); } }

  #bottomBar { display: flex; justify-content: space-between; align-items: center; gap: var(--sp-3); flex: 0 0 auto; }
  #bottomBar .seg { width: 240px; }

  /* ---------- Overlay, toast ---------- */
  #dropOverlay {
    position: fixed; inset: 0; z-index: 50;
    background: rgba(79, 70, 229, .12);
    backdrop-filter: blur(2px);
    display: flex; align-items: center; justify-content: center;
    pointer-events: none;
  }
  #dropOverlay .box {
    background: var(--card); border: 2px dashed var(--accent); border-radius: var(--r-lg);
    padding: 26px 44px; font-size: var(--fs-lg); font-weight: 600; color: var(--accent);
    box-shadow: var(--shadow-3);
  }
  /* One coherent toast pattern (QoL-8): neutral dark surface, the kind carried
     by the icon — ✓ ok, ! error, i disclosure — errors held longer. */
  #toast {
    position: fixed; bottom: calc(24px + env(safe-area-inset-bottom, 0px)); left: 50%; transform: translateX(-50%); z-index: 80;
    display: flex; align-items: flex-start; gap: 9px;
    background: #1f1f26; color: #fff; padding: 11px 15px; border-radius: var(--r-lg);
    font-size: 13px; line-height: 1.45; box-shadow: var(--shadow-3);
    max-width: min(560px, calc(100vw - 32px));
    animation: toastIn .25s var(--ease);
  }
  @keyframes toastIn { from { opacity: 0; transform: translate(-50%, 10px); } }
  #toast .tIcon {
    flex: none; width: 17px; height: 17px; margin-top: 1px; border-radius: var(--r-full);
    display: flex; align-items: center; justify-content: center;
    font-size: 11px; font-weight: 700; line-height: 1;
  }
  #toast.ok .tIcon { background: rgba(34, 197, 94, .25); color: #7ce8a8; }
  #toast.info .tIcon { background: rgba(129, 140, 248, .3); color: #c3c8ff; }
  #toast.err .tIcon { background: rgba(248, 113, 113, .28); color: #ffb4b4; }

  /* The page-boot veil (QoL-10): visible only under the head script's boot
     flags, above every other surface (#dlScreen 70, #toast 80), so an
     /account arrival — or, since QoL-10.1, a checkout/inbox-link return —
     shows app background, never home content, until its surface is ready.
     Anything that toasts must unveil first: at z-90 the veil would cover the
     toast itself. */
  #bootVeil { display: none; }
  .account-boot #bootVeil, .billing-boot #bootVeil { display: block; position: fixed; inset: 0; z-index: 90; background: var(--bg); }
  /* Only billing boots speak (QoL-10.1) — a silent veil over a paid moment is
     worse than the flash it hides. The /account veil stays silent: its window
     is a parse gap, not a network round trip. */
  #bootVeil p {
    display: none; position: absolute; left: 50%; top: 45%; transform: translate(-50%, -50%);
    color: var(--muted); font-size: var(--fs-md); white-space: nowrap;
    animation: skelPulse 1.2s ease-in-out infinite;
  }
  .billing-boot #bootVeil .vBuy { display: block; }
  .billing-boot.restore-boot #bootVeil .vBuy { display: none; }
  .billing-boot.restore-boot #bootVeil .vRestore { display: block; }
  @media (prefers-reduced-motion: reduce) { #bootVeil p { animation: none; } }

  /* ---------- The Download screen (S3.1, ADR-0051): one paid surface ---------- */
  #dlScreen { position: fixed; inset: 0; z-index: 70; background: var(--bg); overflow-y: auto; }
  #dlPage { max-width: 760px; margin: 0 auto; padding: 22px var(--sp-5) calc(44px + env(safe-area-inset-bottom, 0px)); display: flex; flex-direction: column; gap: var(--sp-4); }
  #dlTop { display: flex; justify-content: flex-start; }
  #dlConfirm {
    background: var(--ok-weak); border: 1px solid var(--ok-line); color: var(--ok-deep);
    border-radius: var(--r-md); padding: 11px 14px; font-size: 13px; font-weight: 550;
  }
  #dlHead {
    display: flex; gap: 14px; align-items: center;
    background: var(--card); border: 1px solid var(--line); border-radius: var(--r-lg);
    padding: var(--sp-4) 18px; box-shadow: var(--shadow-1);
  }
  #dlThumb {
    width: 64px; height: 64px; object-fit: contain; border-radius: var(--r-md); flex: none;
    background: conic-gradient(#ececf1 25%, #fff 0 50%, #ececf1 0 75%, #fff 0) 0 0/16px 16px;
    border: 1px solid var(--line);
  }
  #dlHead h2 { font-size: var(--fs-xl); letter-spacing: -.01em; }
  #dlMeta { color: var(--muted); font-size: var(--fs-sm); margin-top: 3px; line-height: 1.5; }
  #dlFormats { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
  .fmtCard {
    text-align: left; padding: 14px; border-radius: var(--r-lg); display: flex; flex-direction: column;
    gap: 6px; position: relative; border: 1px solid var(--line); background: var(--card);
    cursor: pointer;
    transition: background var(--t-fast), border-color var(--t-fast), box-shadow var(--t-fast), transform var(--t-fast);
  }
  .fmtCard:hover:not(.off):not(.on) { border-color: var(--line-strong); box-shadow: var(--shadow-2); transform: translateY(-2px); }
  .fmtCard strong { font-size: 14.5px; }
  .fmtCard > span { color: var(--muted); font-size: var(--fs-xs); line-height: 1.45; }
  .fmtCard.on { border-color: var(--accent); background: var(--accent-weak); box-shadow: inset 0 0 0 1px var(--accent); }
  /* --muted on the accent-weak wash is 4.43:1 — a hair under AA (QoL-9). */
  .fmtCard.on > span { color: var(--text-2); }
  .fmtCard.on::after { content: '✓'; position: absolute; top: 10px; right: 12px; color: var(--accent); font-weight: 700; }
  .fmtCard.off { opacity: .5; cursor: not-allowed; }
  .fmtCard.off:hover { background: var(--card); border-color: var(--line); box-shadow: none; }
  #dlSizeRow { display: flex; align-items: center; gap: var(--sp-2); margin-top: var(--sp-3); font-size: var(--fs-sm); color: var(--muted); flex-wrap: wrap; }
  #dlSizeRow .lbl { font-weight: 600; color: var(--text); }
  #dlSizeRow input {
    width: 76px; padding: 6px 8px; font-size: 13px; font: inherit;
    border: 1px solid var(--field-line); border-radius: var(--r-sm);
    font-variant-numeric: tabular-nums;
  }
  #dlSizeRow #dlDpi { width: 58px; }
  #dlSizeRow select { padding: 5px 6px; font-size: 13px; }
  #dlScaleSeg { display: flex; gap: 6px; margin-top: 4px; }
  #dlScaleSeg button { padding: 3px 10px; font-size: var(--fs-xs); border-radius: var(--r-sm); }
  #dlScaleSeg button.on { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
  #dlRecap { display: flex; justify-content: space-between; align-items: center; gap: 10px; color: var(--muted); font-size: var(--fs-sm); }
  /* Two actions, one primary (S2.6): Download takes the width, Copy SVG sits
     beside it as a plain button; under 640 they stack, each full width. */
  #dlAction { display: flex; gap: var(--sp-2); flex-wrap: wrap; }
  #dlDownload { flex: 1 1 240px; padding: 13px; font-size: var(--fs-md); border-radius: var(--r-lg); }
  #dlCopy { flex: 0 1 auto; padding: 13px 16px; font-size: var(--fs-md); border-radius: var(--r-lg); }
  #dlSub {
    background: var(--card); border: 1px solid var(--line); border-radius: var(--r-lg);
    padding: var(--sp-6); box-shadow: var(--shadow-2);
    scroll-margin-top: var(--sp-4); /* the 402 route scrolls the card into view (S2.3) */
  }
  #dlSub h2 { font-size: var(--fs-xl); letter-spacing: -.01em; margin-bottom: 6px; }
  #dlSub .lede { color: var(--muted); font-size: 13.5px; line-height: 1.55; }
  #dlSub ul { list-style: none; margin: var(--sp-3) 0 0; display: grid; gap: 7px; }
  #dlSub li { font-size: 13.5px; padding-left: 22px; position: relative; }
  #dlSub li::before { content: '✓'; position: absolute; left: 0; color: var(--accent); font-weight: 700; }
  #dlNoImage {
    background: var(--accent-weak); border: 1px solid var(--accent-line); border-radius: var(--r-lg);
    padding: 14px 16px; font-size: 13px; color: #3b3480; line-height: 1.55;
  }
  #dlDrop {
    border: 1.5px dashed var(--line-strong); border-radius: var(--r-lg); padding: var(--sp-8); text-align: center;
    color: var(--muted); cursor: pointer; font-size: 13.5px; transition: border-color var(--t-fast), color var(--t-fast);
  }
  #dlDrop:hover { border-color: var(--accent); color: var(--text); }
  #resumeChip { margin-top: var(--sp-2); }
  @media (max-width: 640px) {
    #dlFormats { grid-template-columns: 1fr; }
    #dlHead { flex-wrap: wrap; }
    #dlCopy { flex: 1 1 100%; }
  }
  #payPrice {
    display: flex; align-items: baseline; gap: 8px; margin: 18px 0 4px;
  }
  #payPrice .amount { font-size: var(--fs-3xl); font-weight: 700; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
  #payPrice .per { color: var(--muted); font-size: 13px; }
  #payStatus {
    margin-top: 18px; background: var(--accent-weak); border: 1px solid var(--accent-line);
    border-radius: var(--r-md); padding: 11px 13px; font-size: var(--fs-sm); line-height: 1.5; color: #3b3480;
  }
  /* The card's third state (QoL-10): health hasn't answered yet, so neither
     the CTA nor the till-closed note — a skeleton where the answer will
     land. Sized to #payAction's footprint so the settle doesn't jump. */
  #payPending { margin-top: var(--sp-5); display: grid; gap: 8px; }
  #payPending .skel {
    height: 13px; border-radius: var(--r-sm); background: var(--bg-sunken);
    animation: skelPulse 1.2s ease-in-out infinite;
  }
  #payPending .skel-btn { height: 46px; width: 100%; max-width: 380px; border-radius: var(--r-lg); margin-bottom: 4px; }
  @keyframes skelPulse { 50% { opacity: .55; } }
  @media (prefers-reduced-motion: reduce) { #payPending .skel { animation: none; } }
  /* One primary action (S2.3, 2026-09-13): the CTA stands alone under the
     value and the price — full width on phones — and the trust line under it
     answers what is charged, how to cancel, the refund and "do I need an
     account": /terms' own promises, pinned to that page by test. */
  #payAction { margin-top: var(--sp-5); }
  #paySubscribe { width: 100%; max-width: 380px; padding: 13px 18px; font-size: var(--fs-md); border-radius: var(--r-lg); }
  #payTrust { margin-top: 10px; color: var(--muted); font-size: var(--fs-xs); line-height: 1.6; max-width: 560px; }
  #payTrust span + span::before { content: " · "; }
  /* Existing subscribers, behind a disclosure: restore access and manage
     billing share the email field and the inbox round-trip (ADR-0037). */
  #payRestore { margin-top: var(--sp-5); padding-top: var(--sp-3); border-top: 1px solid var(--line); font-size: var(--fs-sm); color: var(--muted); line-height: 1.5; }
  #payRestore summary { cursor: pointer; font-weight: 550; color: var(--text-2); width: fit-content; border-radius: var(--r-sm); }
  #payRestore summary:hover { color: var(--text); }
  #payRestore .body { margin-top: var(--sp-3); }
  #payRestore label { display: block; font-size: var(--fs-xs); font-weight: 600; color: var(--text-2); margin-bottom: 4px; }
  #payRestore input {
    display: block; width: 100%; max-width: 380px; border: 1px solid var(--field-line); border-radius: var(--r-md);
    padding: 8px 10px; font: inherit; font-size: 13px; background: var(--card); color: var(--text);
  }
  #payRestore .row { display: flex; gap: var(--sp-2); margin-top: var(--sp-2); flex-wrap: wrap; }
  #payRestore .hint { margin-top: var(--sp-2); font-size: var(--fs-xs); }
  @media (max-width: 640px) {
    #paySubscribe, #payPending .skel-btn, #payRestore input { max-width: none; }
    #payRestore .row button { flex: 1 1 140px; }
  }
  /* The /account page's own chrome (S2.4 §3): on a page boot the real header
     sits inert under the overlay, so the screen carries a brand row — the
     header's .brand (chrome block above) and the site links — shown only
     then; every in-app open of the screen hides it. */
  #dlChrome {
    display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
    flex-wrap: wrap; padding-bottom: var(--sp-3); border-bottom: 1px solid var(--line);
  }
  #dlChrome nav { display: flex; align-items: center; gap: 2px; }
  #dlChrome nav a {
    color: var(--text-2); text-decoration: none; font-weight: 500; font-size: 13.5px;
    padding: 6px 11px; border-radius: var(--r-full);
    transition: background var(--t-fast), color var(--t-fast);
  }
  #dlChrome nav a:hover { color: var(--text); background: var(--bg-sunken); }
  /* The account panel (S2.4, ADR-0058): the heading, then the pending
     skeleton until the status read and health have both answered, then the
     state the server named — never a guess. The subscriber's lines, the
     failed-payment lead (warn ink, Manage billing promoted to the one
     action), or the ended plan's date above the card. */
  #dlAccount {
    background: var(--card); border: 1px solid var(--line); border-radius: var(--r-lg);
    padding: var(--sp-5) var(--sp-6); box-shadow: var(--shadow-1);
  }
  #dlAccount h2 { font-size: var(--fs-xl); letter-spacing: -.01em; }
  #acctPending { display: grid; gap: 8px; margin-top: var(--sp-3); }
  #acctPending .skel {
    height: 13px; border-radius: var(--r-sm); background: var(--bg-sunken);
    animation: skelPulse 1.2s ease-in-out infinite;
  }
  @media (prefers-reduced-motion: reduce) { #acctPending .skel { animation: none; } }
  #acctError {
    display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; margin-top: var(--sp-3);
    color: var(--text-2); font-size: 13.5px; line-height: 1.5;
  }
  #acctStatus { margin-top: var(--sp-2); font-size: 13.5px; line-height: 1.55; }
  #acctWho { color: var(--text-2); }
  #acctPlan { margin-top: 6px; font-weight: 600; }
  #acctPlan.due { color: var(--warn); }
  #acctNote { margin-top: 6px; color: var(--muted); }
  #acctActions { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; margin-top: var(--sp-4); }
  #acctManageHint { color: var(--muted); font-size: var(--fs-xs); line-height: 1.5; max-width: 440px; }
  @media (max-width: 640px) {
    #dlAccount { padding: var(--sp-4); }
    #acctManage { width: 100%; }
  }
  #fixChip {
    position: absolute; left: 50%; bottom: 12px; transform: translateX(-50%);
    display: flex; align-items: center; gap: 8px; max-width: calc(100% - 24px);
    background: var(--card); border: 1px solid var(--line); border-radius: var(--r-md);
    padding: 7px 8px 7px 12px; font-size: var(--fs-sm); white-space: nowrap;
    box-shadow: var(--shadow-3);
  }
  #fixChip::before { content: ''; width: 8px; height: 8px; border-radius: var(--r-full); background: var(--accent); flex: none; }
  #fixChip span { color: var(--text); overflow: hidden; text-overflow: ellipsis; }

  /* ---------- Small screens (QoL-9, B-010): the payoff leads ---------- */
  /* ≤980 the frame stacks and the STAGE comes first — the DOM itself reorders
     (placeSidebar() moves the aside below the stage) so keyboard tab order
     matches what the eye sees, not just the CSS. One page scroll, no nested
     scroll areas: on touch, an inner overflow pane is where flings go to die. */
  @media (max-width: 980px) {
    #shell { height: auto; min-height: 100dvh; }
    main { padding: var(--sp-2); }
    #appFrame { flex-direction: column; border-radius: var(--r-lg); }
    aside {
      width: auto; flex: 0 0 auto; overflow: visible;
      border-right: none; border-top: 1px solid var(--line);
    }
    /* The visual order is fixed here, not only by placeSidebar()'s DOM move:
       app.js is deferred, so the first frame can paint the aside above the
       stage and the move then reads as a layout shift — R3 measured it as
       a 0.28 CLS on #stage, in 2 of 3 runs. With the stage first from the
       first paint the DOM move (kept, for tab order) changes nothing on
       screen (S2.1b). */
    #stage { order: 1; }
    aside { order: 2; }
    .canvas { min-height: 300px; }
  }
  /* Tablet (~601–980): both panes stay up; the control sheet reads as cards,
     two abreast, and the intro's three steps sit in a row. */
  @media (min-width: 601px) and (max-width: 980px) {
    #traceControls { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); align-items: start; }
    #traceControls details.group {
      border: 1px solid var(--line); border-radius: var(--r-md);
      padding: 10px 12px;
    }
    .introSteps { flex-direction: row; gap: var(--sp-5); }
    .introSteps li { flex: 1; }
  }
  /* Phone (≤600): ONE pane at a time behind the Original/Preview toggle, the
     canvas capped so head + picture + readout + zoom row all land inside the
     first viewport — that cap is what closes B-010's "preview in-viewport
     with zero scrolling" bar at 375×812. */
  @media (max-width: 600px) {
    #stage { padding: var(--sp-3); gap: var(--sp-3); }
    #paneTabs:not([hidden]) { display: flex; }
    #panes { grid-template-columns: 1fr; }
    #panes.show-orig .pane.result { display: none; }
    #panes:not(.show-orig) .pane:not(.result) { display: none; }
    /* flex-basis auto, or the pane's flex column sizes the canvas from its
       220px min and ignores the height — the clamp is the whole point here. */
    .canvas { flex: 0 0 auto; min-height: 0; height: clamp(220px, 46vh, 560px); height: clamp(220px, 46svh, 560px); }
    #empty { min-height: 56vh; min-height: 56svh; }
    /* The phone's drop zone (S2.5): the phone copy and the camera control
       show, the desktop copy goes, and the zone tightens — icon, chips,
       padding — so the samples still land inside the first screen under a
       landing hero (measured at 375×812 in the roadmap log). */
    #empty { padding: var(--sp-4) var(--sp-3); gap: 8px; }
    #empty .onDesk { display: none; }
    #empty .onPhone { display: inline; }
    #empty button.onPhone { display: inline-block; }
    #empty .icon svg { width: 36px; height: 36px; }
    #empty h2 { font-size: 24px; }
    /* The four proof claims read as one wrapped line here, not four pills:
       the hero above already states them, and the pills cost three rows —
       the difference between the samples inside the first screen and under
       it on the landing pages (S2.5, measured at 375×812). */
    .proofRow { gap: 2px 12px; margin-top: var(--sp-1); max-width: none; }
    .proof { background: none; border: 0; box-shadow: none; padding: 0; font-size: var(--fs-xs); color: var(--muted); }
    .proof:hover { transform: none; box-shadow: none; }
    .sampleRow { margin-top: var(--sp-2); }
    /* The proof line stays off the phone's first screen (S2.6): the hero
       above states the promise and S2.5 measured the samples' 812 px bar
       with 5 px to spare on the landing pages. */
    .proofLine { display: none; }
    .paneFoot, .paneFoot .stats { gap: var(--sp-3) var(--sp-4); }
    .paneFoot { padding: 8px 12px 9px; }
    /* One CTA per screen (S2.6): the sticky header reads "Download" on a
       phone, so the readout's own button does not show here. */
    .resultCta { display: none; }
    #bottomBar { flex-wrap: wrap; row-gap: var(--sp-2); }
    #bottomBar .seg { width: auto; }
    #zoomBar { order: -1; flex: 1 1 100%; flex-wrap: wrap; }
    #bottomBar > .row { margin-left: auto; }
    header .brand .sub { display: none; }
    #downloadBtn .btnTxt { display: none; }
    .actions .btnTxtShort { display: inline; }
    .actions { gap: 6px; }
  }
  /* Touch (any width): the named targets — zoom segment, layer eyes, merge
     menus — reach 44px, sliders get a thumb a finger can land on, and text
     fields hold 16px so iOS Safari stops zooming the page into them. */
  @media (pointer: coarse) {
    .actions button { min-height: 44px; }
    .seg button { min-height: 44px; min-width: 44px; }
    input[type=range] { height: 44px; }
    input[type=range]::-webkit-slider-thumb { width: 22px; height: 22px; margin-top: -9px; }
    input[type=range]::-moz-range-thumb { width: 18px; height: 18px; }
    input[type=checkbox] { width: 20px; height: 20px; }
    .layer-row { min-height: 48px; }
    .layer-row .eye { width: 44px; height: 44px; }
    .layer-row input[type=color] { width: 38px; height: 32px; }
    /* 16px in every text-entry and select, or iOS Safari zooms the page into
       the field on focus — selectors match the base rules' specificity. */
    select, .layer-row select { min-height: 44px; font-size: 16px; }
    .layer-row select { max-width: 108px; }
    #payRestore input, #dlSizeRow input { font-size: 16px; min-height: 42px; }
    .why { width: 22px; height: 22px; font-size: var(--fs-xs); }
    .why::after { inset: -11px; }
    button.small { min-height: 36px; }
    #navToggle { height: 36px; }
  }
  /* The pane toggle strip (phone only; hidden alongside #panes). */
  #paneTabs { display: none; flex: 0 0 auto; }
  #paneTabs .seg { flex: 1; }
  /* Announcements for assistive tech (QoL-9): visually hidden, never display:
     none — a live region must stay in the accessibility tree to speak. */
  .srOnly {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  }
  /* App-page extension, not a chrome-block edit: the home indicator's inset. */
  footer#siteFooter { padding-bottom: calc(36px + env(safe-area-inset-bottom, 0px)); }

  /* ---------- App shell ---------- */
  /* The app keeps the whole viewport: #shell is the 100dvh app frame and the
     footer sits below the fold, reached by scroll — the preview pane never
     shrinks for chrome. */
  #shell { height: 100vh; height: 100dvh; display: flex; flex-direction: column; flex: 0 0 auto; }
  /* The mount div is a marker, not a box (ADR-0054): it pins the static
     skeleton and tells app.js the page carries the converter; display:
     contents keeps #appFrame a direct flex child of main exactly as before. */
  #unpixel-app { display: contents; }

  /* ---------- Embedded converter pages (S2.1b, ADR-0057) ---------- */
  /* A landing page carries the converter mid-document: the same skeleton,
     this same stylesheet, and this block — everything a page needs to host
     #appFrame below a hero and above its own copy. Hooked on <body class="lp">
     so the home page (no class) is untouched. The element rules above (main,
     button, aside, select, the inputs) are lived with rather than scoped: a
     landing page writes no <aside>, <select> or range input of its own, and
     every class it introduces is lp-*. */
  body.lp { height: auto; min-height: 100%; }
  .lp #shell { height: auto; min-height: 0; }
  .lp main { display: block; padding: 0; background: none; min-height: 0; flex: 1 0 auto; }
  .lp-app { padding: 0 var(--sp-5); }
  /* The frame's height is the document's to set here — before S2.5 the home
     page's came from #shell's 100dvh; mid-page it is a section: header + a
     two-line hero measure ~314px at 1280 wide, so 100vh − 300px lands the
     frame's foot near the fold on a 900px laptop and the drop zone stays
     generous on anything taller. The home page's own line (.lp-home, below)
     is shorter, and its frame is given the difference. */
  .lp #appFrame { height: clamp(540px, calc(100vh - 300px), 800px); scroll-margin-top: 76px; }
  .lp-hero { max-width: 780px; margin: 0 auto; padding: 30px var(--sp-5) 18px; text-align: center; }
  .lp-kicker {
    font-size: 11.5px; font-weight: 700; letter-spacing: .12em;
    text-transform: uppercase; color: var(--accent); margin-bottom: 8px;
  }
  .lp-hero h1 { font-size: var(--fs-3xl); line-height: 1.15; letter-spacing: -.02em; font-weight: 700; }
  .lp-hero p { margin-top: 10px; font-size: var(--fs-md); line-height: 1.6; color: var(--text-2); }
  /* The home page's hero (S2.5, M51 → (a)): a line above the app, not a page.
     Tighter than a landing page's, the h1 sized between the drop zone's h2
     and a landing h1, and the frame given the height the shorter hero leaves:
     header (64) + hero (~105) + a breath = 100vh − 212px, so at 720 tall the
     frame (520 min) still ends inside the first screen with the drop zone —
     samples, resume chip and all — inside it (measured: S2.5's log). */
  .lp-home .lp-hero { max-width: 920px; padding: 22px var(--sp-5) 12px; }
  .lp-home .lp-hero h1 { font-size: clamp(var(--fs-2xl), 2.4vw, 30px); }
  .lp-home #appFrame { height: clamp(520px, calc(100vh - 212px), 860px); }
  /* Before an image exists the frame is content-sized (S2.5): the drop zone
     grew a sample row, and with a resume chip on a 720-tall window a fixed
     520–540 px frame clipped its foot. The clamp becomes the floor, the zone
     can never overflow it, and the moment an image is in — #empty hidden —
     the fixed height is back: it is what makes the workspace a viewport and
     the sidebar scroll. Side-by-side layouts only; the stacked ones (≤980)
     are content-sized already. A browser without :has() keeps the fixed
     height, exactly as before. */
  @media (min-width: 981px) {
    .lp #appFrame:has(#empty:not([hidden])) { height: auto; min-height: clamp(540px, calc(100vh - 300px), 800px); }
    .lp-home #appFrame:has(#empty:not([hidden])) { min-height: clamp(520px, calc(100vh - 212px), 860px); }
  }
  .lp-hero a, .lp-body a { color: var(--accent); }
  .lp-body { max-width: 960px; margin: 0 auto; padding: 40px var(--sp-5) 56px; }
  .lp-section { padding: 30px 0; border-top: 1px solid var(--line); }
  .lp-section:first-child { border-top: none; padding-top: 0; }
  .lp-section h2 { font-size: 22px; line-height: 1.3; letter-spacing: -.01em; margin-bottom: 12px; }
  .lp-section p { font-size: 14.5px; line-height: 1.65; margin-bottom: 12px; }
  .lp-section p:last-child { margin-bottom: 0; }
  .lp-fine { color: var(--muted); font-size: var(--fs-sm) !important; line-height: 1.5; }
  .lp-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; margin-top: 18px; }
  .lp-offer { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-top: 18px; }
  .lp-card {
    background: var(--card); border: 1px solid var(--line); border-radius: var(--r-lg);
    padding: 18px; box-shadow: var(--shadow-1);
  }
  .lp-card h3 { font-size: var(--fs-md); margin-bottom: 8px; letter-spacing: -.01em; }
  .lp-card p { font-size: 13.5px; color: var(--text-2); margin: 0; line-height: 1.55; }
  .lp-card .lp-fine { margin-top: 10px; }
  .lp-facts { list-style: none; display: grid; gap: 8px; margin: 0; }
  .lp-facts li { position: relative; padding-left: 22px; font-size: 14px; line-height: 1.55; }
  .lp-card .lp-facts li { font-size: 13.5px; color: var(--text-2); }
  .lp-facts li::before { content: '✓'; position: absolute; left: 0; color: var(--accent); font-weight: 700; }
  .lp-facts strong, .lp-card strong { color: var(--text); font-weight: 600; }
  .lp-price { font-size: var(--fs-2xl); font-weight: 700; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
  .lp-price .lp-per { font-size: 13px; font-weight: 500; color: var(--muted); letter-spacing: 0; margin-left: 4px; }
  .lp-faq details { border-bottom: 1px solid var(--line); }
  .lp-faq details:first-child { border-top: 1px solid var(--line); }
  .lp-faq summary {
    list-style: none; cursor: pointer; display: flex; align-items: center; gap: 12px;
    padding: 13px 0; user-select: none; -webkit-user-select: none;
  }
  .lp-faq summary::-webkit-details-marker { display: none; }
  .lp-faq summary h3 { font-size: var(--fs-md); font-weight: 600; letter-spacing: -.01em; flex: 1; }
  .lp-faq summary::after {
    content: ''; width: 8px; height: 8px; flex: none; margin-right: 4px;
    border-right: 1.5px solid var(--muted); border-bottom: 1.5px solid var(--muted);
    transform: rotate(45deg); transition: transform var(--t-fast);
  }
  .lp-faq details[open] summary::after { transform: rotate(-135deg); }
  .lp-faq summary:hover h3 { color: var(--accent); }
  .lp-answer { padding: 0 0 16px; }
  .lp-answer p { font-size: 14.5px; line-height: 1.65; color: var(--text-2); margin-bottom: 10px; }
  .lp-answer p:last-child { margin-bottom: 0; }
  .lp-closer {
    margin-top: 34px; padding: 30px 24px; text-align: center;
    background: var(--card); border: 1px solid var(--line); border-radius: var(--r-xl); box-shadow: var(--shadow-2);
  }
  .lp-closer h2 { font-size: 20px; margin-bottom: 6px; letter-spacing: -.01em; }
  .lp-closer p { color: var(--muted); font-size: var(--fs-base); }
  .lp-cta-row { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; margin-top: 16px; }
  .lp-btn {
    display: inline-flex; align-items: center; gap: 8px; text-decoration: none;
    font-weight: 600; font-size: 14px; padding: 10px 18px; border-radius: var(--r-md);
    border: 1px solid var(--line-strong); background: var(--card); color: var(--text) !important;
    transition: background var(--t-fast), box-shadow var(--t-fast), transform var(--t-fast);
  }
  .lp-btn:hover { background: var(--bg-sunken); transform: translateY(-1px); box-shadow: var(--shadow-1); }
  .lp-btn.lp-primary {
    background: linear-gradient(180deg, #6159f2, var(--accent)); border-color: var(--accent);
    color: var(--on-accent) !important; box-shadow: var(--shadow-1);
  }
  .lp-btn.lp-primary:hover { background: linear-gradient(180deg, #574ee8, var(--accent-strong)); box-shadow: 0 3px 10px rgba(79, 70, 229, .3); }
  .lp-links { margin-top: 18px; font-size: 13.5px; color: var(--muted); }
  .lp-list { padding-left: 20px; display: grid; gap: 8px; margin: 0; }
  .lp-list li { font-size: 14.5px; line-height: 1.6; }
  .lp-grid.lp-4 { grid-template-columns: repeat(4, 1fr); }
  @media (max-width: 980px) {
    .lp #appFrame { height: auto; min-height: 62vh; }
    .lp-grid.lp-4 { grid-template-columns: 1fr 1fr; }
  }
  @media (max-width: 820px) {
    .lp-grid, .lp-offer { grid-template-columns: 1fr; }
  }
  @media (max-width: 600px) {
    .lp-grid.lp-4 { grid-template-columns: 1fr; }
  }
  @media (max-width: 600px) {
    .lp-hero { padding: 16px var(--sp-4) 10px; }
    .lp-home .lp-hero { padding: 14px var(--sp-4) 10px; }
    .lp-hero h1 { font-size: 24px; }
    .lp-hero p { font-size: var(--fs-base); }
    .lp-app { padding: 0 var(--sp-2); }
    .lp-body { padding: 30px var(--sp-4) 40px; }
    .lp-section h2 { font-size: 20px; }
  }
