/* Stitch Slop — 2a "Zoned editor" chrome (Claude Design handoff 2026-08-11).
 * Industry design-system tokens, square-cornered blueprint look. Layout rule
 * from the design: a control lives at the edge that matches its scope —
 * job (top), tools (left), placement (canvas bar), object (right panel),
 * viewing/diagnostics (canvas overlays), thread (bottom strip). */

:root {
  --color-bg: #f2f2f3;
  --color-surface: #e9e9ea;
  --color-text: #1d1f20;
  --color-accent: #4d6e8f;   /* 4.76:1 under --color-bg text on fills and as hover text (WCAG 1.4.3; was #5980a6, 3.71:1) */
  --color-divider: color-mix(in srgb, #1d1f20 16%, transparent);

  --color-neutral-100: #f5f5f8;
  --color-neutral-200: #e7e7ea;
  --color-neutral-300: #d4d4d7;
  --color-neutral-400: #b7b7ba;
  --color-neutral-500: #98989b;
  --color-neutral-600: #7a7a7d;
  --color-neutral-700: #5d5d60;
  --color-neutral-800: #424244;
  --color-neutral-900: #2b2b2d;

  --color-accent-100: #eef6ff;
  --color-accent-300: #b5d9fd;
  --color-accent-400: #94bce3;
  --color-accent-600: #597ea3;
  --color-accent-700: #416180;
  --color-accent-800: #2c455d;
  --color-accent-900: #1d2d3d;

  /* Semantic surfaces, so dark mode has one place to re-point them. */
  --color-canvas: #fbfbfc;   /* default paper when doc.canvasColor is AUTO */
  --color-card:   #ffffff;   /* raised cards, popovers, thumbnails */
  --color-danger: #c0392b;   /* destructive actions (delete / leave / purge) */
  /* Secondary TEXT (labels, units, hints, counts). Separate from the neutral
     ramp, whose 500/600 steps also draw borders and icons: as text they were
     2.3–4.3:1 on the panel backgrounds, under WCAG 1.4.3's 4.5:1. These hold
     ≥4.8:1 on bg, surface, neutral-100/200 and card. */
  --color-text-muted:  #646467;   /* was neutral-500 as text */
  --color-text-subtle: #5d5d60;   /* was neutral-600 as text */
  /* On-screen cues (overlay.js): violet, so a tip never reads as the blue
     selection or an amber warning. Ring ≥3:1 on any paper (the halo and edge
     carry it on a dark one). */
  --cue-ring: #6d28d9; --cue-halo: #ffffff; --cue-edge: #1d1f20;
  --cue-ink: #5b21b6; --cue-mark-ink: #ffffff; --cue-dim: rgb(0 0 0 / .35);
  --cue-mark-bg: #000000; --cue-mark-fg: #ffffff;   /* default marker: maximum contrast */

  --warn: #a8813f;
  --warn-text: #7d5f26;   /* --warn as TEXT: #a8813f is 2.9–3.6:1 on the panels; this is ≥4.8 */

  --font-heading: "Barlow Condensed", "Arial Narrow", system-ui, sans-serif;
  --font-body: "Barlow", system-ui, -apple-system, sans-serif;

  --shadow-sm: 0 1px 3px color-mix(in srgb, #1d1f20 14%, transparent);
  --shadow-md: 0 4px 14px color-mix(in srgb, #1d1f20 18%, transparent);
}

/* Dark mode (user go 2026-09-13). UI-ONLY: this re-points the palette tokens;
   the 344 var() uses across the sheet flip for free. The canvas paper is NOT
   themed here — it follows the theme only while AUTO, decided in render.js from
   --color-canvas. The neutral ramp is INVERTED (100 = subtlest fill … 900 =
   strongest text) so a rule written for light keeps its meaning in dark. Set on
   [data-theme="dark"] which the <head> boot script stamps before first paint. */
:root[data-theme="dark"] {
  --color-bg: #17191b;
  --color-surface: #212427;
  --color-text: #e7e8ea;
  --color-accent: #6f99c2;
  --color-divider: color-mix(in srgb, #e7e8ea 18%, transparent);

  --color-neutral-100: #202325;
  --color-neutral-200: #2a2d30;
  --color-neutral-300: #363a3d;
  --color-neutral-400: #4b4f53;
  --color-neutral-500: #7d8186;
  --color-neutral-600: #9a9ea3;
  --color-neutral-700: #b7babe;
  --color-neutral-800: #d2d4d7;
  --color-neutral-900: #edeef0;

  --color-accent-100: #1f2c38;
  --color-accent-300: #2d4257;
  --color-accent-400: #3c5b78;
  --color-accent-600: #7ba1c7;
  --color-accent-700: #9abcdd;
  --color-accent-800: #b9d3ef;
  --color-accent-900: #d6e5f7;

  --color-canvas: #1a1c1e;
  --color-card:   #26292c;
  --color-danger: #e05a4d;   /* lifted so it reads on a dark surface */
  --color-text-muted:  #909499;   /* ≥4.5:1 on every dark surface (n500 was 3.5) */
  --color-text-subtle: #9a9ea3;
  --cue-ring: #a78bfa; --cue-halo: #111214; --cue-edge: #ffffff;
  --cue-ink: #c4b5fd; --cue-mark-ink: #111214; --cue-dim: rgb(0 0 0 / .5);
  --cue-mark-bg: #ffffff; --cue-mark-fg: #000000;

  --warn: #d0a24e;
  --warn-text: #d0a24e;   /* already ≥5.9:1 on every dark surface */

  --shadow-sm: 0 1px 3px color-mix(in srgb, #000000 40%, transparent);
  --shadow-md: 0 4px 14px color-mix(in srgb, #000000 50%, transparent);
  /* Native controls, scrollbars and <select> popups render dark too */
  color-scheme: dark;
}
:root { color-scheme: light; }   /* the dark block above (higher specificity) sets dark */
/* Controls NO rule of ours styles (the Stitch out and player buttons, the
   text tool's Load/Reload, the Show panel's number fields) used to keep the
   browser's LIGHT face under the dark theme's light ink: white on white.
   They take the palette instead. :where() holds this at bare-element
   specificity, so every class or id rule that does style a control wins. */
:where(:root[data-theme="dark"]) button {
  background-color: var(--color-neutral-200);
  border: 1px solid var(--color-neutral-400);
}
:where(:root[data-theme="dark"]) :is(select, textarea,
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"])) {
  background-color: var(--color-surface);
  color: var(--color-text);
  border: 1px solid var(--color-neutral-400);
}

* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
/* No two-finger swipe-back navigation (or page bounce) out of the editor:
   a sideways trackpad swipe over a panel left the app (Accessibility,
   WCAG 3.2, 2026-09-21). */
html, body { overscroll-behavior: none; }
body {
  font: 13px/1.4 var(--font-body);
  color: var(--color-text);
  background: var(--color-bg);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  user-select: none;
  -webkit-user-select: none;   /* Safari needs the prefix */
}
button { font: inherit; color: inherit; }

/* Stubs — designed chrome that is not wired yet. Visible, inert, honest. */
.stub { opacity: .45; cursor: not-allowed !important; }
.stub:hover { background: inherit; }
/* Wired buttons that are merely unavailable right now (undo with an empty
   stack) dim the same way but keep an honest cursor. */
button:disabled:not(.stub) { opacity: .45; cursor: default; }

/* ---------------- top bar (job scope) ---------------- */
#topbar {
  height: 46px; flex: none;
  display: flex; align-items: center; gap: 12px;
  padding: 0 14px;
  border-bottom: 1px solid var(--color-divider);
  background: var(--color-neutral-100);
}
/* `margin: 0` is load-bearing since this became an <h1>: a heading's default
   .67em block margin would push the whole flex toolbar out of shape. */
#topbar .logo { font: 600 17px/1 var(--font-heading); letter-spacing: .04em; margin: 0; }
#topbar .vsep { width: 1px; height: 22px; background: var(--color-divider); }
#topbar .docname { display: flex; align-items: center; gap: 8px; font-size: 13px; }
#topbar .docname b { font: 600 15px/1 var(--font-heading); cursor: text; }
/* Non-empty doc still named Untitled: a soft prompt, never a gate. */
#topbar .docname b.unnamed {
  color: var(--color-text-muted);
  border-bottom: 1px dashed var(--color-neutral-400, #aaa);
}
#topbar .docname input {
  font: 600 15px/1 var(--font-heading); color: var(--color-text);
  height: 26px; padding: 0 6px; width: 16em;
  border: 1px solid var(--color-divider); background: transparent;
}
#topbar .docname span { font-size: 11px; color: var(--color-text-muted); }
#topbar .right { margin-left: auto; display: flex; align-items: center; gap: 8px; }
#topbar button {
  height: 28px; padding: 0 10px;
  border: 1px solid var(--color-divider); background: transparent;
  font-size: 12px; color: var(--color-neutral-700); cursor: pointer;
}
#topbar button:hover:not(.stub) { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
#topbar .primary {
  border-color: var(--color-accent-800);
  background: var(--color-accent-800); color: var(--color-bg);
  font-weight: 600; padding: 0 14px;
}
/* :not(.stub) matches the generic `#topbar button:hover:not(.stub)` tint's
   specificity — without it that tint won, and a hovered Export button lost its
   fill and its label (both themes, found by the contrast audit 2026-09-20). */
#topbar .primary:hover:not(.stub) { background: var(--color-accent-900); }
/* ONE LINE, always (user report 2026-09-20: at ~1200px "Project settings",
   "Download Project", Export's caret, Agent's glyph and Talk's "1 waiting"
   badge all wrapped to two lines). Nothing in the bar wraps or shrinks; as
   the window narrows, lower-priority text steps out instead (below). */
#topbar button, #topbar .docname span, #voice-btn .voice-count { white-space: nowrap; }
#topbar button, #topbar .docname, #topbar .logo { flex: none; }
#topbar { min-width: 0; }
/* The signed-in email can be long: truncate it, never grow the bar. */
#topbar #account-btn { display: inline-block; max-width: 190px; overflow: hidden; text-overflow: ellipsis; line-height: 26px; }
@media (max-width: 1240px) { #topbar .tb-long { display: none; } #topbar #account-btn { max-width: 140px; } }
@media (max-width: 1120px) { #topbar .docname span { display: none; } #topbar { gap: 8px; } #topbar .right { gap: 6px; } }
@media (max-width: 1000px) { #topbar #account-btn { max-width: 90px; } #new-btn span, #import-btn span { display: none; } }
#proj-btn {
  display: flex; align-items: center; gap: 8px;
  border: 1px solid var(--color-accent) !important;
  background: var(--color-accent-100) !important;
  color: var(--color-accent-800) !important;
}

/* popovers */
.pop {
  position: absolute; z-index: 30;
  background: var(--color-bg);
  border: 1px solid var(--color-divider);
  box-shadow: var(--shadow-md);
  padding: 14px 16px;
  display: none;
}
.pop.open { display: block; }
.pop h5, .modal-card h5 {
  margin: 0 0 12px;
  font: 600 16px/1 var(--font-heading);
  display: flex; align-items: baseline; gap: 9px;
}
.pop h5 small, .modal-card h5 small { font: 400 11px/1 var(--font-body); color: var(--color-text-subtle); }
.pop .field, .modal-card .field { margin-bottom: 11px; }
.pop .field > label, .modal-card .field > label { display: block; font-size: 12px; color: var(--color-text-subtle); margin-bottom: 4px; }
.pop .field select, .pop .field .faux, .modal-card .field select, .modal-card .field .faux {
  width: 100%; height: 30px; padding: 0 9px;
  border: 1px solid var(--color-divider); background: transparent;
  font: 13px var(--font-body); color: var(--color-text);
  display: flex; align-items: center; justify-content: space-between;
}
.pop .field .note, .modal-card .note { font-size: 11px; color: var(--color-text-subtle); margin-top: 4px; }
.pop .chk, .modal-card .chk { display: flex; align-items: center; gap: 7px; font-size: 12px; color: var(--color-neutral-700); cursor: pointer; }
.pop .chk[hidden] { display: none; }   /* beats the display:flex above */
.pop .field .chk + .chk { margin-top: 6px; }   /* two switches in one field */
#proj-pop {
  top: 42px; left: 210px; width: 330px;
  max-height: calc(100vh - 42px - 12px);   /* short screens: scroll inside, never off the bottom */
  overflow-y: auto; overscroll-behavior: contain;
}
#export-pop { top: 42px; right: 14px; width: 170px; padding: 8px; }
#account-pop { top: 42px; right: 14px; width: 300px; }
/* the sign-up consent line under Send sign-in email: the same quiet note as the others */
#account-out .account-legal { font-size: 11px; color: var(--color-text-subtle); margin-top: 8px; line-height: 1.45; }
#account-out .account-legal a { color: inherit; text-decoration: underline; }
#account-pop .account-links { margin-top: 12px; padding-top: 8px; border-top: 1px solid var(--color-divider);
  font-size: 11px; color: var(--color-text-subtle); text-align: center; }
#account-pop .account-links a { color: inherit; text-decoration: underline; }
#account-out:not([hidden]) ~ .account-links .dup { display: none; }
/* Center-screen modal shell (the logout collision dialog): a dim backdrop
   that takes over the canvas, with a focused centered card, in place of the
   small corner pop for dialogs that deserve the whole screen (user 2026-09-08). */
.modal {
  position: fixed; inset: 0; z-index: 100;
  display: none; align-items: center; justify-content: center;
  padding: 20px;
  background: color-mix(in srgb, var(--color-text) 45%, transparent);
}
.modal.open { display: flex; }
.modal-card {
  position: relative;
  background: var(--color-bg);
  border: 1px solid var(--color-divider);
  box-shadow: var(--shadow-md);
  padding: 22px 24px;
  width: min(94vw, 340px);
  max-height: 88vh; overflow-y: auto;
}
.modal-close {
  position: absolute; top: 8px; right: 10px;
  width: 26px; height: 26px; padding: 0;
  border: 0; background: transparent; cursor: pointer;
  font: 400 20px/1 var(--font-body); color: var(--color-text-subtle);
}
.modal-close:hover { color: var(--color-text); }
/* Keyboard shortcut sheet */
.modal-card.keys-card { width: min(94vw, 460px); }
.keys-group { margin-top: 12px; }
.keys-head { font: 600 11px/1 var(--font-heading); letter-spacing: .08em; text-transform: uppercase; color: var(--color-text-subtle); margin-bottom: 6px; }
.keys-row { display: flex; gap: 12px; align-items: baseline; font-size: 12.5px; padding: 3px 0; }
.keys-row kbd, .keys-link kbd {
  flex: none; min-width: 108px; font: 500 11.5px/1.4 var(--font-heading); font-variant-numeric: tabular-nums;
  color: var(--color-text);
}
.keys-link {
  display: flex; justify-content: space-between; width: 100%; margin-top: 8px;
  border: 0; background: none; padding: 4px 0; cursor: pointer; font-size: 12px; color: var(--color-accent-800);
}
.keys-link kbd { min-width: 0; color: var(--color-text-subtle); }
/* System-font picker list (Local Font Access API) */
.fp-title { font-weight: 600; font-size: 14px; margin-bottom: 10px; }
#fp-search {
  width: 100%; box-sizing: border-box; height: 30px; padding: 0 8px; margin-bottom: 8px;
  border: 1px solid var(--color-divider); background: var(--color-bg);
  color: var(--color-text); font: 13px var(--font-body);
}
.fp-list {
  max-height: 46vh; overflow-y: auto;
  border: 1px solid var(--color-divider);
}
.fp-item {
  display: block; width: 100%; text-align: left; padding: 6px 10px;
  border: 0; border-bottom: 1px solid var(--color-divider);
  background: transparent; color: var(--color-text); cursor: pointer;
  font: 13px var(--font-body); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.fp-item:last-child { border-bottom: 0; }
.fp-item:hover { background: color-mix(in srgb, var(--color-accent-800) 12%, transparent); }
.fp-item small { color: var(--color-text-muted); margin-left: 6px; }
.linkish {
  border: 0; background: none; padding: 0; cursor: pointer;
  color: var(--color-accent-800); font: inherit; text-decoration: underline;
}
.modal-actions { display: flex; gap: 8px; margin-top: 16px; }
.modal-actions button {
  flex: 1; height: 32px; padding: 0 12px; cursor: pointer;
  border: 1px solid var(--color-divider); background: transparent;
  font: 13px var(--font-body); color: var(--color-text);
}
.modal-actions button:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.modal-actions .primary {
  border-color: var(--color-accent-800); background: var(--color-accent-800);
  color: var(--color-bg); font-weight: 600;
}
.modal-actions .primary:hover { background: var(--color-accent-900); }
/* Force Logout — destructive, so it reads as danger (loses unsynced work). */
.modal-force { border-color: var(--color-danger) !important; color: var(--color-danger) !important; }
/* Delete account: a quiet danger link under Log out, never next to it as a twin */
#account-in .account-delete { display: block; margin-top: 10px; padding: 0; border: 0; background: none;
  color: var(--color-danger); font-size: 12px; text-decoration: underline; cursor: pointer; }
#delete-modal .modal-force:disabled { opacity: .45; cursor: default; background: none !important; }
.modal-force:hover { background: var(--color-danger) !important; color: #fff !important; }
/* Spinner — an in-flight cloud sync during logout, so the wait reads as motion
   (user 2026-09-09: give confidence something is happening). */
.spinner {
  display: inline-block; width: 14px; height: 14px; vertical-align: -2px; margin-right: 7px;
  border: 2px solid var(--color-neutral-400);
  border-top-color: var(--color-accent-800);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* library sidebar */
#library {
  width: 272px; flex: none;
  border-right: 1px solid var(--color-divider);
  background: var(--color-bg);
  padding: 10px;
  /* Two independent scroll fields (user ruling 2026-09-02): the aside is
     a column, headers pin, and My designs / Library each scroll alone. */
  display: flex; flex-direction: column; overflow: hidden;
}
#library[hidden] { display: none; }
/* Sidebar search: pinned above the lists (it never scrolls away) */
.lib-search { flex: none; margin-bottom: 8px; }
#lib-search {
  width: 100%; padding: 6px 9px; font: inherit; font-size: 12.5px;
  border: 1px solid var(--color-divider); border-radius: 5px;
  background: var(--color-surface); color: var(--color-text);
}
#lib-search:focus { outline: none; border-color: var(--color-accent); }
/* ---- tabs (redesign 2026-09-20): Mine / Shared / Library ---- */
.lib-tabs {
  flex: none; display: grid; grid-template-columns: repeat(3, 1fr);
  border: 1px solid var(--color-divider); margin-bottom: 10px;
}
.lib-tabs button {
  border: 0; border-right: 1px solid var(--color-divider); background: transparent;
  padding: 8px 4px; cursor: pointer; color: var(--color-neutral-700);
  font: 600 11px/1 var(--font-heading); text-transform: uppercase; letter-spacing: .08em;
}
.lib-tabs button:last-child { border-right: 0; }
.lib-tabs button:hover:not(.on) { background: color-mix(in srgb, var(--color-text) 5%, transparent); }
.lib-tabs button.on { background: var(--color-text); color: var(--color-bg); }
.lib-tabs .lt-n { opacity: .6; font-weight: 400; margin-left: 3px; }
.lib-tabs button:focus-visible { outline: 2px solid var(--color-accent); outline-offset: -2px; }
/* one pane at a time, each the whole column */
.lib-pane { flex: 1 1 0; min-height: 0; display: flex; flex-direction: column; }
.lib-pane[hidden] { display: none; }
#lib-mine, #lib-shared, #lib-catalog { flex: 1 1 0; min-height: 0; overflow-y: auto; }
/* ---- the grid: two tiles a row; folders, messages and the sync line span both ---- */
#lib-mine, #lib-shared, #lib-trash, .lib-folder-body, #lib-catalog .lib-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 10px 8px; align-content: start;
}
#lib-mine > .lib-folder, #lib-mine > .lib-empty, #lib-mine > .cloud-line,
#lib-mine > .cloud-conflict, #lib-mine > .lib-folder-new, #lib-shared > .lib-empty,
#lib-trash > .lib-empty { grid-column: 1 / -1; }
#lib-catalog .lib-grid + .lib-grid, #lib-catalog .lib-cat-group + .lib-grid { margin-top: 10px; }
#library .lib-head { flex: none; }
#library .lib-head {
  font: 600 11px/1 var(--font-body); text-transform: uppercase;
  letter-spacing: .06em; color: var(--color-text-subtle);
  margin: 8px 0 6px;
}
#library .lib-empty { font-size: 12px; color: var(--color-text-subtle); padding: 4px 2px; }
/* Library entries are TILES in the grid (redesign 2026-09-20): a 3:2 picture,
   the name under it (two lines, then ellipsis), the state chip over the
   picture's corner. The meta line rides the tooltip + the info panel. */
.lib-tile {
  position: relative; display: flex; flex-direction: column; gap: 5px; min-width: 0;
  cursor: default; background: transparent;
}
.lib-tile .t-thumb {
  aspect-ratio: 3 / 2; width: 100%; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  background: var(--color-card); border: 1px solid var(--color-divider);
  color: var(--color-text-muted); font-size: 22px;
}
.lib-tile:hover .t-thumb { border-color: var(--color-neutral-400); }
.lib-tile.sel .t-thumb { border-color: var(--color-accent); box-shadow: 0 0 0 1px var(--color-accent); }
.lib-tile .t-thumb img { max-width: 88%; max-height: 88%; display: block; object-fit: contain; }
.lib-tile .t-label {
  font: 600 12px/1.3 var(--font-body);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.lib-tile .t-chip {
  position: absolute; top: 5px; right: 5px; padding: 1px 6px;
  font-size: 10px; border: 1px solid var(--color-divider); background: var(--color-bg);
  color: var(--color-text-subtle); white-space: nowrap;
}
/* the Library's stitch-font group: folder chrome on a native <details> */
.lib-cat-group summary {
  list-style: none; display: flex; align-items: center; gap: 6px; cursor: pointer;
  padding: 5px 2px; font-size: 12.5px;
}
.lib-cat-group summary::-webkit-details-marker { display: none; }
.lib-cat-group summary .lf-caret { font-size: 9px; color: var(--color-text-muted); transition: transform .12s; }
.lib-cat-group[open] summary .lf-caret { transform: rotate(90deg); }
.lib-cat-group summary .lf-name { flex: 1; font-weight: 600; }
.lib-cat-group summary .lf-count { color: var(--color-text-muted); font-size: 11px; }
.lib-cat-group .lib-grid { padding: 4px 0 0 12px; border-left: 1px solid var(--color-divider); margin-left: 5px; }
#library-info .swatches { display: flex; height: 16px; margin: 8px 0; }
#library-info .swatches span { flex: 1 1 auto; }
#library-info .li-row { font-size: 12px; margin: 4px 0; }
#library-info .li-row label { color: var(--color-text-subtle); margin-right: 6px; }
#library-info .li-how { margin-top: 10px; color: var(--color-text-subtle); }
#library-info button { width: 100%; height: 30px; margin-top: 8px; }
/* library tile selected → the props half shows #library-info instead */
#panel .half.props.libmode #obj-ident,
#panel .half.props.libmode #prop-foot,
#panel .half.props.libmode #params,
#panel .half.props.libmode #prop-tabs { display: none; }
@media (max-width: 900px) {
  /* tablet: overlay rather than squeeze — library sits at the window edge,
     left of the rail (user ruling 2026-09-02) */
  #library { position: absolute; left: 0; top: 0; bottom: 0; z-index: 25;
    box-shadow: var(--shadow-md); }
}
#account-pop input[type="email"], #account-pop #account-code-in {
  width: 100%; height: 30px; padding: 0 9px;
  border: 1px solid var(--color-divider); background: transparent;
  font: 13px var(--font-body); color: var(--color-text);
}
#account-pop button { width: 100%; height: 30px; margin-top: 6px; }
#account-in .note { margin-bottom: 8px; }
#export-pop button {
  display: block; width: 100%; height: 30px; text-align: left; padding: 0 10px;
  border: 0; background: transparent; cursor: pointer; font-size: 13px;
}
#export-pop button:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }

/* ---------------- main row ---------------- */
#mainrow { flex: 1; display: flex; min-height: 0; }

/* left rail (tools) */
#rail {
  width: 56px; flex: none;
  border-right: 1px solid var(--color-divider);
  display: flex; flex-direction: column; align-items: center;
  padding: 10px 0; gap: 4px;
  background: var(--color-bg);
}
#rail button {
  width: 38px; height: 38px; flex: none;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px;
  border: 1px solid var(--color-divider); background: transparent;
  cursor: pointer; font-size: 15px; line-height: 1; padding: 0;
}
#rail button .k { font-size: 8px; color: var(--color-text-muted); }
#rail button:hover:not(.on):not(.stub) { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
#rail button.on { border-color: var(--color-accent); background: var(--color-accent); color: var(--color-bg); }
#rail button.on .k { color: color-mix(in srgb, var(--color-bg) 75%, transparent); }
#rail .gap { height: 8px; flex: none; }
/* the foot sits at the bottom of the rail, however many tools are above it */
#rail .rail-foot { margin-top: auto; border-color: transparent; color: var(--color-text-subtle); }
/* Cloud-save trouble (too-large, offline): a standing dot on the library button
   so the warning is visible without opening the sidebar (user report 2026-09-07). */
#library-btn.cloud-warn { position: relative; }
#library-btn.cloud-warn::after {
  content: ''; position: absolute; top: 3px; right: 3px;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--warn); box-shadow: 0 0 0 1.5px var(--color-bg);
}

/* Shapes flyout tray (user spec 2026-08-17): pops out beside the rail while
   the shapes tool is active; JS pins its top to the rail button. */
#mainrow { position: relative; }
#shape-tray {
  position: absolute; left: 56px; top: 220px; z-index: 30;
  display: none; flex-direction: column; gap: 4px;
  padding: 6px; background: var(--color-bg);
  border: 1px solid var(--color-divider); border-left: none;
  box-shadow: 2px 2px 8px rgba(12, 17, 22, .12);
}
#shape-tray.open { display: flex; }
#shape-tray button {
  width: 34px; height: 34px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--color-divider); background: transparent;
  cursor: pointer; font-size: 16px; line-height: 1;
}
#shape-tray button:hover:not(.on) { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
#shape-tray button.on { border-color: var(--color-accent); background: var(--color-accent); color: var(--color-bg); }

/* Canvas long-press menu (user ask 2026-09-11): object actions that were
   keyboard-only, reachable by holding a finger or the Pencil on the canvas in
   object select. Sits inside #canvaswrap, which is `overflow: hidden` — main.js
   clamps the position so a press near an edge opens inward rather than being
   clipped. */
#canvas-menu {
  position: absolute; z-index: 40;
  min-width: 168px; padding: 5px;
  display: flex; flex-direction: column; gap: 1px;
  background: var(--color-bg);
  border: 1px solid var(--color-divider);
  box-shadow: var(--shadow-md);
}
#canvas-menu[hidden] { display: none; }
/* Busy veil (main.js whileBusy). The spinner animates TRANSFORM only, on its
   own compositor layer, so it keeps turning while the main thread is held —
   the one kind of animation that can. */
#busy-veil {
  /* the whole canvas, lightly dimmed: the work is blocking, so the canvas
     takes no input until it ends (user 2026-09-24: make it more obvious) */
  position: absolute; inset: 0; z-index: 25;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--color-bg) 45%, transparent);
  cursor: progress;
}
#busy-veil[hidden] { display: none; }
.busy-card {
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  padding: 22px 30px 18px; max-width: calc(100% - 32px);
  background: var(--color-bg); color: var(--color-text);
  border: 1px solid var(--color-divider); border-radius: 16px; box-shadow: var(--shadow-md);
  font-size: 15px; font-weight: 500; text-align: center;
}
.busy-spin {
  width: 48px; height: 48px; flex: none; border-radius: 50%;
  border: 5px solid var(--color-accent); border-right-color: transparent;
  animation: busy-spin .8s linear infinite; will-change: transform;
}
@keyframes busy-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .busy-spin { animation-duration: 2.4s; } }

/* Screen-reader-only text: in the accessibility tree, not on screen. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Talk to your agent (speech.js). Listening is shown by colour AND a ring,
   never by colour alone; the pulse is decoration and stops under reduced
   motion. */
#voice-btn { display: inline-flex; align-items: center; gap: 4px; }
#voice-btn[hidden] { display: none; }
#voice-btn.listening {
  color: var(--color-card); background: var(--color-danger); border-color: var(--color-danger);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-danger) 30%, transparent);
  animation: voice-pulse 1.4s ease-in-out infinite;
}
:root[data-theme="dark"] #voice-btn.listening { color: var(--color-bg); }   /* 4.8:1 on the lifted red; card was 4.0 */
@keyframes voice-pulse { 50% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--color-danger) 18%, transparent); } }
@media (prefers-reduced-motion: reduce) { #voice-btn.listening { animation: none; } }
.voice-bubble {
  position: fixed; z-index: 60; max-width: min(420px, calc(100vw - 32px));
  padding: 8px 12px; border-radius: 6px; font-size: 13px; line-height: 1.4;
  background: var(--color-card); color: var(--color-text);
  border: 1px solid var(--color-divider); box-shadow: 0 4px 14px rgb(0 0 0 / .14);
}
.voice-bubble.interim { color: var(--color-text-subtle); font-style: italic; }
/* A message waiting for the agent: violet (the agent's colour) breathing,
   slower and softer than the red recording pulse. Reduced motion: steady
   violet plus a dashed outline, so it isn't colour alone. */
/* Recording always wins: listening while an earlier message still waits shows red. */
#voice-btn.waiting:not(.listening) { border-color: var(--cue-ring); animation: voice-wait 2.4s ease-in-out infinite; }
@keyframes voice-wait {
  0%, 100% { background-color: color-mix(in srgb, var(--cue-ring) 8%, transparent); }
  50% { background-color: color-mix(in srgb, var(--cue-ring) 34%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
  #voice-btn.waiting:not(.listening) { animation: none; background-color: color-mix(in srgb, var(--cue-ring) 22%, transparent); outline: 2px dashed var(--cue-ring); outline-offset: 2px; }
}

/* On-screen cues (overlay.js). The layer never takes a click: only the
   bubble's own buttons do. No animation at all, so reduced motion needs no
   special case. */
#cue-layer { position: fixed; inset: 0; z-index: 70; pointer-events: none; }
#cue-layer[hidden] { display: none; }
.cue-ring {
  position: absolute; left: 0; top: 0; box-sizing: border-box;
  border: 3px solid var(--cue-ring); border-radius: 7px;
  box-shadow: 0 0 0 2px var(--cue-halo), 0 0 0 3px var(--cue-edge);
}
.cue-ring.spot { box-shadow: 0 0 0 2px var(--cue-halo), 0 0 0 9999px var(--cue-dim); }
.cue-ring[hidden], .cue-mark[hidden] { display: none; }
.cue-mark {
  position: absolute; left: 0; top: 0; width: 24px; height: 24px; border-radius: 50%;
  background: var(--cue-ring); color: var(--cue-mark-ink); text-align: center;
  font: 700 13px/24px var(--font-body); box-shadow: 0 0 0 2px var(--cue-halo), 0 0 0 3px var(--cue-edge);
  box-sizing: border-box; overflow: hidden; white-space: nowrap;
}
.cue-mark.default { background: var(--cue-mark-bg); color: var(--cue-mark-fg); }
.cue-arrow { position: absolute; left: 0; top: 0; width: 26px; height: 26px; transform-origin: 50% 50%; }
.cue-arrow[hidden] { display: none; }
.cue-arrow path { fill: var(--cue-ring); stroke: var(--cue-halo); stroke-width: 2.5; stroke-linejoin: round; paint-order: stroke; }
@media (forced-colors: active) {
  .cue-ring, .cue-mark { border-color: Highlight; box-shadow: none; outline: 2px solid CanvasText; }
  .cue-arrow path { fill: Highlight; stroke: CanvasText; }
}
.keys-row .keys-show { margin-left: auto; min-height: 24px; padding: 0 8px; font-size: 11.5px; cursor: pointer; }
#canvas-menu button {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  /* 40px: a touch target, not a pointer target — this menu exists BECAUSE
     there is no keyboard, so it is sized for the finger that opened it. */
  min-height: 40px; padding: 0 10px;
  border: 0; background: none; cursor: pointer;
  font: 13px/1 var(--font-body); color: var(--color-text); text-align: left;
}
#canvas-menu button:disabled { opacity: .35; cursor: default; }
#canvas-menu button:not(:disabled):hover,
#canvas-menu button:not(:disabled):active {
  background: color-mix(in srgb, var(--color-text) 8%, transparent);
}
#canvas-menu .k { font-size: 11px; opacity: .55; font-variant-numeric: tabular-nums; }
#canvas-menu hr { border: 0; border-top: 1px solid var(--color-divider); margin: 4px 2px; }
/* Touch Shift. Top-left of the canvas: the left edge is where the free thumb
   sits on a held tablet, and the bottom-left is taken by the diagnose stack and
   stats box, which reach mid-height at iPad size (measured 1180x820). */
#mod-shift {
  position: absolute; left: 12px; top: 12px; z-index: 20;
  width: 52px; height: 52px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  font: 600 24px/1 var(--font-body); color: var(--color-text);
  background: var(--color-bg);
  border: 1px solid var(--color-divider);
  border-radius: 12px;
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  /* A held finger must not select text, open the callout or magnifier, or pan. */
  touch-action: none; user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;
}
#mod-shift[hidden] { display: none; }
/* Cut bar: beside ⇧, same size and look, so a thumb finds it where ⇧ is. */
/* The Point bar takes the same place: the two never show together (Cut tool
   vs vertex editing). */
#cut-bar, #point-bar {
  position: absolute; left: 72px; top: 12px; z-index: 20;
  display: flex; gap: 8px;
}
#cut-bar[hidden], #point-bar[hidden] { display: none; }
#cut-bar button, #point-bar button {
  height: 52px; min-width: 76px; padding: 0 14px;
  font: 600 15px/1 var(--font-body); color: var(--color-text);
  background: var(--color-bg);
  border: 1px solid var(--color-divider); border-radius: 12px;
  box-shadow: var(--shadow-sm); cursor: pointer;
  touch-action: manipulation; user-select: none; -webkit-user-select: none;
}
#cut-bar #cut-go { background: var(--color-accent); color: var(--color-bg); border-color: var(--color-accent); }
#cut-bar button:disabled { opacity: .45; cursor: default; }
#mod-shift.on { background: var(--color-accent); color: var(--color-bg); border-color: var(--color-accent); }
#canvas-menu .cm-note {
  padding: 6px 10px 4px; font-size: 11px; color: var(--color-text-subtle);
}

/* center column */
#center { flex: 1; display: flex; flex-direction: column; min-width: 0; background: var(--color-canvas); }

/* placement bar (selection scope) */
#canvasbar {
  height: 40px; flex: none;
  display: flex; align-items: center; gap: 12px;
  padding: 0 14px;
  border-bottom: 1px solid var(--color-divider);
  background: var(--color-bg);
}
#canvasbar .fld { display: flex; align-items: center; gap: 5px; }
#canvasbar .fld label { font-size: 12px; color: var(--color-text-muted); }
#canvasbar .fld input {
  width: 74px; height: 26px; padding: 0 7px;
  border: 1px solid var(--color-divider); background: transparent;
  font: 12px var(--font-body); font-variant-numeric: tabular-nums; color: var(--color-text);
}
#canvasbar .vsep { width: 1px; height: 20px; background: var(--color-divider); }
#canvasbar .mini {
  width: 26px; height: 26px; display: flex; align-items: center; justify-content: center;
  padding: 0; border: 1px solid var(--color-divider); background: transparent; cursor: pointer;
  font-size: 12px;
}
#canvasbar .fld input:disabled { color: var(--color-text-muted); }
#canvasbar .fld input:focus { outline: none; border-color: var(--color-text); }
#canvasbar .mini.active {
  border-color: var(--color-text);
  background: color-mix(in srgb, var(--color-text) 10%, transparent);
}
#canvasbar .mini:disabled { color: var(--color-text-muted); cursor: default; }
#align-pop { width: 190px; padding: 8px; }
#align-pop button {
  display: block; width: 100%; height: 28px; text-align: left; padding: 0 10px;
  border: 0; background: transparent; cursor: pointer; font-size: 13px;
}
#align-pop button:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
#align-pop .asep { height: 1px; background: var(--color-divider); margin: 6px 0; }

/* Drag-the-label numeric scrub (user spec 2026-08-16): a label that fronts
   a live data-math field is a horizontal drag handle — arith.js does the
   math; this is the affordance. Disabled fields keep the default cursor. */
label:has(+ input[data-math]:not(:disabled)),
label:has(+ .accspan input[data-math]:not(:disabled)) {
  cursor: ew-resize;
  user-select: none;
  -webkit-user-select: none;
}
/* ...and any label arith.js has found to be a live handle (it marks
   .scrub-handle on hover with the drag's own test), so the cursor never
   disagrees with the drag: a label before a .fieldrow (text Size, Taper) had
   none, and the Show menu's pointer won (user 2026-09-24). */
label.scrub-handle, #show-pop label.scrub-handle {
  cursor: ew-resize;
  user-select: none;
  -webkit-user-select: none;
}

/* canvas + overlays */
#canvaswrap { flex: 1; position: relative; overflow: hidden; min-height: 0; }
/* touch-action: none — the canvas owns every gesture on it (iOS_App
   2026-09-06). Without this the browser claims pinch and pans/zooms the
   PAGE instead of the design, which is what it did on the iPad: the UI
   scaled and the artwork did not move. The two preview canvases below
   already set it for the same reason. Input plumbing, not layout. */
/* -webkit-touch-callout: none — the long-press menu (2026-09-11) holds a
   finger still on the canvas, which is exactly the gesture WKWebView reads
   as "raise the callout/magnifier". user-select:none alone does not stop it. */
#c { -webkit-touch-callout: none;
  position: absolute; inset: 0; width: 100%; height: 100%; display: block;
  touch-action: none; }
/* Live-stroke overlay: sits directly over #c, never intercepts pointers. */
#c-ov { position: absolute; inset: 0; width: 100%; height: 100%; display: block; pointer-events: none; }

/* P1a lease banner: floats over the canvas top edge, never blocks tools */
#ro-banner {
  position: absolute; top: 10px; left: 50%; transform: translateX(-50%);
  z-index: 6; max-width: min(92%, 560px);
  padding: 6px 10px; border-radius: 6px;
  background: var(--color-bg); border: 1px solid var(--color-divider);
  box-shadow: var(--shadow-sm);
  font-size: 12.5px; line-height: 1.45;
}
#ro-banner i { opacity: 0.75; }
#ro-banner button {
  margin-left: 8px; padding: 2px 8px; font-size: 12px; cursor: pointer;
}

.blueprint-card {
  background: var(--color-bg);
  border: 1px solid var(--color-divider);
  box-shadow: var(--shadow-sm);
}

/* bottom-left: diagnose stack + stats */
#diagnose {
  position: absolute; left: 14px; bottom: 14px; z-index: 10;
  display: flex; flex-direction: column; gap: 8px; width: 148px;
}
#diagnose .cap {
  font: 400 9px/1 var(--font-heading); letter-spacing: .16em; text-transform: uppercase;
  color: var(--color-text-muted); padding-left: 1px;
}
#diagnose .dstack { display: flex; flex-direction: column; gap: 5px; }
#diagnose .dstack button {
  display: flex; align-items: center; gap: 8px;
  height: 30px; padding: 0 9px;
  border: 1px solid var(--color-divider); background: var(--color-bg);
  font-size: 12px; color: var(--color-neutral-700); cursor: pointer; text-align: left;
}
#diagnose .dstack button:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
#diagnose .dstack button.on {
  border-color: var(--color-accent); background: var(--color-accent-100); color: var(--color-accent-800);
}
#diagnose .dstack button .g { font-size: 12px; width: 12px; text-align: center; }
#statsbox {
  padding: 8px 10px; font-size: 11px; color: var(--color-neutral-700);
  display: flex; flex-direction: column; gap: 4px;
}
#statsbox span { font-variant-numeric: tabular-nums; }
#statsbox #status { color: var(--color-neutral-700); }
#statsbox #vertsel { color: var(--color-text-muted); }
#statsbox #coords { color: var(--color-text); }
/* Mid-draw takeover (user spec 2026-08-31): while a polyline is being
   placed, the line statistics own the box — the wordy lines yield, the
   cursor coords stay (they are part of the same readout). */
#statsbox #drawstats { color: var(--color-accent-800); }
#statsbox.drawing #status,
#statsbox.drawing #vertsel,
#statsbox.drawing #stats { display: none; }

/* bottom-center: stitch-type tiles */
#typetiles {
  position: absolute; left: 50%; transform: translateX(-50%); bottom: 14px; z-index: 10;
  padding: 8px; display: flex; gap: 6px;
}
#typetiles button {
  width: 64px; padding: 6px 4px 5px;
  border: 1px solid var(--color-divider); background: transparent;
  display: flex; flex-direction: column; align-items: center; gap: 5px; cursor: pointer;
}
#typetiles button:hover:not(.on):not(:disabled) { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
#typetiles button.on { border-color: var(--color-accent); background: var(--color-accent-100); }
#typetiles button:disabled { opacity: .35; cursor: not-allowed; }
#typetiles svg { width: 100%; height: 24px; display: block; }
#typetiles .tname { font-size: 10.5px; line-height: 1.1; text-align: center; color: var(--color-neutral-700); }
#typetiles button.on .tname { color: var(--color-accent-800); }

/* A grouped tile: same 64px cell, plus a second line naming the member that is
   active. The caret marks it as expandable — without it a group tile and a
   plain tile are indistinguishable until you press one. */
#typetiles .tgroup { position: relative; }
#typetiles .tgroup::after {
  content: '';
  position: absolute; right: 4px; top: 5px;
  border-left: 3px solid transparent; border-right: 3px solid transparent;
  border-top: 4px solid var(--color-neutral-500);
}
#typetiles .tgroup.on::after { border-top-color: var(--color-accent-700); }
#typetiles .tsub {
  font-size: 9.5px; line-height: 1.05; text-align: center;
  color: var(--color-accent-800); margin-top: -3px;
}
/* The expanded list sits ABOVE the row, so it never covers the tile just
   pressed, and is centred on the row rather than the tile — the row is already
   centre-anchored and a per-tile anchor would need JS for no gain at 3 items. */
#typetiles .tgroup-menu {
  position: absolute; bottom: calc(100% + 6px); left: 50%; transform: translateX(-50%);
  display: flex; gap: 6px; padding: 6px;
  background: var(--color-bg);
  border: 1px solid var(--color-divider);
  box-shadow: var(--shadow-md);
}

/* bottom-right: fit / show / 3d */
#viewctl {
  position: absolute; right: 14px; bottom: 14px; z-index: 10;
  display: flex; align-items: flex-end; gap: 7px;
}
#viewctl button { cursor: pointer; }
#fit {
  height: 32px; padding: 0 12px;
  border: 1px solid var(--color-divider); background: var(--color-bg);
  font-size: 12px; color: var(--color-neutral-700);
}
#fit:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
#show-btn {
  height: 32px; padding: 0 12px; display: flex; align-items: center; gap: 7px;
  border: 1px solid var(--color-accent); background: var(--color-accent-100);
  font-size: 12px; color: var(--color-accent-800);
}
#view-3d {
  width: 44px; height: 44px; display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--color-divider); background: var(--color-bg);
  font: 600 15px/1 var(--font-heading); color: var(--color-text);
}
#view-3d:hover:not(.on) { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
#view-3d.on { border-color: var(--color-accent); background: var(--color-accent); color: var(--color-bg); }
#show-pop {
  bottom: calc(100% + 7px); right: 0; top: auto; left: auto;
  width: 168px; padding: 9px 11px 10px;
  display: none; flex-direction: column; gap: 7px;
}
#show-pop.open { display: flex; }
#show-pop label { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--color-neutral-800); cursor: pointer; }
#show-pop input { margin: 0; accent-color: var(--color-accent); }

/* ---------------- right panel (object scope) ---------------- */
#panel {
  width: 372px; flex: none;
  border-left: 1px solid var(--color-divider);
  display: flex; flex-direction: column; min-height: 0;
  background: var(--color-bg);
}
#panel .half { flex: 1 1 50%; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; }
/* Props half: the frozen zone (head, identity, tabs) stays put; only #params
   scrolls — so the tab bar never scrolls with the property rows. */
#panel .half.props { border-bottom: 1px solid var(--color-divider); overflow: hidden; }
.phead {
  padding: 11px 14px 9px; flex: none;
  display: flex; align-items: center; gap: 9px;
  border-bottom: 1px solid var(--color-divider);
}
.phead .cap {
  font: 400 9px/1 var(--font-heading); letter-spacing: .16em; text-transform: uppercase;
  color: var(--color-text-muted);
}
/* line-height 1: at the default 1.4 the count ("607 st") stood 16.8px, taller
   than the title's 15.4, and the header grew 1.4px the moment an object was
   selected (user 2026-09-23: the bar "grows a little bit instantly"). */
.phead .meta { margin-left: auto; font-size: 12px; line-height: 1; color: var(--color-text-subtle); font-variant-numeric: tabular-nums; }
#obj-ident {
  padding: 11px 14px; border-bottom: 1px solid var(--color-divider);
  display: flex; align-items: center; gap: 9px;
}
#obj-ident .chip { width: 11px; height: 11px; flex: none; }
#obj-ident b { font: 600 16px/1 var(--font-heading); }
#obj-ident .sub { font-size: 11px; color: var(--color-text-muted); }
#obj-ident[hidden] { display: none; }   /* beats the #obj-ident display:flex */
/* Header identity: fill-type marker + object name (user go 2026-09-05). */
#props-cap {
  font: 600 14px/1.1 var(--font-heading); letter-spacing: 0; text-transform: none;
  color: var(--color-text); display: flex; align-items: center; gap: 8px; min-width: 0;
}
#props-cap .obj-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#props-cap .chip { width: 12px; height: 12px; flex: none; }
/* Fill-type markers: solid = fill, hollow = outline, hollow-open = open shape.
   Colour rides --chipc so the variant class picks fill vs stroke. */
.chip, .chip-dot { box-sizing: border-box; }
.chip.fill, .chip-dot.fill { background: var(--chipc); }
.chip.outline, .chip-dot.outline { background: transparent; border: 1.5px solid var(--chipc); }
.chip.open, .chip-dot.open { background: transparent; border: 1.5px solid var(--chipc); border-right-color: transparent; }

/* parameter rows — restyled but structurally identical (drive.mjs contract:
 * .prow, input[data-k], output, .inh-tag, .revert, [data-autotag]) */
#params { padding: 12px 14px; flex: 1 1 0; min-height: 0; overflow-y: auto; }
#library-info { flex: 1 1 0; min-height: 0; overflow-y: auto; }
/* Frozen object-properties tab bar (Visual / Technical). */
#prop-tabs {
  flex: none; display: flex; gap: 4px;
  padding: 8px 12px 0;                          /* tabs sit ON the bottom line */
  border-bottom: 1px solid var(--color-divider);
}
#prop-tabs[hidden] { display: none; }   /* beats the display:flex above */
/* Filing-cabinet folder tabs (user feedback 2026-09-06): the active tab drops
   its bottom edge and merges into the parameter panel below. Inactive tabs sit
   recessed on the line like closed folders. */
#prop-tabs .ptab {
  appearance: none; cursor: pointer; padding: 6px 15px;
  font: 600 11px/1 var(--font-heading); letter-spacing: .02em; text-transform: none;
  color: var(--color-text-subtle); background: var(--color-neutral-100);
  border: 1px solid var(--color-divider); border-radius: 7px 7px 0 0;
  margin-bottom: -1px;                          /* pull the base onto the line */
}
#prop-tabs .ptab:hover:not(.on) { color: var(--color-neutral-800); }
#prop-tabs .ptab.on {
  color: var(--color-text); background: var(--color-bg);
  border-bottom-color: var(--color-bg);         /* open bottom → merges with the panel */
}
/* A property locked by an incompatible setting: greyed, still hoverable so its
   title tooltip explains what and why (user go 2026-09-05). */
#params .locked, #params input.locked {
  opacity: .45; cursor: not-allowed; pointer-events: auto;
}
/* Small muted note under a control (e.g. the Fuzz tool hint). */
#params .fuzz-note { margin-top: -2px; }
#params .fuzz-note .sub { display: block; font-size: 11px; line-height: 1.35; color: var(--color-text-muted); }
/* Underlay per-layer numeric fields. */
/* Underlay layers (user 2026-09-27): vertical, each layer's own fields
   indented under its type so it is plain which setting belongs to which. */
#params .ul-layer { margin-bottom: 8px; }
#params .ul-layer .ul-slot { min-width: 140px; }
#params .ul-layer .ul-f { margin-left: 12px; padding-left: 10px; border-left: 2px solid var(--color-divider); margin-bottom: 6px; }
#params .ul-layer .ul-f input { width: 72px; font-size: 12px; padding: 3px 5px; border: 1px solid var(--color-neutral-300); border-radius: 4px; background: transparent; color: var(--color-text); }
#params .ul-layer.inherited .ul-f input { font-style: italic; }
/* Collapsible settings groups (details.psec). */
#params details.psec { border-top: 1px solid var(--color-divider); padding: 6px 0 2px; }
#params details.psec:first-child { border-top: 0; }
#params details.psec > summary { cursor: pointer; font-size: 11px; font-weight: 600; letter-spacing: .06em;
  text-transform: uppercase; color: var(--color-text-subtle); padding: 4px 0 8px; list-style: none; }
#params details.psec > summary::-webkit-details-marker { display: none; }
/* The arrow is decoration: the alt text after "/" keeps it out of the
   heading's accessible name (Accessibility 2026-09-27). */
#params details.psec > summary::before { content: '▸'; content: '▸' / ''; display: inline-block; width: 1em; transition: transform .12s; }
#params details.psec[open] > summary::before { transform: rotate(90deg); }
#params details.psec > summary:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
#params .prow { margin-bottom: 10px; }
#params .prow label { display: block; font-size: 12px; color: var(--color-neutral-700); margin-bottom: 4px; }
/* Accordion Spacing preview: the real sew rows as ticks (Fill_Opus, 2026-09-05) */
.accpreview {
  display: block; width: 100%; height: 24px; box-sizing: border-box;
  background: var(--color-neutral-100); border: 1px solid var(--color-neutral-300);
  border-radius: 3px;
}
/* Custom accordion density graph: an interactive line the user draws */
.accgraph {
  display: block; width: 100%; height: 88px; box-sizing: border-box;
  background: var(--color-neutral-100); border: 1px solid var(--color-neutral-300);
  border-radius: 3px; cursor: crosshair; touch-action: none;
}
/* Gradient ramp: the colour stops as draggable handles */
.gradramp {
  display: block; width: 100%; height: 30px; box-sizing: border-box; margin-bottom: 6px;
  border-radius: 3px; cursor: pointer; touch-action: none;
}
/* Two-way choice row (sketch Style: Swipe / Coil). Plain buttons with
   aria-pressed, so Tab + Enter/Space work with no extra key handling. */
#params .p-seg { display: flex; }
#params .p-seg button { flex: 1; border-radius: 0; }
#params .p-seg button:first-child { border-radius: 4px 0 0 4px; }
#params .p-seg button:last-child { border-radius: 0 4px 4px 0; margin-left: -1px; }
#params .p-seg button[aria-pressed="true"] { background: var(--color-accent); border-color: var(--color-accent); color: var(--color-bg); }
/* The unit and the default/↺ tag sit right AFTER the field, not at the far
   edge of the panel (user 2026-09-27: "very far away from their field"). */
#params .prow .pv { display: inline-flex; align-items: center; gap: 6px; margin-left: 6px; vertical-align: middle; }
#params .prow output { font-size: 12px; font-variant-numeric: tabular-nums; }
#params .prow.inherited output { color: var(--color-text-muted); font-style: italic; }
/* Param NUMERIC fields (user spec 2026-08-19, ex-sliders): the label is
   the drag handle (global label-scrub); the field types + takes arithmetic.
   Inherited/auto rows read grey italic until touched. */
#params .prow > input[data-k] {
  width: 72px; font-size: 12px; padding: 3px 5px;
  font-variant-numeric: tabular-nums;
  border: 1px solid var(--color-neutral-300); border-radius: 4px;
  background: transparent; color: var(--color-text);
}
#params .prow > input[data-k]:focus { outline: none; border-color: var(--color-text); }
/* The two TEXT fields are textareas so Enter can make a line (2026-09-10).
   They fill the panel because lettering is read as a block, and they grow by
   rows rather than scrolling so a two-line placement is visible as two lines
   in the field that made it. `resize: vertical` and not `both`: horizontal
   resize would let the field escape a 320px panel with no way back. */
#params .prow > textarea {
  display: block; width: 100%; box-sizing: border-box;
  font: inherit; font-size: 12px; line-height: 1.35; padding: 4px 6px;
  border: 1px solid var(--color-neutral-300); border-radius: 4px;
  background: transparent; color: var(--color-text);
  resize: vertical;
}
#params .prow > textarea:focus { outline: none; border-color: var(--color-text); }
#params .prow.inherited > input[data-k] {
  color: var(--color-text-muted); font-style: italic;
}
.inh-tag, [data-autotag] {
  font-size: 9px; letter-spacing: .08em; text-transform: uppercase;
  border: 1px solid var(--color-divider); padding: 0 4px; color: var(--color-text-muted);
}
[data-autotag] { color: var(--color-accent-700); border-color: var(--color-accent-300); background: var(--color-accent-100); }
/* Object-style actions (Merge/Join/Underlay/Copy/Paste): a frozen footer at
   the bottom of the props half (user go 2026-09-05, moved out of the header). */
#prop-foot { flex: none; padding: 8px 14px; border-top: 1px solid var(--color-divider); }
#prop-foot[hidden] { display: none; }
.prop-foot .stylebtns { display: flex; gap: 4px; flex-wrap: wrap; }
.prop-foot .stylebtns button {
  font-size: 10px; padding: 3px 8px; cursor: pointer;
  border: 1px solid var(--color-neutral-300); border-radius: 4px;
  background: transparent; color: var(--color-neutral-700);
}
.prop-foot .stylebtns button:hover:not(:disabled) { border-color: var(--color-text); color: var(--color-text); }
.prop-foot .stylebtns button:disabled { opacity: .45; cursor: default; }

/* Soft-baked stitch objects (2026-08-17): the frozen-settings notice and
   its two exits, sitting where the sliders would have been. */
#params .bakebar { margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--color-neutral-300); }
#params .bakebar button {
  display: block; width: 100%; margin-top: 6px; padding: 6px 8px;
  font-size: 12px; cursor: pointer;
  border: 1px solid var(--color-neutral-300); border-radius: 5px;
  background: transparent; color: var(--color-text);
}
#params .bakebar button:hover { border-color: var(--color-text); }

.revert {
  border: 0; background: none; color: var(--color-accent-700); cursor: pointer;
  font-size: 12px; padding: 0 2px;
}
/* the Stitch Type select stays in the DOM (driver + a11y single code path)
 * but the tile picker is the visible control */
#params .prow.treatment-row { position: absolute; left: -9999px; }
#params select, #params .ul-slot {
  height: 27px; padding: 0 8px;
  border: 1px solid var(--color-divider); background: transparent;
  font: 12px var(--font-body); color: var(--color-neutral-700);
}
#params .chk { display: flex; align-items: center; gap: 7px; font-size: 12px; color: var(--color-neutral-700); }
#params .subhead {
  font: 400 9px/1 var(--font-heading); letter-spacing: .16em; text-transform: uppercase;
  color: var(--color-text-muted);
  margin: 12px 0 8px; padding-top: 10px; border-top: 1px solid var(--color-divider);
}
#params .ul-slots { display: flex; gap: 6px; }
#params .ul-slots .ul-slot { flex: 1; }
#params .hint { font-size: 11px; color: var(--color-text-subtle); }
.docstats div {
  display: flex; align-items: baseline; justify-content: space-between;
  padding: 7px 0; font-size: 12px; color: var(--color-neutral-700);
  border-bottom: 1px solid color-mix(in srgb, var(--color-text) 8%, transparent);
}
.docstats b { font-variant-numeric: tabular-nums; color: var(--color-text); font-weight: 600; }

/* thread shopping list (document properties): the spools this design needs */
.threadlist { margin-top: 10px; }
.tl-head {
  display: flex; align-items: baseline; gap: 6px;
  font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--color-neutral-700); margin-bottom: 4px;
}
.tl-head span {
  font-variant-numeric: tabular-nums; font-weight: 600;
  color: var(--color-text-subtle);
}
.tl-row {
  display: flex; align-items: center; gap: 8px;
  padding: 5px 0; font-size: 12px;
  border-bottom: 1px solid color-mix(in srgb, var(--color-text) 8%, transparent);
}
.tl-sw {
  flex: none; width: 20px; height: 20px; border-radius: 4px;
  display: flex; align-items: center; justify-content: center;
  font-size: 10px; font-weight: 600; font-variant-numeric: tabular-nums;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-text) 18%, transparent);
}
.tl-id { flex: 1; color: var(--color-text); font-weight: 600; }
.tl-id.tl-unspec { color: var(--color-text-subtle); font-weight: 500; font-style: italic; }
.tl-hex {
  font-variant-numeric: tabular-nums; font-size: 11px;
  color: var(--color-text-subtle); text-transform: uppercase;
}

/* sequence (stitch order): color-block folders + neutral object rows */
#objects { flex: 1; min-height: 0; overflow-y: auto; padding: 4px 8px 8px; }
.seq-item { margin-bottom: 4px; }

/* folder row — tinted with the block's thread color */
.brow {
  display: flex; align-items: center; gap: 9px;
  height: 48px; padding: 0 9px;
  cursor: pointer;
  color: var(--ink, var(--color-text));
}
.brow .caret { width: 12px; text-align: center; font-size: 12px; line-height: 1; }
.brow .oname b { font: 600 15px/1 var(--font-heading); }
/* Text folder header — neutral (not thread-tinted like a color block), so a
   word reads as its own kind of unit. Its letters are display-only children. */
.brow.txrow {
  background: color-mix(in srgb, var(--color-text) 7%, var(--color-bg));
  color: var(--color-text);
  border: 1px solid var(--color-divider);
}
.brow.txrow .oact, .brow.txrow .del { opacity: .8; }
.brow.txrow .oact.on { opacity: 1; color: var(--color-accent-800); }

/* object rows — NOT colored; the thread shows in the chip + thumbnail ink */
.orow {
  display: flex; align-items: center; gap: 9px;
  height: 42px; padding: 0 9px;
  cursor: pointer;
  color: var(--color-text);
  background: var(--color-bg);
  border: 1px solid var(--color-divider);
}
.orow.child { margin: 2px 0 0 24px; }
.orow .chip-dot { width: 9px; height: 9px; flex: none; }
.brow .thumb, .orow .thumb {
  width: 34px; height: 34px; flex: none;
  border: 1px solid color-mix(in srgb, currentColor 35%, transparent);
  background: color-mix(in srgb, #ffffff 45%, transparent);
  display: flex; align-items: center; justify-content: center;
}
.orow .thumb { background: var(--color-card); }
.brow .thumb svg, .orow .thumb svg { width: 30px; height: 30px; display: block; }
.brow .oname, .orow .oname { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.orow .oname b { font: 600 13.5px/1 var(--font-heading); }
.brow .oname span, .orow .oname span { font-size: 10.5px; opacity: .8; }
.brow .ocount, .orow .ocount { font-size: 12px; font-variant-numeric: tabular-nums; }
.brow .warng, .orow .warng { width: 12px; text-align: right; font-size: 12px; color: var(--warn-text); }
.brow .warng { color: var(--ink); }
.brow .idx, .orow .idx { font: 400 11px/1 var(--font-heading); letter-spacing: .1em; opacity: .7; font-variant-numeric: tabular-nums; }
/* Layer number, shown only when an object's layer differs from its sewing slot */
.orow .lyr {
  flex: none; font: 600 9.5px/1 var(--font-heading); letter-spacing: .04em;
  padding: 2px 4px; border-radius: 3px; font-variant-numeric: tabular-nums;
  color: var(--color-accent-800); background: var(--color-accent-100);
}
.orow .del {
  border: 0; background: none; color: inherit; opacity: 0; cursor: pointer;
  font-size: 13px; padding: 0 2px;
}
/* Hover reveals only where hover is real. On iPad Safari a tap that REVEALS
   something on :hover (the × going from 0 to .7) is taken as a hover, and
   the click needs a second tap (user 2026-09-26: rows, hide, lock and
   optimize all took two). Touch shows the × at rest instead. */
@media (hover: hover) { .orow:hover .del { opacity: .7; } }
@media (hover: none) { .orow .del { opacity: .6; } }   /* 3:1 on the light row (Accessibility, WCAG 1.4.11) */
.orow.on { outline: 2px solid var(--color-accent-800); outline-offset: -2px; }
/* Hide/lock row actions: always present (user 2026-08-12 — a control you
   can't see isn't a control), brighter on hover, full-on while active.
   Resting at .75 (user 2026-09-28): the lowest that keeps the icon at 3:1
   against the row, WCAG 1.4.11 for a clickable icon — 3.18:1 light, 3.82:1
   dark (Accessibility). At .45 it was 1.89:1 / 2.19:1. */
.orow .oact {
  width: 20px; height: 20px; flex: none; padding: 0;
  border: 0; background: none; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--color-text-muted);
  opacity: .75;
}
@media (hover: hover) { .orow:hover .oact { opacity: .8; } }
.orow .oact.on { opacity: 1; color: var(--color-accent-800); }
@media (hover: hover) { .orow .oact:hover { opacity: 1; } }
.orow .oact svg { width: 14px; height: 14px; display: block; }
/* Optimize toggle (user redesign 2026-08-29): every row carries ⟳; rows
   whose object is in a derived group get the accent edge. */
.orow.olinked { border-left: 3px solid color-mix(in srgb, var(--color-accent-800) 55%, transparent); }
.orow .optoggle { font-size: 13px; line-height: 1; }
.orow .optoggle.on { opacity: 1; color: var(--color-accent-800); }
/* Sequence drag-reorder: source rows dim, the drop slot draws as a 2px
   accent line on the row edge it will land against. */
.orow.dragging { opacity: .35; }
/* LIFTED — a finger has held the row long enough to pick it up (2026-09-11).
   The only feedback a touch user gets that the hold worked, so it is deliberately
   loud: raised, outlined in the accent, and nudged right so the row visibly
   leaves the column it was sitting in. */
.orow.lifted, .brow.lifted {
  outline: 2px solid var(--color-accent); outline-offset: -2px;
  box-shadow: 0 6px 16px rgba(12, 17, 22, .28);
  transform: translateX(4px);
  position: relative; z-index: 2;
}
.orow.lifted.dragging { opacity: .9; }
.orow.drop-above, .brow.drop-above { box-shadow: inset 0 2px 0 0 var(--color-accent-800); }
.orow.drop-below, .brow.drop-below { box-shadow: inset 0 -2px 0 0 var(--color-accent-800); }
/* Hidden object: the row stays fully operable, its identity dims. */
.orow.ghosted .thumb, .orow.ghosted .oname, .orow.ghosted .ocount { opacity: .4; }
.bkids.hidden { display: none; }

/* problems — always in the DOM (driver counts .prob rows); shown as a
 * collapsible section under the sequence */
#problems-wrap { flex: none; border-top: 1px solid var(--color-divider); }
#problems-wrap.collapsed #problems { display: none; }
#problems { padding: 4px 14px 10px; max-height: 150px; overflow-y: auto; }
.prob { display: flex; gap: 7px; padding: 4px 0; font-size: 12px; color: var(--color-neutral-700); cursor: pointer; }
.prob:hover .prob-text { color: var(--color-text); }
.prob .badge.warn { color: var(--warn-text); background: none; border: 0; padding: 0; font-size: 12px; }
.prob-text b { color: var(--color-text); }
.warn-badge {
  font-size: 10px; color: var(--warn-text); border: 1px solid var(--warn); padding: 0 4px;
  background: none; cursor: pointer;
}
.hint { font-size: 11px; color: var(--color-text-subtle); }

/* stub add-background row */
#add-bg {
  display: flex; align-items: center; gap: 9px; width: 100%;
  padding: 9px 14px 12px; border: 0; background: transparent; text-align: left;
}
#add-bg .plus {
  width: 26px; height: 26px; flex: none;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--color-accent); color: var(--color-accent-700); font-size: 16px; line-height: 1;
}
#add-bg span:last-child { font-size: 12px; color: var(--color-accent-700); }

/* ---------------- stitch-out player ---------------- */
#player {
  height: 40px; flex: none;
  border-top: 1px solid var(--color-divider);
  background: var(--color-neutral-100);
  display: flex; align-items: center; gap: 10px;
  padding: 0 12px;
}
#player.closed { display: none; }
#player button {
  width: 28px; height: 24px; padding: 0; cursor: pointer;
  font-size: 12px; line-height: 1;
}
#pl-strip {
  flex: 1; position: relative; height: 18px; cursor: pointer;
  border: 1px solid var(--color-neutral-300); border-radius: 3px;
  overflow: hidden; background: var(--color-neutral-200);
}
#pl-blocks { position: absolute; inset: 0; display: flex; }
#pl-blocks span { height: 100%; }
#pl-head {
  position: absolute; top: -1px; bottom: -1px; left: 0; width: 2px;
  background: #1b2733; pointer-events: none;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, .65);
}
#pl-count {
  font: 500 11px/1 var(--font-mono, monospace);
  color: var(--color-neutral-700); white-space: nowrap; min-width: 96px;
  text-align: right;
}
#pl-speedwrap {
  display: flex; align-items: center; gap: 6px;
  font-size: 11px; color: var(--color-text-subtle);
}
#pl-speedwrap input { width: 110px; }
#pl-sps { min-width: 62px; }

/* ---------------- thread tray (spec 13A: bar + palette; palette.js) ---------------- */
#threadtray { flex: none; border-top: 1px solid var(--color-divider); background: var(--color-card); }
#threadbar { display: flex; height: 48px; }
#tp-handle {
  flex: none; width: 16px; display: flex; align-items: center; justify-content: center;
  cursor: grab; color: var(--color-text-subtle); font-size: 11px; letter-spacing: -2px; user-select: none;
}
#tp-toggle { flex: none; width: 48px; border: 0; border-radius: 0; font-size: 18px; background: var(--color-neutral-800, #1d2d3d); color: #fff; }
#threadstrip { flex: 1; min-width: 0; display: flex; }
#threadstrip span {
  flex: 1; min-width: 0; cursor: pointer; position: relative;
  display: flex; align-items: flex-end; justify-content: center; padding-bottom: 7px;
  font: 600 11px/1 var(--font-heading); overflow: hidden;
}
#threadstrip span b { font-weight: 600; white-space: nowrap; }
#threadstrip.dense span:not(.th-off) b { display: none; }   /* off-bar threads keep their code (or #n) */
#threadstrip.dense span { align-items: center; padding-bottom: 0; }
#threadstrip.dense .th-n { position: static; font-size: 10px; opacity: 1; }
#threadstrip .th-n { position: absolute; top: 4px; left: 5px; font: 600 9px/1 var(--font-heading); font-style: normal; opacity: .8; }
#threadstrip .th-used { position: absolute; top: 5px; right: 5px; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
#threadstrip .th-empty, #threadstrip .th-add {
  background: var(--color-card); color: var(--color-text-subtle); border: 1px dashed var(--color-divider);
  align-items: center; padding: 0; font-size: 11px; cursor: default;
}
#threadstrip .th-off { outline: 2px dashed var(--color-text-subtle); outline-offset: -4px; }
#threadstrip .th-unowned::after { content: ''; position: absolute; bottom: 0; right: 0; border: 7px solid transparent; border-right-color: var(--color-text-subtle); border-bottom-color: var(--color-text-subtle); }
#threadstrip span:hover { filter: brightness(1.12); }
#threadstrip span:focus-visible { outline: 3px solid var(--color-text); outline-offset: -3px; box-shadow: inset 0 0 0 5px var(--color-card); position: relative; z-index: 1; }

/* ---------------- file drag overlay ---------------- */
#dropzone {
  position: fixed; inset: 0; z-index: 60;
  display: none; align-items: center; justify-content: center;
  background: rgba(12, 17, 22, .55);
  /* Never participate in drag hit-testing. Safari fires dragleave with a
     null relatedTarget when the pointer crosses onto a newly-shown overlay,
     which the window listeners misread as "left the window" → hide → the
     drag is over the canvas again → dragover re-shows → strobe. With the
     backdrop transparent to the pointer, the live drag stays on the canvas
     beneath and no enter/leave cycle happens over the overlay. */
  pointer-events: none;
}
#dropzone.on { display: flex; }
/* While a drag is live (armed), the card passes through too; its buttons are
   only used in the post-drop error/companion states, when no drag exists. */
#dropzone.armed .dz-card { pointer-events: none; }
.dz-card {
  background: var(--color-card); border: 1px solid var(--color-divider); border-radius: 8px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, .3);
  padding: 18px 22px 20px; min-width: 420px; max-width: 540px;
  display: flex; flex-direction: column; gap: 12px; align-items: stretch;
  pointer-events: auto;
}
.dz-title {
  font: 700 13px/1 var(--font-heading); letter-spacing: .12em;
  text-transform: uppercase; color: var(--color-neutral-700);
}
.dz-box {
  border: 2px dashed var(--color-accent); border-radius: 6px;
  min-height: 150px; padding: 18px;
  display: flex; flex-direction: column; gap: 10px;
  align-items: center; justify-content: center; text-align: center;
}
#dropzone.error .dz-box { border-color: var(--color-danger); }
.dz-msg { font-size: 12.5px; line-height: 1.5; color: var(--color-neutral-700); }
#dropzone.error .dz-msg b { color: var(--color-danger); }
.dz-file {
  font: 600 12px/1 var(--font-mono, monospace);
  background: var(--color-neutral-100); border: 1px solid var(--color-neutral-300);
  border-radius: 4px; padding: 6px 10px; color: var(--color-neutral-800, #333);
}
.dz-actions { display: flex; gap: 8px; justify-content: center; }
.dz-actions button { cursor: pointer; }
/* Cloud sync in My designs (2026-09-02): a one-line state under the open
   document's tile, then the account's design rows. The conflict block is
   the queued fork choice — visible, never modal. */
.cloud-line {
  font-size: 11px; color: var(--color-text-muted);
  padding: 4px 2px 6px; line-height: 1.4;
}
.cloud-conflict {
  font-size: 11.5px; line-height: 1.45; padding: 8px;
  border: 1px solid #c8642a; margin: 4px 0 8px;
}
.cloud-conflict button { margin: 6px 6px 0 0; height: 24px; padding: 0 8px; cursor: pointer; }
.lib-row {
  position: relative; display: flex; flex-direction: column; gap: 5px; min-width: 0;
  cursor: default;
}
.lib-row .lr-thumb {
  aspect-ratio: 3 / 2; width: 100%;
  display: flex; align-items: center; justify-content: center;
  font-size: 22px; color: var(--color-neutral-400, #bbb);
  background: var(--color-card); border: 1px solid var(--color-divider); overflow: hidden;
}
.lib-row:hover .lr-thumb { border-color: var(--color-neutral-400); }
.lib-row .lr-thumb img { max-width: 88%; max-height: 88%; object-fit: contain; }
.lib-row .lr-title {
  font: 600 12px/1.3 var(--font-body);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
/* the date lives in the tooltip now; the span stays for anything that reads it */
.lib-row .lr-meta { display: none; }
/* the open document: accent frame + OPEN badge on the picture */
.lib-row.isopen .lr-thumb { border-color: var(--color-accent); box-shadow: 0 0 0 1px var(--color-accent); }
.lib-row.isopen .lr-title { color: var(--color-accent-800); }
.lib-row .lr-chip {
  position: absolute; top: 0; left: 0; padding: 2px 5px;
  font: 700 9px/1 var(--font-heading); letter-spacing: .08em; text-transform: uppercase;
  background: var(--color-text); color: var(--color-bg);
}
.lib-row .lr-chip.lr-shared {
  left: auto; right: 5px; top: 5px; background: var(--color-bg); color: var(--color-text-subtle);
  border: 1px solid var(--color-divider); font: 400 10px/1.2 var(--font-body); letter-spacing: 0; text-transform: none;
}
/* tile actions sit on the picture's top-right corner: shown on hover or focus,
   and ALWAYS on a device with no hover (touch), where hover never comes */
.lib-row .lr-share, .lib-row .lr-del, .lib-row .lr-leave,
.lib-row .lr-restore, .lib-row .lr-purge {
  position: absolute; top: 5px; width: 22px; height: 22px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--color-divider); background: var(--color-bg);
  color: var(--color-text-subtle); font-size: 11px; cursor: pointer;
  opacity: 0; transition: opacity .1s;
}
.lib-row .lr-del, .lib-row .lr-leave, .lib-row .lr-purge { right: 5px; }
.lib-row .lr-share, .lib-row .lr-restore { right: 31px; }
.lib-row:hover :is(.lr-share, .lr-del, .lr-leave, .lr-restore, .lr-purge),
.lib-row:focus-within :is(.lr-share, .lr-del, .lr-leave, .lr-restore, .lr-purge),
.lib-row.confirm :is(.lr-del, .lr-leave, .lr-purge) { opacity: 1; }
@media (hover: none) {
  .lib-row :is(.lr-share, .lr-del, .lr-leave, .lr-restore, .lr-purge) { opacity: 1; }
}
.lib-row .lr-del,
.lib-row .lr-leave {
  color: var(--color-text-muted);
}
.lib-row .lr-del:hover { color: var(--color-danger); }
.lib-row .lr-leave:hover { color: var(--color-danger); }
.lib-row.confirm .lr-del,
.lib-row.confirm .lr-leave { color: var(--color-danger); font-weight: 600; }

/* Trash: a full-column TAKEOVER reached from the "View trash" button beside
   My designs — not an inline section. In trash-mode the normal library rows
   are hidden and the trash view fills the column with its own scroll region. */
#library .lib-mine-head {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
}
#library .lib-mine-head.drop {
  outline: 1px dashed var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 8%, transparent);
}
.lib-mine-acts { flex: none; display: flex; align-items: center; gap: 6px; }
#lib-trash-btn, #lib-newfolder-btn {
  flex: none; border: 1px solid var(--color-divider); background: none;
  cursor: pointer; color: var(--color-text-subtle);
  font: 600 10px/1 var(--font-body); text-transform: uppercase;
  letter-spacing: .05em; padding: 3px 7px;
}
#lib-trash-btn:hover, #lib-newfolder-btn:hover {
  color: var(--color-accent); border-color: var(--color-accent);
}

/* Folder groups inside My designs (0028). Collapsible header (caret · name ·
   count · delete) over the rows filed into it; the header is a drop target. */
.lib-folder { margin: 2px 0; }
.lib-folder-head {
  display: flex; align-items: center; gap: 6px; padding: 4px 6px;
  cursor: pointer; border: 1px solid transparent; user-select: none;
}
.lib-folder-head:hover { border-color: var(--color-divider); }
.lib-folder-head.drop {
  border-color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 10%, transparent);
}
.lib-folder-head .lf-caret {
  flex: none; font-size: 9px; color: var(--color-text-muted);
  transition: transform .12s ease;
}
.lib-folder.open .lib-folder-head .lf-caret { transform: rotate(90deg); }
.lib-folder-head .lf-icon {
  flex: none; display: flex; align-items: center;
  color: var(--color-text-muted);
}
.lib-folder-head:hover .lf-icon { color: var(--color-neutral-700); }
/* The automatic Fonts CATEGORY: same chrome as a folder, a type specimen
   instead of the folder glyph so it reads as a system group, not a filing. */
.lib-fonts .lf-fonts-icon {
  font: 700 10px/1 var(--font-heading); letter-spacing: .02em;
}
.lib-folder-head .lf-name {
  flex: 1; font-size: 12px; font-weight: 600; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; color: var(--color-neutral-700);
}
.lib-folder-head .lf-count {
  flex: none; font-size: 10px; color: var(--color-text-muted);
}
.lib-folder-head .lf-del {
  flex: none; border: none; background: none; cursor: pointer;
  color: var(--color-text-muted); font-size: 11px; padding: 2px 4px;
}
.lib-folder-head .lf-del:hover { color: var(--color-danger); }
.lib-folder-head.confirm .lf-del { color: var(--color-danger); font-weight: 600; }
.lib-folder-body { padding: 4px 0 6px 12px; border-left: 1px solid var(--color-divider); margin-left: 5px; }
.lf-edit, .lib-folder-new .lf-edit {
  flex: 1; min-width: 0; font-size: 12px; padding: 2px 6px;
  border: 1px solid var(--color-accent);
}
.lib-folder-new { padding: 4px 6px 4px 12px; }
#library.trash-mode > .lib-tabs,
#library.trash-mode > .lib-pane,
#library.trash-mode > .lib-search { display: none; }
#library.trash-mode #lib-trash-view {
  display: flex; flex-direction: column; flex: 1 1 0; min-height: 0;
}
.lib-trash-head-row { display: flex; align-items: center; gap: 8px; }
#lib-trash-back {
  flex: none; border: none; background: none; cursor: pointer;
  color: var(--color-text-subtle); font-size: 12px; padding: 2px 4px;
}
#lib-trash-back:hover { color: var(--color-accent); }
#lib-trash-view #lib-trash { flex: 1 1 0; min-height: 0; overflow-y: auto; }
.lib-row.trash-row { opacity: .72; }
.lib-row.trash-row:hover { opacity: 1; }
.lib-row .lr-restore:hover { color: var(--color-accent); }
.lib-row .lr-purge:hover { color: var(--color-danger); }
.lib-row.confirm .lr-purge { color: var(--color-danger); font-weight: 600; }

/* P1b sharing: row affordance + expanding share box */
.lib-row .lr-share:hover { color: var(--color-accent); }
.lr-chip.lr-shared {
  max-width: 9em; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap;
}
/* Share dialog body — lives inside .modal-card now (user 2026-09-09). */
#share-body { font-size: 12.5px; line-height: 1.5; }
#share-body .sb-line {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; padding: 3px 0;
}
#share-body .sb-row { display: flex; gap: 6px; margin-top: 8px; }
#share-body #sb-email { flex: 1; min-width: 0; font-size: 12.5px; padding: 4px 7px; }
#share-body #sb-send { font-size: 12px; padding: 4px 12px; cursor: pointer; }
#share-body .sb-public {
  margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--color-divider);
}
#share-body .sb-pub-head { color: var(--color-text-subtle); margin-bottom: 6px; }
#share-body #sb-pub-create { font-size: 12px; padding: 4px 12px; cursor: pointer; }
#share-body .sb-pub-copy { font-size: 11px; padding: 2px 8px; cursor: pointer; }
#share-body .sb-pub-revoke {
  border: none; background: none; cursor: pointer;
  color: var(--color-text-muted); font-size: 12px; padding: 1px 4px;
}
#share-body .sb-pub-revoke:hover { color: var(--color-danger); }
#share-body .sb-acts { display: flex; gap: 6px; align-items: center; flex: none; }
#share-body .sb-getlink { font-size: 11px; padding: 2px 8px; cursor: pointer; }
#share-body .sb-unshare {
  border: none; background: none; cursor: pointer;
  color: var(--color-text-muted); font-size: 12px; padding: 1px 4px;
}
#share-body .sb-unshare:hover { color: var(--color-danger); }
#share-body .sb-fonts, #share-body .sb-note {
  margin-top: 8px; opacity: 0.8;
}
#share-body .sb-role { opacity: 0.65; font-size: 11px; }
#share-body .sb-invite { margin-top: 10px; }
#share-body #sb-invite-url {
  flex: 1; min-width: 0; font-size: 11.5px; padding: 4px 7px;
  color: var(--color-text-subtle);
}
#share-body #sb-copyinvite { font-size: 12px; padding: 4px 12px; cursor: pointer; }

/* Destructive dialog action (2026-09-02): the discard path reads as the
   dangerous one — outlined in the error red, never the filled primary. */
.dz-actions button.danger { color: var(--color-danger); border: 1px solid var(--color-danger); background: transparent; }
.dz-actions button.danger:hover { background: var(--color-danger); color: #fff; }
/* Naming row inside the new-project guard: pre-filled suggestion, one
   Enter to accept — the commitment-moment naming pattern (2026-09-02). */
.nc-name-row { display: flex; align-items: center; gap: 8px; margin: 12px 0 4px; }
.nc-name-row label {
  font: 600 10px/1 var(--font-heading); letter-spacing: .08em;
  text-transform: uppercase; color: var(--color-neutral-700);
}
.nc-name-row input {
  flex: 1; height: 30px; padding: 0 9px;
  border: 1px solid var(--color-divider); background: transparent;
  font: 13px var(--font-body); color: var(--color-text);
}

/* ---------------- designed iconography (2026-08-14) ---------------- */
#rail button svg { display: block; flex: none; }
#topbar button, #viewctl button, #player button {
  display: inline-flex; align-items: center; justify-content: center; gap: 5px;
}
#topbar button svg, #viewctl button svg, #player button svg { display: block; flex: none; }

/* ---------------- font project (2026-08-15) ---------------- */
/* Character-binding fields ride the canvas in screen space (positioned per
   frame from the camera). The container ignores the pointer; the fields
   don't — canvas gestures pass straight through everywhere else. */
#fontcells { position: absolute; inset: 0; pointer-events: none; overflow: hidden; }
.fontcell {
  position: absolute; display: flex; align-items: center; gap: 4px;
  padding-top: 3px; pointer-events: none;
}
.fontcell input { pointer-events: auto; }
.fontcell .fc-text {
  width: 100%; min-width: 34px; max-width: 120px; box-sizing: border-box;
  font: 600 12px/1 var(--font-mono, monospace); text-align: center;
  padding: 3px 4px; border: 1px solid var(--color-neutral-300);
  border-radius: 4px; background: rgba(251, 251, 252, .92);
  color: var(--color-neutral-800, #333);
}
.fontcell .fc-text:focus { border-color: var(--color-accent); outline: none; }
.fontcell .fc-text.dup { border-color: var(--color-danger); color: var(--color-danger); }
.fontcell .fc-extra { display: none; align-items: center; gap: 3px; }
.fontcell.active .fc-extra { display: inline-flex; }
.fontcell .fc-extra label { font-size: 10px; color: var(--color-text-muted); }
.fontcell .fc-extra input {
  width: 44px; font-size: 11px; padding: 2px 3px;
  border: 1px solid var(--color-neutral-300); border-radius: 4px;
  background: rgba(251, 251, 252, .92);
}
#fontcell-add {
  position: absolute; pointer-events: auto; cursor: pointer;
  border: 2px dashed var(--color-neutral-300); border-radius: 6px;
  background: transparent; color: var(--color-text-muted);
  font-size: 20px; line-height: 1;
}
#fontcell-add:hover { border-color: var(--color-accent); color: var(--color-accent); }
#font-fields .metric-row, #hoop-custom { display: flex; gap: 8px; margin-bottom: 6px; }
#hoop-custom { margin-top: 6px; margin-bottom: 0; align-items: center; }
#hoop-custom[hidden] { display: none; }   /* the display:flex above outranks the UA [hidden] rule */
#font-fields .fld, #hoop-custom .fld { display: flex; align-items: center; gap: 4px; }
#font-fields .fld label, #hoop-custom .fld label { font-size: 11px; color: var(--color-text-muted); }
#font-fields .fld input, #hoop-custom .fld input { width: 52px; font-size: 12px; padding: 3px 4px;
  border: 1px solid var(--color-neutral-300); border-radius: 4px; }
#hoop-custom .mini {
  border: 1px solid var(--color-neutral-300); border-radius: 4px;
  background: transparent; padding: 3px 7px; font-size: 12px; cursor: pointer;
}
#hoop-custom .mini:hover { border-color: var(--color-text); }

/* ---------------- design-pack (zip) view (2026-08-15) ---------------- */
#packview {
  position: fixed; inset: 0; z-index: 61;
  display: none; align-items: center; justify-content: center;
  background: rgba(12, 17, 22, .55);
}
#packview.open { display: flex; }
/* Title and Import/Cancel stay pinned; only the design + skipped-file
   lists scroll (user report 2026-08-16: the buttons scrolled away). */
.pack-card { width: min(640px, 92vw); max-height: 80vh; }
.pack-scroll { overflow-y: auto; min-height: 0; }
.pk-row {
  display: flex; align-items: center; gap: 10px; cursor: pointer;
  padding: 9px 10px; border: 1px solid var(--color-neutral-300);
  border-radius: 6px; margin-bottom: 7px; font-size: 12.5px;
}
.pk-row.sel { border-color: var(--color-accent); background: rgba(89, 128, 166, .06); }
.pk-dims { font: 700 13px/1 var(--font-heading); white-space: nowrap; }
.pk-meta { display: flex; align-items: center; gap: 4px; color: var(--color-text-subtle); white-space: nowrap; }
.pk-sw {
  width: 11px; height: 11px; border-radius: 3px; display: inline-block;
  border: 1px solid rgba(0, 0, 0, .18);
}
.pk-file {
  margin-left: auto; font: 600 11px/1.3 var(--font-mono, monospace);
  color: var(--color-text-subtle); text-align: right; overflow: hidden;
  text-overflow: ellipsis; max-width: 200px;
}
.pk-cw { font-size: 11px; max-width: 170px; }
.pack-skipped {
  font-size: 11px; line-height: 1.55; color: var(--color-text-subtle);
  max-height: 110px; overflow-y: auto; border-top: 1px dashed var(--color-neutral-300);
  padding-top: 8px; margin-top: 4px;
}

/* ---------------- new-project guard + dirty cue (2026-08-15) ---------------- */
#newconfirm {
  position: fixed; inset: 0; z-index: 62;
  display: none; align-items: center; justify-content: center;
  background: rgba(12, 17, 22, .55);
}
#newconfirm.open { display: flex; }
#newconfirm .dz-card { width: min(460px, 90vw); }
#newconfirm .dz-msg { text-align: left; }
#importchoice {
  position: fixed; inset: 0; z-index: 62;
  display: none; align-items: center; justify-content: center;
  background: rgba(12, 17, 22, .55);
}
#importchoice.open { display: flex; }
#importchoice .dz-card { width: min(460px, 90vw); }
#importchoice .dz-msg { text-align: left; }
#save-btn { position: relative; }
#save-btn.dirty::after {
  content: ''; position: absolute; top: 3px; right: 3px;
  width: 7px; height: 7px; border-radius: 50%;
  background: #c8642a;   /* amber — "unsaved", louder than accent, calmer than error red */
}

/* ---------------- gradient blend + accordion (2026-08-15) ---------------- */
.blendbox { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.blendbox .bstop {
  display: inline-flex; align-items: center; gap: 3px;
  border: 1px solid var(--color-neutral-300); border-radius: 4px; padding: 2px 4px;
}
.blendbox .chip { width: 12px; height: 12px; border-radius: 3px; display: inline-block;
  border: 1px solid rgba(0, 0, 0, .18); }
.blendbox input[data-math] { width: 44px; font-size: 11px; padding: 1px 2px;
  border: 1px solid var(--color-neutral-300); border-radius: 3px; }
.blendbox button { cursor: pointer; font-size: 11px; }
.blendbox .hint { font-size: 11px; color: var(--color-text-muted); }
.accspan { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; }
.accspan input { width: 50px; font-size: 12px; padding: 2px 3px;
  border: 1px solid var(--color-neutral-300); border-radius: 4px; }
.accspan .unit { color: var(--color-text-muted); font-size: 11px; min-width: 14px; text-align: center; }
/* A `.unit` rendered as a <label> is a per-field scrub handle (arith.js label
   scrub). Keep it inline (beating the block `#params .prow label` rule) and let
   the ew-resize affordance from `label:has(+ input[data-math])` show through. */
#params .prow label.unit { display: inline-flex; align-items: center; margin: 0; padding: 0 2px; }

/* Pattern parameters (Generate tab, user 2026-09-20): compact lines of two or
   three fields, each with its own label above it (the label scrubs its field). */
#params .pgrid { display: grid; gap: 8px; }
#params .pgrid-2 { grid-template-columns: 1fr 1fr; }
#params .pgrid-3 { grid-template-columns: 1fr 1fr 1fr; }
#params .pcell { display: flex; flex-direction: column; min-width: 0; }
#params .pcell > span { display: flex; align-items: center; gap: 4px; font-size: 12px; color: var(--color-neutral-700); }
#params .pcell input { width: 100%; min-width: 0; }
#params .pcell .punit { flex: none; width: 2em; }
#params .pcell select { width: 100%; min-width: 0; }
/* A row's trailing words ("into this object"): not a .hint, which gates and
   screen-reader flows read as the panel's one explanatory line. */
#params .unit-note { color: var(--color-neutral-500); font-size: 11px; }
#params .prow label.phead { font-weight: 600; color: var(--color-text); }

/* Photoshop-style text toolbar (user go 2026-09-12). Fields sit two-up; the
   align / shape / case pickers are segmented icon buttons reusing the
   stitch-type tile active state (.on = accent border + tint + accent ink). */
/* A disabled paste line reads as "not yet", not as broken — same opacity the
   disabled buttons use, so the whole row dims together when the agent switch is
   off. Tokens rather than literals: dark mode redefines both. */
.pop input:disabled {
  opacity: .45;
  cursor: default;
  background: var(--color-neutral-100);
  color: var(--color-text-muted);
}
.fieldrow { display: flex; gap: 8px; }
/* The Agent panel is one switch and one sentence; a fixed width keeps the
   sentence wrapping instead of stretching the popover across the canvas. */
#agent-pop { width: 400px; max-width: calc(100vw - 32px); }
/* "Get the plugin": two commands to copy by hand, selectable in one click. */
#agent-plugin code {
  display: block; margin-top: 4px; padding: 3px 6px; border-radius: 4px;
  font: 500 11.5px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--color-text); background: var(--color-bg); border: 1px solid var(--color-divider);
  user-select: all; overflow-x: auto; white-space: nowrap;
}
#agent-plugin a { display: inline-block; margin-top: 6px; font-size: 12px; color: var(--color-accent-800); }
.fieldrow > .accspan, .fieldrow > select, .fieldrow > input { flex: 1; min-width: 0; }
.fieldrow > .accspan input { width: 100%; }
.segbtns { display: flex; gap: 4px; }
.segbtns .segbtn { flex: 1; }
.segbtn {
  appearance: none; cursor: pointer;
  display: inline-flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  min-height: 28px; padding: 4px 6px;
  border: 1px solid var(--color-divider); border-radius: 5px; background: transparent;
  color: var(--color-neutral-700); font: 600 11px/1 var(--font-heading);
}
.segbtn:hover:not(.on) { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.segbtn.on { border-color: var(--color-accent); background: var(--color-accent-100); color: var(--color-accent-800); }
.segbtn svg { display: block; }
.segbtn.on svg { color: var(--color-accent-700); }
.segtiles .segbtn { padding: 5px 4px; }
.segbtn .segname { font-size: 9.5px; letter-spacing: .02em; color: var(--color-text-subtle); }
.segbtn.on .segname { color: var(--color-accent-800); }

/* ---------------- background layer row (2026-08-15) ---------------- */
/* display:flex would silently beat the hidden attribute — the row lingered
   after Remove/New (user bug 2026-08-19). */
#bg-row[hidden] { display: none; }
#bg-row {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 8px; margin: 4px 0;
  border: 1px solid var(--color-neutral-300); border-radius: 4px;
  cursor: pointer; font-size: 12px;
}
#bg-row.sel { border-color: var(--color-accent); background: rgba(89, 128, 166, .07); }
#bg-row .bgthumb {
  width: 22px; height: 22px; object-fit: cover; border-radius: 3px;
  border: 1px solid var(--color-neutral-300); background: var(--color-card);
}
#bg-row .bgname { font-weight: 600; }
#bg-row .bgmeta { color: var(--color-text-muted); font-size: 11px; margin-left: auto; }
#bg-row button {
  width: 24px; height: 22px; display: inline-flex; align-items: center;
  justify-content: center; padding: 0; border: 0; background: transparent;
  cursor: pointer; color: var(--color-text-subtle);
}
#bg-row button:hover { color: var(--color-text); }

/* ---------------- drag selection lockdown (2026-08-16) ---------------- */
/* Body-level user-select:none does not reach form controls — their text is
   selectable by nature, so swatch/reorder drags sweeping the panels left
   blue highlights behind. While a drag is live, nothing selects. */
.drag-noselect, .drag-noselect * {
  user-select: none !important;
  -webkit-user-select: none !important;
}
.blendbox .bstop input[data-bmin], .blendbox .bstop input[data-bmax] { width: 40px; }

/* Fur coats (2026-09-13): four numbers per coat, and relying on the
   placeholder to name them left every field unreadable at 40px — the label IS
   the placeholder there, and it does not fit ("I can't read them in the webapp
   there's not enough space for the text in them"). Two labelled rows instead:
   spacing min→max over hair root→tip, with the chip spanning both. */
.blendbox .bstop.coat {
  display: grid;
  grid-template-columns: auto auto auto auto auto auto;
  gap: 2px 4px;
  align-items: center;
}
.blendbox .bstop.coat .chip { grid-row: 1 / span 2; align-self: center; }
.blendbox .bstop.coat .clab {
  font-size: 10px; color: var(--color-text-muted);
  text-align: right; letter-spacing: .02em;
}
.blendbox .bstop.coat .carr { font-size: 10px; color: var(--color-neutral-400); }
.blendbox .bstop.coat input[data-math] { width: 46px; }
.blendbox .bstop.coat button { justify-self: end; }

/* ---------------- canvas color row (2026-08-16) ---------------- */
#canvas-color-row {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 8px; margin-top: 4px;
  border: 1px dashed var(--color-neutral-300); border-radius: 4px;
  cursor: pointer; font-size: 12px; color: var(--color-text-subtle);
}
#canvas-color-row:hover { border-color: var(--color-accent); color: var(--color-text); }
/* 24x24 is the WCAG 2.2 AA floor (2.5.8 Target Size Minimum). This swatch was
   24x20 — the ONLY control in the editor under it, measured across 51. */
#canvas-color-row input[type="color"] {
  width: 24px; height: 24px; padding: 0; border: 1px solid var(--color-neutral-300);
  border-radius: 3px; background: none; cursor: pointer;
}
/* 24px min-height is the WCAG 2.2 AA target floor (2.5.8). This button was
   47x18: the swatch beside it was fixed when the September audit named it, and
   this one was never re-measured, so the criterion stayed failing on the
   neighbouring control for weeks. Height only — the padding keeps the label
   optically where it was. */
#canvas-color-auto {
  margin-left: auto; flex: none; font: 600 10px/1 var(--font-body);
  text-transform: uppercase; letter-spacing: .04em; padding: 3px 7px; cursor: pointer;
  border: 1px solid var(--color-divider); border-radius: 3px;
  background: none; color: var(--color-text-subtle);
  min-height: 24px;
}
#canvas-color-auto:hover { color: var(--color-accent-700); border-color: var(--color-accent); }
/* Active AUTO: the swatch is following the theme, so the button is lit. */
#canvas-color-row.is-auto #canvas-color-auto {
  color: var(--color-accent-700); border-color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 14%, transparent);
}
#canvas-color-row.is-auto input[type="color"] { opacity: .6; }

/* missing-glyph warning under text fields (2026-08-16) */
.missrow { min-height: 0; }
.misswarn { font-size: 11px; color: var(--warn-text, #7d5f26); line-height: 1.4; }
.misswarn b { letter-spacing: .15em; }
/* Why a text's font can't be used, and the one button that fixes it
   (user 2026-09-25) — the same words as the canvas banner. */
.font-state { font-size: 12px; line-height: 1.45; color: var(--color-text); }
.font-state button { display: block; margin-top: 5px; }
#te-text:disabled { opacity: .6; cursor: not-allowed; }

/* pack thumbnails (2026-08-16) */
.pk-thumb { flex: none; background: var(--color-card); border: 1px solid var(--color-neutral-200);
  border-radius: 4px; }

/* 3D thread sliders in the Show popover (2026-08-16) */
.show-density { border-top: 1px dashed var(--color-neutral-300); margin-top: 8px; padding-top: 6px; }
.show-density .cap3d { font: 600 10px/1 var(--font-heading); letter-spacing: .1em;
  text-transform: uppercase; color: var(--color-text-muted); margin-bottom: 4px; }
.show-density label { display: flex; align-items: center; gap: 6px; font-size: 11px;
  color: var(--color-text-subtle); margin: 2px 0; cursor: pointer; }
.show-density input { margin: 0; accent-color: var(--color-accent); }
.show-3d { border-top: 1px dashed var(--color-neutral-300); margin-top: 8px; padding-top: 6px; }
.show-3d .cap3d { font: 600 10px/1 var(--font-heading); letter-spacing: .1em;
  text-transform: uppercase; color: var(--color-text-muted); margin-bottom: 4px; }
.show-3d .row3d { display: flex; align-items: center; gap: 6px; margin: 2px 0; }
.show-3d .row3d label { font-size: 11px; color: var(--color-text-subtle); flex: 1; }
.show-3d .row3d input { width: 56px; font-size: 11px; padding: 2px 4px;
  border: 1px solid var(--color-neutral-300); border-radius: 4px; }

/* iOS focus auto-zoom STOPGAP (user go 2026-09-06). Safari zooms the page when
   a focused input is under 16px, which shifts the header hit areas and makes
   the iPad hard to evaluate. A blanket `input {16px}` loses on specificity
   (measured by iOS_App), so this is !important inside a narrow media query —
   the honest place for a platform workaround. Coarse-gated: desktop keeps its
   compact type. TEMPORARY — the touch sizing sweep raises the ~8 source rules
   to 16px and this block comes out then; `npm run audit:touch` tracks it. */
@media (any-pointer: coarse) {
  input, select, textarea { font-size: 16px !important; }
}

/* Missing-font notice (user 2026-09-20): a design whose text font isn't in
   this browser (licence-stripped on share/download, or another device), or a
   font project whose source face was stripped. Sits under the read-only
   banner's slot; drop or Browse… supplies the font. */
#font-missing {
  position: absolute; top: 10px; left: 50%; transform: translateX(-50%);
  z-index: 6; max-width: min(92%, 620px);
  padding: 8px 30px 8px 12px; border-radius: 6px;
  background: var(--color-bg); border: 1px solid var(--color-divider);
  box-shadow: var(--shadow-sm);
  font-size: 12.5px; line-height: 1.45;
}
#ro-banner:not([hidden]) ~ #font-missing { top: 52px; }
#font-missing .fm-row + .fm-row { margin-top: 6px; }
#font-missing button { margin-left: 4px; padding: 1px 8px; font-size: 12px; cursor: pointer; }
#font-missing .fm-close { position: absolute; top: 4px; right: 4px; margin: 0; padding: 0 6px;
  border: 0; background: none; font-size: 16px; line-height: 1; }

/* Report bug (user 2026-09-20): a native <dialog>, so Escape closes it and
   focus stays inside while it's open. */
#bugrep {
  width: min(92vw, 460px); padding: 16px 18px; border-radius: 8px;
  border: 1px solid var(--color-divider); background: var(--color-bg); color: var(--color-text);
  box-shadow: var(--shadow-sm);
}
#bugrep::backdrop { background: rgba(0, 0, 0, .35); }
#bugrep h5 { margin: 0 0 10px; font-size: 14px; }
#bugrep label { display: block; font-size: 12.5px; margin-bottom: 4px; }
#bugrep textarea { width: 100%; box-sizing: border-box; font: inherit; font-size: 13px; padding: 6px 8px;
  border: 1px solid var(--color-divider); border-radius: 4px; background: var(--color-bg); color: var(--color-text); }
#bugrep fieldset { border: 0; margin: 10px 0 0; padding: 0; }
#bugrep legend { font-size: 12px; font-weight: 600; margin-bottom: 4px; }
#bugrep .chk { display: flex; gap: 6px; align-items: baseline; margin: 3px 0; }
#bugrep .hint { font-size: 11.5px; }
#bugrep .bug-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 12px; }
#bugrep .bug-actions #bug-download { margin-right: auto; }

/* the palette panel under the bar (spec 13A) */
#tp-panel { padding: 8px 12px 10px; display: flex; flex-direction: column; gap: 8px; font-size: 11px; }
#tp-panel[hidden] { display: none; }
.tp-label { display: flex; justify-content: space-between; color: var(--color-text-subtle); letter-spacing: .04em; margin-bottom: 4px; }
.tp-spools { display: flex; gap: 2px; }
.tp-spool {
  flex: 1; min-width: 0; height: 38px; border: 0; border-radius: 0; padding: 0 0 4px; position: relative;
  display: flex; align-items: flex-end; justify-content: center; cursor: grab; touch-action: none;
  font: 600 9px/1 var(--font-heading);
}
.tp-spool.hi { box-shadow: inset 0 0 0 2px #fff, inset 0 0 0 3px #1d2d3d; }
.tp-spool .tp-own { position: absolute; top: 3px; right: 4px; font-style: normal; }
.tp-spool .tp-used { position: absolute; top: 3px; left: 4px; font-style: normal; font-size: 8px; }
.tp-blank { background: var(--color-card); color: var(--color-text-subtle); border: 1px dashed var(--color-divider); flex: 1.4; }
.tp-scrub { display: flex; gap: 10px; align-items: center; }
.tp-search input { width: 150px; }
.tp-bar { flex: 1; height: 24px; display: flex; position: relative; cursor: pointer; touch-action: none; border-radius: 3px; overflow: hidden; }
.tp-bar i { flex: 1; min-width: 0; }
.tp-bar.dim { opacity: .45; }
.tp-window { position: absolute; top: 0; bottom: 0; border: 2px solid #1d2d3d; box-shadow: 0 0 0 1px #fff inset; border-radius: 3px; pointer-events: none; }
.tp-seg { display: flex; }
.tp-seg button { border-radius: 0; }
.tp-seg button[aria-pressed="true"] { background: #1d2d3d; color: #fff; }
.tp-sets { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.tp-cap { color: var(--color-text-subtle); letter-spacing: .08em; text-transform: uppercase; font-size: 10px; }
.tp-set { display: inline-flex; align-items: center; gap: 6px; }
.tp-set.on { background: #1d2d3d; color: #fff; }
.tp-set.edited b::after { content: ' •'; }
.tp-set small { opacity: .7; }
.tp-sw { display: inline-flex; gap: 1px; }
.tp-sw i { width: 5px; height: 14px; }
.tp-add { border-style: dashed; }
.tp-new { display: inline-flex; gap: 4px; align-items: center; }
.tp-new input:not([type]) { width: 120px; }
.tp-new input[type="number"] { width: 50px; }
.tp-detail { margin-left: auto; display: inline-flex; align-items: center; gap: 8px; }
.tp-detail span { display: flex; flex-direction: column; }
.tp-dsw { width: 28px; height: 28px; border-radius: 3px; }
.tp-ghost { position: fixed; z-index: 99; pointer-events: none; min-width: 36px; height: 28px; border-radius: 4px; border: 2px solid #fff;
  box-shadow: 0 1px 6px rgba(0,0,0,.4); font: 600 10px/28px var(--font-heading); text-align: center; color: #fff; text-shadow: 0 0 2px #000; }
.tp-ghost-bar { display: flex; width: 60px; overflow: hidden; }
/* Nested Library folders (Patches ▸ shield ▸ tiles): each level indents a step. */
.lib-cat-group .lib-cat-group { margin-left: 10px; }
.tp-ghost-bar i { flex: 1; }

/* Auto-digitize preview (2026-09-22): the on state of its toggle, and the
   thread swatches in its summary line. */
#p-bg-adprev.on { border-color: var(--color-accent); background: var(--color-accent); color: var(--color-bg); }
#p-bg-adsum .sw { display: inline-block; width: 10px; height: 10px; border-radius: 2px;
  margin: 0 1px; vertical-align: -1px; box-shadow: inset 0 0 0 1px rgba(0,0,0,.25); }

/* A font's licence where it's chosen and used (2026-09-23). */
.lic { font-size: 11px; line-height: 1.35; color: var(--color-text-muted, inherit); }
.lic .lic-id { font-weight: 600; color: var(--color-text); }
.lic .lic-travel { opacity: .85; }
.lic a { color: inherit; text-decoration: underline; }

/* The optimizer's background re-run (2026-09-23): a quiet pill beside the
   stitch count, instead of the blocking veil. */
.opt-pending { font-size: 11px; color: var(--color-text-muted); margin-left: 6px; }
.opt-pending::before { content: ''; display: inline-block; width: 8px; height: 8px; margin-right: 4px;
  border: 1.5px solid currentColor; border-right-color: transparent; border-radius: 50%;
  vertical-align: -1px; animation: optspin .8s linear infinite; }
@keyframes optspin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .opt-pending::before { animation: none; } }

/* No double-tap zoom (user 2026-09-27: on the iPad two quick taps zoomed the
   whole UI and there was no obvious way back). manipulation keeps scrolling and
   pinch zoom and drops only double-tap zoom, and with it the tap delay.
   touch-action is not inherited and stops at each scroll container, so it goes
   on every element; :where() gives it zero specificity, so the canvas and the
   other touch-action: none controls keep theirs. */
:where(*) { touch-action: manipulation; }

/* Properties of a LOCKED selection (user 2026-09-26): readable, not editable.
   Controls are disabled in main.js; the graphs are canvases, which have no
   disabled state, so they stop taking the pointer here. */
#params.locked-sel canvas { pointer-events: none; opacity: .6; }
#params .lock-note {
  margin: 0 0 8px; padding: 6px 8px; border-radius: 6px;
  font-size: 12px; color: var(--color-text);
  background: var(--color-card); border: 1px solid var(--color-divider);
}

/* Isolate (user 2026-09-27): while it's on, the button IS the notice and the
   exit, so it stands out in the accent colour. */
#canvasbar .iso-btn { width: auto; padding: 0 8px; }
#canvasbar .iso-btn.on { background: var(--color-accent); border-color: var(--color-accent); color: var(--color-bg); font-weight: 600; }

/* Library, signed out (user 2026-09-28): what an account adds, and the way in. */
#library .lib-signin-note button { display: block; margin-top: 8px; }
