  /* Registering the tone hue as a typed <number> is what lets a theme change ANIMATE: the browser
     can interpolate it, so every colour derived from it travels together instead of snapping.
     Engines without @property simply switch instantly, which is a fine fallback. */
  @property --tone-hue {
    syntax: '<number>';
    inherits: true;
    initial-value: 256;
  }
  /* tone-boot.js sets this while applying the SAVED theme at first paint — that one must land
     instantly, or the studio would visibly travel from blue to the chosen colour on every load. */
  :root[data-tone-immediate] { transition: none !important; }
  @media (prefers-reduced-motion: reduce) { :root { transition: none !important; } }

  :root {
    /* ============================================================================
       Re:Form — a precise, professional dark studio tool. DEFAULT THEME = blue.
       Direction: refined cool-slate near-black chrome + ONE confident, classy BLUE accent
       used scarcely. The 3D VIEWPORT stays a fixed neutral grey for every theme (see
       --viewport-gradient) so the sculpt reads true. Alternate palettes live under
       html[data-theme="…"] (View ▸ Theme). Authored in OKLCH (modern engines only).
       ============================================================================ */

    /* ---- TONE (2026-08-24) ----
       Both ramps below are anchored to ONE hue. --tone-hue is written to <html> by
       app/src/tone-boot.js from the tone the visitor picked on the marketing site (shared
       localStorage key 'reform.tone'), so the studio arrives in the same colour as the site they
       came from. Every hue below is expressed as an offset from it, which keeps the default
       rendering identical to the hand-tuned original (neutral 256, accent 252) while letting a
       tone change carry the whole interface. The 3D viewport gradient is NOT tone-driven — the
       sculpt must read true in every tone. ---- */
    --tone-hue: 256;
    /* Sweeping the hue rather than cutting to it is what makes a tone change read as ambience.
       Slow and long: the studio is a place to concentrate, so the colour must never pull the eye
       away from the sculpt. landing/theme.js suppresses this for the first paint. */
    transition: --tone-hue 2600ms cubic-bezier(0.37, 0, 0.24, 1);

    /* ---- Cool-slate neutral ramp (hue follows --tone-hue, very low chroma) ---- */
    --neutral-950: oklch(15% 0.008 var(--tone-hue));  --neutral-950-oklch: oklch(15% 0.008 var(--tone-hue));  /* deepest backdrop */
    --neutral-900: oklch(18% 0.010 var(--tone-hue));  --neutral-900-oklch: oklch(18% 0.010 var(--tone-hue));  /* page base */
    --neutral-850: oklch(21% 0.011 var(--tone-hue));  --neutral-850-oklch: oklch(21% 0.011 var(--tone-hue));  /* chrome base */
    --neutral-800: oklch(24% 0.012 var(--tone-hue));  --neutral-800-oklch: oklch(24% 0.012 var(--tone-hue));  /* panel surface */
    --neutral-750: oklch(28% 0.014 var(--tone-hue));  --neutral-750-oklch: oklch(28% 0.014 var(--tone-hue));  /* raised control */
    --neutral-700: oklch(33% 0.016 var(--tone-hue));  --neutral-700-oklch: oklch(33% 0.016 var(--tone-hue));  /* hover surface */
    --neutral-650: oklch(38% 0.017 var(--tone-hue));  --neutral-650-oklch: oklch(38% 0.017 var(--tone-hue));
    --neutral-600: oklch(45% 0.018 var(--tone-hue));  --neutral-600-oklch: oklch(45% 0.018 var(--tone-hue));  /* strong border */
    --neutral-500: oklch(57% 0.018 var(--tone-hue));  --neutral-500-oklch: oklch(57% 0.018 var(--tone-hue));
    --neutral-400: oklch(68% 0.016 var(--tone-hue));  --neutral-400-oklch: oklch(68% 0.016 var(--tone-hue));  /* muted text */
    --neutral-300: oklch(79% 0.013 var(--tone-hue));  --neutral-300-oklch: oklch(79% 0.013 var(--tone-hue));  /* secondary text */
    --neutral-200: oklch(89% 0.009 var(--tone-hue));  --neutral-200-oklch: oklch(89% 0.009 var(--tone-hue));
    --neutral-100: oklch(96% 0.005 var(--tone-hue));  --neutral-100-oklch: oklch(96% 0.005 var(--tone-hue));  /* primary text */

    /* ---- Accent ramp (hue follows --tone-hue, offset -4; classy blue by default) ---- */
    --accent-700: oklch(38% 0.085 calc(var(--tone-hue) - 4));  --accent-700-oklch: oklch(38% 0.085 calc(var(--tone-hue) - 4));    /* tinted fill / pressed */
    --accent-600: oklch(52% 0.130 calc(var(--tone-hue) - 4));  --accent-600-oklch: oklch(52% 0.130 calc(var(--tone-hue) - 4));
    --accent-500: oklch(66% 0.150 calc(var(--tone-hue) - 4));  --accent-500-oklch: oklch(66% 0.150 calc(var(--tone-hue) - 4));    /* primary accent — blue */
    --accent-400: oklch(76% 0.135 calc(var(--tone-hue) - 4));  --accent-400-oklch: oklch(76% 0.135 calc(var(--tone-hue) - 4));    /* bright accent / on-selected */

    /* ---- Semantic roles (use THESE in components) ---- */
    --surface-base:   var(--neutral-900);   /* legacy name kept */
    --surface-panel:  var(--neutral-800);   /* legacy name kept */
    --surface-raised: var(--neutral-750);   /* legacy name kept */
    --surface-hover:  var(--neutral-700);   /* legacy name kept */
    --surface-chrome:    color-mix(in oklab, var(--neutral-850) 86%, transparent);
    --surface-popover:   var(--neutral-800);
    --surface-inset:     var(--neutral-950);  /* slider tracks, wells */
    --accent:        var(--accent-500);      /* legacy name kept */
    --accent-strong: var(--accent-400);      /* legacy name kept */
    --accent-dim:    var(--accent-700);      /* legacy name kept */
    --accent-soft:   color-mix(in oklab, var(--accent-500) 16%, transparent);
    --text-primary:   var(--neutral-100);    /* legacy name kept */
    --text-secondary: var(--neutral-300);
    --text-muted:     var(--neutral-400);    /* legacy name kept */
    --hairline:        var(--neutral-700);   /* legacy name kept */
    --hairline-soft:   color-mix(in oklab, var(--neutral-700) 60%, transparent);
    --hairline-strong: var(--neutral-600);
    --focus-ring: oklch(76% 0.130 calc(var(--tone-hue) - 4) / 0.75);

    /* ---- Control affordance roles (one border weight + one disabled treatment everywhere) ---- */
    --control-border:    var(--hairline);      /* default 1px control outline */
    --control-border-hover: var(--hairline-strong);
    --disabled-opacity:  0.4;                  /* the single muted level for any disabled control */

    /* ---- Radii (one scale) ---- */
    /* ONE CORNER EVERYWHERE (2026-09-23). The scale below used to run 5 / 7 / 9 /
 12px; every step is now the same 4px the marketing site and the share viewer use, so a panel, a menu, a chip and a tile
 all share one corner. The four names are kept because the rest of the stylesheet is written in terms of them — and
 because a future decision to give, say, large surfaces their own corner back has somewhere to land. */
    --radius-xs: 4px;
    --radius-sm: 4px;
    --radius-md: 4px;
    --radius-lg: 4px;
    /* The start screen and the Projects window follow the MARKETING SITE, not the studio chrome (2026-09-23).
 The site is
 built on 4px corners. Everything else in the studio — menus, popovers, dialogs, the properties panel — keeps the softer
 radii above; these two surfaces are where the site's design and the studio's meet, so they read as the site. */
    --radius-project-box: 4px;
    /* Still a real pill, and still used by the things that want one: the scrollbar thumb and the colour picker's hue track.
       The account chips moved off it when everything else squared. */
    --radius-pill: 999px;

    /* ---- Spacing rhythm (4px base; every control padding/gap/margin pulls from THIS scale) ---- */
    --space-0_5: 2px;   /* hairline gaps only (menu-bar button cluster, rail dividers) */
    --space-1: 4px; --space-1_5: 6px; --space-2: 8px; --space-2_5: 10px;
    --space-3: 12px; --space-3_5: 14px; --space-4: 16px; --space-5: 20px; --space-6: 24px;
    --brush-tile-size: 84px;   /* picker tiles (2026-09-23 no longer truncates); the picker has no width of its own, so this one number sizes it */

    /* ---- Elevation: layered, low-opacity, tinted toward the slate hue ---- */
    --shadow-sm: 0 1px 2px oklch(10% 0.02 var(--tone-hue) / 0.42);
    --shadow-md: 0 2px 6px oklch(8% 0.02 var(--tone-hue) / 0.36), 0 8px 20px oklch(6% 0.02 var(--tone-hue) / 0.42);
    --shadow-lg: 0 4px 12px oklch(7% 0.02 var(--tone-hue) / 0.42), 0 18px 44px oklch(5% 0.02 var(--tone-hue) / 0.55);
    --shadow-focus: 0 0 0 3px var(--accent-soft);

    /* ---- Typography ---- */
    --font-sans: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    --font-mono: "Azeret Mono", ui-monospace, "SF Mono", "Cascadia Code", Menlo, Consolas, monospace;   /* Azeret Mono since 2026-10-29 (Red Hat Mono before it, IBM Plex Mono before that) */
    /* Bumped up for readability (owner: a lot of text was too small). */
    --text-2xs: 12px; --text-xs: 13px; --text-sm: 15px; --text-md: 16px; --text-lg: 20px; --text-xl: 24px;
    --tracking-label: 0.07em;   /* uppercase micro-labels */
    --tracking-tight: -0.01em;

    /* ---- Motion ---- */
    --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
    --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
    --dur-fast: 120ms;
    --dur-base: 180ms;

    /* ---- Layout skeleton (MEASURED by app.js applyViewportFraming — DO NOT CHANGE) ---- */
    --header-h: 54px;
    --footer-h: 30px;
    --rail-w: 76px;
    --panel-w: 322px;
    --quickbar-base-h: 54px;   /* one row of the options bar; --quickbar-h below follows the bar's REAL height when it wraps (app.js) */
    --quickbar-h: 54px;                                                 /* the OPTIONS bar under the menus (2026-09-11: like a photo editor's options bar; icon + word buttons) */
    --chrome-top: calc(var(--header-h) + var(--quickbar-h));            /* where the rail, panel and viewport overlays start */

    /* Viewport background: a NEUTRAL STUDIO GREY like pro 3D software, so the sculpt
       reads true. A soft radial keeps the centre brighter than the vignetted edges. The WebGPU/Three
       canvases clear transparent, so this shows through as the 3D backdrop. Deliberately NOT warm —
       only the chrome is warm; the working surface stays a calibrated grey. */
    /* --viewport-grey: the centre's lightness in percent (Preferences ▸ Viewport grey, 2026-09-26; 40 is the studio default). The
       other stops scale with it, so the vignette keeps its shape at any grey. */
    --viewport-grey: 40;
    --viewport-gradient:
      radial-gradient(125% 100% at 50% 44%,
        oklch(calc(var(--viewport-grey) * 1%) 0.003 250) 0%,
        oklch(calc(var(--viewport-grey) * 0.775%) 0.004 250) 40%,
        oklch(calc(var(--viewport-grey) * 0.575%) 0.005 250) 72%,
        oklch(calc(var(--viewport-grey) * 0.4%) 0.006 250) 100%);
  }

  /* ---- LIGHT appearance (owner 2026-09-09: a light theme and a dark theme). The same tone-driven ramps
     with the lightness scale turned over: the deepest backdrop becomes the palest paper, primary text
     becomes near-black, and the accents drop in lightness so they keep contrast on pale surfaces. Every
     component reads the semantic roles, so nothing else changes. The 3D viewport keeps its own neutral
     gradient in both appearances. ---- */
  html[data-appearance="light"] {
    --neutral-950: oklch(99% 0.004 var(--tone-hue));  --neutral-950-oklch: oklch(99% 0.004 var(--tone-hue));
    --neutral-900: oklch(97% 0.005 var(--tone-hue));  --neutral-900-oklch: oklch(97% 0.005 var(--tone-hue));
    --neutral-850: oklch(95% 0.006 var(--tone-hue));  --neutral-850-oklch: oklch(95% 0.006 var(--tone-hue));
    --neutral-800: oklch(92% 0.008 var(--tone-hue));  --neutral-800-oklch: oklch(92% 0.008 var(--tone-hue));
    --neutral-750: oklch(88% 0.010 var(--tone-hue));  --neutral-750-oklch: oklch(88% 0.010 var(--tone-hue));
    --neutral-700: oklch(82% 0.012 var(--tone-hue));  --neutral-700-oklch: oklch(82% 0.012 var(--tone-hue));
    --neutral-650: oklch(76% 0.013 var(--tone-hue));  --neutral-650-oklch: oklch(76% 0.013 var(--tone-hue));
    --neutral-600: oklch(68% 0.015 var(--tone-hue));  --neutral-600-oklch: oklch(68% 0.015 var(--tone-hue));
    --neutral-500: oklch(56% 0.017 var(--tone-hue));  --neutral-500-oklch: oklch(56% 0.017 var(--tone-hue));
    --neutral-400: oklch(46% 0.018 var(--tone-hue));  --neutral-400-oklch: oklch(46% 0.018 var(--tone-hue));
    --neutral-300: oklch(36% 0.016 var(--tone-hue));  --neutral-300-oklch: oklch(36% 0.016 var(--tone-hue));
    --neutral-200: oklch(26% 0.012 var(--tone-hue));  --neutral-200-oklch: oklch(26% 0.012 var(--tone-hue));
    --neutral-100: oklch(14% 0.008 var(--tone-hue));  --neutral-100-oklch: oklch(14% 0.008 var(--tone-hue));
    --accent-700: oklch(86% 0.070 calc(var(--tone-hue) - 4));  --accent-700-oklch: oklch(86% 0.070 calc(var(--tone-hue) - 4));
    --accent-600: oklch(62% 0.150 calc(var(--tone-hue) - 4));  --accent-600-oklch: oklch(62% 0.150 calc(var(--tone-hue) - 4));
    --accent-500: oklch(52% 0.165 calc(var(--tone-hue) - 4));  --accent-500-oklch: oklch(52% 0.165 calc(var(--tone-hue) - 4));
    --accent-400: oklch(44% 0.150 calc(var(--tone-hue) - 4));  --accent-400-oklch: oklch(44% 0.150 calc(var(--tone-hue) - 4));
    --focus-ring: oklch(48% 0.150 calc(var(--tone-hue) - 4) / 0.75);
    color-scheme: light;
  }
  /* MONO: the ramps with no chroma, a plain grey studio (dark or light). */
  html[data-theme="mono"] {
    --neutral-950: oklch(15% 0 0);  --neutral-900: oklch(18% 0 0);  --neutral-850: oklch(21% 0 0);  --neutral-800: oklch(24% 0 0);
    --neutral-750: oklch(28% 0 0);  --neutral-700: oklch(33% 0 0);  --neutral-650: oklch(38% 0 0);  --neutral-600: oklch(45% 0 0);
    --neutral-500: oklch(57% 0 0);  --neutral-400: oklch(68% 0 0);  --neutral-300: oklch(79% 0 0);  --neutral-200: oklch(89% 0 0);  --neutral-100: oklch(96% 0 0);
    --accent-700: oklch(40% 0 0);  --accent-600: oklch(55% 0 0);  --accent-500: oklch(70% 0 0);  --accent-400: oklch(82% 0 0);
  }
  html[data-theme="mono"][data-appearance="light"] {
    --neutral-950: oklch(99% 0 0);  --neutral-900: oklch(97% 0 0);  --neutral-850: oklch(95% 0 0);  --neutral-800: oklch(92% 0 0);
    --neutral-750: oklch(88% 0 0);  --neutral-700: oklch(82% 0 0);  --neutral-650: oklch(76% 0 0);  --neutral-600: oklch(68% 0 0);
    --neutral-500: oklch(56% 0 0);  --neutral-400: oklch(46% 0 0);  --neutral-300: oklch(36% 0 0);  --neutral-200: oklch(26% 0 0);  --neutral-100: oklch(14% 0 0);
    --accent-700: oklch(88% 0 0);  --accent-600: oklch(60% 0 0);  --accent-500: oklch(48% 0 0);  --accent-400: oklch(36% 0 0);
  }

  /* Rainbow (2026-09-09): the neutral ramp stays a quiet slate and
 the whole spectrum is painted ON it — a wash across the header, rail, panel and footer, and a
 saturated fill on anything that is "on" (the active tool, selected chips, slider thumbs) with
 spectrum-coloured readouts. The sculpt viewport stays untouched, as for every theme. */
  html[data-theme="rainbow"] {
    --rainbow-fill: linear-gradient(135deg in oklch, oklch(70% 0.17 20), oklch(78% 0.16 85), oklch(76% 0.16 140), oklch(72% 0.13 215), oklch(66% 0.17 285), oklch(70% 0.18 345));
    --rainbow-wash-across: linear-gradient(90deg in oklch, oklch(32% 0.10 20 / 0.62), oklch(34% 0.10 85 / 0.62), oklch(32% 0.10 145 / 0.62), oklch(31% 0.09 215 / 0.62), oklch(31% 0.10 285 / 0.62), oklch(32% 0.10 345 / 0.62));
    --rainbow-wash-down: linear-gradient(180deg in oklch, oklch(32% 0.10 20 / 0.62), oklch(34% 0.10 85 / 0.62), oklch(32% 0.10 145 / 0.62), oklch(31% 0.09 215 / 0.62), oklch(31% 0.10 285 / 0.62), oklch(32% 0.10 345 / 0.62));
    --rainbow-on-text: oklch(14% 0.01 260);
    --accent-700: oklch(38% 0.085 320);  --accent-600: oklch(52% 0.130 320);  --accent-500: oklch(66% 0.150 320);  --accent-400: oklch(78% 0.135 320);   /* the single-colour fallbacks (menu hover, focus, borders) */
    --focus-ring: oklch(76% 0.130 320 / 0.75);
  }
  html[data-theme="rainbow"][data-appearance="light"] {
    --rainbow-wash-across: linear-gradient(90deg in oklch, oklch(90% 0.07 20 / 0.72), oklch(93% 0.08 85 / 0.72), oklch(91% 0.07 145 / 0.72), oklch(90% 0.06 215 / 0.72), oklch(89% 0.07 285 / 0.72), oklch(90% 0.07 345 / 0.72));
    --rainbow-wash-down: linear-gradient(180deg in oklch, oklch(90% 0.07 20 / 0.72), oklch(93% 0.08 85 / 0.72), oklch(91% 0.07 145 / 0.72), oklch(90% 0.06 215 / 0.72), oklch(89% 0.07 285 / 0.72), oklch(90% 0.07 345 / 0.72));
    --accent-700: oklch(86% 0.070 320);  --accent-600: oklch(62% 0.150 320);  --accent-500: oklch(52% 0.165 320);  --accent-400: oklch(44% 0.150 320);
    --focus-ring: oklch(48% 0.150 320 / 0.75);
  }
  html[data-theme="rainbow"] #appHeader, html[data-theme="rainbow"] #quickBar, html[data-theme="rainbow"] #appFooter { background: var(--rainbow-wash-across), var(--surface-chrome); }
  html[data-theme="rainbow"] #toolRail, html[data-theme="rainbow"] #propertiesPanel { background: var(--rainbow-wash-down), var(--surface-chrome); }
  html[data-theme="rainbow"] .brushButton.brushButtonActive, html[data-theme="rainbow"] .railTool.railToolActive,
  html[data-theme="rainbow"] .toggleButton.toggleButtonActive, html[data-theme="rainbow"] .toggleButton.toggleButtonActive:hover {
    background: var(--rainbow-fill); color: var(--rainbow-on-text); border-color: transparent;
  }
  html[data-theme="rainbow"] input[type="range"]::-webkit-slider-thumb, html[data-theme="rainbow"] input[type="range"]:hover::-webkit-slider-thumb { background: var(--rainbow-fill); }
  html[data-theme="rainbow"] input[type="range"]::-moz-range-thumb, html[data-theme="rainbow"] input[type="range"]:hover::-moz-range-thumb { background: var(--rainbow-fill); }
  html[data-theme="rainbow"] .sliderLabel span, html[data-theme="rainbow"] #viewportStats b, html[data-theme="rainbow"] .statReadout b {
    background: var(--rainbow-fill); -webkit-background-clip: text; background-clip: text; color: transparent;
  }
  html[data-theme="rainbow"] .sliderLabel span:focus, html[data-theme="rainbow"] .sliderLabel span[contenteditable="true"] { background: var(--surface-inset); color: var(--accent-strong); }

  /* Banana (2026-10-02; the 2026-09-26 version was a dark orange-brown chrome, the
 same hue as Tangerine's, under gold, and read as chocolate and honey). The accent is a clear banana yellow (hue 95 to 103, a touch toward
 green, where a banana sits) that pales to cream; the light appearance is cream and yellow with brown text; the dark appearance is brown. */
  html[data-theme="banana"] {   /* the dark appearance: the brown of a ripe peel's spots and stem (2026-10-02), lifted so it reads as brown, under the banana yellow */
    --neutral-950: oklch(24% 0.055 58);  --neutral-900: oklch(27% 0.062 58);  --neutral-850: oklch(30% 0.068 59);  --neutral-800: oklch(34% 0.074 60);
    --neutral-750: oklch(38% 0.080 61);  --neutral-700: oklch(43% 0.086 62);  --neutral-650: oklch(48% 0.090 64);  --neutral-600: oklch(55% 0.092 67);
    --neutral-500: oklch(65% 0.090 72);  --neutral-400: oklch(75% 0.084 80);  --neutral-300: oklch(85% 0.070 90);  --neutral-200: oklch(92% 0.050 97);  --neutral-100: oklch(98% 0.026 102);
    --accent-700: oklch(70% 0.140 95);  --accent-600: oklch(84% 0.165 98);  --accent-500: oklch(92% 0.165 101);  --accent-400: oklch(96% 0.100 103);
    --focus-ring: oklch(90% 0.160 100 / 0.75);
  }
  html[data-theme="banana"][data-appearance="light"] {
    --neutral-950: oklch(99% 0.024 102);  --neutral-900: oklch(97% 0.046 101);  --neutral-850: oklch(95% 0.064 100);  --neutral-800: oklch(92% 0.080 100);
    --neutral-750: oklch(89% 0.092 99);  --neutral-700: oklch(84% 0.100 98);  --neutral-650: oklch(78% 0.100 96);  --neutral-600: oklch(70% 0.092 92);
    --neutral-500: oklch(58% 0.078 85);  --neutral-400: oklch(47% 0.066 78);  --neutral-300: oklch(37% 0.056 70);  --neutral-200: oklch(27% 0.046 62);  --neutral-100: oklch(15% 0.030 58);
    --accent-700: oklch(94% 0.100 100);  --accent-600: oklch(80% 0.150 92);  --accent-500: oklch(66% 0.130 80);  --accent-400: oklch(50% 0.100 68);
    --focus-ring: oklch(68% 0.130 82 / 0.75);
  }

  /* Watermelon: rind and flesh — a green-tinted chrome with a pink-red accent. Two hues, so the accent
     ramp does not follow --tone-hue (which drives the green neutrals) and is authored here instead. */
  html[data-theme="watermelon"] {
    --neutral-950: oklch(15% 0.024 150);  --neutral-900: oklch(18% 0.030 150);  --neutral-850: oklch(21% 0.034 150);  --neutral-800: oklch(24% 0.038 150);
    --neutral-750: oklch(28% 0.042 150);  --neutral-700: oklch(33% 0.046 150);  --neutral-650: oklch(38% 0.048 150);  --neutral-600: oklch(45% 0.050 150);
    --neutral-500: oklch(57% 0.050 150);  --neutral-400: oklch(68% 0.046 150);  --neutral-300: oklch(79% 0.038 150);  --neutral-200: oklch(89% 0.028 150);  --neutral-100: oklch(97% 0.016 150);
    --accent-700: oklch(40% 0.115 14);  --accent-600: oklch(56% 0.175 16);  --accent-500: oklch(68% 0.190 14);  --accent-400: oklch(78% 0.155 12);
    --focus-ring: oklch(76% 0.160 14 / 0.75);
  }
  html[data-theme="watermelon"][data-appearance="light"] {
    --neutral-950: oklch(99% 0.012 150);  --neutral-900: oklch(97% 0.022 150);  --neutral-850: oklch(95% 0.030 150);  --neutral-800: oklch(92% 0.038 150);
    --neutral-750: oklch(88% 0.044 150);  --neutral-700: oklch(82% 0.048 150);  --neutral-650: oklch(76% 0.048 150);  --neutral-600: oklch(68% 0.046 150);
    --neutral-500: oklch(56% 0.042 150);  --neutral-400: oklch(46% 0.038 150);  --neutral-300: oklch(36% 0.030 150);  --neutral-200: oklch(26% 0.022 150);  --neutral-100: oklch(14% 0.014 150);
    --accent-700: oklch(88% 0.075 14);  --accent-600: oklch(62% 0.195 16);  --accent-500: oklch(54% 0.205 16);  --accent-400: oklch(46% 0.185 14);
    --focus-ring: oklch(50% 0.185 14 / 0.75);
  }

  /* Tangerine: peel and pith — a warm orange-tinted chrome under a bright citrus accent. The shared accent ramp at this hue
     lands as a muted brown at the lower lightnesses, so the ramp is re-authored here to keep the orange reading as orange. */
  html[data-theme="tangerine"] {
    --neutral-950: oklch(15% 0.022 58);  --neutral-900: oklch(18% 0.027 58);  --neutral-850: oklch(21% 0.031 58);  --neutral-800: oklch(24% 0.035 58);
    --neutral-750: oklch(28% 0.039 58);  --neutral-700: oklch(33% 0.043 58);  --neutral-650: oklch(38% 0.045 58);  --neutral-600: oklch(45% 0.047 58);
    --neutral-500: oklch(57% 0.047 58);  --neutral-400: oklch(68% 0.044 58);  --neutral-300: oklch(79% 0.036 58);  --neutral-200: oklch(89% 0.027 58);  --neutral-100: oklch(97% 0.016 58);
    --accent-700: oklch(42% 0.110 52);  --accent-600: oklch(60% 0.165 55);  --accent-500: oklch(73% 0.185 58);  --accent-400: oklch(84% 0.160 62);
    --focus-ring: oklch(78% 0.175 58 / 0.75);
  }
  html[data-theme="tangerine"][data-appearance="light"] {
    --neutral-950: oklch(99% 0.012 58);  --neutral-900: oklch(97% 0.022 58);  --neutral-850: oklch(95% 0.030 58);  --neutral-800: oklch(92% 0.038 58);
    --neutral-750: oklch(88% 0.044 58);  --neutral-700: oklch(82% 0.048 58);  --neutral-650: oklch(76% 0.048 58);  --neutral-600: oklch(68% 0.046 58);
    --neutral-500: oklch(56% 0.042 58);  --neutral-400: oklch(46% 0.038 58);  --neutral-300: oklch(36% 0.030 58);  --neutral-200: oklch(26% 0.022 58);  --neutral-100: oklch(14% 0.014 58);
    --accent-700: oklch(89% 0.080 58);  --accent-600: oklch(68% 0.175 55);  --accent-500: oklch(58% 0.175 52);  --accent-400: oklch(48% 0.150 50);
    --focus-ring: oklch(58% 0.175 52 / 0.75);
  }

  /* Blueberry (owner's palette 2026-09-26: #1C1F2B #1B3D6A #3B6EA5 #B8CBD9 #E8F0F7): a near-black-to-navy chrome under a mid-blue accent
     that lightens to grey-blue and ice. */
  html[data-theme="blueberry"] {
    --neutral-950: oklch(15% 0.024 268);  --neutral-900: oklch(18% 0.032 266);  --neutral-850: oklch(21% 0.040 264);  --neutral-800: oklch(24% 0.050 262);
    --neutral-750: oklch(28% 0.060 260);  --neutral-700: oklch(33% 0.070 258);  --neutral-650: oklch(38% 0.074 256);  --neutral-600: oklch(45% 0.076 255);
    --neutral-500: oklch(57% 0.070 252);  --neutral-400: oklch(68% 0.056 248);  --neutral-300: oklch(79% 0.040 244);  --neutral-200: oklch(89% 0.026 240);  --neutral-100: oklch(97% 0.014 236);
    --accent-700: oklch(34% 0.090 258);  --accent-600: oklch(52% 0.115 252);  --accent-500: oklch(68% 0.090 248);  --accent-400: oklch(84% 0.035 240);
    --focus-ring: oklch(66% 0.100 250 / 0.75);
  }
  html[data-theme="blueberry"][data-appearance="light"] {
    --neutral-950: oklch(99% 0.010 236);  --neutral-900: oklch(97% 0.018 238);  --neutral-850: oklch(95% 0.026 240);  --neutral-800: oklch(92% 0.034 242);
    --neutral-750: oklch(88% 0.040 244);  --neutral-700: oklch(82% 0.046 246);  --neutral-650: oklch(76% 0.050 248);  --neutral-600: oklch(68% 0.054 250);
    --neutral-500: oklch(56% 0.058 252);  --neutral-400: oklch(46% 0.060 255);  --neutral-300: oklch(36% 0.056 258);  --neutral-200: oklch(26% 0.044 262);  --neutral-100: oklch(14% 0.024 268);
    --accent-700: oklch(90% 0.030 240);  --accent-600: oklch(60% 0.110 252);  --accent-500: oklch(50% 0.115 255);  --accent-400: oklch(40% 0.095 258);
    --focus-ring: oklch(52% 0.115 254 / 0.75);
  }

  /* Lemonade (2026-09-26 before: #93E5E5 #93FF99 #FFFFCC #FFFB87 #E5E55C): a soft cyan-mint chrome
 under a pale lemon accent, kept pastel. Two hues, so the accent ramp is authored here rather than following --tone-hue. */
  html[data-theme="lemonade-lake"] {
    --neutral-950: oklch(15% 0.018 185);  --neutral-900: oklch(18% 0.022 183);  --neutral-850: oklch(21% 0.026 181);  --neutral-800: oklch(24% 0.030 180);
    --neutral-750: oklch(28% 0.034 178);  --neutral-700: oklch(33% 0.038 176);  --neutral-650: oklch(38% 0.040 174);  --neutral-600: oklch(45% 0.042 172);
    --neutral-500: oklch(57% 0.044 170);  --neutral-400: oklch(68% 0.044 168);  --neutral-300: oklch(79% 0.040 166);  --neutral-200: oklch(89% 0.032 164);  --neutral-100: oklch(97% 0.018 162);
    --accent-700: oklch(62% 0.080 108);  --accent-600: oklch(82% 0.105 106);  --accent-500: oklch(93% 0.115 105);  --accent-400: oklch(97% 0.070 108);
    --focus-ring: oklch(90% 0.110 106 / 0.75);
  }
  html[data-theme="lemonade-lake"][data-appearance="light"] {
    --neutral-950: oklch(99% 0.010 170);  --neutral-900: oklch(97% 0.020 172);  --neutral-850: oklch(95% 0.028 174);  --neutral-800: oklch(92% 0.036 176);
    --neutral-750: oklch(88% 0.042 178);  --neutral-700: oklch(82% 0.046 180);  --neutral-650: oklch(76% 0.046 180);  --neutral-600: oklch(68% 0.044 180);
    --neutral-500: oklch(56% 0.040 180);  --neutral-400: oklch(46% 0.036 180);  --neutral-300: oklch(36% 0.030 180);  --neutral-200: oklch(26% 0.022 180);  --neutral-100: oklch(14% 0.014 180);
    --accent-700: oklch(96% 0.070 106);  --accent-600: oklch(88% 0.115 106);  --accent-500: oklch(80% 0.125 104);  --accent-400: oklch(70% 0.115 102);
    --focus-ring: oklch(80% 0.125 104 / 0.75);
  }

  /* Passionfruit (2026-09-26, from a photo of the fruit): a plum-purple skin chrome under the golden yellow
 of the fruit behind it. Two hues, so the accent ramp is authored here instead of following --tone-hue (which drives the purple). */
  html[data-theme="passionfruit"] {
    --neutral-950: oklch(15% 0.036 322);  --neutral-900: oklch(18% 0.046 322);  --neutral-850: oklch(21% 0.054 322);  --neutral-800: oklch(24% 0.062 322);
    --neutral-750: oklch(28% 0.070 322);  --neutral-700: oklch(33% 0.078 322);  --neutral-650: oklch(38% 0.082 322);  --neutral-600: oklch(45% 0.084 322);
    --neutral-500: oklch(57% 0.080 322);  --neutral-400: oklch(68% 0.068 322);  --neutral-300: oklch(79% 0.050 322);  --neutral-200: oklch(89% 0.034 322);  --neutral-100: oklch(97% 0.016 322);
    --accent-700: oklch(58% 0.130 74);  --accent-600: oklch(74% 0.160 78);  --accent-500: oklch(85% 0.165 86);  --accent-400: oklch(92% 0.125 92);
    --focus-ring: oklch(84% 0.160 84 / 0.75);
  }
  html[data-theme="passionfruit"][data-appearance="light"] {   /* purple, not pink (owner 2026-09-26): the pale end sits at a true purple hue */
    --neutral-950: oklch(98% 0.016 302);  --neutral-900: oklch(95% 0.032 302);  --neutral-850: oklch(92% 0.046 302);  --neutral-800: oklch(88% 0.058 302);
    --neutral-750: oklch(84% 0.068 302);  --neutral-700: oklch(78% 0.078 302);  --neutral-650: oklch(72% 0.084 302);  --neutral-600: oklch(64% 0.088 302);
    --neutral-500: oklch(53% 0.090 302);  --neutral-400: oklch(43% 0.086 302);  --neutral-300: oklch(34% 0.076 302);  --neutral-200: oklch(25% 0.060 302);  --neutral-100: oklch(14% 0.034 302);
    --accent-700: oklch(92% 0.100 92);  --accent-600: oklch(78% 0.160 84);  --accent-500: oklch(68% 0.160 78);  --accent-400: oklch(56% 0.130 72);
    --focus-ring: oklch(70% 0.160 80 / 0.75);
  }

  /* Bubblegum (2026-09-26, from photos of bubblegum ice cream): a pastel sky-aqua chrome (bubblegum blue, not
 slate, not teal) under a pastel bubblegum pink accent. The chrome is lighter than the
 other themes so the blue reads. Two hues, so the accent ramp is authored here. */
  html[data-theme="bubblegum"] {
    --neutral-950: oklch(32% 0.066 226);  --neutral-900: oklch(36% 0.074 226);  --neutral-850: oklch(40% 0.082 225);  --neutral-800: oklch(44% 0.090 225);
    --neutral-750: oklch(48% 0.096 224);  --neutral-700: oklch(53% 0.102 223);  --neutral-650: oklch(58% 0.106 222);  --neutral-600: oklch(64% 0.108 221);
    --neutral-500: oklch(71% 0.100 220);  --neutral-400: oklch(79% 0.084 219);  --neutral-300: oklch(87% 0.060 218);  --neutral-200: oklch(93% 0.036 217);  --neutral-100: oklch(98% 0.014 216);
    --accent-700: oklch(60% 0.112 356);  --accent-600: oklch(70% 0.118 356);  --accent-500: oklch(78% 0.102 356);  --accent-400: oklch(88% 0.060 354);
    --focus-ring: oklch(77% 0.105 356 / 0.75);
  }
  html[data-theme="bubblegum"][data-appearance="light"] {
    --neutral-950: oklch(98% 0.020 222);  --neutral-900: oklch(95% 0.038 222);  --neutral-850: oklch(92% 0.054 222);  --neutral-800: oklch(88% 0.068 222);
    --neutral-750: oklch(84% 0.078 222);  --neutral-700: oklch(78% 0.086 223);  --neutral-650: oklch(72% 0.090 223);  --neutral-600: oklch(64% 0.092 224);
    --neutral-500: oklch(54% 0.086 225);  --neutral-400: oklch(44% 0.074 226);  --neutral-300: oklch(35% 0.060 227);  --neutral-200: oklch(26% 0.046 228);  --neutral-100: oklch(16% 0.028 229);
    --accent-700: oklch(91% 0.050 354);  --accent-600: oklch(74% 0.115 356);  --accent-500: oklch(66% 0.122 356);  --accent-400: oklch(57% 0.114 356);
    --focus-ring: oklch(66% 0.120 356 / 0.75);
  }

  /* Mint (2026-09-26): the dark mint palette — a slate blue-teal chrome under a pale mint-green accent. Two
 hues, so the accent ramp is authored here instead of following --tone-hue (which drives the blue-teal neutrals). */
  html[data-theme="mint"] {
    --neutral-950: oklch(15% 0.026 205);  --neutral-900: oklch(18% 0.032 205);  --neutral-850: oklch(21% 0.036 205);  --neutral-800: oklch(24% 0.040 205);
    --neutral-750: oklch(28% 0.044 205);  --neutral-700: oklch(33% 0.048 205);  --neutral-650: oklch(38% 0.050 205);  --neutral-600: oklch(45% 0.052 205);
    --neutral-500: oklch(57% 0.052 205);  --neutral-400: oklch(68% 0.048 205);  --neutral-300: oklch(79% 0.040 205);  --neutral-200: oklch(89% 0.030 205);  --neutral-100: oklch(97% 0.016 205);
    --accent-700: oklch(46% 0.090 170);  --accent-600: oklch(67% 0.130 168);  --accent-500: oklch(85% 0.130 166);  --accent-400: oklch(93% 0.095 165);
    --focus-ring: oklch(86% 0.125 166 / 0.75);
  }
  html[data-theme="mint"][data-appearance="light"] {
    --neutral-950: oklch(99% 0.012 205);  --neutral-900: oklch(97% 0.022 205);  --neutral-850: oklch(95% 0.030 205);  --neutral-800: oklch(92% 0.038 205);
    --neutral-750: oklch(88% 0.044 205);  --neutral-700: oklch(82% 0.048 205);  --neutral-650: oklch(76% 0.048 205);  --neutral-600: oklch(68% 0.046 205);
    --neutral-500: oklch(56% 0.042 205);  --neutral-400: oklch(46% 0.038 205);  --neutral-300: oklch(36% 0.030 205);  --neutral-200: oklch(26% 0.022 205);  --neutral-100: oklch(14% 0.014 205);
    --accent-700: oklch(92% 0.080 166);  --accent-600: oklch(75% 0.130 168);  --accent-500: oklch(63% 0.135 170);  --accent-400: oklch(53% 0.115 172);
    --focus-ring: oklch(64% 0.130 169 / 0.75);
  }

  /* The pen pressure response curve editor (2026-09-23), a custom preference row. */
  .penPressureCurveEditor { display: flex; flex-direction: column; gap: var(--space-2); align-items: flex-start; }
  .penPressureCurveCanvas {
    border: 1px solid var(--hairline-border, var(--neutral-700));
    border-radius: 4px;
    background: var(--surface-inset, var(--neutral-900));
    cursor: crosshair;
    touch-action: none;   /* a pen or finger drag shapes the curve instead of scrolling the dialog */
  }
  .penPressureCurveReadout { font-size: var(--text-xs); color: var(--text-muted); }

  /* A saved-project tile while it is being deleted (2026-09-23): it dims, stops taking clicks, and fills a bar across its
     bottom edge as each step of the delete lands, so a slow connection reads as progress rather than as a failure. */
  .lightboxTile.lightboxTileDeleting { pointer-events: none; opacity: 0.55; position: relative; }
  .lightboxTileDeleteProgress {
    position: absolute; left: 0; right: 0; bottom: 0; height: 3px;
    background: oklch(var(--tint-neutral) / .25);
    border-bottom-left-radius: inherit; border-bottom-right-radius: inherit;
    overflow: hidden;
  }
  .lightboxTileDeleteProgressFill {
    width: 0%; height: 100%;
    background: var(--accent-500);
    transition: width 0.25s var(--ease-out);
  }
  .lightboxTileDeleteLabel {
    position: absolute; left: 0; right: 0; bottom: 9px;
    text-align: center;
    font-size: var(--text-xs); color: var(--text-primary);
    text-shadow: 0 1px 3px rgba(0, 0, 0, .85);
    pointer-events: none;
  }

  /* File ▸ Share and File ▸ My shares (2026-09-23). Both sit on the shared modal shell (.appModalDialog / .confirmDialogPanel),
     so the backdrop, the panel and the button row all match every other dialog in the studio. */
  .appModalDialog .confirmDialogPanel.shareDialogPanel { width: min(640px, 92vw); }   /* the extra class outranks the 420px base panel rule (2026-09-24) */
  .shareDialogBody { display: flex; gap: var(--space-4); align-items: flex-start; }
  .shareDialogPreview {
    position: relative; flex: 0 0 auto; width: 200px; aspect-ratio: 1200 / 630;
    border: 1px solid var(--control-border); border-radius: var(--radius-sm);
    background: var(--surface-inset); overflow: hidden;
    display: flex; align-items: center; justify-content: center;
  }
  .shareDialogPreview img { width: 100%; height: 100%; object-fit: cover; display: none; }
  .shareDialogPreview img.isReady { display: block; }
  #shareDialogPreviewEmpty { font-size: var(--text-2xs); color: var(--text-faint); padding: 0 var(--space-2); text-align: center; }
  .shareDialogFields { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: var(--space-1_5); }
  .shareDialogLabel { font-size: var(--text-2xs); color: var(--text-muted); }
  .shareDialogFact { font-size: var(--text-xs); color: var(--text-secondary); }
  .shareDialogSwitch { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-xs); color: var(--text-primary); cursor: pointer; margin-top: var(--space-2); }
  .shareDialogSwitch input { margin: 0; }
  .shareDialogSwitch.shareDialogSwitchUnavailable { opacity: 0.5; cursor: default; }
  /* How it opens (2026-10-12): three labelled choices in two columns, and the turning switch under them. */
  .shareDialogPresentation { display: flex; flex-direction: column; gap: var(--space-1_5); margin-top: var(--space-3); padding-top: var(--space-3); border-top: 1px solid var(--control-border); }
  .shareDialogPresentationTitle { font-size: var(--text-xs); font-weight: 600; color: var(--text-primary); }
  .shareDialogPresentationGrid { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: var(--space-1_5) var(--space-3); }
  .shareDialogSelect { width: 100%; min-width: 0; font: inherit; font-size: var(--text-xs); color: var(--text-primary); background: var(--surface-raised); border: 1px solid var(--control-border); border-radius: var(--radius-sm); padding: var(--space-1) var(--space-2); }
  .shareDialogSelect:hover { border-color: var(--control-border-hover); }
  .shareDialogSelect:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 1px; }
  .shareDialogPresentation .shareDialogSwitch { margin-top: var(--space-1); }
  .shareDialogCreditRow { display: flex; flex-direction: column; gap: var(--space-1); }
  /* The level note only appears when the share had to come from a coarser level than the one being sculpted. */
  .shareDialogNote { font-size: var(--text-2xs); color: var(--text-warning, #e0a33e); line-height: 1.45; }
  .shareDialogProgress { display: flex; flex-direction: column; gap: var(--space-1); }
  .shareDialogProgressTrack { height: 3px; background: oklch(var(--tint-neutral) / .25); border-radius: var(--radius-sm); overflow: hidden; }
  .shareDialogProgressFill { width: 0%; height: 100%; background: var(--accent-500); transition: width 0.25s var(--ease-out); }
  .shareDialogProgressLabel { font-size: var(--text-2xs); color: var(--text-muted); }
  .shareDialogResult { display: flex; flex-direction: column; gap: var(--space-1_5); }
  .shareDialogLinkRow { display: flex; gap: var(--space-2); align-items: center; }
  .shareDialogLinkRow .textEntryInput { flex: 1 1 auto; min-width: 0; }

  .appModalDialog .confirmDialogPanel.mySharesPanel { width: min(780px, 94vw); max-height: 80vh; display: flex; flex-direction: column; gap: var(--space-3); }   /* the extra class outranks the 420px base panel rule; at 420px the rows wrapped a character a line (owner 2026-09-24) */
  .mySharesHeader { display: flex; flex-direction: column; gap: var(--space-1); }   /* 2026-09-26: title + one line, closed by the button row like every other dialog (the old × was unstyled) */
  .mySharesList { display: flex; flex-direction: column; gap: var(--space-2); overflow-y: auto; }
  .myShareRow {
    display: flex; align-items: center; gap: var(--space-4);
    padding: var(--space-2_5) var(--space-3); border: 1px solid var(--hairline-border); border-radius: var(--radius-sm);
    background: var(--surface-raised);
  }
  .myShareRowThumbnail {
    flex: 0 0 auto; width: 152px; aspect-ratio: 1200 / 630; object-fit: cover;
    border-radius: var(--radius-xs); background: var(--surface-inset); display: block;
  }
  .myShareRowText { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
  .myShareRowTitle { font-size: var(--text-xs); font-weight: 600; color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .myShareRowMeta { font-size: var(--text-2xs); color: var(--text-faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .myShareRowButtons { flex: 0 0 auto; display: flex; gap: var(--space-1_5); }
  /* A row being deleted reads like a saved-project tile being deleted: dimmed, out of reach, with its own progress. */
  .myShareRow.myShareRowBusy { opacity: 0.55; pointer-events: none; }
  /* Submit to gallery (v0.8.8): the same rows, one of them chosen */
  .myShareRow.gallerySubmitRow { cursor: pointer; }
  .myShareRow.gallerySubmitRowSelected { outline: 1px solid var(--accent-500); outline-offset: -1px; }
  .gallerySubmitFields { display: flex; flex-direction: column; gap: var(--space-1_5); margin-top: var(--space-3); }
  .gallerySubmitFields a { color: inherit; text-decoration: underline; }

  * { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; }

  html, body {
    height: 100%;
    overflow: hidden;
    background: var(--surface-base);
    /* radial viewport glow (var(--surface-base) is the fallback above). Preferences ▸ Viewport background (owner 2026-09-27) sets
       --studio-viewport-background on <html> for Solid / Gradient; Theme removes it, so this stays the grey. Only the studio viewport
       reads it — the autosave tiles and the thumbnail / share-preview backdrop keep var(--viewport-gradient). */
    background: var(--studio-viewport-background, var(--viewport-gradient));
    color: var(--text-primary);
    font-family: var(--font-sans);
    font-size: var(--text-sm);
    font-feature-settings: "cv01", "ss01", "tnum";  /* tabular figures where supported; harmless otherwise */
    line-height: 1.45;
    letter-spacing: 0.005em;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
    touch-action: none;
    overscroll-behavior: none;
  }

  /* Numeric readouts use lining tabular figures so digits don't jitter as values change. */
  .statReadout, #viewportStats, #perfReadout, .sliderLabel span, #subdivisionLevelReadout {
    font-variant-numeric: tabular-nums;
  }

  /* Shared focus treatment for the chrome: one consistent accent ring on every keyboard-focused control
     (the 3D canvases manage their own pointer feedback). The ring follows each control's OWN corner radius
     so it never looks square on the larger rail tools. Mouse clicks don't trigger it (:focus-visible only),
     so it stays out of the way until a keyboard user needs it. */
  #appHeader :focus-visible,
  #toolRail :focus-visible,
  #brushFlyout :focus-visible,
  #propertiesPanel :focus-visible,
  #panelToggle:focus-visible,
  #appFooter :focus-visible {
    outline: 2px solid var(--accent-strong);
    outline-offset: 2px;
  }

  /* Render canvas fills the whole window; the chrome floats above it (z-index 0/1 = WebGPU + Three). */
  #sculptCanvas { display: block; width: 100vw; height: 100vh; touch-action: none; }
  /* The Project brush's image, laid over the viewport (under every panel, never catching the pointer). */
  #projectionOverlayImage { position: fixed; left: 0; top: 0; z-index: 2; pointer-events: none; transform-origin: 50% 50%; display: none; user-select: none; }
  /* Hide the OS cursor over the 3D viewport (both render canvases) — the brush ring + dot IS the cursor.
     The UI panels are separate elements stacked on top, so their arrow/pointer cursor is unaffected. */
  #sculptCanvas, #webgpuMeshCanvas { cursor: none; }

  /* ---------- top header / menu bar ---------- */
  #appHeader {
    position: fixed; top: 0; left: 0; right: 0; height: var(--header-h); z-index: 60;
    display: flex; align-items: center; gap: var(--space-2); padding: 0 var(--space-4);
    background: var(--surface-chrome);
    -webkit-backdrop-filter: blur(18px) saturate(140%);
    backdrop-filter: blur(18px) saturate(140%);
    border-bottom: 1px solid var(--hairline-soft);
    box-shadow: 0 1px 0 oklch(100% 0 0 / 0.03) inset, var(--shadow-sm);
  }
  .brand {
    display: flex; align-items: center; margin-right: var(--space-3); color: var(--text-primary);
  }
  .brandLogo { height: 30px; width: auto; display: block; }
  .brand .version {
    font-weight: 500; font-size: var(--text-2xs); color: var(--text-muted);
    margin-left: var(--space-1); letter-spacing: 0.04em;
  }

  .menuBar { display: flex; align-items: center; gap: var(--space-0_5); }
  .menu { position: relative; }
  .menuButton {
    background: transparent; border: 1px solid transparent; color: var(--text-secondary); font: inherit; font-weight: 500;
    padding: var(--space-2) var(--space-3); border-radius: var(--radius-md); cursor: pointer;
    transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard),
                border-color var(--dur-fast) var(--ease-standard);
  }
  .menuButton:hover { background: var(--surface-raised); color: var(--text-primary); }
  .menuButton:active { background: var(--surface-hover); }
  .menu.open .menuButton { background: var(--surface-hover); color: var(--text-primary); border-color: var(--hairline-soft); }
  .menuDropdown {
    display: none; position: absolute; top: calc(100% + var(--space-1_5)); left: 0; min-width: 188px; z-index: 70;
    background: var(--surface-popover); border: 1px solid var(--hairline); border-radius: var(--radius-lg);
    padding: var(--space-1);
    box-shadow: var(--shadow-lg);
    transform-origin: top left;
  }
  .menu.open .menuDropdown { display: block; animation: popover-in var(--dur-base) var(--ease-out); }
  .menuItem {
    display: block; width: 100%; text-align: left; background: transparent; border: none; color: var(--text-secondary);
    font: inherit; text-decoration: none; padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm); cursor: pointer;
    transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
  }
  .menuItem:hover { background: var(--accent-soft); color: var(--text-primary); }
  .menuItem:active { background: var(--accent-dim); }
  .menuItem.menuItemActive { color: var(--accent-strong); }
  .menuItem.menuItemActive::after { content: '✓'; float: right; opacity: 0.9; margin-left: var(--space-3); }
  .menuSectionLabel {
    font-size: var(--text-2xs); color: var(--text-muted); padding: var(--space-2) var(--space-2_5) var(--space-1);
    text-transform: uppercase; letter-spacing: var(--tracking-label); font-weight: 600;
  }
  .menuSep { height: 1px; background: var(--hairline-soft); margin: var(--space-1) var(--space-1_5); }
  /* A non-interactive line inside a menu (the signed-in address). Same metrics as .menuItem so it
     sits on the menu's rhythm, but no hover and no pointer — it is information, not a control. */
  .menuReadout {
    padding: var(--space-1_5) var(--space-2_5) var(--space-2); color: var(--text-secondary);
    font-size: var(--text-xs); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 260px;
  }
  .menuItem:disabled { opacity: 0.45; pointer-events: none; }

  /* ── Nested pop-out submenu (File ▸ Import / Export) — hover-driven, same popover language. */
  .menuSubMenu { position: relative; }
  .menuSubMenu .menuSubMenuTrigger::after {
    content: '▸'; float: right; font-size: 10px; opacity: 0.6; margin-left: var(--space-3);
  }
  .menuSubDropdown {
    display: none; position: absolute; left: calc(100% - 4px); top: calc(-1 * var(--space-1)); min-width: 110px; z-index: 72;
    background: var(--surface-popover); border: 1px solid var(--hairline); border-radius: var(--radius-lg);
    padding: var(--space-1);
    box-shadow: var(--shadow-lg);
    transform-origin: top left;
  }
  .menuSubMenu:hover .menuSubDropdown,
  .menuSubMenu.open .menuSubDropdown,
  .menuSubMenu:focus-within .menuSubDropdown { display: block; animation: popover-in var(--dur-base) var(--ease-out); }
  .menuSubMenu:hover > .menuSubMenuTrigger { background: var(--accent-soft); color: var(--text-primary); }
  /* A pop-out that hosts a whole properties-panel section (View ▸ Gizmo / Environment / Grid): panel
     width, panel rhythm, no section rule — the trigger already names the section. */
  .menuPanelHost { width: 272px; padding: var(--space-2) var(--space-3) var(--space-3); }
  .menuPanelHost .panelSection { padding: 0; border-bottom: none; }
  .menuPanelHost .sectionTitle { display: none; }
  /* The Theme pop-out grew to seventeen themes plus the appearance pair (2026-09-09): two columns keep it on
     screen; the separator, the Appearance label and its two items span both. */
  .menuThemeDropdown { display: none; grid-template-columns: 1fr 1fr; column-gap: var(--space-1); min-width: 236px; }
  .menuSubMenu:hover .menuThemeDropdown, .menuSubMenu.open .menuThemeDropdown, .menuSubMenu:focus-within .menuThemeDropdown { display: grid; }
  .menuThemeDropdown .menuSep, .menuThemeDropdown .menuSectionLabel { grid-column: 1 / -1; }
  .menuShortcut { float: right; margin-left: var(--space-4); color: var(--text-muted); font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: 500; letter-spacing: 0.02em; }

  /* ---------- OPTIONS bar (2026-09-11): one thin full-width row under the menus —
 projection, the snap views and Fit, the viewport aids, shading and Symmetry X (undo / redo moved to the header, 2026-09-15).
 The rail and the panel start beneath it (--chrome-top). Every control is a PROXY of the real one. ---------- */
  #quickBar {
    position: fixed; top: var(--header-h); left: 0; right: var(--panel-w); min-height: var(--quickbar-base-h); z-index: 54;
    flex-wrap: nowrap; padding-block: 9px 8px;   /* 9 + 36 + 8 + the 1px border = 54px, ALWAYS one row (2026-09-15): a narrow bar drops its button words instead (quickBarIconsOnly, app.js fitQuickBarOnOneRow) */   /* stops at the panel */
    display: flex; align-items: center; gap: var(--space-1_5); padding-inline: 13px var(--space-3);   /* 13px: the Home button lines up with the rail's buttons (50px centred in the 76px rail) */
    transition: right var(--dur-base) var(--ease-out);
    background: var(--surface-chrome);
    -webkit-backdrop-filter: blur(18px) saturate(140%);
    backdrop-filter: blur(18px) saturate(140%);
    border-bottom: 1px solid var(--hairline-soft);
    overflow: hidden; white-space: nowrap;
  }
  body:has(#propertiesPanel.collapsed) #quickBar { right: 0; }

  /* THE SHORTCUT BAR (owner 2026-10-30): the top bar's look, along the bottom of the viewport from the rail to the panel, sitting on the
     footer. Folded, it is gone and only its fold button stays, in the same place. A bar too narrow for its words drops them (shortcut-bar.js). */
  #shortcutBar {
    position: fixed; left: var(--rail-w); right: var(--panel-w); bottom: var(--footer-h); min-height: var(--quickbar-base-h); z-index: 54;
    display: flex; align-items: center; gap: var(--space-1_5); padding-block: 9px 8px; padding-inline: 13px 62px; white-space: nowrap; overflow: hidden;
    background: var(--surface-chrome);
    -webkit-backdrop-filter: blur(18px) saturate(140%);
    backdrop-filter: blur(18px) saturate(140%);
    border-top: 1px solid var(--hairline-soft);
    transition: right var(--dur-base) var(--ease-out);
  }
  #shortcutBar .quickIcon:disabled { opacity: 0.45; cursor: default; }
  #shortcutBar.shortcutBarIconsOnly .quickIcon > span { display: none; }
  #shortcutBar.shortcutBarIconsOnly .quickIcon { padding: 0 var(--space-2); }
  body.shortcutBarFolded #shortcutBar { display: none; }
  #shortcutBarFoldButton {
    position: fixed; right: calc(var(--panel-w) + var(--space-3)); bottom: calc(var(--footer-h) + 9px); z-index: 55; width: 36px; padding: 0;
    color: var(--accent-strong); border-color: color-mix(in oklab, var(--accent-500) 42%, transparent);
    transition: right var(--dur-base) var(--ease-out);
  }
  #shortcutBarFoldButton svg { transition: transform var(--dur-base) var(--ease-out); }
  #shortcutBarFoldButton svg { transform: rotate(-90deg); }   /* the arrow (drawn pointing left) points down while the bar is open */
  body.shortcutBarFolded #shortcutBarFoldButton svg { transform: rotate(90deg); }   /* and up when it is folded (2026-10-30) */
  body:has(#propertiesPanel.collapsed) #shortcutBar { right: 0; }
  body:has(#propertiesPanel.collapsed) #shortcutBarFoldButton { right: var(--space-3); }
  /* (UI polish, 2026-10-23) a clear gap each side of the divider between the groups:
 navigation · scene display · shading · focus · symmetry and sets · Timeline */
  .quickSep { width: 1px; height: 22px; background: color-mix(in oklab, var(--hairline) 70%, var(--text-muted)); margin: 0 var(--space-2_5); flex: none; }
  /* Icon + word buttons (2026-09-11), 36px tall; undo / redo stay icon-only. */
  .quickIcon {
    height: 36px; padding: 0 var(--space-2_5) 0 var(--space-2); flex: none; display: flex; align-items: center; justify-content: center; gap: var(--space-1_5);
    border-radius: var(--radius-md); background: var(--surface-raised); border: 1px solid var(--control-border); color: var(--text-secondary);
    font: inherit; font-size: var(--text-xs); font-weight: 600; letter-spacing: 0.01em;
    cursor: pointer; transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard);
  }
  .quickIcon svg { width: 16px; height: 16px; display: block; flex: none; }
  /* A bar too narrow for its words: icon-only buttons (the tooltips still name them), then tighter spacing. */
  #quickBar.quickBarIconsOnly .quickIcon > span { display: none; }
  #quickBar.quickBarIconsOnly .quickIcon { padding: 0 var(--space-2_5); gap: 0; }
  #quickBar.quickBarTight { gap: var(--space-1); }
  #quickBar.quickBarTight .quickSep { margin: 0; }
  #quickBar.quickBarTight .quickIcon { padding: 0 var(--space-2); }
  .quickIcon:hover { background: var(--surface-hover); color: var(--text-primary); border-color: var(--control-border-hover); }
  /* (UI polish, 2026-10-23)
 a faint wash of the accent, an accent edge and a thin accent underline, the word in the accent */
  .quickIcon.quickToggleActive { background: color-mix(in oklab, var(--accent-500) 14%, var(--surface-raised)); border-color: color-mix(in oklab, var(--accent-500) 42%, transparent); color: var(--accent-strong); box-shadow: inset 0 -2px 0 color-mix(in oklab, var(--accent-500) 80%, transparent); }
  .quickIcon.disabledButton { opacity: var(--disabled-opacity); pointer-events: none; }
  .quickIcon[hidden] { display: none; }
  /* The snap views live in a pop-out under one square button. */
  .quickPopoutHost { position: relative; flex: none; }
  .quickPopout {
    display: none; position: absolute; top: calc(100% + 6px); left: 0; z-index: 75;
    grid-template-columns: repeat(3, auto); gap: var(--space-1); padding: var(--space-1_5);
    background: var(--surface-popover); border: 1px solid var(--hairline); border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
  }
  .quickPopoutHost.open .quickPopout { display: grid; animation: popover-in var(--dur-base) var(--ease-out); }
  .quickPopout .quickToggle { min-width: 58px; justify-content: center; }
  /* The Symmetry pop-out holds every symmetry option (2026-10-12), in three labelled groups of three columns. */
  .quickPopout.quickPopoutSymmetry { grid-template-columns: repeat(3, minmax(64px, 1fr)); gap: var(--space-1_5); padding: var(--space-3); min-width: 250px; }
  .quickPopoutLabel { grid-column: 1 / -1; font-size: var(--text-2xs); text-transform: uppercase; letter-spacing: var(--tracking-label); font-weight: 600; color: var(--text-muted); }
  .quickPopoutLabel:not(:first-child) { margin-top: var(--space-2); }
  .quickPopoutSymmetry .quickPopoutWide { grid-column: 1 / -1; }
  .quickPopoutSymmetry .quickSliderRow { grid-template-columns: auto 1fr 28px; }
  .quickPopout { width: max-content; }
  /* The Shading dropdown (owner 2026-10-25): one bar button naming the mode, with a chevron; its menu has the header menus' rows, and
     the mode on show is lit the way the bar lights a button that is on. */
  .quickShadingMenuButton .quickShadingMenuChevron { width: 12px; height: 12px; opacity: 0.7; }
  #quickShadingHost.open > .quickShadingMenuButton { background: var(--surface-hover); color: var(--text-primary); border-color: var(--control-border-hover); }
  .quickPopoutHost.open .quickPopout.quickShadingMenu { display: block; padding: var(--space-1); min-width: 188px; }
  .quickShadingMenu .menuItem.menuItemActive { background: color-mix(in oklab, var(--accent-500) 14%, transparent); }
  .quickShadingMenu .menuItem.menuItemActive:hover { background: var(--accent-soft); }
  /* The light rig's host sits inside the Shading host, out of the bar's row, so the rig opens under the Shading button. */
  .quickPopoutHost.quickShadingLightRigAnchor { position: absolute; left: 0; top: 0; width: 0; height: 100%; }
  #quickLitHost:not(.open) > .quickPopout { display: none; }
  /* The Reference section, WIDE (2026-09-24): three columns wherever it is
 presented — the toolbar pop-out and the View menu alike. The section keeps every id and handler; only its shape changes. */
  #referenceImagePanelSection.referenceSectionWide { display: grid; grid-template-columns: auto auto 232px; grid-template-areas: "title title title" "master image sliders" "views image sliders" "views flips sliders"; column-gap: var(--space-5); row-gap: var(--space-2_5); align-items: start; padding: var(--space-3) var(--space-4) var(--space-4); }
  #referenceImagePanelSection.referenceSectionWide > .sectionTitle { grid-area: title; }
  #referenceImagePanelSection.referenceSectionWide > .referenceMasterToggleRow { grid-area: master; }
  #referenceImagePanelSection.referenceSectionWide > #referenceViewChipRow { grid-area: views; display: grid; grid-template-columns: repeat(3, minmax(64px, 1fr)); gap: var(--space-1_5); }
  #referenceImagePanelSection.referenceSectionWide > .alphaControl { grid-area: image; }
  #referenceImagePanelSection.referenceSectionWide > #referenceImageAdjustRows { display: contents; }
  #referenceImagePanelSection.referenceSectionWide > #referenceImageAdjustRows[style*="display: none"] > * { display: none; }
  #referenceImagePanelSection.referenceSectionWide > #referenceImageAdjustRows > .chipRow { grid-area: flips; }
  #referenceImagePanelSection.referenceSectionWide > #referenceImageAdjustRows > .referenceSliderRows { grid-area: sliders; }
  .quickPopoutReferences { display: none; padding: 0; min-width: 620px; }
  /* The Refs pop-out is a window, not a panel section: its title has no fold triangle and does not fold (owner 2026-10-20). */
  .quickPopoutReferences .sectionTitle { pointer-events: none; cursor: default; }
  .quickPopoutReferences .sectionTitle::after { content: none; }
  .quickPopoutHost.open .quickPopoutReferences { display: block; }
   /* an absolutely positioned grid otherwise shrinks to its host button's width and clips the names */
  .quickPopoutMaterials { grid-template-columns: repeat(4, minmax(92px, auto)); }
  .quickPopoutRow { display: flex; gap: var(--space-1); flex-wrap: wrap; }
  .quickPopoutLighting { grid-template-columns: 1fr; gap: var(--space-1_5); padding: var(--space-2_5) var(--space-3); min-width: 300px; }
  .quickSliderRow { display: grid; grid-template-columns: 84px 1fr 44px; align-items: center; gap: var(--space-2); font-size: var(--text-xs); color: var(--text-secondary); }
  .quickSliderRow input[type="range"] { width: 100%; margin: 0; }
  .quickSliderValue { font-family: var(--font-mono); color: var(--accent-strong); font-weight: 600; text-align: right; }
  /* The light rig pop-out (owner 2026-10-20): two columns, the key light with the overall controls, then the fill and rim lights. */
  .quickPopoutLighting.quickPopoutLightRig { grid-template-columns: minmax(300px, 1fr) minmax(300px, 1fr); column-gap: var(--space-5); row-gap: 0; min-width: 640px; }
  .quickLightingGroup { display: grid; grid-template-columns: 1fr; gap: var(--space-1_5); align-content: start; }
  .quickPopoutLightRig .quickSliderRow { grid-template-columns: 84px 1fr 64px; }   /* room for a colour's #RRGGBB beside its well */
  .quickLightingHeading { font-size: var(--text-2xs); font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-muted); margin-top: var(--space-1); }
  .quickLightingHeading:not(:first-child) { margin-top: var(--space-2_5); }
  .quickColourRow input[type="color"] { width: 44px; height: 20px; padding: 0; border: 1px solid var(--control-border); border-radius: var(--radius-sm); background: none; cursor: pointer; }
  .quickGizmoGroup { display: flex; align-items: center; gap: var(--space-1); flex: none; }
  .quickGizmoGroup[hidden] { display: none; }
  .quickHistory { display: flex; align-items: center; gap: var(--space-1); flex: none; }   /* in the header, beside fullscreen */
  .quickToggle {
    height: 28px; padding: 0 var(--space-2_5); flex: none; border-radius: var(--radius-sm);
    background: var(--surface-raised); border: 1px solid var(--control-border); color: var(--text-secondary);
    font: inherit; font-size: var(--text-xs); font-weight: 600; cursor: pointer;
    transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard);
  }
  .quickToggle:hover { background: var(--surface-hover); color: var(--text-primary); border-color: var(--control-border-hover); }
  .quickToggle.quickToggleActive { background: color-mix(in oklab, var(--accent-500) 14%, var(--surface-raised)); border-color: color-mix(in oklab, var(--accent-500) 42%, transparent); color: var(--accent-strong); box-shadow: inset 0 -2px 0 color-mix(in oklab, var(--accent-500) 80%, transparent); }   /* the same subtle on (UI polish) */
  .quickToggle.disabledButton { opacity: var(--disabled-opacity); pointer-events: none; }
  .quickToggle[hidden] { display: none; }
  .railTool[hidden] { display: none; }   /* a rail tool hidden for now (Pose, 2026-10-01) stays wired but off the rail */
  /* Narrow windows: the bar sheds the least-used snaps before anything gets clipped. */
  #quickBar { overflow: visible; }   /* the snap pop-out hangs below the bar */

  .brushOptionsBlock { margin: var(--space-3) 0 var(--space-2); }

  /* ---------- Touch layout (tablet support, 2026-09-11): html[data-input="touch"] is set by app.js from the
     device (coarse pointer / a first touch) or Preferences ▸ Input ▸ Touch layout. Full-size chrome, finger-sized
     targets, and the modifier strip for hands without a keyboard. ---------- */
  html[data-input="touch"] { --interface-density: 1; }
  html[data-input="touch"] .brushButton { width: 56px; height: 56px; }
  html[data-input="touch"] .railTool { width: 62px; height: 62px; }   /* the rail buttons are already 62px (2026-10-29), so touch keeps them */
  html[data-input="touch"] .menuButton { padding-top: var(--space-2_5); padding-bottom: var(--space-2_5); }
  html[data-input="touch"] .menuItem { padding-top: var(--space-2_5); padding-bottom: var(--space-2_5); }
  html[data-input="touch"] .toggleButton { min-height: 40px; }
  html[data-input="touch"] .quickToggle { height: 36px; }
  html[data-input="touch"] .quickIcon { height: 44px; }
  html[data-input="touch"] .quickIcon svg { width: 20px; height: 20px; }
  html[data-input="touch"] input[type="range"] { height: 28px; }
  html[data-input="touch"] input[type="range"]::-webkit-slider-thumb { width: 22px; height: 22px; }
  html[data-input="touch"] input[type="range"]::-moz-range-thumb { width: 22px; height: 22px; }
  html[data-input="touch"] .brushButton::after, html[data-input="touch"] .railTool::after { display: none; }   /* hover labels never show on glass */
  #appHeader, #quickBar, #toolRail, #appFooter, #propertiesPanel .toggleButton, #propertiesPanel .sectionTitle { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
  body { overscroll-behavior: none; }

  /* ---------- Edit ▸ Preferences dialog: category list on the left, the page on the right ---------- */
  /* the brush alpha library window */
  .appModalDialog .confirmDialogPanel.alphaLibraryPanel { width: min(900px, 94vw); height: min(660px, 88vh); padding: 0; gap: 0; overflow: hidden; display: flex; flex-direction: column; }
  /* The compact Matcaps window (right-click Matcap on the options bar, 2026-09-21): the same tiles, smaller, no explanation, sized to its contents. */
  .appModalDialog .confirmDialogPanel.alphaLibraryPanel.alphaLibraryPanelCompact { width: min(640px, 94vw); height: auto; max-height: min(560px, 86vh); }
  .alphaLibraryPanelCompact .alphaLibraryHint { display: none; }
  .alphaLibraryPanelCompact .alphaLibraryHeader { padding: var(--space-3) var(--space-4) var(--space-2); }
  .alphaLibraryPanelCompact .alphaLibraryGrid { grid-template-columns: repeat(auto-fill, minmax(72px, 1fr)); gap: var(--space-1_5); }
  .alphaLibraryPanelCompact .alphaLibraryTile { padding: var(--space-1); gap: var(--space-1); }
  .alphaLibraryPanelCompact .alphaLibraryTileName { font-size: var(--text-2xs); }
  .alphaLibraryHeader { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-3); padding: var(--space-4) var(--space-5) var(--space-3); border-bottom: 1px solid var(--hairline-soft); }
  .alphaLibraryHint { margin: var(--space-1) 0 0; max-width: 60ch; }
  .alphaLibraryHeaderButtons { display: flex; gap: var(--space-2); flex: 0 0 auto; }
  .alphaLibraryScroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: var(--space-3) var(--space-5) var(--space-5); }
  .alphaLibrarySectionLabel { font-size: var(--text-2xs); font-weight: 600; text-transform: uppercase; letter-spacing: var(--tracking-label); color: var(--text-muted); margin: var(--space-3) 0 var(--space-2); }
  .alphaLibraryGrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap: var(--space-2); }
  .alphaLibraryTile { position: relative; display: flex; flex-direction: column; gap: var(--space-1_5); padding: var(--space-1_5); background: var(--surface-inset); border: 1px solid var(--hairline-soft); border-radius: var(--radius-md); cursor: pointer; color: inherit; font: inherit; text-align: center; min-width: 0; transition: border-color var(--dur-fast) var(--ease-standard); }
  .alphaLibraryTile:hover { border-color: var(--control-border-hover); }
  .alphaLibraryTile:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 2px; }
  .alphaLibraryTile.alphaLibraryTileActive { border-color: var(--accent-strong); box-shadow: 0 0 0 1px var(--accent-strong) inset; }
  .alphaLibraryTilePreview { width: 100%; height: auto; aspect-ratio: 1; display: block; border-radius: var(--radius-xs); background: #000; }
  .alphaLibraryTileName { font-size: var(--text-2xs); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .alphaLibraryTileRemove { position: absolute; top: 4px; right: 4px; width: 20px; height: 20px; line-height: 18px; border-radius: 50%; background: rgba(0,0,0,0.6); color: #fff; font-size: 14px; text-align: center; opacity: 0; transition: opacity var(--dur-fast) var(--ease-standard); }
  .alphaLibraryTile:hover .alphaLibraryTileRemove, .alphaLibraryTile:focus-within .alphaLibraryTileRemove { opacity: 1; }
  .alphaLibraryEmpty { font-size: var(--text-xs); color: var(--text-muted); padding: var(--space-2) 0; }
  /* The right-click menu on an alpha of your own (2026-09-26): Edit / Rename. */
  .alphaLibraryTileMenu { position: fixed; z-index: 200; display: flex; flex-direction: column; min-width: 140px; padding: var(--space-1); background: var(--surface-raised); border: 1px solid var(--control-border); border-radius: var(--radius-md); box-shadow: var(--shadow-lg, 0 8px 24px rgba(0,0,0,0.45)); }
  .alphaLibraryTileMenuItem { background: none; border: none; color: var(--text-primary); font: inherit; font-size: var(--text-xs); text-align: left; padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm, 4px); cursor: pointer; }
  .alphaLibraryTileMenuItem:hover, .alphaLibraryTileMenuItem:focus-visible { background: var(--surface-hover); outline: none; }
  .alphaLibraryRenameInput { width: 100%; min-width: 0; box-sizing: border-box; font: inherit; font-size: var(--text-2xs); text-align: center; padding: 1px 4px; border-radius: 4px; border: 1px solid var(--accent-strong); background: var(--surface-inset); color: inherit; outline: none; }
  .appModalDialog .confirmDialogPanel.preferencesPanel { width: min(800px, 94vw); height: min(600px, 88vh); padding: 0; gap: 0; overflow: hidden; }   /* the extra class outranks the 420px base panel rule further down */
  .preferencesLayout { display: grid; grid-template-columns: 188px minmax(0, 1fr); flex: 1; min-height: 0; }
  .preferencesCategories { display: flex; flex-direction: column; gap: 2px; padding: var(--space-4) var(--space-3); border-right: 1px solid var(--hairline-soft); background: var(--surface-inset); }
  .preferencesCategoryButton {
    text-align: left; font: inherit; font-size: var(--text-sm); font-weight: 600; padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-md); background: transparent; border: 1px solid transparent; color: var(--text-secondary); cursor: pointer;
  }
  .preferencesCategoryButton:hover { background: var(--surface-hover); color: var(--text-primary); }
  .preferencesCategoryButton.preferencesCategoryActive { background: var(--accent-soft); color: var(--accent-strong); }
  /* The pages scroll behind a thin rounded bar like the right panel's, with air between the controls and the bar (2026-10-29;
 it was the browser's own bar against the content). */
  .preferencesContent { padding: var(--space-4) var(--space-5) var(--space-5) var(--space-5); margin-right: var(--space-2); overflow-y: auto; min-height: 0; scrollbar-width: thin; scrollbar-color: var(--neutral-650) transparent; }
  .preferencesContent::-webkit-scrollbar { width: 9px; }
  .preferencesContent::-webkit-scrollbar-thumb { background: var(--neutral-650); border-radius: var(--radius-pill); border: 2px solid transparent; background-clip: padding-box; }
  .preferencesContent::-webkit-scrollbar-thumb:hover { background: var(--neutral-600); background-clip: padding-box; }
  /* EVERY scrollbar in the studio in the studio's own look (2026-10-29): thin, a
 rounded thumb in the panel grey on nothing, in Firefox by the standard properties and in Chromium by the WebKit rules. Elements
 with a look of their own (the rail hides its bar, the panel and the preferences set the same look by id) keep it. */
  * { scrollbar-width: thin; scrollbar-color: var(--neutral-650) transparent; }
  *::-webkit-scrollbar { width: 9px; height: 9px; }
  *::-webkit-scrollbar-track { background: transparent; }
  *::-webkit-scrollbar-thumb { background: var(--neutral-650); border-radius: var(--radius-pill); border: 2px solid transparent; background-clip: padding-box; }
  *::-webkit-scrollbar-thumb:hover { background: var(--neutral-600); background-clip: padding-box; }
  *::-webkit-scrollbar-corner { background: transparent; }
  .preferencesTitle { font-size: var(--text-md); font-weight: 700; color: var(--text-primary); margin-bottom: var(--space-1); }
  .preferencesDescription { font-size: var(--text-xs); color: var(--text-muted); line-height: 1.5; margin-bottom: var(--space-3); }
  .preferenceRow { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--space-4); align-items: center; padding: var(--space-3) 0; border-bottom: 1px solid var(--hairline-soft); }
  .preferenceRow:last-child { border-bottom: none; }
  .preferenceLabel { font-size: var(--text-sm); font-weight: 600; color: var(--text-primary); }
  .preferenceHint { font-size: var(--text-xs); color: var(--text-muted); margin-top: 2px; line-height: 1.45; }
  /* Preferences > Keyboard (2026-10-12): a search box, a layout dropdown, then every shortcut with its key. A shortcut that lost its key is red. */
  .keyboardPreferencesTools { display: flex; gap: var(--space-2); align-items: stretch; margin-top: var(--space-3); }
  /* The search field is drawn like the brush picker's (owner 2026-10-20): the magnifying glass inside at its left, the same border,
     hover and focus ring. It stretches to the height of the buttons beside it. */
  .keyboardShortcutSearchField { position: relative; flex: 1 1 auto; min-width: 0; display: flex; align-items: center; }
  .keyboardShortcutSearchIcon { position: absolute; left: 10px; width: 14px; height: 14px; color: var(--text-muted); pointer-events: none; transition: color var(--dur-fast) var(--ease-standard); }
  .keyboardShortcutSearchField:focus-within .keyboardShortcutSearchIcon { color: var(--text-secondary); }
  .keyboardShortcutSearch {
    width: 100%; min-width: 0; height: 100%; min-height: 32px; margin: 0; box-sizing: border-box; padding: 0 var(--space-3) 0 32px; font: inherit; font-size: var(--text-sm); color: var(--text-primary);
    background: var(--surface-raised); border: 1px solid var(--control-border); border-radius: var(--radius-md);
    transition: border-color var(--dur-fast) var(--ease-standard);
  }
  .keyboardShortcutSearch::placeholder { color: var(--text-muted); }
  .keyboardShortcutSearch:hover { border-color: var(--control-border-hover); }
  .keyboardShortcutSearch:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 0; border-color: transparent; }
  .keyboardPreferencesNotice { margin: var(--space-2) 0 var(--space-3); min-height: 1.45em; }
  .keyboardShortcutCategory { margin-top: var(--space-4); padding-bottom: var(--space-1); font-size: var(--text-2xs); font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-muted); border-bottom: 1px solid var(--hairline-soft); }
  .keyboardShortcutRow { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--space-4); align-items: center; padding: var(--space-1_5) 0; border-bottom: 1px solid var(--hairline-soft); }
  .keyboardShortcutName { font-size: var(--text-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .keyboardShortcutKey { min-width: 132px; font-family: var(--font-mono); font-size: var(--text-xs); }
  /* The key and, beside it, a cross that takes the key away (2026-10-20). */
  .keyboardShortcutKeyCell { display: flex; align-items: stretch; gap: var(--space-2); }
  .keyboardPreferencesPage .keyboardShortcutRemove { width: 41px; min-width: 41px; flex: 0 0 auto; padding: 0; display: inline-flex; align-items: center; justify-content: center; }
  .keyboardShortcutRemoveIcon { width: 14px; min-width: 14px; height: 14px; flex: 0 0 auto; }
  .keyboardShortcutRemove:disabled { opacity: 0.35; cursor: default; }
  .keyboardShortcutMissing .keyboardShortcutName { color: var(--danger-400, #f26d6d); }
  .keyboardShortcutMissing .keyboardShortcutKey { color: var(--danger-400, #f26d6d); border-color: var(--danger-400, #f26d6d); }
  .preferenceControl { display: flex; align-items: center; justify-content: flex-end; gap: var(--space-1_5); flex-wrap: wrap; max-width: 380px; }
  .preferenceControl .toggleButton { flex: 0 0 auto; }
  .preferenceControl input[type="range"] { width: 150px; margin: 0; }
  .preferenceControl select { min-width: 160px; }
  .preferenceNumber { font-family: var(--font-mono); color: var(--accent-strong); font-weight: 600; min-width: 44px; text-align: right; font-size: var(--text-xs); }
  .preferenceCustom { padding: var(--space-2) 0; display: flex; flex-direction: column; gap: var(--space-3); }
  .preferenceAccountLine { font-size: var(--text-sm); color: var(--text-primary); }
  .preferenceAccountLine b { font-family: var(--font-mono); font-weight: 600; }
  .preferenceAccountActions { display: flex; gap: var(--space-2); flex-wrap: wrap; }
  .preferenceLinkRow { display: flex; gap: var(--space-4); font-size: var(--text-xs); }
  .preferenceLinkRow a { color: var(--accent-strong); }
  .preferencesFooter { display: flex; justify-content: space-between; padding: var(--space-3) var(--space-5); border-top: 1px solid var(--hairline-soft); }

  .headerSpacer { flex: 1; }
  .headerActions { display: flex; align-items: center; gap: var(--space-2); }
  .statReadout { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-muted); white-space: nowrap; }
  .statReadout b { color: var(--accent-strong); font-weight: 600; }
  .engineBadge {
    font-size: var(--text-2xs); font-weight: 700; letter-spacing: 0.06em; padding: 3px var(--space-2);   /* 3px vertical: a deliberately compact micro-badge */
    border-radius: var(--radius-xs); text-transform: uppercase;
    background: var(--accent-soft); color: var(--accent-strong);
    border: 1px solid color-mix(in oklab, var(--accent-500) 45%, transparent);
  }
  .iconButton {
    background: var(--surface-raised); border: 1px solid var(--control-border); color: var(--text-secondary);
    width: 34px; height: 34px; border-radius: var(--radius-md); cursor: pointer; font-size: 17px; line-height: 1;
    display: inline-flex; align-items: center; justify-content: center;
    transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard),
                border-color var(--dur-fast) var(--ease-standard), opacity var(--dur-fast) var(--ease-standard);
  }
  .iconButton:hover { background: var(--surface-hover); color: var(--accent-strong); border-color: var(--control-border-hover); }
  .iconButton:active { background: var(--accent-dim); transform: translateY(0.5px); }
  /* Disabled undo/redo (app.js toggles .disabledButton on these): clearly inert, no hover/press response. */
  .iconButton.disabledButton {
    opacity: var(--disabled-opacity); cursor: default; pointer-events: none;
    background: var(--surface-raised); color: var(--text-muted); border-color: var(--hairline-soft);
  }

  @keyframes popover-in {
    from { opacity: 0; transform: translateY(-4px) scale(0.98); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
  }

  /* ---------- left brush rail ---------- */
  #toolRail {
    position: fixed; top: var(--chrome-top); left: 0; bottom: var(--footer-h); width: var(--rail-w); z-index: 55;
    display: flex; flex-direction: column; align-items: center; gap: var(--space-1_5); padding: var(--space-2_5) 0;
    background: var(--surface-chrome);
    -webkit-backdrop-filter: blur(18px) saturate(140%);
    backdrop-filter: blur(18px) saturate(140%);
    border-right: 1px solid var(--hairline-soft);
    overflow: visible;   /* fixed rail; never scroll (overflow-y:auto promoted overflow-x→auto and the
                            hover tooltips pushed a stray horizontal scrollbar onto the rail bottom) */
  }
  #toolRail::-webkit-scrollbar { width: 0; height: 0; }
  #brushRowContainer { display: flex; flex-direction: column; gap: var(--space-1_5); align-items: center; }

  /* Brush-grid buttons (live in the flyout) and rail tool buttons share one tactile control style. */
  .brushButton, .railTool {
    width: 50px; height: 50px; border-radius: var(--radius-lg); cursor: pointer; position: relative;
    background: var(--surface-raised); border: 1px solid var(--control-border); color: var(--text-muted);
    display: flex; align-items: center; justify-content: center;
    transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard),
                border-color var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard),
                transform var(--dur-fast) var(--ease-standard);
  }
  .brushButton:hover, .railTool:hover {
    background: var(--surface-hover); color: var(--text-primary); border-color: var(--control-border-hover);
  }
  .brushButton:active, .railTool:active { transform: scale(0.95); }
  .brushButton.brushButtonActive, .railTool.railToolActive {
    background: var(--accent-dim);
    border-color: color-mix(in oklab, var(--accent-500) 65%, transparent);
    color: var(--accent-strong);
    box-shadow: var(--shadow-focus), var(--shadow-sm);
  }
  .brushIcon { width: 27px; height: 27px; }
  .brushIcon svg { width: 100%; height: 100%; display: block; }
  .brushName { display: none; }
  /* NAMES ON THE RAIL (2026-10-28): the icon above a
 small name inside the same 50px square; the hover label beside the rail stays as it was. */
  /* BIGGER RAIL BUTTONS (2026-10-29):
 50px -> 62px squares in the same 76px rail, the icon 22px -> 28px and the name 9px -> 11px. */
  .railTool { width: 62px; height: 62px; flex-direction: column; gap: 3px; padding-top: 3px; }
  .railTool .brushIcon { width: 28px; height: 28px; }
  .railTool .railName { font-size: 11px; line-height: 1; font-weight: 500; letter-spacing: 0.01em; white-space: nowrap; pointer-events: none; }
  .railTool .brushIcon svg { width: 100%; height: 100%; display: block; }

  /* hover tooltip (label appears beside the icon — keeps the rail clean but discoverable) */
  .brushButton::after, .railTool::after {
    content: attr(data-label) attr(data-shortcut-hint); position: absolute; left: calc(100% + var(--space-2_5));   /* + the current key (shortcut-hints-in-tooltips.js, 2026-10-20) */ top: 50%; transform: translateY(-50%) translateX(-3px);
    background: var(--surface-popover); color: var(--text-primary); border: 1px solid var(--hairline);
    padding: var(--space-1) var(--space-2); border-radius: var(--radius-sm); font-size: var(--text-xs); font-weight: 500;
    white-space: nowrap; pointer-events: none; box-shadow: var(--shadow-md);
    opacity: 0; transition: opacity var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out); z-index: 70;
  }
  .brushButton:hover::after, .railTool:hover::after { opacity: 1; transform: translateY(-50%) translateX(0); }
  .railTool::after { z-index: 71; }

  .railSpacer { margin-top: auto; }   /* push what follows to the bottom of the rail */
  /* THE RABBIT AT THE FOOT OF THE RAIL (2026-10-20). */
  #toolRail .railRabbit { position: absolute; left: 50%; bottom: var(--space-3); transform: translateX(-50%); width: 38px; height: auto; opacity: 0.16; pointer-events: none; user-select: none; }   /* pinned to the rail's foot (the rail is fixed, so it holds it) */
  html[data-appearance="light"] #toolRail .railRabbit { filter: brightness(0); opacity: 0.18; }
  .railDivider { width: 28px; height: 1px; background: var(--hairline-soft); margin: var(--space-0_5) 0; flex: none; }

  /* brush flyout (right-click the Brush tool) */
  #brushFlyout {
    position: fixed; left: calc(var(--rail-w) + var(--space-2)); top: calc(var(--chrome-top) + var(--space-2)); z-index: 65;
    background: var(--surface-popover); border: 1px solid var(--hairline); border-radius: var(--radius-lg); padding: var(--space-4) var(--space-5);   /* ROOM ROUND THE BRUSHES (2026-10-12): it was 12px all round, and less than that in practice, see the scroll bar note below */
    box-shadow: var(--shadow-lg); display: none;
    max-height: calc(100vh / (var(--interface-density) * var(--interface-size-scale)) - var(--chrome-top) - var(--space-2) * 2); overflow-y: auto;   /* (2026-10-14) the window height is in screen pixels and the picker is zoomed with the interface, so it is divided by the zoom: before, the picker stopped a fifth of the window short of the bottom */
    scrollbar-gutter: stable;   /* the scroll bar has its own lane: without it the bar took its width out of the content, and the groups (centred in their column) slid left over the padding until the labels touched the edge */   /* a short window scrolls the picker instead of cutting brushes off */
  }
  #brushFlyout.open { display: block; animation: popover-in var(--dur-base) var(--ease-out); }
  #brushFlyout .flyoutTitle {
    font-size: var(--text-2xs); text-transform: uppercase; letter-spacing: var(--tracking-label);
    font-weight: 600; color: var(--text-muted);
  }
  /* The title row: Brushes at the left, Find a brush at the right (brush-picker-search.js). The field's negative margins keep the row
     exactly as tall as the title alone, so the picker below starts where it always did. */
  #brushFlyout .brushPickerHeader { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); margin-bottom: var(--space-4); }
  /* The magnifying glass sits inside the field at its left (owner 2026-10-19); the label wraps both, so a click on the glass focuses the field. */
  #brushFlyout .brushPickerSearchField { position: relative; display: inline-flex; align-items: center; margin: -8px 0; }
  #brushFlyout .brushPickerSearchIcon { position: absolute; left: 9px; width: 14px; height: 14px; color: var(--text-muted); pointer-events: none; transition: color var(--dur-fast) var(--ease-standard); }
  #brushFlyout .brushPickerSearchField:focus-within .brushPickerSearchIcon { color: var(--text-secondary); }
  #brushFlyout .brushPickerSearchInput {
    width: 220px; height: 28px; margin: 0; box-sizing: border-box; padding: 0 var(--space-2_5) 0 30px; font: inherit; font-size: var(--text-xs); color: var(--text-primary);
    background: var(--surface-raised); border: 1px solid var(--control-border); border-radius: var(--radius-md);
    transition: border-color var(--dur-fast) var(--ease-standard);
  }
  #brushFlyout .brushPickerSearchInput::placeholder { color: var(--text-muted); }
  #brushFlyout .brushPickerSearchInput:hover { border-color: var(--control-border-hover); }
  #brushFlyout .brushPickerSearchInput:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 0; border-color: transparent; }
  #brushFlyout .brushPickerNoMatch { margin: var(--space-2) 0 0; font-size: var(--text-xs); color: var(--text-muted); }
  #brushFlyout .brushPickerSearchHidden { display: none !important; }
  /* Grouped picker: the 19 brushes read as four labeled families (Build / Surface / Planes / Utility) —
     grouping-by-proximity carries the structure, the micro-labels confirm it. Rows of four keep the
     popover compact; the group gap is deliberately larger than the tile gap so the families separate. */
  #brushFlyout #brushRowContainer { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-5); }   /* (groups start at the padding's edge, never centred over it; a clearer gap between families, 2026-10-12) */
  #brushFlyout .brushGroupLabel {
    font-size: var(--text-2xs); text-transform: uppercase; letter-spacing: var(--tracking-label);
    font-weight: 600; color: var(--text-muted); opacity: 0.75; margin-bottom: var(--space-2);
  }
  #brushFlyout .brushGroupGrid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-2_5) var(--space-2); }
  #brushFlyout .brushGroupColumns { display: flex; align-items: flex-start; gap: var(--space-6); }   /* two columns of groups: no scrolling on an ordinary window */
  #brushFlyout .brushGroupColumn { display: flex; flex-direction: column; gap: var(--space-5); }
  #brushFlyout .brushGroupRow { display: flex; justify-content: flex-start; align-items: flex-start; gap: var(--space-1_5); align-self: stretch; }   /* stretch: the container centres its items, and with only Texture and Move (three tiles) the row no longer filled the width, so it drifted to the middle */
  #brushFlyout .brushGroupRow .brushGroup { flex: 0 0 auto; }
  /* Rows of four in three columns (2026-09-15, 2026-10-14):
 Build, Move, Sharp, Detail down the first; Surface, Planes, Mask down the second; Mesh Sets, Clip and Mesh (the Mesh
 brush with its stamps) down the third. Every column is four tile rows tall, so the picker is two tile rows shorter than before and
 no column stands half empty; the one-row groups of each column sit level with their neighbours, and so do the two-row ones. */
  #brushFlyout .brushGroupStacks { display: flex; align-items: flex-start; column-gap: var(--space-6); }
  /* TWO COLUMNS OF ROWS (owner 2026-10-20): each group a row, the label in a fixed column on the left and the tiles in one line, every
     row the same height, so the rows of the two columns line up and no group wraps or leaves a hole. */
  #brushFlyout .brushGroupRows { column-gap: 40px; }
  #brushFlyout .brushGroupRows .brushGroupColumn { gap: var(--space-3); }
  #brushFlyout .brushGroupRows .brushGroup { display: grid; grid-template-columns: 92px auto; align-items: center; column-gap: var(--space-3); min-height: calc(var(--brush-tile-size) + 26px); }
  #brushFlyout .brushGroupRows .brushGroupLabel { margin: 0; align-self: center; white-space: nowrap; }
  #brushFlyout .brushGroupRows .brushGroupGrid { grid-template-columns: none; grid-auto-flow: column; grid-auto-columns: var(--brush-tile-size); }
  /* THE MACRO BRUSH TILE (owner 2026-10-20): drawn exactly like a brush tile (it is not a .brushButton, so it copies their look), and the
     macro row of the picker is gone: macros are picked, recorded and moved in the Brush panel. */
  #brushFlyout #strokeMacroGroup { display: none !important; }
  #brushFlyout .macroBrushTile { display: flex; flex-direction: column; align-items: center; gap: var(--space-1); width: var(--brush-tile-size); padding: 0; background: none; border: none; cursor: pointer; color: var(--text-muted); font: inherit; }   /* the brush buttons' own text colour */
  #brushFlyout .macroBrushTile .brushIcon { width: var(--brush-tile-size); height: var(--brush-tile-size); box-sizing: border-box; padding: 6px; border-radius: var(--radius-lg); background: var(--surface-raised); border: 1px solid var(--control-border); display: flex; align-items: center; justify-content: center; transition: background var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard); }   /* the brush tiles' own sizes (the general .brushIcon is 27px) */
  #brushFlyout .macroBrushTile .brushIcon svg { width: 100%; height: 100%; display: block; }
  #brushFlyout .macroBrushTile .brushIcon { color: inherit; }
  #brushFlyout .macroBrushTile:hover .brushIcon { background: var(--surface-hover); border-color: var(--control-border-hover); }
  #brushFlyout .macroBrushTile.macroBrushTileActive .brushIcon { background: var(--accent-dim); border-color: var(--accent-strong); }
  #brushFlyout .macroBrushTile.macroBrushTileActive { color: var(--accent-strong); }
  #brushFlyout .macroBrushTile .brushName { display: block; font-size: var(--text-xs); line-height: 1.1; font-weight: 500; letter-spacing: 0.01em; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 0 2px; }
  html[data-appearance="light"] #brushFlyout .macroBrushTile .brushIcon { color: var(--neutral-600); }
  /* the macro rows of the Brush panel: the chooser, then Record / Import / Export at even padding in one row */
  #strokeMacroPanelRows { display: flex; flex-direction: column; gap: var(--space-2); margin-bottom: var(--space-3); }
  #strokeMacroPanelRows .rowLabel { margin: 0; }
  #strokeMacroPanelRows .strokeMacroFileRow { display: grid; grid-template-columns: 1.3fr 1fr 1fr; gap: var(--space-2); }
  #strokeMacroPanelRows .strokeMacroFileRow .toggleButton { display: inline-flex; align-items: center; justify-content: center; gap: var(--space-1_5); min-height: 30px; padding: 0 var(--space-2_5); white-space: nowrap; }
  #strokeMacroPanelRows .strokeMacroFileRow .pickerFileButtonIcon { width: 14px; height: 14px; flex: 0 0 auto; }
  #brushFlyout .brushGroupRow .brushGroupGrid { grid-template-columns: none; grid-auto-flow: column; grid-auto-columns: var(--brush-tile-size); }   /* one line of tiles per small group */
  /* Picker tiles (owner 2026-09-11): the icon FILLS a square, the name sits below it. The button itself is
     transparent; the square (.brushIcon) carries the raised look, the hover and the active accent. */
  #brushFlyout .brushButton { width: var(--brush-tile-size); height: auto; flex-direction: column; gap: var(--space-1); padding: 0; background: none; border: none; box-shadow: none; border-radius: 0; }
  #brushFlyout .brushButton .brushIcon {
    width: var(--brush-tile-size); height: var(--brush-tile-size); box-sizing: border-box; padding: 6px; border-radius: var(--radius-lg);
    background: var(--surface-raised); border: 1px solid var(--control-border); display: flex; align-items: center; justify-content: center;
    transition: background var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard);
  }
  #brushFlyout .brushButton:hover .brushIcon { background: var(--surface-hover); border-color: var(--control-border-hover); }
  /* Light appearance, every theme (2026-09-26): the brush icons on the
 panel card and in the picker take the theme's mid-tone chrome shade instead of the near-black text colour. */
  html[data-appearance="light"] #brushPreviewMedia .brushPreviewIcon, html[data-appearance="light"] #brushFlyout .brushButton .brushIcon, html[data-appearance="light"] #brushFlyout .savedBrushTile .brushIcon { color: var(--neutral-600); }
  #brushFlyout .brushButton.brushButtonActive .brushIcon {
    background: var(--accent-dim); border-color: color-mix(in oklab, var(--accent-500) 65%, transparent); box-shadow: var(--shadow-focus), var(--shadow-sm);
  }
  html[data-theme="rainbow"] #brushFlyout .brushButton.brushButtonActive .brushIcon { background: var(--rainbow-fill); border-color: transparent; }
  html[data-theme="rainbow"] #brushFlyout .brushButton.brushButtonActive { background: none; color: var(--rainbow-on-text); }
  #brushFlyout .brushButton .brushName {
    display: block; font-size: var(--text-xs); line-height: 1.1; font-weight: 500; letter-spacing: 0.01em;
    max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 0 2px;
  }
  /* Mask Expand (2026-09-27) is the longest name in the picker: it may use the gap between tiles and sits a touch tighter, so it reads whole. */
  #brushFlyout .brushButton[data-brush="maskExpand"] .brushName { max-width: calc(100% + var(--space-1_5)); margin: 0 calc(var(--space-1_5) / -2); padding: 0; letter-spacing: -0.01em; }
  #brushFlyout .brushButton::after { display: none; }
  /* My brushes (saved brushes): the same tile vocabulary as the built-in brushes. */
  /* Save brush and Record macro (2026-10-14): each on its own section's label row, before Import and Export, instead of together at the
 bottom right of the picker; the same compact button as before (owner 2026-09-15). */
  /* (2026-10-19): every button on these rows is one height,
 Save brush / Record macro first, then Import and Export joined as one control with an icon each, the same on both rows. */
  #brushFlyout .savedBrushHeaderButtons .toggleButton { flex: 0 0 auto; width: auto; height: 28px; box-sizing: border-box; padding: 0 var(--space-4); display: inline-flex; align-items: center; justify-content: center; gap: var(--space-1_5); text-transform: none; letter-spacing: 0; }
  /* The PRO badge on a feature the plan does not include (2026-09-26: brush export on Free) — the control stays visible and says why. */
  .proBadge { display: inline-block; margin-left: var(--space-1_5); padding: 0 5px; border-radius: 999px; font-size: 9px; font-weight: 700; letter-spacing: 0.06em; line-height: 15px; vertical-align: middle; background: var(--accent-dim); color: var(--accent-strong); border: 1px solid color-mix(in oklab, var(--accent-500) 55%, transparent); }
  #brushFlyout .savedBrushGroupHeader { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); opacity: 1; }
  #brushFlyout .savedBrushGroupHeader > span:first-child { opacity: 0.75; }   /* only the label is dimmed like the other group labels; Save brush and Record macro keep their full colour */
  #brushFlyout .brushGroupSaved .brushGroupGrid { grid-template-columns: repeat(auto-fill, var(--brush-tile-size)); }   /* MY BRUSHES and MACROS span the picker: their tiles keep the groups' spacing and fill a row before wrapping (2026-10-14; before, four or eight stretched cells spread a few tiles across the whole width) */
  #brushFlyout .savedBrushHeaderButtons { display: inline-flex; align-items: center; gap: var(--space-2); }
  #brushFlyout .brushGroupSaved { align-self: stretch; }   /* at the bottom the section spans the picker: My brushes at the left, Import… / Export… at the right */   /* Import… and Export… side by side (2026-09-15) */
  #brushFlyout .pickerFileButtons {
    display: inline-flex; align-items: stretch; height: 28px; box-sizing: border-box; overflow: hidden;
    border: 1px solid var(--control-border); border-radius: var(--radius-md); background: var(--surface-raised);
  }
  #brushFlyout .savedBrushImportButton {
    display: inline-flex; align-items: center; gap: var(--space-1_5); margin: 0; padding: 0 var(--space-3); border: none; border-radius: 0; background: none;
    font: inherit; font-size: var(--text-xs); font-weight: 600; text-transform: none; letter-spacing: 0; white-space: nowrap; color: var(--text-secondary); cursor: pointer;
    transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
  }
  #brushFlyout .savedBrushImportButton + .savedBrushImportButton { border-left: 1px solid var(--control-border); }
  #brushFlyout .savedBrushImportButton:hover { color: var(--text-primary); background: var(--surface-hover); }
  #brushFlyout .savedBrushImportButton:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: -2px; }
  #brushFlyout .pickerFileButtonIcon { width: 13px; height: 13px; flex: 0 0 auto; }
  #brushFlyout .savedBrushImportButton .proBadge { margin-left: 0; }
  #brushFlyout .savedBrushTile { width: var(--brush-tile-size); display: flex; flex-direction: column; align-items: center; gap: var(--space-1); padding: 0; background: none; border: none; cursor: pointer; color: var(--text-muted); font: inherit; }
  #brushFlyout .savedBrushTile .brushIcon {
    width: var(--brush-tile-size); height: var(--brush-tile-size); box-sizing: border-box; padding: 6px; border-radius: var(--radius-lg); overflow: hidden;
    background: var(--surface-raised); border: 1px solid var(--control-border); display: flex; align-items: center; justify-content: center;
    transition: background var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard);
  }
  #brushFlyout .savedBrushTile .brushIcon.savedBrushThumbnail, #brushFlyout .savedBrushTile .brushIcon.strokeMacroPictureIcon { padding: 0; }   /* (a macro's picture fills its tile too, 2026-10-19) */
  #brushFlyout .savedBrushTile .brushIcon img { width: 100%; height: 100%; object-fit: cover; display: block; }
  #brushFlyout .savedBrushTile:hover .brushIcon { background: var(--surface-hover); border-color: var(--control-border-hover); }
  #brushFlyout .savedBrushTile.brushButtonActive .brushIcon { background: var(--accent-dim); border-color: color-mix(in oklab, var(--accent-500) 65%, transparent); box-shadow: var(--shadow-focus), var(--shadow-sm); }
  #brushFlyout .savedBrushTile .brushName { display: block; font-size: var(--text-xs); line-height: 1.1; font-weight: 500; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 0 2px; }
  #brushFlyout .savedBrushEmptyHint { grid-column: 1 / -1; font-size: var(--text-2xs); color: var(--text-muted); line-height: 1.4; }
  /* VDM stamps in the Brush panel (2026-10-12): tiles like the saved brushes', four to a row. */
  .vdmStampGrid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-1_5); margin: var(--space-1_5) 0; }
  .vdmStampGrid .savedBrushTile { width: 100%; display: flex; flex-direction: column; align-items: center; gap: var(--space-1); padding: 0; background: none; border: none; cursor: pointer; color: var(--text-muted); font: inherit; }
  .vdmStampGrid .savedBrushTile .brushIcon { width: 100%; height: auto; aspect-ratio: 1; box-sizing: border-box; padding: 0; border-radius: var(--radius-md); overflow: hidden; background: var(--surface-raised); border: 1px solid var(--control-border); display: flex; }
  .vdmStampGrid .savedBrushTile .brushIcon img { width: 100%; height: 100%; object-fit: cover; display: block; }
  .vdmStampGrid .savedBrushTile:hover .brushIcon { border-color: var(--control-border-hover); }
  .vdmStampGrid .savedBrushTile.brushButtonActive .brushIcon { border-color: color-mix(in oklab, var(--accent-500) 65%, transparent); box-shadow: var(--shadow-focus); }
  .vdmStampGrid .savedBrushTile .brushName { display: block; font-size: var(--text-2xs); line-height: 1.1; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* The cross on a stamp of the person's own (2026-10-12: "have no way to delete created stamps"): shown when the tile is under the pointer or holds the focus. */
  .vdmStampGrid .savedBrushTile { position: relative; }
  .vdmStampTileDelete { position: absolute; top: 3px; right: 3px; width: 18px; height: 18px; display: none; align-items: center; justify-content: center; border-radius: var(--radius-sm);
    font-size: 13px; line-height: 1; color: var(--text-primary); background: oklch(18% 0.01 var(--tone-hue) / 0.82); border: 1px solid var(--control-border); cursor: pointer; }
  .vdmStampGrid .savedBrushTile:hover .vdmStampTileDelete, .vdmStampGrid .savedBrushTile:focus-within .vdmStampTileDelete { display: inline-flex; }
  .vdmStampTileDelete:hover { border-color: var(--danger-strong, oklch(50% 0.16 25)); color: #fff; background: var(--danger-strong, oklch(50% 0.16 25)); }
  /* The stamp library window: the same tiles, six to a row and larger. */
  .vdmLibraryPanel { width: min(760px, 94vw); max-height: 86vh; display: flex; flex-direction: column; gap: var(--space-3); }
  .vdmStampGrid.vdmLibraryGrid { grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap: var(--space-3); margin: 0; overflow-y: auto; padding: 2px; min-height: 120px; }
  .vdmLibraryGrid .savedBrushTile .brushName { font-size: var(--text-xs); }
  .vdmStampGrid .savedBrushEmptyHint { grid-column: 1 / -1; font-size: var(--text-2xs); color: var(--text-muted); line-height: 1.4; }
  /* RENDER PREVIEW WINDOW (2026-10-12): a floating panel that can be dragged anywhere: above the tool rail and the properties panel (55, 56), below the pickers, the menus and the dialogs (60 and up). */
  .renderPreviewWindow {
    position: fixed; z-index: 58; display: flex; flex-direction: column; min-width: 220px; min-height: 170px; overflow: hidden;
    background: var(--surface-popover); border: 1px solid var(--hairline); border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
  }
  .renderPreviewWindowHeader { display: flex; align-items: center; gap: var(--space-1_5); padding: var(--space-1_5) var(--space-2); cursor: move; user-select: none; touch-action: none; border-bottom: 1px solid var(--hairline); }
  .renderPreviewWindowTitle { min-width: 58px; flex: 1 1 auto; font-size: var(--text-2xs); text-transform: uppercase; letter-spacing: var(--tracking-label); font-weight: 600; color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .renderPreviewWindowHeader .toggleButton { flex: 0 0 auto; height: 24px; padding: 0 var(--space-2); font-size: var(--text-2xs); }
  #renderPreviewWindow .renderPreviewWindowSelect { flex: 0 0 auto; width: auto; min-width: 0; max-width: 120px; height: 24px; font: inherit; font-size: var(--text-2xs); color: var(--text-primary); background: var(--surface-raised); border: 1px solid var(--control-border); border-radius: var(--radius-sm); padding: 0 var(--space-1_5); cursor: pointer; }
  .renderPreviewWindowSelect:hover { border-color: var(--control-border-hover); }
  .renderPreviewWindowCloseButton { flex: 0 0 auto; width: 24px; height: 24px; border: none; background: none; color: var(--text-secondary); font-size: 16px; line-height: 1; border-radius: var(--radius-sm); cursor: pointer; }
  .renderPreviewWindowCloseButton:hover { background: var(--surface-hover); color: var(--text-primary); }
  .renderPreviewWindowBody { position: relative; flex: 1 1 auto; min-height: 0; background: var(--surface-popover); display: flex; align-items: center; justify-content: center; }   /* no black behind the picture: the window keeps the picture's shape (2026-10-14) */
  .renderPreviewWindowBody canvas { max-width: 100%; max-height: 100%; width: 100%; height: 100%; object-fit: cover; display: block; cursor: pointer; }   /* cover: a rounding hairline is cropped rather than shown as a bar */
  .renderPreviewWindowStatus { position: absolute; left: 0; right: 0; bottom: 0; padding: var(--space-1) var(--space-2); font-size: var(--text-2xs); color: var(--text-secondary); background: color-mix(in oklab, var(--surface-popover) 80%, transparent); text-align: center; pointer-events: none; }
  .renderPreviewWindowResizeHandle { position: absolute; right: 0; bottom: 0; width: 16px; height: 16px; cursor: nwse-resize; touch-action: none;
    background: linear-gradient(135deg, transparent 50%, var(--control-border-hover) 50%, var(--control-border-hover) 60%, transparent 60%, transparent 72%, var(--control-border-hover) 72%, var(--control-border-hover) 82%, transparent 82%); }
  .savedBrushMenu {
    position: fixed; z-index: 90; min-width: 200px; padding: var(--space-1); display: flex; flex-direction: column;
    background: var(--surface-popover); border: 1px solid var(--hairline); border-radius: var(--radius-md); box-shadow: var(--shadow-lg);
  }
  .savedBrushMenu[hidden] { display: none; }
  .savedBrushMenu button { font: inherit; font-size: var(--text-xs); text-align: left; padding: var(--space-1_5) var(--space-2); border: none; background: none; color: var(--text-primary); border-radius: var(--radius-sm); cursor: pointer; }
  .savedBrushMenu button:hover, .savedBrushMenu button:focus-visible { background: var(--surface-hover); outline: none; }
  .savedBrushMenu .savedBrushMenuDanger { color: var(--danger, #e5484d); }
  /* When opened from the right-panel preview, anchor the picker just left of the properties panel. */
  #brushFlyout.fromPreview { left: auto; right: calc(var(--panel-w) + var(--space-2)); top: calc(var(--chrome-top) + var(--space-2)); }

  /* brush preview window — square thumbnail of the current brush at the top of the Brush section; click opens the picker */
  #brushPreviewButton {
    width: 100%; aspect-ratio: 1 / 1; margin: 0 0 var(--space-3); padding: 0;
    position: relative; display: block; overflow: hidden; cursor: pointer;
    background: var(--surface-raised); border: 1px solid var(--hairline); border-radius: var(--radius-lg);
    transition: border-color var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out);
  }
  #brushPreviewButton:hover { border-color: rgba(255,255,255,0.28); box-shadow: var(--shadow-md); }
  #brushPreviewButton:active { transform: translateY(1px); }
  #brushPreviewMedia { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
  /* The icon span fills the whole preview and centres its glyph; the svg gets a real size to resolve
     against (72% of the square) so every brush icon renders large and centred, not shrink-wrapped tiny. */
  #brushPreviewMedia .brushPreviewIcon { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: var(--text-secondary); }
  #brushPreviewMedia .brushPreviewIcon svg { width: 72%; height: 72%; }
  #brushPreviewImage { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: none; }
  /* The loaded alpha on the large brush card (2026-09-26), over the brush icon. */
  #brushPreviewAlphaImage { position: absolute; inset: 10%; width: 80%; height: 80%; object-fit: contain; display: none; border-radius: var(--radius-md); background: #000; box-shadow: var(--shadow-sm); }
  /* The bar along the bottom of the preview: the brush's name on the left, the "Change brush" hint on the right.
     A name too long to share the line (some translations) keeps its whole width and the hint drops to a second line. */
  #brushPreviewCaption {
    position: absolute; left: 0; right: 0; bottom: 0; padding: var(--space-2) var(--space-2_5) var(--space-2_5);   /* bottom = sides, so the hint sits as far from the bottom edge as from the right (owner 2026-10-30) */
    display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-1) var(--space-2);
    background: linear-gradient(to top, rgba(0,0,0,0.6), rgba(0,0,0,0));
    pointer-events: none;
  }
  #brushPreviewName {
    max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    font-size: var(--text-sm); font-weight: 600; letter-spacing: 0.01em; color: #fff; text-align: left;
    text-shadow: 0 1px 4px rgba(0,0,0,0.85);
  }
  /* "Change brush" (2026-10-30): a quiet pill tinted
 with the theme's accent, so a newcomer can see the preview opens the brush list. The bar under it is always
 dark, so the text stays light in every appearance. */
  #brushPreviewChangeHint {
    flex-shrink: 0; margin-left: auto; align-self: flex-end; display: inline-flex; align-items: center; gap: 3px;
    padding: 2px var(--space-2); border-radius: var(--radius-sm);
    font-size: var(--text-xs); font-weight: 500; white-space: nowrap; color: rgba(255,255,255,0.82);
    background: color-mix(in oklab, var(--accent-500) 26%, transparent);
    border: 1px solid color-mix(in oklab, var(--accent-500) 42%, transparent);
    transition: background var(--dur-base) var(--ease-out), color var(--dur-base) var(--ease-out);
    pointer-events: auto; cursor: pointer;   /* a button of its own on the preview (2026-10-30): it lights on its own hover; the whole preview opens the list too */
  }
  #brushPreviewChangeHint svg { width: 10px; height: 10px; }
  #brushPreviewChangeHint:hover { color: #fff; background: color-mix(in oklab, var(--accent-500) 40%, transparent); }
  #brushPreviewChangeHint:active { transform: translateY(1px); }

  /* ---------- right properties panel ---------- */
  #propertiesPanel {
    position: fixed; top: var(--header-h); right: 0; bottom: var(--footer-h); width: var(--panel-w); z-index: 55;
    background: var(--surface-chrome);
    -webkit-backdrop-filter: blur(18px) saturate(140%);
    backdrop-filter: blur(18px) saturate(140%);
    border-left: 1px solid var(--hairline-soft);
    overflow-y: auto; padding: var(--space-1) var(--space-4) var(--space-6);
    transition: transform var(--dur-base) var(--ease-out);
    scrollbar-width: thin;
    scrollbar-color: var(--neutral-650) transparent;
  }
  #propertiesPanel::-webkit-scrollbar { width: 9px; }
  #propertiesPanel::-webkit-scrollbar-thumb {
    background: var(--neutral-650); border-radius: var(--radius-pill);
    border: 2px solid transparent; background-clip: padding-box;
  }
  #propertiesPanel::-webkit-scrollbar-thumb:hover { background: var(--neutral-600); background-clip: padding-box; }
  #propertiesPanel.collapsed { transform: translateX(100%); }
  #panelToggle {
    position: fixed; top: calc(var(--chrome-top) + var(--space-2)); right: calc(var(--panel-w) + var(--space-2)); z-index: 56;
    width: 26px; height: 26px; border-radius: var(--radius-sm); cursor: pointer;
    transition: right var(--dur-base) var(--ease-out), background var(--dur-fast) var(--ease-standard),
                color var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard);
    background: var(--surface-raised); border: 1px solid var(--control-border); color: var(--text-muted); font-size: 13px;
    display: inline-flex; align-items: center; justify-content: center; box-shadow: var(--shadow-sm);
  }
  #panelToggle:hover { background: var(--surface-hover); color: var(--accent-strong); border-color: var(--control-border-hover); }
  #panelToggle:active { background: var(--accent-dim); }
  #propertiesPanel.collapsed ~ #panelToggle { right: var(--space-2); }

  /* Each section gets generous vertical padding so the gap BETWEEN sections (across the hairline) reads as
     clearly larger than the gaps WITHIN a section — proximity grouping carries the structure. */
  .panelSection { padding: var(--space-5) 0; border-bottom: 1px solid var(--hairline-soft); }
  .panelSection:last-child { border-bottom: none; }
  .sectionTitle {
    font-size: var(--text-2xs); text-transform: uppercase; letter-spacing: var(--tracking-label);
    font-weight: 600; color: var(--text-muted);
    margin-bottom: var(--space-3); cursor: pointer; user-select: none; display: flex; align-items: center; justify-content: space-between;
    transition: color var(--dur-fast) var(--ease-standard);
  }
  .sectionTitle:hover { color: var(--text-secondary); }
  /* The collapse triangle (owner 2026-09-26: bigger, it was hard to see and hit). */
  .sectionTitle::after { content: '▾'; font-size: 20px; line-height: 1; width: 20px; text-align: center; opacity: 0.6; transition: transform var(--dur-base) var(--ease-out); }
  .sectionTitle:hover::after { opacity: 0.9; }
  .panelSection.sectionCollapsed .sectionTitle { margin-bottom: 0; }
  .panelSection.sectionCollapsed .sectionTitle::after { content: '▸'; }
  /* !important so collapse wins over element-level display rules set by ID selectors (e.g.
     #brushPreviewButton { display:block } — higher specificity — used to keep the Brush preview box
     and the Layers list visible even when their section was collapsed). */
  .panelSection.sectionCollapsed > *:not(.sectionTitle) { display: none !important; }

  .sliderRow { display: flex; align-items: center; gap: var(--space-3); margin: var(--space-2_5) 0; }
  .sliderLabel { width: 72px; font-size: var(--text-xs); color: var(--text-secondary); flex-shrink: 0; }
  /* ONE SLIDER LAYOUT IN THE PANEL (UI polish, 2026-10-23).
 The same elements and the same readouts (still click-to-type); only their places change. */
  #propertiesPanel .sliderRow { flex-wrap: wrap; row-gap: var(--space-1_5); column-gap: var(--space-2); }
  #propertiesPanel .sliderRow > .sliderLabel { flex: 1 1 100%; width: auto; display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-2); }
  #propertiesPanel .sliderRow > input[type="range"] { flex: 1 1 100%; width: 100%; min-width: 0; margin: 0; }
  /* (v0.8.9 localisation) Japanese, Chinese and Korean write words without spaces, so a label may break in the middle of a word
     ("フォールオ / フ"); there it breaks only at a space, as the English and the other languages do. English is untouched. */
  html:is([lang="ja"], [lang="zh-CN"], [lang="ko"]) :is(.sliderLabel, .toggleButton, .menuItem, .panelSectionTitle, .brushName) { word-break: keep-all; }
  #reflowTargetReadout { cursor: text; }   /* the Reflow count is typed in place (2026-10-11) */
  #reflowTargetReadout[contenteditable="true"] { background: var(--surface-inset); outline: 1px solid var(--accent-400); border-radius: var(--radius-sm); padding: 0 3px; }
  .sliderLabel span { color: var(--accent-strong); font-family: var(--font-mono); font-weight: 600; }
  /* Slider readouts are click-to-type (setupAppChrome, app.js): the number invites a click, then a
     small inline editor replaces it until Enter/blur commits or Escape cancels. */
  .sliderLabel span { cursor: text; border-bottom: 1px dotted transparent; -webkit-user-select: none; -moz-user-select: none; user-select: none; }
  .sliderLabel span:hover { border-bottom-color: var(--accent-strong); }
  .sliderReadoutEditable { cursor: text; }   /* a tool window's readout opens a field on click (2026-10-29) */
  .sliderValueEditor {
    width: 56px; font-family: var(--font-mono); font-size: var(--text-xs); font-weight: 600;
    color: var(--accent-strong); background: var(--surface-inset); border: 1px solid var(--hairline);
    border-radius: var(--radius-sm, 4px); padding: 1px 4px; outline: none;
    -moz-appearance: textfield;   /* hide the spinner arrows — this is a type-in field, not a stepper */
  }
  .sliderValueEditor::-webkit-outer-spin-button, .sliderValueEditor::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
  /* The master References toggle sits in its own row above the view chips — breathing room on both
     sides so it reads as the section's switch, not part of the chip grid. */
  .referenceMasterToggleRow { margin: var(--space-2, 6px) 0 var(--space-2_5, 8px); }
  /* Axis-guide chips carry a coloured dot matching their world axis so the toggles read as X/Y/Z. */
  .axisGuideChip::before { content: ''; display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; vertical-align: middle; }
  .axisGuideChipX::before { background: #e63d3d; }
  .axisGuideChipY::before { background: #57cc5b; }
  .axisGuideChipZ::before { background: #578af2; }
  /* Reference view chips: a small dot marks every view that holds a loaded image (app.js
     syncReferenceControlsFromSelectedView toggles the class). */
  #referenceViewChipRow .toggleButton { position: relative; }
  #referenceViewChipRow .toggleButton.referenceViewChipHasImage::after {
    content: ''; position: absolute; top: 4px; right: 5px; width: 6px; height: 6px;
    border-radius: 50%; background: var(--accent-strong); box-shadow: 0 0 3px rgba(0,0,0,0.4);
  }
  input[type="range"] {
    -webkit-appearance: none; appearance: none; flex: 1; height: 7px; border-radius: var(--radius-pill);
    background: var(--surface-inset); outline: none; cursor: pointer;
    border: 1px solid var(--hairline-soft);
  }
  input[type="range"]::-webkit-slider-thumb {
    -webkit-appearance: none; appearance: none; width: 19px; height: 19px; border-radius: 50%;
    background: var(--accent); cursor: pointer; border: 2px solid var(--neutral-900);
    box-shadow: var(--shadow-sm);
    transition: background var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard),
                transform var(--dur-fast) var(--ease-standard);
  }
  input[type="range"]::-moz-range-thumb {
    width: 19px; height: 19px; border-radius: 50%; background: var(--accent); cursor: pointer;
    border: 2px solid var(--neutral-900); box-shadow: var(--shadow-sm);
    transition: background var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard);
  }
  input[type="range"]:hover { border-color: var(--hairline); }
  input[type="range"]:hover::-webkit-slider-thumb { background: var(--accent-strong); }
  input[type="range"]:hover::-moz-range-thumb { background: var(--accent-strong); }
  input[type="range"]:active::-webkit-slider-thumb { transform: scale(1.12); }
  input[type="range"]:active::-moz-range-thumb { transform: scale(1.12); }
  /* Keyboard focus = the shared chrome outline around the track (one consistent focus language); the thumb
     also brightens to the accent-strong fill so the active handle is unmistakable. */
  input[type="range"]:focus-visible::-webkit-slider-thumb { background: var(--accent-strong); }
  input[type="range"]:focus-visible::-moz-range-thumb { background: var(--accent-strong); }

  /* the Print height row of the Hollow and Print check windows (2026-10-12): a label, a number field and "mm" on one line */
  .printHeightRow { display: flex; align-items: center; gap: var(--space-2); }
  .printHeightRow .sliderLabel { flex: 1 1 auto; }
  .printHeightRow .printHeightField { flex: 0 0 96px; width: 96px; text-align: right; }
  .printHeightRow .printHeightUnit { flex: none; font-size: var(--text-xs); color: var(--text-muted); }
  .confirmDialogMessage.printHeightNote { margin: var(--space-1) 0 var(--space-2); font-size: var(--text-xs); }
  .chipRow { display: flex; flex-wrap: wrap; gap: var(--space-1_5); }
  /* ONE RULE FOR EVERY PANEL (2026-10-12). Rows of
 controls that follow one another are never touching: a row of buttons after a row of buttons, a button after a row, a row after
 a button, a button after a button, a list after either. It used to take a rule of its own for every such pair, so any new row
 sat against the one above until somebody noticed. The gate test:panel-spacing measures every panel and fails if two touch. */
  .panelSection .chipRow + .chipRow, .panelSection .chipRow + .toggleButton, .panelSection .toggleButton + .chipRow,
  .panelSection .fullWidthButton + .chipRow, .panelSection .fullWidthButton + .fullWidthButton, .panelSection .chipRow + select,
  .panelSection select + .chipRow, .panelSection .fullWidthButton + select, .panelSection select + .fullWidthButton { margin-top: var(--space-1_5); }
  /* Radial: its switch on a line of its own, its three axes under it in thirds (the long "Radial: 6 around Z" used to push one axis onto a line by itself). */
  #radialSymmetryRow #radialSymmetryToggleButton { flex: 1 1 100%; }
  /* A full-width action under a row of chips (Remesh under Sharp edges / Fill holes) gets breathing room, not a touching edge. */
  .panelSection .chipRow + .fullWidthButton { margin-top: var(--space-2_5); }
  .toggleButton {
    background: var(--surface-raised); border: 1px solid var(--control-border); color: var(--text-secondary);
    font: inherit; font-size: var(--text-xs); font-weight: 600; padding: var(--space-2_5) var(--space-3_5); border-radius: var(--radius-md); cursor: pointer;
    flex: 1 1 auto; text-align: center; white-space: nowrap;
    transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard),
                border-color var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard),
                transform var(--dur-fast) var(--ease-standard), opacity var(--dur-fast) var(--ease-standard);
  }
  .toggleButton:hover { background: var(--surface-hover); color: var(--text-primary); border-color: var(--control-border-hover); }
  .toggleButton:active { transform: translateY(0.5px); }
  .toggleButton.toggleButtonActive {
    background: var(--accent-dim); border-color: color-mix(in oklab, var(--accent-500) 55%, transparent);
    color: var(--accent-strong); box-shadow: var(--shadow-sm);
  }
  /* A selected chip stays unmistakably "on" on hover (deepen the accent, don't fall back to a neutral hover). */
  .toggleButton.toggleButtonActive:hover {
    background: color-mix(in oklab, var(--accent-dim) 80%, var(--accent-600));
    border-color: color-mix(in oklab, var(--accent-500) 70%, transparent); color: var(--accent-strong);
  }
  .toggleButton.disabledButton { opacity: var(--disabled-opacity); pointer-events: none; cursor: default; }
  /* SWITCHES (UI polish, owner 2026-10-23): a right-panel "Label: On / Off" button is drawn as its label on the left and a small switch
 on the right (panel-switches.js marks them; the button, its id, its click and its text stay as they were, the text only not drawn).
 One to a row, so no label is ever cut short. (2026-10-24)
 The rows are plain, no box of their own, a faint wash under the pointer; the state is a tick box on the right (option B, below). */
  #propertiesPanel .toggleButton.panelSwitch,
  #propertiesPanel .toggleButton.panelSwitch:hover,
  #propertiesPanel .toggleButton.panelSwitch.toggleButtonActive,
  #propertiesPanel .toggleButton.panelSwitch.toggleButtonActive:hover {
    display: inline-flex; align-items: center; justify-content: space-between; gap: var(--space-2_5);
    flex: 1 1 100%; min-width: 0; text-align: left;
    font-size: 0; padding: var(--space-1_5) var(--space-1);
    background: transparent; border-color: transparent; color: var(--text-secondary); box-shadow: none;
  }
  #propertiesPanel .toggleButton.panelSwitch:hover { background: color-mix(in oklab, var(--surface-hover) 60%, transparent); color: var(--text-primary); }
  #propertiesPanel .toggleButton.panelSwitch.panelSwitchOn { color: var(--text-primary); }
  #propertiesPanel .toggleButton.panelSwitch::before {
    content: attr(data-switch-label); font-size: var(--text-xs); line-height: 1.3; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  /* (2026-10-24) the state as a small tick box on the right: an empty square with the
 control's edge when off, filled with the accent and ticked when on */
  #propertiesPanel .toggleButton.panelSwitch::after {
    content: ''; flex: none; width: 16px; height: 16px; box-sizing: border-box; border-radius: 4px;
    border: 1.5px solid var(--control-border-hover); background: var(--surface-inset);
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 11px; font-weight: 700; line-height: 1; color: var(--surface-base);
    transition: background var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard);
  }
  #propertiesPanel .toggleButton.panelSwitch.panelSwitchOn::after { content: '\2713'; background: var(--accent-500); border-color: var(--accent-500); }
  .fullWidthButton { width: 100%; flex-basis: 100%; }
  #liveTopologyToggleButton { margin-top: var(--space-2_5); }   /* clear space above the Live button (owner 2026-10-02, twice: the row gap alone still read as touching the Subdivide buttons) */
  /* LIVE TOPOLOGY IN A BOX OF ITS OWN (2026-10-24):
 unlike the plain tick rows, it sits in a raised box with the control edge and a bold label; on, the box takes a faint accent wash
 and an accent edge as well as its tick. */
  #propertiesPanel #liveTopologyToggleButton.toggleButton.panelSwitch,
  #propertiesPanel #liveTopologyToggleButton.toggleButton.panelSwitch:hover {
    padding: var(--space-2_5) var(--space-3); margin-bottom: var(--space-1);
    background: var(--surface-raised); border: 1px solid var(--control-border); border-radius: var(--radius-md); color: var(--text-primary);
  }
  #propertiesPanel #liveTopologyToggleButton.toggleButton.panelSwitch:hover { border-color: var(--control-border-hover); background: var(--surface-hover); }
  #propertiesPanel #liveTopologyToggleButton.toggleButton.panelSwitch::before { font-weight: 700; }
  #propertiesPanel #liveTopologyToggleButton.toggleButton.panelSwitch.panelSwitchOn {
    background: color-mix(in oklab, var(--accent-500) 12%, var(--surface-raised)); border-color: color-mix(in oklab, var(--accent-500) 55%, transparent);
  }

  select {
    background-color: var(--surface-raised); color: var(--text-primary); border: 1px solid var(--control-border);
    border-radius: var(--radius-md); padding: var(--space-2_5) var(--space-3); font: inherit; font-size: var(--text-xs); outline: none; width: 100%;
    cursor: pointer; -webkit-appearance: none; appearance: none;
    background-image: linear-gradient(45deg, transparent 50%, var(--text-muted) 50%),
                      linear-gradient(135deg, var(--text-muted) 50%, transparent 50%);
    background-position: calc(100% - var(--space-4)) 52%, calc(100% - 11px) 52%;
    background-size: 5px 5px, 5px 5px;
    background-repeat: no-repeat;
    transition: background-color var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard),
                box-shadow var(--dur-fast) var(--ease-standard), opacity var(--dur-fast) var(--ease-standard);
  }
  select:hover { border-color: var(--control-border-hover); background-color: var(--surface-hover); }
  select:active { background-color: var(--surface-hover); border-color: var(--control-border-hover); }
  /* Focus ring is the shared chrome outline (see the #propertiesPanel :focus-visible rule); just warm the
     border to the accent so the control itself reads active — no doubled glow. */
  select:focus-visible { border-color: var(--accent); }
  select:disabled { opacity: var(--disabled-opacity); cursor: default; pointer-events: none; }
  .rowLabel { font-size: var(--text-xs); color: var(--text-muted); margin: var(--space-2_5) 0 var(--space-1_5); }
  /* ONE HEADER STYLE INSIDE A SECTION (UI polish, 2026-10-23): a row label
 that heads the rows under it (Stroke, Alpha, Macro, Paint by curvature …) reads as a small header, with clear space above it. */
  #propertiesPanel .rowLabel { font-weight: 600; color: var(--text-secondary); margin-top: var(--space-4); }
  #propertiesPanel .sectionTitle + .rowLabel, #propertiesPanel .sectionTitle + * > .rowLabel:first-child { margin-top: 0; }
  /* The rarely used sub-sections fold (panel-subsections.js): the header takes a small triangle and the pointer; folded rows are hidden. */
  #propertiesPanel .rowLabel.subSectionTitle { display: flex; align-items: center; justify-content: space-between; cursor: pointer; user-select: none; transition: color var(--dur-fast) var(--ease-standard); }
  #propertiesPanel .rowLabel.subSectionTitle:hover { color: var(--text-primary); }
  #propertiesPanel .rowLabel.subSectionTitle::after { content: '▸'; font-size: 16px; line-height: 1; width: 20px; text-align: center; opacity: 0.6; }
  #propertiesPanel .rowLabel.subSectionTitle.subSectionOpen::after { content: '▾'; }
  #propertiesPanel .subSectionFoldedRow { display: none !important; }
  /* Brush alpha (grayscale stamp): a large square preview beside a stacked Load / Clear column. The
     min-width:0 on both the container and the button column lets the buttons shrink instead of pushing
     the panel wider than its width (which would add a page-level horizontal scrollbar). */
  .alphaControl { display: flex; align-items: center; gap: var(--space-3); margin: var(--space-1_5) 0 var(--space-2_5); min-width: 0; }
  /* The preview IS the load/replace control — click it to pick an alpha (no separate button). Sized well
     below the brush preview so it never steals focus from it. */
  .alphaPreviewBox { flex: 0 0 auto; width: 132px; height: 132px; padding: 0; border-radius: var(--radius-md); border: 1px solid var(--hairline-soft); background: var(--surface-inset); overflow: hidden; display: flex; align-items: center; justify-content: center; cursor: pointer; font: inherit; transition: border-color var(--dur-fast) var(--ease-standard); }
  .alphaPreviewBox:hover { border-color: var(--control-border-hover); }
  .alphaPreviewBox img { width: 100%; height: 100%; object-fit: cover; display: block; }
  .alphaPreviewEmpty { font-size: var(--text-2xs); color: var(--text-muted); text-transform: uppercase; letter-spacing: var(--tracking-label); text-align: center; padding: 0 var(--space-2); }
  .alphaSideButtons { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: var(--space-2); justify-content: center; }
  /* Adjust alpha window (right-click on the alpha preview box, 2026-09-23): a big live preview beside the same adjustments the panel rows carry. */
  .appModalDialog .confirmDialogPanel.alphaAdjustPanel { width: min(900px, 94vw); height: auto; max-height: 88vh; padding: 0; gap: 0; overflow: hidden; display: flex; flex-direction: column; }   /* the alpha library panel's shell, without its fixed height */
  .alphaAdjustBody { display: flex; gap: var(--space-5); align-items: flex-start; padding: var(--space-4) var(--space-5) var(--space-5); }
  .alphaAdjustPreview { width: min(512px, 48vw, calc(100vh - 220px)); height: auto; aspect-ratio: 1; flex: 0 0 auto; display: block; background: #000; border: 1px solid var(--hairline); border-radius: var(--radius-md); }
  .alphaAdjustControls { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
  .alphaAdjustControls .sliderLabel { width: 96px; }
  .alphaAdjustButtonRow { display: flex; gap: var(--space-2); margin-top: var(--space-2); }
  .alphaAdjustButtonRow > button { flex: 0 0 auto; }
  .alphaAdjustResetRow { display: flex; justify-content: flex-end; margin-top: var(--space-1); }   /* under the alpha adjustment sliders (2026-09-23: the controls live here, not in the library window) */
  .alphaSideButtons .toggleButton { min-width: 0; overflow: hidden; text-overflow: ellipsis; }

  /* ---------- in-app colour picker (Paint) — replaces the native <input type=color> OS dialog ---------- */
  /* Trigger: a colour well that sits in the Color slider row where the native swatch used to be. */
  .colorSwatchButton {
    flex: 1; height: 30px; padding: 3px; border-radius: var(--radius-md);
    background: var(--surface-inset); border: 1px solid var(--control-border); cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard);
  }
  .colorSwatchButton:hover { border-color: var(--control-border-hover); }
  .paintColourSwapButton { flex: 0 0 auto; height: 30px; padding: 0 var(--space-2_5); }   /* Paint ▸ Swap (owner 2026-09-27), beside the Secondary swatch */
  /* Gradient Fill's Linear line while it is dragged (owner 2026-09-27): colour A's dot at the press, colour B's at the pointer. */
  #gradientFillLineOverlay { position: fixed; left: 0; top: 0; width: 100vw; height: 100vh; pointer-events: none; z-index: 28; display: none; overflow: visible; }   /* the Clip overlay's layer */
  #gradientFillLineOverlay line { stroke: oklch(98% 0 0 / 0.9); stroke-width: 2; stroke-dasharray: 6 4; }
  #gradientFillLineOverlay circle { stroke: oklch(98% 0 0 / 0.95); stroke-width: 2; }
  .colorSwatchButton:focus-visible { border-color: var(--accent); box-shadow: var(--shadow-focus); outline: none; }
  /* The fill sits over a checkerboard so a future alpha reads correctly; opaque colours just cover it. */
  .colorSwatchFill {
    display: block; width: 100%; height: 100%; border-radius: var(--radius-xs);
    box-shadow: inset 0 0 0 1px oklch(100% 0 0 / 0.10);
  }

  /* Popover: a floating panel matching the brush flyout's elevation/geometry. */
  .colorPickerPopover {
    position: fixed; z-index: 190;   /* above.appModalDialog (160): the render, turntable and timelapse windows open colour pickers too (2026-09-15) */ width: 232px; padding: var(--space-3);
    background: var(--surface-popover); border: 1px solid var(--hairline); border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg); user-select: none;
  }
  .colorPickerPopover.open { animation: popover-in var(--dur-base) var(--ease-out); }

  /* Saturation / value square: base colour = the pure hue (set inline); the two gradients fade to white + black. */
  .colorPickerSaturationArea {
    position: relative; width: 100%; height: 148px; border-radius: var(--radius-sm);
    overflow: hidden; cursor: crosshair; touch-action: none;
    box-shadow: inset 0 0 0 1px oklch(0% 0 0 / 0.28);
  }
  .colorPickerSaturationArea::before { content: ''; position: absolute; inset: 0; background: linear-gradient(to right, #fff, rgba(255,255,255,0)); }
  .colorPickerSaturationArea::after  { content: ''; position: absolute; inset: 0; background: linear-gradient(to top, #000, rgba(0,0,0,0)); }
  .colorPickerSaturationHandle {
    position: absolute; width: 15px; height: 15px; border-radius: 50%; transform: translate(-50%, -50%);
    border: 2px solid #fff; box-shadow: 0 0 0 1px oklch(0% 0 0 / 0.45), var(--shadow-sm); pointer-events: none; z-index: 1;
  }

  /* Hue bar. */
  .colorPickerHueTrack {
    position: relative; height: 14px; margin-top: var(--space-3); border-radius: var(--radius-pill);
    cursor: pointer; touch-action: none; box-shadow: inset 0 0 0 1px oklch(0% 0 0 / 0.22);
    background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
  }
  .colorPickerHueHandle {
    position: absolute; top: 50%; width: 16px; height: 16px; border-radius: 50%; transform: translate(-50%, -50%);
    background: var(--neutral-100); border: 2px solid var(--neutral-900); box-shadow: var(--shadow-sm); pointer-events: none;
  }

  /* Fields row: eyedropper · #hex · R · G · B. */
  .colorPickerControls { display: flex; align-items: stretch; gap: var(--space-1_5); margin-top: var(--space-3); }
  .colorPickerEyedropper {
    flex: 0 0 auto; width: 30px; display: inline-flex; align-items: center; justify-content: center;
    background: var(--surface-raised); border: 1px solid var(--control-border); color: var(--text-secondary);
    border-radius: var(--radius-sm); cursor: pointer; padding: 0;
    transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard);
  }
  .colorPickerEyedropper svg { width: 17px; height: 17px; }
  .colorPickerEyedropper:hover { background: var(--surface-hover); color: var(--text-primary); border-color: var(--control-border-hover); }
  .colorPickerEyedropper:focus-visible { border-color: var(--accent); box-shadow: var(--shadow-focus); outline: none; }
  .colorPickerHexField {
    flex: 1 1 auto; display: flex; align-items: center; gap: 2px; min-width: 0;
    background: var(--surface-inset); border: 1px solid var(--control-border); border-radius: var(--radius-sm);
    padding: 0 var(--space-2); transition: border-color var(--dur-fast) var(--ease-standard);
  }
  .colorPickerFieldSymbol { color: var(--text-muted); font-family: var(--font-mono); font-size: var(--text-xs); }
  .colorPickerHexInput {
    flex: 1 1 auto; min-width: 0; width: 100%; background: none; border: none; outline: none;
    color: var(--text-primary); font-family: var(--font-mono); font-size: var(--text-xs); font-weight: 500;
    text-transform: uppercase; letter-spacing: 0.02em; padding: var(--space-2) 0;
  }
  .colorPickerChannels { display: flex; gap: var(--space-1_5); margin-top: var(--space-1_5); }
  .colorPickerChannelField {
    flex: 1 1 0; display: flex; align-items: center; gap: var(--space-1_5);
    background: var(--surface-inset); border: 1px solid var(--control-border); border-radius: var(--radius-sm);
    padding: 0 var(--space-2); transition: border-color var(--dur-fast) var(--ease-standard);
  }
  .colorPickerChannelField:focus-within { border-color: var(--accent); }
  .colorPickerChannelLabel { font-size: 10px; color: var(--text-muted); letter-spacing: 0.04em; order: -1; }
  .colorPickerChannelInput {
    flex: 1 1 auto; width: 100%; min-width: 0; text-align: right; background: none; border: none;
    color: var(--text-primary); font-family: var(--font-mono); font-size: var(--text-2xs);
    padding: var(--space-2) 0; outline: none; -moz-appearance: textfield;
  }
  .colorPickerHexField:focus-within, .colorPickerChannelInput:focus { border-color: var(--accent); }
  .colorPickerHexField:focus-within { box-shadow: var(--shadow-focus); }

  /* Recent swatches. */
  .colorPickerRecents { display: flex; flex-wrap: wrap; gap: var(--space-1_5); margin-top: var(--space-3); }
  .colorPickerRecentSwatch {
    width: 22px; height: 22px; border-radius: var(--radius-sm); cursor: pointer; padding: 0;
    border: 1px solid oklch(100% 0 0 / 0.14); box-shadow: var(--shadow-sm);
    transition: transform var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard);
  }
  .colorPickerRecentSwatch:hover { transform: translateY(-1px); border-color: var(--accent); }
  .colorPickerRecentSwatch:focus-visible { border-color: var(--accent); box-shadow: var(--shadow-focus); outline: none; }
  /* Swatches: saved (removable) + the starter palette, eight across. */
  .colorPickerSwatchHeader { display: flex; align-items: center; justify-content: space-between; margin-top: var(--space-3); }
  .colorPickerSwatchHeaderLabel { font-size: var(--text-2xs); color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.08em; }
  .colorPickerAddSwatch {
    width: 22px; height: 22px; padding: 0; border-radius: var(--radius-sm); cursor: pointer; line-height: 1;
    background: var(--surface-raised); border: 1px solid var(--control-border); color: var(--text-secondary); font-size: var(--text-sm);
  }
  .colorPickerAddSwatch:hover { background: var(--surface-hover); color: var(--text-primary); border-color: var(--control-border-hover); }
  .colorPickerAddSwatch:focus-visible { border-color: var(--accent); box-shadow: var(--shadow-focus); outline: none; }
  .colorPickerSwatchGrid { display: grid; grid-template-columns: repeat(8, 1fr); gap: 5px; margin-top: var(--space-1_5); }
  .colorPickerSwatchGrid .colorPickerSwatch { width: auto; height: auto; aspect-ratio: 1; }
  @media (prefers-reduced-motion: reduce) {
    .colorPickerPopover.open { animation: none; }
    .colorPickerRecentSwatch:hover { transform: none; }
  }

  #subdivisionLevelReadout { font-family: var(--font-mono); color: var(--text-secondary); font-size: var(--text-xs); }
  /* Layer stack: a VERTICAL list, one row per layer —
 the full-width name button on the left, the compact visibility eye on the right. */
  #layerListContainer { display: flex; flex-direction: column; gap: var(--space-1_5); margin-bottom: var(--space-2); }
  #layerListContainer:empty { display: none; }   /* no stray gap above the Add/Delete/Merge row before any layer chip exists */
  .layerRow { display: flex; gap: var(--space-1_5); align-items: stretch; }
  .layerRow .layerNameButton { flex: 1 1 auto; min-width: 0; text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .layerRow .layerVisibilityButton { flex: 0 0 auto; width: 34px; padding-left: 0; padding-right: 0; text-align: center; }
  /* Passes (2026-10-12): a pass's row is a layer's row with a quiet note at its right end (what is sculpted into, and its Strength). */
  .passRow .layerNameButton { display: flex; justify-content: space-between; gap: var(--space-2); }
  .passRow .passRowNote { flex: 0 0 auto; opacity: 0.65; font-size: var(--text-2xs); align-self: center; }
  #passListContainer { display: flex; flex-direction: column; gap: var(--space-1); margin: var(--space-1_5) 0; }

  /* ---------- floating overlays (don't obstruct / don't catch clicks) ---------- */
  #perfReadout {
    position: fixed; left: calc(var(--rail-w) + var(--space-3_5)); top: calc(var(--chrome-top) + var(--space-2_5) + 26px); z-index: 40;
    background: color-mix(in oklab, var(--neutral-900) 80%, transparent);
    -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
    border: 1px solid var(--hairline-soft); border-radius: var(--radius-md); padding: var(--space-2) var(--space-3);
    font-family: var(--font-mono); font-size: var(--text-xs); line-height: 1.55; pointer-events: none;
    color: var(--text-secondary); box-shadow: var(--shadow-sm);
  }
  #viewportStats {
    position: fixed; top: calc(var(--chrome-top) + var(--space-2_5)); left: calc(var(--rail-w) + var(--space-3_5)); z-index: 40;
    font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-muted);
    pointer-events: none; transition: left var(--dur-base) var(--ease-out); text-align: left;
    text-shadow: 0 1px 3px oklch(7% 0.02 var(--tone-hue) / 0.6);
  }
  #viewportStats b { color: var(--accent-strong); font-weight: 600; }
  /* The plan's triangle limit beside the count (owner 2026-10-20): amber near it, red at it; on Free a click says what Pro gives.
     The only part of the counters that takes the pointer. */
  #viewportStats .triangleLimitReadout {
    pointer-events: auto; margin-left: var(--space-1_5); padding: 0 var(--space-1); border: 1px solid transparent; border-radius: var(--radius-sm);
    background: none; font: inherit; color: inherit; text-shadow: inherit; cursor: default;
  }
  #viewportStats .triangleLimitReadout.triangleLimitReadoutClickable { cursor: pointer; }
  #viewportStats .triangleLimitReadout.triangleLimitReadoutClickable:hover { border-color: var(--control-border); background: color-mix(in oklab, var(--surface-raised) 70%, transparent); }
  #viewportStats.triangleLimitNear #triangleCountReadout, #viewportStats.triangleLimitNear .triangleLimitReadout { color: oklch(80% 0.15 78); }
  #viewportStats.triangleLimitReached #triangleCountReadout, #viewportStats.triangleLimitReached .triangleLimitReadout { color: oklch(66% 0.21 27); }
  /* Extract's status line: the piece's triangle count in the counter's own amber and red against the plan (2026-10-25) */
  .extractStatusTrianglesNear { color: oklch(80% 0.15 78); font-weight: 600; }
  .extractStatusTrianglesReached { color: oklch(66% 0.21 27); font-weight: 600; }
  /* JUST THE COLOUR (2026-10-20):
 the "/ 4M" is not shown on any plan; the count itself still turns amber near the plan's limit and red at it. The element
 stays (app.js still keeps its text and state), so it can come back by deleting this one rule. */
  #viewportStats .triangleLimitReadout { display: none; }
  /* (2026-10-24) the "0.0M / 4M" shown on Free since 2026-10-23 is gone
 again, on every plan: only the count's colour shows the limit (amber near it, red at it, following the whole project's total). */
  /* Light mode (owner 2026-09-26): the counts sit on the viewport grey, not on the pale chrome, so the theme's dark text vanished —
     a light tone for the labels and white numbers, like the navigation gizmo's label. */
  html[data-appearance="light"] #viewportStats { color: #dde3ea; text-shadow: 0 1px 3px rgba(0, 0, 0, 0.55); }
  html[data-appearance="light"]:not([data-theme="rainbow"]) #viewportStats b { color: #ffffff; }

  #appFooter {
    position: fixed; left: 0; right: 0; bottom: 0; height: var(--footer-h); z-index: 60;
    display: flex; align-items: center; gap: var(--space-3); padding: 0 var(--space-4);
    background: var(--surface-chrome);
    -webkit-backdrop-filter: blur(18px) saturate(140%);
    backdrop-filter: blur(18px) saturate(140%);
    border-top: 1px solid var(--hairline-soft);
    font-size: var(--text-2xs); color: var(--text-muted);
  }
  #appFooter #appVersion { color: var(--text-secondary); font-weight: 600; letter-spacing: 0.02em; }
  #appFooter #footerContext { color: var(--accent-strong); font-weight: 500; white-space: nowrap; flex: 0 0 auto; }
  /* ONE LINE (2026-10-12): since the History slider joined the footer, a long tool line (Reflow's) wrapped onto a second line at 1600 pixels.
     The version and the tool line keep their width; the key hints on the right give way and end in an ellipsis when there is no room. */
  #appFooter #appVersion { white-space: nowrap; flex: 0 0 auto; }
  #appFooter #footerControls { flex: 0 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  #appFooter .footerSep { width: 1px; height: 14px; background: var(--hairline); }
  #appFooter .saveStateReadout { white-space: nowrap; flex: 0 0 auto; color: var(--text-muted); }
  #appFooter .saveStateReadout[data-save-state="saved"], #appFooter .saveStateReadout[data-save-state="autosaved"] { color: oklch(76% 0.13 150); }
  #appFooter .saveStateReadout[data-save-state="unsaved"] { color: oklch(80% 0.14 78); }
  #appFooter .saveStateReadout[hidden] { display: none; }
  #appFooter .footerGrow { flex: 1; }
  /* The History slider (v0.8.8). */
  .historySliderGroup { display: inline-flex; align-items: center; gap: var(--space-2); margin-left: var(--space-3); color: var(--text-muted); font-size: var(--text-xs); white-space: nowrap; }
  .historySliderGroup.historySliderGroupEmpty { opacity: 0.45; }
  #appFooter .historySliderLabel { letter-spacing: var(--tracking-tight); }
  #historySlider { width: clamp(280px, 34vw, 620px); margin: 0; }   /* wider than it was in the status bar (120 to 280px): more steps a pixel of travel */
  /* The History pop-out under its button on the top toolbar (2026-10-12): one row, the slider long. */
  .quickPopout.quickPopoutHistory { grid-template-columns: auto; padding: var(--space-2_5) var(--space-3); left: auto; right: 0; }   /* opens leftwards from its button, which sits near the right panel */
  .quickPopoutHistory .historySliderGroup { margin-left: 0; color: var(--text-secondary); }
  /* THE ROW OF STEP BLOCKS (2026-10-20):
 the slider is hidden (kept, the blocks drive the same scrub). One small block a step, coloured
 by kind (a stroke, an operation, a saved state), the viewed one outlined, the later ones dimmed, Now at the end; hover for a card. */
  .quickPopoutHistory .historySliderGroup { display: none; }
  .historyBlocks { position: relative; display: flex; flex-direction: column; gap: var(--space-2); width: clamp(320px, 40vw, 720px); }
  .historyBlocksHeader { display: flex; align-items: baseline; justify-content: space-between; font-size: var(--text-xs); color: var(--text-secondary); }
  .historyBlocksTitle { font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-primary); }
  .historyBlocksCount { font-variant-numeric: tabular-nums; }
  /* Redrawn 2026-10-22: the steps are taller
 rounded bars on a faint track, each kind with its own colour and a soft top light; the viewed one glows; Now is a pill on the right
 with a dot, lit like the viewed step while the studio shows the present; a legend under the strip names the colours.
 Redrawn again 2026-10-23:
 the steps sit edge to edge as square cells of one bar (a hairline between them, the bar's ends
 rounded), the hover lifts the colour instead of the cell, Now is a square cell at the end of the same bar, and the scroll bar is a
 thin themed thumb on a clear track with no arrows. */
  .historyBlocksStrip { display: flex; align-items: stretch; gap: 0; overflow-x: auto; overflow-y: hidden; padding: 0 0 8px; }
  @supports not selector(::-webkit-scrollbar) {   /* Firefox: the standard properties (in Chromium they would switch off the styling below and bring back the arrows) */
    .historyBlocksStrip { scrollbar-width: thin; scrollbar-color: color-mix(in oklab, var(--text-muted) 70%, transparent) transparent; }
  }
  .historyBlocksStrip::-webkit-scrollbar { height: 6px; }
  .historyBlocksStrip::-webkit-scrollbar-track { background: transparent; }
  .historyBlocksStrip::-webkit-scrollbar-thumb { background: color-mix(in oklab, var(--text-muted) 70%, transparent); border-radius: 3px; }
  .historyBlocksStrip::-webkit-scrollbar-thumb:hover { background: var(--text-secondary); }
  .historyBlocksStrip::-webkit-scrollbar-button { display: none; width: 0; height: 0; }
  .historyBlock { position: relative; flex: 0 0 auto; width: 22px; height: 26px; padding: 0; margin: 0; border: 0; border-radius: 0; cursor: pointer;
    background-image: linear-gradient(to bottom, color-mix(in oklab, white 18%, transparent), transparent 55%); background-blend-mode: soft-light;
    transition: opacity var(--dur-fast) var(--ease-standard), filter var(--dur-fast) var(--ease-standard); }
  .historyBlock:first-child { border-top-left-radius: 4px; border-bottom-left-radius: 4px; }
  /* (2026-10-24) the hairline between two steps was a 1px shadow drawn
 inside each cell, and at a browser zoom or display scale that puts the cells between whole pixels some of those lines were lost, so
 two steps read as one. The steps are now kept apart by a real 2px gap, which always shows; the ends of the bar stay rounded. */
  .historyBlock + .historyBlock { margin-left: 2px; }
  .historyBlockNow { margin-left: 2px; }
  .historyBlock:hover:not(:disabled) { filter: brightness(1.22); }
  .historyBlockStroke { background-color: var(--accent-strong); }
  .historyBlockOperation { background-color: oklch(76% 0.13 75); }
  .historyBlockKeyframe { background-color: var(--text-muted); }
  .historyBlockAfterViewed { opacity: 0.28; }
  .historyBlockViewed { z-index: 1; box-shadow: inset 0 0 0 2px var(--text-primary), 0 0 12px color-mix(in oklab, var(--accent-strong) 55%, transparent); }
  .historyBlockNow { width: auto; min-width: 52px; height: 26px; margin-left: 0; padding: 0 10px; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
    border-radius: 0 4px 4px 0; box-shadow: inset 1px 0 0 color-mix(in oklab, var(--accent-strong) 55%, var(--control-border));
    background-color: color-mix(in oklab, var(--accent-strong) 14%, var(--surface-hover)); background-image: none;
    color: var(--text-primary); font: inherit; font-size: var(--text-2xs); font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; }
  .historyBlockNow::before { content: ""; width: 6px; height: 6px; border-radius: 1px; background: var(--accent-strong); box-shadow: 0 0 6px var(--accent-strong); }
  .historyBlockNow.historyBlockViewed { box-shadow: inset 0 0 0 2px var(--accent-strong), 0 0 12px color-mix(in oklab, var(--accent-strong) 55%, transparent); }
  .historyBlocksLegend { display: flex; gap: var(--space-4); font-size: var(--text-2xs); color: var(--text-secondary); letter-spacing: 0.02em; }
  .historyBlocksLegend span { display: inline-flex; align-items: center; gap: 6px; }
  .historyBlocksLegend span::before { content: ""; width: 10px; height: 10px; border-radius: 3px; }
  .historyBlocksLegend .historyLegendStroke::before { background: var(--accent-strong); }
  .historyBlocksLegend .historyLegendOperation::before { background: oklch(76% 0.13 75); }
  .historyBlocksLegend .historyLegendKeyframe::before { background: var(--text-muted); }
  .historyBlock:disabled { cursor: default; }
  .historyBlocksUnusable .historyBlocksStrip { opacity: 0.45; }
  .historyBlocksCard { position: absolute; top: calc(100% + var(--space-2)); display: flex; gap: var(--space-3); padding: var(--space-2_5); min-width: 180px;
    border-radius: var(--radius-sm); background: var(--surface-raised); border: 1px solid var(--control-border); box-shadow: var(--shadow-lg); pointer-events: none; z-index: 5; }
  .historyBlocksCard[hidden] { display: none; }
  .historyBlocksCardPicture { width: 112px; height: 112px; border-radius: var(--radius-sm); background: oklch(40% 0 0); object-fit: cover; flex: 0 0 auto; }
  .historyBlocksCardText { display: flex; flex-direction: column; gap: 2px; font-size: var(--text-xs); color: var(--text-secondary); white-space: nowrap; }
  .historyBlocksCardStep { font-size: var(--text-2xs); color: var(--text-muted); font-variant-numeric: tabular-nums; }
  .historyBlocksCardTitle { font-weight: 600; color: var(--text-primary); }
  .historyBlocksCardDetail { font-variant-numeric: tabular-nums; }
  .quickPopoutHistory .historySliderReadout { min-width: 76px; text-align: right; font-variant-numeric: tabular-nums; }
  #appFooter .historySliderReadout { min-width: 7ch; font-variant-numeric: tabular-nums; color: var(--text-primary); }
  #appFooter #footerControls { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; opacity: 0.85; }
  #bvhBuildingToast {
    position: fixed; top: 50%; left: 50%; transform: translate(-50%,-50%); z-index: 80;
    background: color-mix(in oklab, var(--neutral-850) 90%, transparent);
    border: 1px solid color-mix(in oklab, var(--accent-500) 50%, transparent); border-radius: var(--radius-lg);
    padding: var(--space-3) var(--space-5); font-size: var(--text-sm); font-weight: 500; color: var(--accent-strong); letter-spacing: 0.01em;
    -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px); pointer-events: none;
    box-shadow: var(--shadow-lg), var(--shadow-focus);
  }
  #flashMessageHost { } /* app injects messages; styled inline by app.js */

  /* ---------- blocking "processing" shield: covers the viewport during remesh / merge / subdivide / layer ops
 so the user can't click mid-op. The shield itself is INVISIBLE (owner: the dark full-app dim flashed
 distractingly on fast ops) —.isBlocking only catches input; the small spinner card fades in (.isVisible)
 only when the op runs long enough to be worth announcing. ---------- */
  /* The render progress card (2026-10-29): above the render dialogs; its bar sweeps while the status carries no count. */
  #renderProgressCard { z-index: 400; }   /* by id: the card's own class sets a z-index later in this file */
  .renderProgressCard .reflowProgressFill.renderProgressSweep { width: 35%; animation: renderProgressSweep 1.1s ease-in-out infinite alternate; }
  @keyframes renderProgressSweep { from { margin-left: 0; } to { margin-left: 65%; } }
  #processingOverlay {
    position: fixed; inset: 0; z-index: 220; display: none;
    align-items: center; justify-content: center;
    background: transparent; cursor: wait;   /* no dim, no blur — an input shield, not a backdrop */
  }
  #processingOverlay.isBlocking { display: flex; }
  #processingOverlay .processingCard {
    display: flex; align-items: center; gap: var(--space-3);
    padding: var(--space-3) var(--space-5); border-radius: var(--radius-lg); max-width: 440px;
    background: color-mix(in oklab, var(--neutral-850) 96%, transparent);
    border: 1px solid color-mix(in oklab, var(--accent-500) 45%, transparent);
    box-shadow: var(--shadow-lg), var(--shadow-focus);
    opacity: 0; transition: opacity 0.16s var(--ease-out);
  }
  #processingOverlay.isVisible .processingCard { opacity: 1; }
  #processingOverlay .processingSpinner {
    flex: none; width: 24px; height: 24px; border-radius: 50%;
    border: 3px solid color-mix(in oklab, var(--accent-500) 26%, transparent);
    border-top-color: var(--accent-500); animation: processingSpin 0.75s linear infinite;
  }
  #processingOverlay .processingText { display: flex; flex-direction: column; gap: 2px; }
  #processingOverlay .processingMessage { font-size: var(--text-sm); font-weight: 600; color: var(--accent-strong); letter-spacing: 0.01em; }
  #processingOverlay .processingHint { font-size: 0.72rem; color: var(--accent-strong); opacity: 0.6; }
  #processingOverlay .processingCancelButton { flex: none; margin-left: var(--space-2); cursor: pointer; }   /* Remesh's Cancel (2026-09-27); the shield's wait cursor stops at the button */
  #processingOverlay .processingCancelButton[hidden] { display: none; }
  @keyframes processingSpin { to { transform: rotate(360deg); } }
  @media (prefers-reduced-motion: reduce) { #processingOverlay .processingSpinner { animation-duration: 1.6s; } }

  /* ---------- stroke cursor dot: a DOM dot that follows the RAW pointer while a stroke is being laid down.
       Scene-rendered cursors only move when a GPU frame renders — under heavy sculpting load they stutter and
       smear ("blurry, doesn't move"). This dot updates on every mousemove (compositor transform, no reflow),
       so the stroke cursor is always crisp and responsive no matter what the engine is doing. ---------- */
  /* Instant, non-blocking feedback for layer/remesh/subdivide ops: a thin indeterminate sweep at the very
     top of the window. The blocking shield stays invisible (owner: no big dark overlay) — this bar appears
     IMMEDIATELY with beginProcessing, unlike the delayed processing card. */
  #processingProgressBar {
    position: fixed; top: 0; left: 0; right: 0; height: 3px; z-index: 260;
    display: none; overflow: hidden; background: transparent; pointer-events: none;
  }
  #processingProgressBar.processingActive { display: block; }
  #processingProgressBar::before {
    content: ""; position: absolute; top: 0; bottom: 0; left: 0; width: 30%;
    background: var(--accent-500); border-radius: 2px;
    transform: translateX(-110%); will-change: transform;
    animation: processingSweep 1.1s linear infinite;
  }
  /* transform (compositor-driven) on purpose: layer/remesh ops BLOCK the main thread for seconds, and a
     left/width animation would freeze exactly when the feedback matters — translateX keeps sweeping. */
  @keyframes processingSweep { from { transform: translateX(-110%); } to { transform: translateX(350%); } }

  #strokeCursorDot {
    position: fixed; left: 0; top: 0; z-index: 30;
    width: 10px; height: 10px; margin: -5px 0 0 -5px; border-radius: 50%;
    background: #ff8a30;                            /* matches the scene cursor's warm orange dot */
    box-shadow: 0 0 0 1.5px oklch(20% 0.02 calc(var(--tone-hue) - 6) / 0.55);   /* thin dark halo so it reads on bright clay */
    pointer-events: none; display: none;
  }
  #strokeCursorDot.isSmoothing { background: #35b87a; }   /* matches the scene cursor's smooth green */
  /* Drag trail: while the Drag slider is above 0 mid-stroke, a thin RED line runs from the trailing
     brush point to the raw cursor, making the drag distance visible at a glance. Compositor transform
     only (translate + rotate + width), same cheap update path as the stroke dot. */
  /* Clip family preview: a full-window SVG — the boundary drawn in the trail-line red, the region that will
     be clipped shaded; see updateClipShapeOverlay (app.js). */
  #clipShapeOverlay { position: fixed; left: 0; top: 0; z-index: 28; pointer-events: none; display: none; overflow: visible; }
  #clipShapeShade { fill: oklch(45% 0.12 25 / 0.28); }
  #clipShapeBoundary { fill: none; stroke: #ff4444; stroke-width: 2; opacity: 0.9; }
  /* The Clip brushes mark the side that will be clipped with a dotted line just off the boundary (no shaded region, 2026-09-21). */
  #clipShapeSide, #clipShapeMirrorSide { fill: none; stroke: #ff4444; stroke-width: 2; stroke-linecap: round; stroke-dasharray: 0.5 6; opacity: 0.85; }
  /* Mask shapes draw through the same overlay in the mask's dark tint with a light boundary; an unmask (Ctrl) is lighter and dashed. */
  #clipShapeOverlay.maskShapeGesture #clipShapeShade { fill: oklch(18% 0.01 260 / 0.42); }
  #clipShapeOverlay.maskShapeGesture #clipShapeBoundary { stroke: #f2f2f2; }
  /* Symmetry X: the mirrored copy a Clip or Mask shape also cuts or masks (app.js updateShapeMirrorOverlay), in the same look but dashed. */
  #clipShapeMirrorShade { fill: oklch(45% 0.12 25 / 0.28); }
  #clipShapeMirrorBoundary { fill: none; stroke: #ff4444; stroke-width: 1.5; stroke-dasharray: 6 4; opacity: 0.9; }
  #clipShapeOverlay.maskShapeGesture #clipShapeMirrorShade { fill: oklch(18% 0.01 260 / 0.42); }
  #clipShapeOverlay.maskShapeGesture #clipShapeMirrorBoundary { stroke: #f2f2f2; }
  #clipShapeOverlay.unmaskShapeGesture #clipShapeMirrorShade { fill: oklch(92% 0.01 260 / 0.22); }
  #clipShapeOverlay.unmaskShapeGesture #clipShapeShade { fill: oklch(92% 0.01 260 / 0.22); }
  #clipShapeOverlay.unmaskShapeGesture #clipShapeBoundary { stroke-dasharray: 6 4; }
  /* Blockout (2026-09-26): stroke centrelines while Ctrl is held, the selected strokes bold over a translucent band as wide as they look. */
  #blockoutOverlay { position: fixed; left: 0; top: 0; z-index: 28; pointer-events: none; overflow: visible; }
  #blockoutCentrelines { fill: none; stroke: oklch(90% 0.02 250 / 0.7); stroke-width: 1.2; stroke-linecap: round; stroke-linejoin: round; }
  #blockoutSelectedLines { fill: none; stroke: var(--accent-400); stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; }
  #blockoutSelectedBand { fill: none; stroke: var(--accent-500); stroke-opacity: 0.22; stroke-linecap: round; stroke-linejoin: round; }
  #blockoutStrokePreview { fill: none; stroke: var(--accent-400); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
  #blockoutCursorRing { fill: none; stroke: var(--accent-400); stroke-width: 1.5; stroke-opacity: 0.9; }
  #blockoutCursorDot { fill: var(--accent-400); }
  #blockoutStabilizeTrail { stroke: var(--accent-400); stroke-width: 1.5; stroke-dasharray: 4 3; stroke-opacity: 0.9; }
  #blockoutJoinTargets circle { fill: var(--accent-500); fill-opacity: 0.18; stroke: var(--accent-400); stroke-width: 2; }
  #blockoutBoxSelect { fill: var(--accent-500); fill-opacity: 0.08; stroke: var(--accent-400); stroke-width: 1; stroke-dasharray: 5 4; }
  #blockoutStrokePreviewBand { fill: none; stroke: var(--accent-500); stroke-opacity: 0.28; stroke-linecap: round; stroke-linejoin: round; }
  #blockoutStrokePreview.blockoutCarve { stroke: oklch(70% 0.19 25); }
  #blockoutStrokePreviewBand.blockoutCarve { stroke: oklch(60% 0.2 25); }
  #blockoutHandles circle { fill: var(--surface-base); stroke: var(--accent-400); stroke-width: 2; }
  #blockoutHandles circle.blockoutHandleActive { fill: var(--accent-500); }
  #blockoutHandles circle.blockoutHandleSelected { fill: var(--accent-400); }
  #blockoutHandles circle.blockoutHandleAnchor { stroke: var(--text-primary); stroke-width: 2.5; }
  /* Curve mode (2026-09-27): a curve's handle lines and handle squares; a sharp point's circle is dashed. */
  #blockoutHandles path.blockoutCurveHandleLine { fill: none; stroke: var(--accent-400); stroke-width: 1.2; stroke-opacity: 0.8; }
  #blockoutHandles rect.blockoutCurveHandleKnob { fill: var(--accent-400); stroke: var(--surface-base); stroke-width: 1; }
  #blockoutHandles rect.blockoutCurveHandleKnob.blockoutHandleActive { fill: var(--accent-500); }
  #blockoutHandles circle.blockoutHandleSharp { stroke-dasharray: 3 2; }
  /* The mode row (2026-09-27): four equal buttons; a "Soon" badge on the one that is not selectable yet. */
  .blockoutModeRow { display: flex; flex-wrap: nowrap; gap: var(--space-1); margin: var(--space-2) 0 var(--space-2); }
  .blockoutModeRow .toggleButton { position: relative; flex: 1 1 auto; min-width: 0; padding-left: 3px; padding-right: 3px; font-size: var(--text-2xs); overflow: visible; white-space: nowrap; }
  .blockoutModeRow .toggleButton[aria-disabled="true"] { opacity: 0.55; cursor: default; }
  .blockoutSoonTag { position: absolute; top: -8px; right: 2px; padding: 0 4px; border-radius: var(--radius-pill); font-size: 9px; line-height: 13px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; background: var(--accent-dim); color: var(--accent-strong); border: 1px solid color-mix(in oklab, var(--accent-500) 45%, transparent); pointer-events: none; }
  .blockoutModeHint { margin-bottom: var(--space-2); line-height: 1.45; }
  /* Sketch mode (owner 2026-09-27): the tube-only Stroke rows step aside (Thickness in the Sketch section stands in for Size); the outline
     being drawn is filled lightly, in the carve colour for a Ctrl sketch. */
  #blockoutPanelSection.blockoutSketchModeActive .blockoutStrokeOnly { display: none; }
  #blockoutPanelSection.blockoutStrokesModeActive .blockoutCurveOnly { display: none; }   /* Strands and Twist are for curves (owner 2026-09-28) */
  .blockoutSketchLabel { font-size: var(--text-2xs); font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-muted); margin-top: var(--space-2); }
  .blockoutSketchResultRow { margin-top: var(--space-2); }
  #blockoutSketchFill { fill: var(--accent-500); fill-opacity: 0.14; fill-rule: evenodd; stroke: none; }
  #blockoutSketchFill.blockoutCarve { fill: oklch(60% 0.2 25); }
  .blockoutProfileRow { display: flex; flex-wrap: wrap; gap: var(--space-1_5); margin-top: var(--space-2); }
  .blockoutProfileRow .toggleButton { flex: 1 1 30%; }
  .blockoutEndsRow { margin: var(--space-2) 0; }   /* Ends sat flush against the profile buttons above it (owner 2026-09-26) */
  .blockoutEndsRow .toggleButton { flex: 1 1 auto; }
  /* Blockout panel groups (2026-09-26 redesign): Stroke, Drawing, Mesh — small titles with a hairline above. */
  .blockoutGroupTitle { font-size: var(--text-2xs); font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-muted);
    margin: var(--space-4) 0 var(--space-2); padding-top: var(--space-2_5); border-top: 1px solid var(--control-border); }
  .blockoutActionRow .blockoutFinishButton { flex: 2 1 auto; background: var(--accent-dim); border-color: color-mix(in oklab, var(--accent-500) 55%, transparent); color: var(--accent-strong); }
  .blockoutActionRow .blockoutFinishButton:hover { background: color-mix(in oklab, var(--accent-dim) 80%, var(--accent-600)); }
  .blockoutActionRow + .blockoutReadout { margin-top: var(--space-2); }
  .blockoutSelectionRow:empty, .blockoutSelectionRow:not(:has(.toggleButton:not([style*="display: none"]):not([style*="display:none"]))) { display: none; }
  .blockoutToggleGrid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-1_5); }
  .blockoutToggleGrid .toggleButton { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
  .blockoutToggleGrid > .toggleButton:only-child { grid-column: 1 / -1; }   /* a button alone in its row takes the whole row, not its left half (Closed in Curve mode; 2026-10-14) */
  #blockoutCurveSection > .chipRow { margin-bottom: var(--space-1_5); }   /* Closed sat on the Finish blockout row under it (0 px; test:panel-spacing, 2026-10-14) */
  .blockoutReadout { font-size: var(--text-2xs); color: var(--text-muted); }
  #transformToolHintText { margin-bottom: var(--space-2); line-height: 1.45; }   /* (2026-10-23) the Transform section's hint line, clear of the Axes row under it */
  /* Reflow (Phase 2): the overlay that draws the guides and the painted density, and the Reflow section's own rows. */
  #reflowOverlay { position: fixed; left: 0; top: 0; z-index: 28; pointer-events: none; }
  #decimateWireOverlay { position: fixed; left: 0; top: 0; z-index: 27; pointer-events: none; }   /* the Decimate preview's triangles (2026-10-12) */
  #creaseOverlay { position: fixed; left: 0; top: 0; z-index: 27; pointer-events: none; }   /* the crease edges of the active level (2026-10-10) */
  /* RE:SYNC (2026-10-12, resync-studio.js): the others' cursors over the viewport, the chip in the status bar, and the window's own
     rows. Nothing in the window touches: every row is a flex row with a gap, and the sections stack with a gap of their own. */
  #resyncCursorOverlay { position: fixed; left: 0; top: 0; z-index: 28; pointer-events: none; }
  #appFooter #resyncSessionChip {
    cursor: pointer; font: inherit; font-size: var(--text-2xs); font-weight: 600; line-height: 1; white-space: nowrap; flex: 0 0 auto;
    padding: var(--space-1) var(--space-2_5); border-radius: 999px; color: var(--accent-strong);
    background: var(--accent-dim); border: 1px solid color-mix(in oklab, var(--accent-500) 55%, transparent);
  }
  #appFooter #resyncSessionChip[hidden] { display: none; }
  #appFooter #resyncSessionChip:hover { border-color: var(--accent-500); }
  #appFooter #resyncSessionChip:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 2px; }
  .appModalDialog .extractDialogPanel.resyncPanel { width: 380px; }
  .resyncPanel .resyncSection { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
  .resyncPanel .resyncFieldLabel { font-size: var(--text-xs); font-weight: 600; color: var(--text-secondary); margin-top: var(--space-2); }
  .resyncPanel .textEntryInput { margin-top: 0; box-sizing: border-box; }
  .resyncPanel .resyncFieldRow { display: flex; align-items: stretch; gap: var(--space-2); }
  .resyncPanel .resyncRosterHeaderRow { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); }   /* People, and the host's Everyone: Watch only (2026-10-30) */
  .resyncPanel .resyncFieldRow .textEntryInput { flex: 1 1 auto; min-width: 0; }
  .resyncPanel .resyncFieldRow .confirmDialogButton { flex: 0 0 auto; }
  .resyncPanel .resyncRoomCodeField { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; letter-spacing: 0.12em; }
  .resyncPanel .resyncWideButton { align-self: stretch; margin-top: var(--space-1); }
  .resyncPanel .resyncStatusLine { color: var(--text-secondary); }
  .resyncPanel .resyncRoster { display: flex; flex-direction: column; gap: var(--space-2); }
  .resyncPanel .resyncRosterRow {
    display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2); padding: var(--space-2) var(--space-2_5);
    background: var(--surface-inset); border: 1px solid var(--hairline-soft); border-radius: var(--radius-lg);
  }
  .resyncPanel .resyncSeatDot { flex: 0 0 auto; width: 10px; height: 10px; border-radius: 50%; }
  .resyncPanel .resyncRosterWho { flex: 1 1 120px; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
  .resyncPanel .resyncRosterName { font-size: var(--text-sm); font-weight: 600; color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .resyncPanel .resyncRosterDetail { font-size: var(--text-2xs); color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .resyncPanel .resyncRosterControls { flex: 0 0 auto; display: flex; align-items: center; gap: var(--space-1_5); }
  .appModalDialog .resyncPanel .resyncSmallButton { font-size: var(--text-2xs); padding: var(--space-1) var(--space-2); }
  .appModalDialog .resyncPanel .resyncSmallButtonOn { background: var(--accent-dim); border-color: color-mix(in oklab, var(--accent-500) 55%, transparent); color: var(--accent-strong); }
  .resyncPanel .resyncChatHistory {
    height: 132px; overflow-y: auto; padding: var(--space-2) var(--space-2_5); display: flex; flex-direction: column; gap: var(--space-1);
    background: var(--surface-inset); border: 1px solid var(--hairline-soft); border-radius: var(--radius-lg);
    font-size: var(--text-xs); line-height: 1.45; color: var(--text-primary); scrollbar-width: thin; scrollbar-color: var(--control-border) transparent;
  }
  .resyncPanel .resyncChatLine { overflow-wrap: anywhere; }
  /* stage B (2026-10-12): who is speaking (a ring round their dot), a person's "more" menu, the sign-in line, History at rest in a session */
  .resyncPanel .resyncSeatDot { transition: box-shadow var(--dur-fast) var(--ease-standard); margin: 0 var(--space-1); }   /* (room for the ring) */
  .resyncPanel .resyncSeatDot.resyncSeatDotSpeaking { box-shadow: 0 0 0 3px var(--surface-inset), 0 0 0 5px currentColor; color: var(--accent-strong); }
  .resyncPanel .resyncRowMenu { flex: 1 1 100%; display: flex; justify-content: flex-end; gap: var(--space-1_5); padding-top: var(--space-1); border-top: 1px solid var(--hairline-soft); }
  .resyncPanel .resyncRosterControls { flex-wrap: wrap; justify-content: flex-end; }
  .resyncPanel .resyncSignInNotice { color: var(--text-primary); padding: var(--space-2) var(--space-2_5); background: var(--surface-inset); border: 1px solid var(--hairline-soft); border-radius: var(--radius-lg); }
  .quickIcon.quickIconAtRestForResync { opacity: 0.4; }
  .resyncPanel .resyncChatSender { font-weight: 600; }
  .resyncPanel .resyncChatEmpty { color: var(--text-muted); }
  .reflowIntroduction { margin-bottom: var(--space-2_5); line-height: 1.45; }
  .reflowTargetButtonRow { margin: var(--space-1_5) 0 var(--space-2); }
  .reflowTargetButtonRow .toggleButton { flex: 1 1 0; }
  .reflowToggleGrid { margin: var(--space-2) 0; grid-template-columns: 1fr; }   /* one switch a row: two a row cut every label short ("Keep Mesh Set…") */
  .reflowActionRow { margin-top: var(--space-2); }
  .reflowRunRow { margin-top: var(--space-4); }
  .reflowRunRow .blockoutFinishButton { flex: 1 1 auto; }
  #reflowPreviewToggleButton { display: block; width: 100%; margin-top: var(--space-2); }
  .reflowProgressRow { margin-top: var(--space-2); display: flex; flex-direction: column; gap: var(--space-1); }   /* the preview's progress (2026-10-10) */
  /* the same progress as a card over the viewport (2026-10-12); it takes no clicks */
  .reflowPreviewCard {
    position: fixed; z-index: 120; top: calc((100vh + var(--header-h) - var(--footer-h)) / 2); left: calc((100vw - var(--panel-w) + 60px) / 2); transform: translate(-50%, -50%);   /* the middle of the viewport, between the tool rail and the right panel */
    display: flex; align-items: center; gap: var(--space-3); min-width: 300px; max-width: 440px; pointer-events: none;
    padding: var(--space-3) var(--space-5); border-radius: var(--radius-lg);
    background: color-mix(in oklab, var(--neutral-850) 96%, transparent);
    border: 1px solid color-mix(in oklab, var(--accent-500) 45%, transparent);
    box-shadow: var(--shadow-lg);
  }
  .reflowPreviewCardSpinner { flex: none; width: 24px; height: 24px; border-radius: 50%; border: 3px solid color-mix(in oklab, var(--accent-500) 26%, transparent); border-top-color: var(--accent-500); animation: processingSpin 0.75s linear infinite; }
  .reflowPreviewCardText { flex: 1 1 auto; display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
  .reflowPreviewCardTitle { font-size: var(--text-sm); font-weight: 600; color: var(--accent-strong); letter-spacing: 0.01em; }
  .reflowPreviewCardStage { font-size: 0.72rem; color: var(--accent-strong); opacity: 0.75; }
  .reflowPreviewCard .reflowProgressTrack { margin-top: var(--space-1); }
  @media (prefers-reduced-motion: reduce) { .reflowPreviewCardSpinner { animation-duration: 1.6s; } }
  .reflowProgressTrack { height: 4px; border-radius: var(--radius-pill); background: var(--surface-inset); overflow: hidden; border: 1px solid var(--hairline-soft); }
  .reflowProgressFill { height: 100%; width: 8%; border-radius: var(--radius-pill); background: var(--accent); transition: width var(--dur-fast) var(--ease-standard); animation: reflowProgressPulse 1.2s ease-in-out infinite; }
  @keyframes reflowProgressPulse { 0%, 100% { opacity: 0.55; } 50% { opacity: 1; } }   /* the switch fills its row like the others (2026-10-10) */
  .reflowReport { margin-top: var(--space-2); line-height: 1.5; white-space: pre-line; }
  .reflowReport.reflowReportShown { color: var(--text-secondary); }
  .reflowWarning { margin-top: var(--space-1); line-height: 1.5; color: var(--warning, oklch(80% 0.15 80)); }
  .reflowShortHint { margin-bottom: var(--space-1_5); }
  #dragTrailLine, #dragInertiaLine {
    position: fixed; left: 0; top: 0; z-index: 29;
    height: 2px; width: 0; transform-origin: 0 50%;
    background: #ff4444; border-radius: 1px; opacity: 0.85;
    pointer-events: none; display: none;
  }
  #dragInertiaLine { background: #ffb340; }   /* the Drag slider's lag, told apart from the red stabilizer string (owner 2026-09-11) */
  /* Deferred stroke-type preview (Line / Dot / Drag): a footprint ring showing where the stamp will land,
     updated live during the press-drag (compositor transform + size, same cheap path as the stroke dot). */
  #strokeGesturePreview {
    position: fixed; left: 0; top: 0; z-index: 29; box-sizing: border-box;
    width: 0; height: 0; border-radius: 50%;
    border: 2px solid #ff8a30; box-shadow: 0 0 0 1px oklch(20% 0.02 calc(var(--tone-hue) - 6) / 0.5);
    pointer-events: none; display: none;
  }

  /* ---------- rich hover tooltips (data-tooltip): one shared bubble, deliberately WIDER than tall —
       width up to 380px so explanations run 1–2 lines instead of a narrow wrapped column ---------- */
  #appTooltip {
    position: fixed; z-index: 1000; display: none;   /* above every panel, window and card (owner 2026-10-29: a tooltip seen sticking out from behind the right panel) */
    width: max-content; max-width: min(380px, calc(100vw - 16px));
    background: color-mix(in oklab, var(--neutral-850) 96%, transparent);
    border: 1px solid color-mix(in oklab, var(--accent-500) 38%, transparent);
    border-radius: var(--radius-md);
    padding: 7px 12px;
    font-size: 0.78rem; font-weight: 450; line-height: 1.5; color: var(--text-primary);
    letter-spacing: 0.005em; text-align: left;
    box-shadow: var(--shadow-lg);
    pointer-events: none;
    -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  }
  /* THE LEFT RAIL'S TOOLTIP (owner 2026-10-25): the same bubble beside the rail, as the tool's name and its key on one row and a short
     line on what it does below (the bubble script in index.html fills it). Only theme variables, so every theme and both appearances
     follow. It takes the place of the rail's old hover label (the ::after below), which showed the name and key alone. */
  #appTooltip.appTooltipRailTool { max-width: 300px; padding: 8px 12px 9px; }
  #appTooltip .appTooltipRailHeading { display: flex; align-items: center; gap: 10px; }
  #appTooltip .appTooltipRailName { font-weight: 600; color: var(--text-primary); }
  #appTooltip .appTooltipRailShortcut {
    margin-left: auto; min-width: 20px; padding: 0 6px; box-sizing: border-box; text-align: center;
    font-family: inherit; font-size: 0.72rem; font-weight: 600; line-height: 1.6; color: var(--text-secondary, var(--text-primary));
    background: var(--surface-raised); border: 1px solid var(--control-border); border-radius: var(--radius-sm);
  }
  #appTooltip .appTooltipRailDescription { margin-top: 3px; color: var(--text-muted); }
  .railTool[data-rail-name]::after { display: none; }

  /* ---------- startup loading screen (solid backdrop, the logo pulsing; fades once the mesh is on screen) ---------- */
  #loadingScreen {
    position: fixed; inset: 0; z-index: 200;
    display: flex; align-items: center; justify-content: center;
    background: var(--neutral-950);   /* solid, deepest neutral — no gradient */
    opacity: 1; transition: opacity 0.55s var(--ease-out);
  }
  #loadingScreen.loaded { opacity: 0; pointer-events: none; }
  /* THE LOADING BAR (owner 2026-10-26): a thin line near the bottom of the screen that fills as the graphics engine makes its
 pipelines (index.html's dismissLoadingScreenWhenReady sets the fill's scale); the screen clears once it is full. White, at the
 bottom (2026-10-26; it first sat under the mark, over the word),
 thicker ("make it a bit thicker too"), with a small "Loading" above it ("say Loading above the bar small"). */
  #loadingScreen .loadingProgressTrack {
    position: absolute; left: 50%; bottom: 64px; width: 300px; height: 8px; margin-left: -150px;
    border-radius: 4px; overflow: hidden; background: rgba(255, 255, 255, 0.14);
  }
  #loadingScreen .loadingProgressLabel {
    position: absolute; left: 0; right: 0; bottom: 84px; text-align: center;
    font-family: var(--font-sans); font-size: 12px; letter-spacing: 0.08em; color: rgba(255, 255, 255, 0.72);
  }
  /* three dots after the word, each fading in and out a third of a beat after the one before (2026-10-26);
 they are their own spans, so the word itself is still translated */
  #loadingScreen .loadingDots span { animation: loadingDotPulse 1.2s ease-in-out infinite; }
  #loadingScreen .loadingDots span:nth-child(2) { animation-delay: 0.2s; }
  #loadingScreen .loadingDots span:nth-child(3) { animation-delay: 0.4s; }
  @keyframes loadingDotPulse { 0%, 100% { opacity: 0.15; } 40% { opacity: 1; } }
  @media (prefers-reduced-motion: reduce) { #loadingScreen .loadingDots span { animation: none; } }
  #loadingScreen .loadingProgressFill {
    display: block; width: 100%; height: 100%; border-radius: 4px; background: #ffffff;
    transform: scaleX(0); transform-origin: left center; transition: transform 0.2s var(--ease-out);
  }
  /* The logo PULSES FROM THE FIRST FRAME (2026-09-23).
 Earlier designs held the scale pulse back until the engine was ready (a faint opacity
 breath while loading, then a handover), which reads as a frozen logo. Now the wrapper runs the pulse from load, on its own
 compositor layer, and the only thing the script decides is WHEN the screen clears. */
  #loadingScreen .loadingLogoWrapper {
    display: block;
    will-change: transform;   /* own GPU layer so the scale composites smoothly (no re-raster jitter) */
    animation: logoPulse 1.4s cubic-bezier(0.37, 0, 0.63, 1) infinite;   /* WITHOUT A PAUSE until the screen clears (2026-10-12); it was three pulses and a rest before */   /* THREE 1.4 s pulses from the first frame, then the logo rests (2026-09-23); the screen still clears the moment the engine is ready, whatever the pulse is doing */
  }
  /* SHARP WHILE IT PULSES (2026-10-12). The wrapper is its own layer, drawn once and
 then scaled; scaling that picture UP past the size it was drawn at is what went soft. The logo is now laid out at the pulse's
 largest size and the pulse scales it DOWN to the resting size and back, so the picture is never stretched: the same two sizes
 on screen as before (480px at rest, 7.5% larger at the peak).
 STILL SOFT at that: a picture drawn at about the size it is shown at
 and then scaled a few percent is resampled pixel against pixel. The logo is now laid out at TWICE the pulse's largest size
 and shown at half, so every pixel on screen is made from four of the drawn picture, at rest and at the peak alike. */
  #loadingScreen { overflow: hidden; }   /* (the logo's box is twice what is seen; nothing of it may open a scrollbar) */
  #loadingScreen .loadingLogoWrapper { flex: 0 0 auto; }
  #loadingScreen .loadingLogo { width: min(1032px, 154.8vw); max-width: none; height: auto; display: block; }
  /* ONE GRAPHIC, THE SIZE PULSE AS IT WAS (2026-10-20): the rabbit and
 the wordmark are one image (reform-loading-mark.svg, still inside), and the wrapper's own logoPulse above scales it on the
 compositor, laid out at twice the pulse's largest size and shown at half, exactly as the wordmark alone was. The image is as wide
 as the wordmark was (the wordmark spans the graphic's full width), so the sizes on screen are the old ones. */
  #loadingScreen .loadingLogo.loadingMark { width: min(1032px, 154.8vw); }
  /* (The faint rabbit grid behind the mark, 2026-10-20, is gone: 2026-10-24.) */
  #loadingScreen .loadingLogoWrapper { position: relative; }
  @keyframes logoPulse {   /* rests at the same size at both ends */
    0%, 100% { transform: scale(0.4651); }
    50%      { transform: scale(0.5); }
  }

  /* ---------- start screen (revealed as the loading screen fades; the user picks how to begin) ----------
     Sits ABOVE the app chrome (z-index 150) but BELOW the loading screen (z-index 200), so it is fully
     occluded by the opaque loading screen until that fades — then it simply shows through (a seamless
     crossfade, both backdrops being the same deepest neutral). app.js wires its buttons and dismisses it. */
  #startScreen {
    position: fixed; inset: 0; z-index: 150;
    display: flex; align-items: center; justify-content: center; padding: var(--space-6);
    overflow: hidden;   /* the start screen never scrolls (owner 2026-10-29): the tiles are sized from the window's height below (--start-tile-size) */
    /* A light grid like drafting paper over the glow (owner 2026-10-12): fine lines every 48px, a slightly stronger one every fifth (twice the first 24px). */
    --start-grid-fine-line: oklch(100% 0 0 / 0.028);
    --start-grid-strong-line: oklch(100% 0 0 / 0.05);
    background:
      linear-gradient(var(--start-grid-strong-line) 1px, transparent 1px) 50% 0 / 240px 240px,
      linear-gradient(90deg, var(--start-grid-strong-line) 1px, transparent 1px) 50% 0 / 240px 240px,
      linear-gradient(var(--start-grid-fine-line) 1px, transparent 1px) 50% 0 / 48px 48px,
      linear-gradient(90deg, var(--start-grid-fine-line) 1px, transparent 1px) 50% 0 / 48px 48px,
      radial-gradient(135% 108% at 50% -12%,
        oklch(34% 0.085 calc(var(--tone-hue) - 6)) 0%,
        oklch(23% 0.050 calc(var(--tone-hue) - 4)) 40%,
        oklch(16% 0.028 calc(var(--tone-hue) - 1)) 74%,
        oklch(11% 0.015 calc(var(--tone-hue) + 2)) 100%);
    opacity: 1; transition: opacity 0.4s var(--ease-out);
  }
  #startScreen.dismissed { opacity: 0; pointer-events: none; }
  /* THE START SCREEN'S BAR (2026-10-30): the rest of the graphics engine's pipelines, made behind the start screen once the loading
     screen has gone (index.html's fillStartScreenBarUntilPrepared). A slim line at the foot of the screen, out of the layout, the
     loading bar's look; it shows only while there is something left to make and fades out when everything is made. */
  #startScreen .startPreparingBar {
    position: fixed; left: 50%; bottom: 22px; width: 160px; height: 3px; margin-left: -80px;
    border-radius: 2px; overflow: hidden; background: rgba(255, 255, 255, 0.12);
    opacity: 0; transition: opacity 0.4s var(--ease-out); pointer-events: none;
  }
  #startScreen .startPreparingBar.preparing { opacity: 1; }
  #startScreen .startPreparingBar.finished { opacity: 0; transition-delay: 0.3s; }
  #startScreen .startPreparingBarFill {
    display: block; width: 100%; height: 100%; border-radius: 2px; background: rgba(255, 255, 255, 0.78);
    transform: scaleX(0); transform-origin: left center; transition: transform 0.2s var(--ease-out);
  }
  html[data-appearance="light"] #startScreen .startPreparingBar { background: rgba(0, 0, 0, 0.1); }
  html[data-appearance="light"] #startScreen .startPreparingBarFill { background: rgba(0, 0, 0, 0.5); }
  /* LIGHT appearance (2026-09-26):
 the start and loading screens are the theme's pale chrome, and the logo (white art)
 is shown in a dark tone there and in the header. Dark appearance is unchanged. */
  html[data-appearance="light"] #startScreen {
    --start-grid-fine-line: oklch(0% 0 0 / 0.035);
    --start-grid-strong-line: oklch(0% 0 0 / 0.06);
    background:
      linear-gradient(var(--start-grid-strong-line) 1px, transparent 1px) 50% 0 / 240px 240px,
      linear-gradient(90deg, var(--start-grid-strong-line) 1px, transparent 1px) 50% 0 / 240px 240px,
      linear-gradient(var(--start-grid-fine-line) 1px, transparent 1px) 50% 0 / 48px 48px,
      linear-gradient(90deg, var(--start-grid-fine-line) 1px, transparent 1px) 50% 0 / 48px 48px,
      radial-gradient(135% 108% at 50% -12%,
        oklch(93% 0.010 var(--tone-hue)) 0%,
        oklch(90% 0.012 var(--tone-hue)) 40%,
        oklch(87% 0.014 var(--tone-hue)) 74%,
        oklch(84% 0.016 var(--tone-hue)) 100%);   /* dimmer than the studio's light neutrals (2026-10-29; it was 99% to 92%) */
  }
  /* The light splash: the same dimmed tone, and the Loading word, its track and its fill dark, as the mark is (2026-10-29;
 they were white on a near-white screen). */
  html[data-appearance="light"] #loadingScreen { background: oklch(89% 0.012 var(--tone-hue)); }
  html[data-appearance="light"] #loadingScreen .loadingProgressTrack { background: rgba(0, 0, 0, 0.12); }
  html[data-appearance="light"] #loadingScreen .loadingProgressLabel { color: rgba(0, 0, 0, 0.62); }
  html[data-appearance="light"] #loadingScreen .loadingProgressFill { background: #3d3d3d; }
  /* The drafting-paper lines are made transparent (their rules stay above); the plain grid below stands in for them, behind the start
 screen's content (the screen isolates its own stacking, the layer is at -1), steady while the content scrolls. (2026-10-20 to
 2026-10-24 a faint rabbit grid did; 2026-10-24.) */
  #startScreen, html[data-appearance="light"] #startScreen { --start-grid-fine-line: transparent; --start-grid-strong-line: transparent; isolation: isolate; }
  /* A PLAIN GRID (2026-10-21; before that a rabbit sat at every crossing):
 reform-start-grid-tile.svg, faint unbroken lines 120px apart, centred on the screen and faded out towards its edges so it frames
 the cards. Still, behind the content, steady while the content scrolls. */
  #startScreen::before {
    content: ""; position: fixed; left: 0; top: 0; right: 0; bottom: 0; z-index: -1; pointer-events: none;
    background: url("../reform-start-grid-tile.svg") 50% 50% / 120px 120px repeat;
    /* (2026-10-20) hidden over the boxes in the middle, fading in towards the sides */
    -webkit-mask-image: radial-gradient(ellipse 52% 76% at 50% 54%, transparent 56%, black 100%);
    mask-image: radial-gradient(ellipse 52% 76% at 50% 54%, transparent 56%, black 100%);
  }
  html[data-appearance="light"] #startScreen::before { filter: brightness(0); }
  html[data-appearance="light"] #startScreen .startLogo,
  html[data-appearance="light"] #loadingScreen .loadingLogo,
  html[data-appearance="light"] .brandLogo { filter: brightness(0.24); }
  #startScreen .startCard {
    display: flex; flex-direction: column; align-items: center; gap: var(--space-6);
    width: min(500px, 94vw); text-align: center;
    /* (2026-10-25, the Autosaves section flush with the window's bottom) a scrolling
 box drops its own bottom padding in Firefox, so the gap under the last section lives inside the content instead. */
    padding-bottom: var(--space-6);
  }
  #startScreen .startLogo {
    width: min(340px, 64vw); height: auto; display: block;
    filter: drop-shadow(0 6px 28px oklch(10% 0.02 var(--tone-hue) / 0.6));
  }
  #startScreen .startTagline {
    font-size: var(--text-sm); color: var(--text-muted); letter-spacing: 0.01em;
    margin-top: calc(-1 * var(--space-2));
  }
  #startScreen .startFooter {
    position: absolute; bottom: var(--space-5); left: var(--space-5);
    text-align: left; font-size: var(--text-2xs); color: var(--text-muted);
    letter-spacing: 0.04em; pointer-events: none;
    display: flex; flex-direction: column; align-items: center; gap: var(--space-2);   /* the rabbit centred over the version (2026-10-29) */
  }
  #startScreen .startFooterRabbit { width: 28px; height: auto; opacity: 0.55; user-select: none; }   /* the rabbit, small, above the version (owner 2026-10-29) */
  html[data-appearance="light"] #startScreen .startFooterRabbit { filter: brightness(0); opacity: 0.5; }
  /* ---------- lightbox composition (auth-ui adds .lightboxRevealed when the project browser
     shows) ----------
     The signed-in start screen re-composes as a masthead page: logo + tagline up top (smaller,
     identity not hero), then the three sections in a column that hugs its content width. The
     signed-out gate keeps the classic centered composition (no .lightboxRevealed → untouched). */
  /* stretch: the card fills the viewport height. Two auto margins (above the logo, below the
     sections) centre the whole logo+sections block, and the gap from the tagline down to the first
     section matches the inter-section gap — one even rhythm from the logo to the autosave row
     (owner 2026-07-31: the old thirds split dumped all the free space between the logo and the
     first row while the rows crowded together underneath). */
  #startScreen.lightboxRevealed { align-items: stretch; }
  /* The gap under the last section (2026-10-25): stretched, the card was exactly the
 window's height and the sections overflowed it, so its bottom padding never came after them. It now starts at the top and is at least
 the window's height, growing with its sections, so the padding (startCard) lands under the last one. */
  #startScreen.lightboxRevealed .startCard { align-self: flex-start; min-height: 100%; box-sizing: border-box; }
  #startScreen.lightboxRevealed .startLogo { width: min(380px, 64vw); margin-top: auto; }
  #startScreen.lightboxRevealed .startActions { margin-top: clamp(8px, 2vh, 20px); margin-bottom: auto; }
  /* Center each section to the view: headers center, and the tile rows center via auto margins on
     the first/last tile — auto margins collapse to zero once a row overflows into a horizontal
     scroll, so long autosave rows stay start-anchored and fully reachable (works everywhere,
     unlike `justify-content: safe center`). */
  /* THREE EQUAL SHEETS, EACH ONE ROW THAT SCROLLS SIDEWAYS (2026-10-12).
 New sculpt, Open
 and Autosaves are each a sheet of the same width and the same height: a header (the title at the left edge, its hint beside it,
 a hairline out to the right edge where Clear all sits) over ONE row of big square tiles. A row with more tiles than fit scrolls
 sideways by itself; a row with none shows its note in the same space. The tile's size follows the window's height as well as
 its width, so the three sheets fit one screen. The sheets are a faint darkening with a soft edge, so the drafting grid behind
 reads through them. */
  /* The tile size from the window's HEIGHT (2026-10-29): the logo, the
 tagline, the three sheets' headers and paddings, the gaps and the autosave tiles' two label lines take about 420px between them, and
 the rest is three rows of tiles. 1080 tall: 220px tiles; 768: 116px; the floor keeps a tile readable on a very short window. */
  #startScreen .startActions { --start-tile-size: clamp(88px, min(15vw, calc((100vh - 420px) / 3)), 256px); --start-sheet-padding: var(--space-4); --start-row-height: calc(var(--start-tile-size) + 30px); }   /* (2026-10-20: 12px more room under the tiles, 18px before) */   /* +4px above the tiles: room for the 2px hover lift, which the row's overflow clipped (2026-10-14) */
  #startScreen .lightboxSection {
    --start-sheet-edge: oklch(55% 0.07 var(--tone-hue) / 0.16); --start-sheet-surface: oklch(13% 0.04 calc(var(--tone-hue) - 4) / 0.38);
    box-sizing: border-box; align-self: center;
    width: min(100%, calc(5 * var(--start-tile-size) + 4 * var(--space-3) + 2 * var(--start-sheet-padding) + 2px));   /* exactly FIVE tiles across, a little bigger (2026-10-12) */
    padding: var(--space-4) var(--start-sheet-padding) var(--space-2) var(--start-sheet-padding);
    gap: var(--space-4);
    background: var(--start-sheet-surface); border: 1px solid var(--start-sheet-edge); border-radius: calc(var(--radius-project-box) * 2);
    box-shadow: 0 12px 36px oklch(0% 0 0 / 0.38), 0 2px 8px oklch(0% 0 0 / 0.22);   /* (2026-10-20) */
  }
  html[data-appearance="light"] #startScreen .lightboxSection { box-shadow: 0 12px 32px oklch(30% 0.03 var(--tone-hue) / 0.14), 0 2px 6px oklch(30% 0.03 var(--tone-hue) / 0.08); }
  html[data-appearance="light"] #startScreen .lightboxSection { --start-sheet-edge: oklch(40% 0.04 var(--tone-hue) / 0.16); --start-sheet-surface: oklch(100% 0 0 / 0.3); }
  #startScreen .lightboxSectionHeader { justify-content: flex-start; align-items: center; min-height: 22px; }
  /* (the rule that ran from the hint to the sheet's edge went on 2026-10-29) */
  #startScreen .lightboxSectionHeader .autosaveClearAllButton { order: 2; margin-left: auto; }
  #whatsNewCard .lightboxSectionHeader #whatsNewCardCloseButton { margin-left: auto; }
  #startScreen .lightboxTileRow {
    --lightbox-tile-size: var(--start-tile-size);
    display: flex; gap: var(--space-3); justify-content: flex-start;
    width: 100%; max-width: none; margin-inline: 0; padding: 4px 0 0;
    height: var(--start-row-height);   /* the tile and room under it for the scroll bar: the same in every sheet, with or without one */
    overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain;
    scrollbar-width: thin; scrollbar-color: oklch(62% 0.06 var(--tone-hue) / 0.45) transparent;
  }
  #startScreen .lightboxTileRow::-webkit-scrollbar { height: 8px; }
  #startScreen .lightboxTileRow::-webkit-scrollbar-thumb { background: oklch(62% 0.06 var(--tone-hue) / 0.45); border-radius: 999px; }
  /* THE BOXES BLEND INTO THE PAGE (2026-10-12): the sheets are a faint darkening
 with a soft edge, and the tiles on them are the page's own blue, a little lighter and see-through, instead of flat grey slabs. */
  #startScreen .lightboxTile:not(.lightboxTileAutosave):not(.lightboxTilePrimary) { background: oklch(30% 0.045 var(--tone-hue) / 0.34); border-color: oklch(62% 0.06 var(--tone-hue) / 0.16); }
  #startScreen .lightboxTile:not(.lightboxTileAutosave):not(.lightboxTilePrimary):hover { background: oklch(36% 0.06 var(--tone-hue) / 0.5); border-color: oklch(70% 0.08 var(--tone-hue) / 0.4); }
  html[data-appearance="light"] #startScreen .lightboxTile:not(.lightboxTileAutosave):not(.lightboxTilePrimary) { background: oklch(100% 0 0 / 0.45); border-color: oklch(40% 0.04 var(--tone-hue) / 0.2); }
  html[data-appearance="light"] #startScreen .lightboxTile:not(.lightboxTileAutosave):not(.lightboxTilePrimary):hover { background: oklch(100% 0 0 / 0.75); border-color: oklch(40% 0.06 var(--tone-hue) / 0.4); }
  #startScreen .lightboxEmptyNote { padding: 0; display: flex; align-items: center; justify-content: center; text-align: center; height: var(--start-row-height); }   /* an empty sheet is as tall as a full one */

  /* The start screen's lightbox breaks out of the 500px card the same way the tier picker does
     (the card is a centered flex column, so a wider child simply centers over it). The column is
     sized to exactly six tiles (6·140px + 5·12px gaps + the row's 4px padding on each side — the four primitives
     plus the Head and Ear starter meshes, 2026-09-14), so the left-aligned sections read as a deliberate rail rather
     than floating in a wider box. */
  #startScreen .startActions {
    display: flex; flex-direction: column; gap: clamp(12px, 1.8vh, 22px);
    width: min(1800px, 94vw); align-self: center; align-items: stretch; margin-top: var(--space-5);   /* eight New sculpt tiles at 212px (8·212 + 7·12 gaps + the row's 8px padding = 1788px): the Blockout tile joined and the tiles grew, twice (2026-10-10) */
  }
  #startScreen .lightboxTile { flex: 0 0 var(--start-tile-size); width: var(--start-tile-size); height: var(--start-tile-size); min-width: 0; }   /* big square tiles, one row a sheet (2026-10-12) */
  #startScreen .lightboxTileArt { width: 51%; height: auto; aspect-ratio: 1; }
  #startScreen .lightboxTileName { font-size: var(--text-sm); }
  #startScreen .lightboxTileAutosave .autosaveTileName, #startScreen .lightboxTileAutosave .autosaveTileAge { font-size: var(--text-xs); }   /* (the labels grow with the tile) */

  /* ---------- lightbox (shared: start screen + in-app Projects window) ----------
     A sculptor-familiar project browser: titled sections of square tiles in horizontally
     scrollable rows — primitives to start from, and dated autosave snapshots. */
  .lightboxSection { display: flex; flex-direction: column; gap: var(--space-3); text-align: left; min-width: 0; }
  .lightboxSectionHeader { display: flex; align-items: baseline; gap: var(--space-3); }
  /* Section titles read as monospace instrument labels — the same "control surface" signature
     as the auth divider and the footer status bar. */
  .lightboxSectionTitle {
    font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: 700;
    color: var(--text-primary); letter-spacing: 0.22em; text-transform: uppercase;
  }
  .lightboxSectionHint { font-size: var(--text-xs); color: var(--text-muted); }
  /* The saved-project count reads as a warning once the plan's allowance is full (2026-09-23). */
  .lightboxSectionHintAtLimit { color: var(--text-warning, #e0a33e); }
  /* Clear all autosaves (2026-09-14) — sits at the end of the header. */
  .autosaveClearAllButton {
    font: inherit; font-size: var(--text-2xs); color: var(--text-secondary); cursor: pointer; align-self: center;
    background: var(--surface-raised); border: 1px solid var(--control-border); border-radius: var(--radius-sm); padding: 2px var(--space-2);
  }
  .autosaveClearAllButton:hover { color: var(--text-primary); border-color: var(--danger-strong, oklch(50% 0.16 25)); }
  /* A GRID, FIVE TILES ACROSS AT MOST (2026-10-12). The rows used to
 be one line that scrolled sideways; with eleven shapes and a long list of autosaves most of it was off the edge. Now a row wraps:
 auto-fit makes as many columns as fit, the width stops it at five, and a row with fewer tiles than columns sits in the middle. */
  .lightboxTileRow {
    --lightbox-tile-size: 140px;
    display: grid; grid-template-columns: repeat(auto-fit, var(--lightbox-tile-size)); justify-content: center; gap: var(--space-3);
    width: 100%; max-width: calc(5 * var(--lightbox-tile-size) + 4 * var(--space-3) + 2 * var(--space-1)); margin-inline: auto;
    padding: var(--space-1) var(--space-1) var(--space-2_5) var(--space-1);
  }
  .lightboxTileRow::-webkit-scrollbar { height: 8px; }
  .lightboxTileRow::-webkit-scrollbar-track { background: transparent; }
  .lightboxTileRow::-webkit-scrollbar-thumb { background: var(--control-border); border-radius: 999px; }
  .lightboxTileRow::-webkit-scrollbar-thumb:hover { background: var(--control-border-hover); }
  .lightboxTile {
    position: relative; flex: 0 0 auto; width: 140px; height: 140px;
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--space-1_5);
    cursor: pointer; font: inherit; text-align: center; padding: var(--space-2);
    border-radius: var(--radius-project-box); color: var(--text-primary);
    background: var(--surface-raised); border: 1px solid var(--control-border);
    transition: background var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard),
                transform var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard);
  }
  .lightboxTile:hover {
    background: var(--surface-hover); border-color: var(--control-border-hover);
    transform: translateY(-2px); box-shadow: var(--shadow-md);
  }
  .lightboxTile:active { transform: translateY(0); }
  .lightboxTile:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 2px; }
  .lightboxTileArt { width: 68px; height: 68px; display: block; pointer-events: none; }
  .lightboxTileArt svg { width: 100%; height: 100%; display: block; }
  .lightboxTileArt .lightboxTileArtImage { width: 100%; height: 100%; display: block; object-fit: contain; }   /* the Head and Ear tiles show renders of the real meshes (app/assets/starter-meshes/*-tile.png, made by tests/regression-rig/_scratch-render-tile-icons.mjs) */
  .lightboxTileName { font-size: var(--text-xs); font-weight: 600; letter-spacing: var(--tracking-tight); pointer-events: none; }
  .lightboxTilePrimary {
    background: var(--accent-dim); border-color: color-mix(in oklab, var(--accent-500) 60%, transparent);
    box-shadow: var(--shadow-sm), 0 0 0 1px color-mix(in oklab, var(--accent-500) 20%, transparent) inset;
  }
  .lightboxTilePrimary .lightboxTileName { color: var(--accent-strong); }
  .lightboxTilePrimary:hover {
    background: color-mix(in oklab, var(--accent-dim) 78%, var(--accent-600));
    border-color: color-mix(in oklab, var(--accent-500) 75%, transparent);
  }
  .lightboxEmptyNote { font-size: var(--text-xs); color: var(--text-muted); padding: var(--space-1) var(--space-1) var(--space-2) var(--space-1); text-align: left; }

  /* Autosave tiles: the viewport thumbnail fills the tile; the date + age labels sit on a
     bottom gradient scrim (like any sculpt-app project browser). Same box size as every
     other tile; long labels ellipsize. */
  .lightboxTileAutosave { justify-content: flex-end; gap: 0; padding: 0; overflow: hidden; }
  /* Thumbnails are saved with a transparent background (2026-09-26), so the tile paints the viewport grey behind them: --viewport-grey
     follows the appearance (40% dark, 60% light by default, Preferences ▸ Viewport). The New sculpt tiles are untouched. */
  .lightboxTileAutosave:not(.lightboxTileAutosaveNoThumbnail), .lightboxTileAutosave:not(.lightboxTileAutosaveNoThumbnail):hover { background: var(--viewport-gradient); }
  .lightboxTileAutosave .autosaveTileName, .lightboxTileAutosave .autosaveTileAge {
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
  }
  .autosaveTileThumbnail {
    position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
    border-radius: inherit; pointer-events: none;
  }
  .lightboxTileAutosave .autosaveTileLabel {
    position: relative; z-index: 1; width: 100%; display: flex; flex-direction: column; gap: 1px;
    padding: var(--space-4) var(--space-2) var(--space-2) var(--space-2); pointer-events: none;
    background: linear-gradient(to top, oklch(12% 0.02 var(--tone-hue) / 0.92) 30%, oklch(12% 0.02 var(--tone-hue) / 0) 100%);
    border-radius: 0 0 var(--radius-project-box) var(--radius-project-box); text-align: left;
  }
  .lightboxTileAutosave .autosaveTileName { font-size: var(--text-2xs); font-weight: 700; color: var(--text-primary); }
  .lightboxTileAutosave .autosaveTileAge { font-size: var(--text-2xs); color: var(--text-muted); }
  /* LIGHT appearance (2026-09-26): a pale scrim under the dark labels, and the
 thumbnail (which carries the viewport's grey from when it was saved) lifted a little so the tiles sit on the pale page. */
  html[data-appearance="light"] .lightboxTileAutosave .autosaveTileLabel { background: linear-gradient(to top, oklch(97% 0.012 var(--tone-hue) / 0.94) 32%, oklch(97% 0.012 var(--tone-hue) / 0) 100%); }
  html[data-appearance="light"] .autosaveTileThumbnailOpaque { filter: brightness(1.3) saturate(1.05); }   /* older thumbnails with the grey baked in */
  html[data-appearance="light"] .autosaveTileDelete { background: oklch(97% 0.012 var(--tone-hue) / 0.85); }
  html[data-appearance="light"] .lightboxTileAutosaveNoThumbnail { background: radial-gradient(90% 75% at 50% 34%, oklch(86% 0.04 var(--tone-hue)) 0%, oklch(78% 0.04 var(--tone-hue)) 60%, oklch(72% 0.04 var(--tone-hue)) 100%); }
  /* No thumbnail stored (pre-thumbnail snapshots): show a soft clay-blob placeholder behind the labels. */
  .lightboxTileAutosaveNoThumbnail {
    background: radial-gradient(90% 75% at 50% 34%, oklch(46% 0.055 calc(var(--tone-hue) - 4)) 0%, oklch(30% 0.04 calc(var(--tone-hue) - 2)) 52%, oklch(22% 0.03 calc(var(--tone-hue) - 1)) 100%);
  }
  .autosaveTileDelete {
    position: absolute; top: var(--space-1_5); right: var(--space-1_5); z-index: 2;
    width: 22px; height: 22px; display: inline-flex; align-items: center; justify-content: center;   /* always shown (owner 2026-09-26: hover-only was undiscoverable) */
    cursor: pointer; font: inherit; font-size: var(--text-sm); line-height: 1; color: var(--text-primary);
    background: oklch(14% 0.02 var(--tone-hue) / 0.72); border: 1px solid var(--control-border); border-radius: var(--radius-sm);
  }
  .autosaveTileDelete:hover { background: var(--danger-strong, oklch(50% 0.16 25)); border-color: transparent; }
  /* Cloud tiles reuse the autosave tile; a small white cloud top-left says where the project lives.
 It used to be a dark pill with the cloud shape CUT OUT of it (mask-composite: exclude), so what you actually saw was a black
 disc with a cloud-shaped hole — (2026-09-23). Now the mark IS the cloud: a
 plain white fill shaped by the mask, with a soft drop shadow underneath so it still reads on a pale thumbnail. */
  .lightboxTileCloud::before {
    content: ''; position: absolute; top: var(--space-1_5); left: var(--space-1_5); z-index: 2; width: 20px; height: 14px;
    background: #ffffff;
    --cloud-mark-shape: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 16'%3E%3Cpath d='M6.6 15.4C3.2 15.4.4 12.8.4 9.6c0-2.9 2.2-5.3 5.2-5.7C6.5 1.6 9 0 11.9 0c3.5 0 6.4 2.4 6.9 5.6 2.7.4 4.8 2.6 4.8 5.2 0 2.9-2.5 4.6-5.4 4.6z' fill='white'/%3E%3C/svg%3E") center / contain no-repeat;
    -webkit-mask: var(--cloud-mark-shape);
    mask: var(--cloud-mark-shape);
    filter: drop-shadow(0 1px 2px oklch(12% 0.02 var(--tone-hue) / 0.75));
  }

  /* ---------- in-app Projects window (File ▸ New sculpt / Projects…) ---------- */
  #projectLightbox { position: fixed; inset: 0; z-index: 120; }
  #projectLightbox .projectLightboxBackdrop {
    position: absolute; inset: 0; background: oklch(10% 0.015 calc(var(--tone-hue) + 2) / 0.62); backdrop-filter: blur(3px);
  }
  #projectLightbox .projectLightboxPanel {
    position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
    width: min(1268px, 94vw); max-height: 86vh; overflow-y: auto; overflow-x: hidden;   /* 1268: the eight New sculpt tiles (1212px, the Blockout tile joined 2026-10-10) fit inside the side padding */
    display: flex; flex-direction: column; gap: var(--space-6);
    padding: var(--space-5) var(--space-6) var(--space-6) var(--space-6);
    background: var(--surface-base, var(--surface-raised)); border: 1px solid var(--control-border);
    border-radius: var(--radius-project-box); box-shadow: var(--shadow-lg, 0 24px 64px oklch(8% 0.02 var(--tone-hue) / 0.55));
    scrollbar-width: thin; scrollbar-color: var(--control-border) transparent;
  }
  #projectLightbox .projectLightboxHeader { display: flex; align-items: center; gap: var(--space-3); }
  #projectLightbox .projectLightboxTitle {
    font-size: var(--text-md); font-weight: 700; letter-spacing: var(--tracking-tight); margin-right: auto;
  }
  #projectLightbox .projectLightboxClose {
    cursor: pointer; font: inherit; font-size: var(--text-lg); line-height: 1; color: var(--text-muted);
    width: 30px; height: 30px; display: inline-flex; align-items: center; justify-content: center;
    background: transparent; border: 1px solid transparent; border-radius: var(--radius-md, 8px);
  }
  #projectLightbox .projectLightboxClose:hover { color: var(--text-primary); background: var(--surface-hover); border-color: var(--control-border); }

  /* ---------- in-app modal dialogs (confirmation + the ＋ Layer primitive chooser) ----------
     These replace window.confirm(), which the browser lets the user permanently suppress ("Don't allow
     this site to prompt you again") — after which confirm() silently returns false and the action it
     guards can NEVER run again. Our own dialogs can't be muted, and they match the app's surfaces.
     z-index sits above #projectLightbox (120) because the delete it guards is raised from inside it. */
  .menuItem.menuItemComingSoon { opacity: 0.6; cursor: default; }
  .renderBackgroundFileName { color: var(--text-muted); font-size: var(--text-xs); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 1 1 auto; }
  .menuItem .menuItemNote { color: var(--text-muted); font-size: 0.92em; }
  .appModalDialog { position: fixed; inset: 0; z-index: 160; }   /* above #startScreen (150) too — the autosave delete confirmation is raised from the start screen's tiles as well, and at 140 it opened BEHIND them (2026-09-11) */
  .appModalDialog .confirmDialogBackdrop {
    position: absolute; inset: 0; background: oklch(10% 0.015 calc(var(--tone-hue) + 2) / 0.62); backdrop-filter: blur(3px);
  }
  .appModalDialog .confirmDialogPanel {
    position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
    width: min(420px, 92vw); display: flex; flex-direction: column; gap: var(--space-2);
    padding: var(--space-5) var(--space-5) var(--space-4) var(--space-5);
    background: var(--surface-base, var(--surface-raised)); border: 1px solid var(--control-border);
    border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
    text-align: left;
  }
  .appModalDialog .confirmDialogTitle {
    font-size: var(--text-md); font-weight: 700; letter-spacing: var(--tracking-tight); color: var(--text-primary);
  }
  .appModalDialog .confirmDialogMessage {
    font-size: var(--text-sm); line-height: 1.5; color: var(--text-muted);
  }
  .appModalDialog .confirmDialogButtonRow {
    display: flex; justify-content: flex-end; gap: var(--space-2_5); margin-top: var(--space-3);
  }
  /* The Extract dialog: no backdrop (pointer events pass through to the viewport), docked just left of the properties panel. */
  .appModalDialog.extractDialog { pointer-events: none; }
  .appModalDialog .extractDialogPanel { pointer-events: auto; left: auto; top: calc(var(--header-h) + 16px); right: calc(var(--panel-w) + 16px); transform: none; width: 340px; }
  /* THE TOOL WINDOWS MOVE (2026-10-12, floating-panels.js): the title is the handle, a cross closes, a corner resizes; a window
     taller than the screen scrolls instead of running off the bottom. */
  .appModalDialog .extractDialogPanel.floatingPanel { max-height: calc(100vh - var(--header-h) - var(--footer-h) - 32px); overflow-y: auto; overflow-x: hidden; min-width: 280px; }
  .floatingPanel .confirmDialogTitle.floatingPanelHandle { cursor: move; user-select: none; padding-right: var(--space-8, 32px); touch-action: none; }
  .floatingPanelCloseButton {
    position: absolute; top: var(--space-3); right: var(--space-3); width: 26px; height: 26px; padding: 0; line-height: 1;
    display: inline-flex; align-items: center; justify-content: center; font: inherit; font-size: 18px;
    color: var(--text-muted); background: transparent; border: 1px solid transparent; border-radius: var(--radius-sm); cursor: pointer;
  }
  .floatingPanelCloseButton:hover { color: var(--text-primary); background: var(--surface-raised); border-color: var(--control-border); }
  .floatingPanelCloseButton:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
  .floatingPanelResizeHandle {
    position: sticky; align-self: flex-end; flex: none; bottom: calc(var(--space-4) * -1); margin: auto calc(var(--space-5) * -1 + 2px) calc(var(--space-4) * -1 + 2px) 0;   /* (the top margin pushes it to the bottom of a window made taller) */
    width: 14px; height: 14px; cursor: nwse-resize; touch-action: none; opacity: 0.55;
    background: linear-gradient(135deg, transparent 0 50%, var(--text-muted) 50% 58%, transparent 58% 70%, var(--text-muted) 70% 78%, transparent 78%);
  }
  .floatingPanelResizeHandle:hover { opacity: 1; }
  .extractDialogPanel .sliderLabel.extractSliderLabel { width: 132px; }
  .extractDialogPanel .extractStatusLine { min-height: 2.8em; }
  /* The rows of the Surface noise window (2026-10-14): a slider row as label | track | value, a label beside a control, a joined row
 of choices with the chosen one lit, and a small heading. Scoped to the tool windows: the right panel went back to its own look
 (2026-10-14). */
  .extractDialogPanel .sliderRow.panelSliderRow { display: grid; grid-template-columns: 96px minmax(0, 1fr) 64px; column-gap: var(--space-3); align-items: center; }   /* (no margin: the window's flex gap spaces its rows) */
  .extractDialogPanel .panelSliderRow .sliderLabel { display: contents; }   /* its name, track and value become the row's three columns */
  .extractDialogPanel .panelSliderRow input[type="range"] { order: 2; width: 100%; min-width: 0; }
  .extractDialogPanel .panelSliderRow .sliderLabel span, .extractDialogPanel .panelSliderRow .sliderValueEditor { order: 3; justify-self: end; text-align: right; }
  .extractDialogPanel .panelLabelledRow { display: grid; grid-template-columns: 96px minmax(0, 1fr); column-gap: var(--space-3); align-items: center; margin: var(--space-2_5) 0; }
  .extractDialogPanel .panelRowLabel { font-size: var(--text-xs); color: var(--text-secondary); }
  .extractDialogPanel .panelSegmented {
    display: flex; gap: var(--space-0_5); padding: var(--space-0_5); margin: var(--space-2_5) 0; min-width: 0;
    background: var(--surface-inset); border: 1px solid var(--hairline-soft); border-radius: var(--radius-md);
  }
  .extractDialogPanel .panelLabelledRow > .panelSegmented { margin: 0; }
  .extractDialogPanel .panelSegmented > button {
    flex: 1 1 0; min-width: 0; padding: var(--space-1_5) var(--space-2); border: 1px solid transparent; border-radius: var(--radius-sm);
    background: transparent; font-family: inherit; font-size: var(--text-xs); font-weight: 600; line-height: 1.2; color: var(--text-muted);
    text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer;
    transition: background var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard);
  }
  .extractDialogPanel .panelSegmented > button:hover { color: var(--text-primary); background: var(--surface-hover); }
  .extractDialogPanel .panelSegmented > button.toggleButtonActive { background: var(--accent-dim); border-color: color-mix(in oklab, var(--accent-500) 55%, transparent); color: var(--accent-strong); box-shadow: var(--shadow-sm); }
  html[data-theme="rainbow"] .extractDialogPanel .panelSegmented > button.toggleButtonActive { background: var(--rainbow-fill); color: var(--rainbow-on-text); border-color: transparent; }
  .extractDialogPanel .panelSegmented.panelSegmentedWrap { flex-wrap: wrap; row-gap: var(--space-1_5); }
  .extractDialogPanel .panelSegmented.panelSegmentedWrap > button { flex: 1 1 30%; }
  .extractDialogPanel .panelSegmented > button:disabled, .extractDialogPanel .panelSegmented > button.disabledButton { opacity: var(--disabled-opacity); cursor: default; pointer-events: none; }
  .extractDialogPanel .panelSubheading { margin: var(--space-4) 0 var(--space-1_5); padding-top: var(--space-2_5); border-top: 1px solid var(--hairline-soft); font-size: var(--text-xs); font-weight: 600; color: var(--text-secondary); }
  /* The panel's new-look rows inside a tool window (Surface noise, 2026-10-14): the window is a flex column whose gap already spaces
     its rows, and margins do not collapse in a flex column, so the rows' own top and bottom margins would double the spacing. */
  .extractDialogPanel > .panelSliderRow, .extractDialogPanel > .panelLabelledRow, .extractDialogPanel > .panelSegmented { margin-top: 0; margin-bottom: 0; }
  .extractDialogPanel > .panelSubheading { margin-bottom: 0; }
  .appModalDialog .confirmDialogButton {
    cursor: pointer; font: inherit; font-size: var(--text-sm); font-weight: 600;
    padding: var(--space-2) var(--space-4); border-radius: var(--radius-lg);
    background: var(--surface-raised); border: 1px solid var(--control-border); color: var(--text-primary);
    transition: background var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard);
  }
  .appModalDialog .confirmDialogButton:hover { background: var(--surface-hover); border-color: var(--control-border-hover); }
  .appModalDialog .confirmDialogButton:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 2px; }
  .appModalDialog .confirmDialogButtonDanger {
    background: var(--danger-strong, oklch(50% 0.16 25)); border-color: transparent; color: #fff;
  }
  .appModalDialog .confirmDialogButtonDanger:hover {
    background: var(--danger-stronger, oklch(56% 0.18 25)); border-color: transparent;
  }
  /* Render section + result dialog rows — label + control pairs in the panel's quiet text tone. */
  .renderImageOptionRow {
    display: flex; align-items: center; gap: var(--space-3); margin-top: var(--space-2);
    font-size: var(--text-sm); color: var(--text-primary);
  }
  .renderImageOptionRow .renderImageOptionLabel { color: var(--text-muted); flex: 0 0 auto; }
  /* Render image ▸ Type (2026-09-27): Silhouette, Depth and Normal have no background or lighting of their own — those rows hide. */
  #renderSharedSettings.renderSharedSettingsBeautyOff .renderBeautyOnly { display: none !important; }
  /* Session timelapse (2026-09-14): start dialog checkbox, recording indicator, capture-frame guide, export list. */
  .timelapseCheckboxRow { display: flex; align-items: center; gap: var(--space-2); color: var(--text-secondary); font-size: var(--text-sm); cursor: pointer; }
  /* The recording readout is one quiet line at the bottom left of the viewport, in the vertex / triangle counts' font and colours
 (2026-09-15). */
  .timelapseIndicator {
    position: fixed; left: calc(var(--rail-w) + var(--space-3_5)); bottom: calc(var(--footer-h) + var(--space-2_5)); z-index: 40;
    display: flex; align-items: center; gap: 0.6em; white-space: nowrap; pointer-events: none;
    font-family: var(--font-mono); font-size: var(--text-xs); letter-spacing: 0.01em; font-feature-settings: "tnum"; color: var(--text-muted);
    text-shadow: 0 1px 3px oklch(7% 0.02 var(--tone-hue) / 0.6);
  }
  .timelapseIndicatorDot { width: 6px; height: 6px; border-radius: 50%; background: oklch(62% 0.22 25); animation: timelapse-recording-pulse 1.6s ease-in-out infinite; }
  @keyframes timelapse-recording-pulse { 50% { opacity: 0.35; } }
  .timelapseIndicatorLabel { color: oklch(68% 0.2 25); font-weight: 600; }
  .timelapseIndicatorReadout { font: inherit; }
  .timelapseIndicatorButton {
    font: inherit; color: var(--text-muted); cursor: pointer; pointer-events: auto;   /* the readout ignores the pointer; its two buttons do not */
    background: none; border: none; padding: 0; text-decoration: underline; text-decoration-color: transparent; text-underline-offset: 2px;
  }
  .timelapseIndicatorButton:hover { color: var(--text-primary); text-decoration-color: currentColor; }
  .timelapseIndicatorButton.toggleButtonActive { color: var(--accent-strong); }
  /* STOP stands out (2026-09-15):
 a small red pill with a stop square, in the interface font, beside the quiet readout. */
  #timelapseStopButton {
    display: inline-flex; align-items: center; gap: 0.5em; margin-left: 0.3em; padding: 0.35em 0.8em;
    font-family: var(--font-sans, inherit); font-size: var(--text-xs); font-weight: 600; letter-spacing: 0; text-decoration: none; text-shadow: none;
    color: oklch(96% 0.02 25); background: oklch(52% 0.19 25); border: 1px solid oklch(62% 0.2 25); border-radius: var(--radius-sm);
    box-shadow: 0 1px 4px oklch(7% 0.02 25 / 0.5);
  }
  #timelapseStopButton::before { content: ""; width: 0.65em; height: 0.65em; border-radius: 2px; background: currentColor; flex: none; }
  /* The capture frame switch is a real button too (2026-09-15):
 a pill that reads Frame: On / Frame: Off, lit in the accent while the frame shows. */
  #timelapseGuideToggleButton {
    display: inline-flex; align-items: center; margin-left: 0.3em; padding: 0.35em 0.8em;
    font-family: var(--font-sans, inherit); font-size: var(--text-xs); font-weight: 600; letter-spacing: 0; text-decoration: none; text-shadow: none;
    color: var(--text-primary); background: var(--surface-raised); border: 1px solid var(--control-border); border-radius: var(--radius-sm);
    box-shadow: 0 1px 4px oklch(7% 0.02 var(--tone-hue) / 0.5);
  }
  #timelapseGuideToggleButton:hover { background: var(--surface-hover); border-color: var(--control-border-hover); text-decoration: none; }
  #timelapseGuideToggleButton.toggleButtonActive { color: var(--accent-strong); border-color: color-mix(in oklab, var(--accent-500) 65%, transparent); background: var(--accent-dim); }
  #timelapseStopButton:hover { color: #fff; background: oklch(58% 0.21 25); text-decoration: none; }
  /* The guide is a DOM overlay over the canvas, never drawn into the render, so it can never appear in a captured frame. */
  .timelapseCaptureGuide { position: fixed; inset: 0; pointer-events: none; z-index: 2; overflow: hidden; }
  .timelapseCaptureGuideFrame { position: absolute; box-shadow: 0 0 0 200vmax rgba(0, 0, 0, 0.45); outline: 1px dashed rgba(255, 255, 255, 0.55); }
  .timelapseExportList { display: flex; flex-direction: column; gap: var(--space-2); }
  /* Record ▸ Whole window: the rendering options and the preview give way to a note (app.js syncTimelapseSourceRows). (Restored
     2026-09-18: the silhouette-window removal that morning cut these five lines along with the window's own styles, so both sets of
     rows showed at once and the Rendered view looked like it needed screen sharing.) */
  #timelapseStartDialog.timelapseWholeWindow [data-timelapse-render-only], #timelapseStartDialog:not(.timelapseWholeWindow) [data-timelapse-window-only] { display: none !important; }
  .timelapseWindowNote { padding: var(--space-6); flex-direction: column; gap: var(--space-4); }
  .timelapseWindowPreviewCanvas { display: block; max-width: 100%; max-height: calc(100% - 7rem); min-height: 0; border: 1px solid var(--hairline); border-radius: var(--radius-md); cursor: pointer; }
  .timelapseWindowNoteText { max-width: 460px; font-size: var(--text-sm); line-height: 1.6; color: var(--text-secondary); text-align: center; }
  .timelapseIndicator.timelapseIndicatorMinimal .timelapseIndicatorReadout, .timelapseIndicator.timelapseIndicatorMinimal #timelapseGuideToggleButton { display: none; }   /* whole-window recordings: the readout would be in the video */
  .timelapseExportRow { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-sm); color: var(--text-secondary); }
  .timelapseExportRow .timelapseExportRowLabel { flex: 1; }
  .renderWatermarkFreeNote { font-size: var(--text-xs); }
  .renderWatermarkWhyProButton { padding: 0; border: none; background: none; font: inherit; font-weight: 600; color: var(--accent-strong); text-decoration: underline; text-underline-offset: 2px; cursor: pointer; }
  .renderWatermarkTextInput { flex: 1; min-width: 0; font: inherit; color: var(--text-primary); background: var(--surface-raised); border: 1px solid var(--control-border); border-radius: var(--radius-sm); padding: 4px var(--space-2); }
  .renderWatermarkLogoName { font-size: var(--text-xs); color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .renderImageOptionRow select { flex: 1; width: auto; }
  /* Render dialogs fill the window: the preview takes the left, the settings sit in a column on the right. */
  .appModalDialog .confirmDialogPanel.renderSettingsPanel {
    width: calc(100vw - 32px); height: calc(100vh - 32px); max-height: none; max-width: none;
    display: flex; flex-direction: row; gap: var(--space-5); padding: var(--space-4);
  }
  .renderPreviewSlot { flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; align-items: center; justify-content: center; }   /* the frame inside is sized to the render's aspect by fitRenderPreviewFrame (owner 2026-09-11: no checkerboard letterbox around the preview) */
  .renderSettingsColumn { flex: 0 0 400px; display: flex; flex-direction: column; gap: var(--space-2); min-height: 0; overflow-y: auto; padding-right: var(--space-5); }   /* room between the settings and the scroll bar (2026-09-15); 20px wider so the controls keep their width */
  .renderSettingsColumn .confirmDialogButtonRow {
    margin-top: auto; padding-top: var(--space-3); padding-bottom: var(--space-1);
    position: sticky; bottom: 0; z-index: 1; flex-shrink: 0;   /* Start recording / Render / Close always visible at the bottom (owner 2026-09-15) */
    background: var(--surface-base, var(--surface-raised)); box-shadow: 0 -12px 12px -4px var(--surface-base, var(--surface-raised));
  }
  /* The settings preview: a true thumbnail render over a checkerboard (so a transparent background
     reads as transparency, exactly like an image editor). Click = refresh after moving the camera. */
  .renderPreviewFrame {
    position: relative; width: auto; height: auto; max-width: 100%; max-height: 100%; min-height: 90px; margin-top: 0;
    display: flex; align-items: center; justify-content: center; overflow: hidden;
    border-radius: var(--radius-lg); border: 1px solid var(--control-border); cursor: pointer;
    background:
      repeating-conic-gradient(oklch(32% 0.01 var(--tone-hue)) 0% 25%, oklch(24% 0.01 var(--tone-hue)) 0% 50%) 0 0 / 16px 16px;
  }
  .renderPreviewFrame:hover { border-color: var(--control-border-hover); }
  .renderPreviewFrame:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 2px; }
  .renderPreviewFrame img { display: block; width: 100%; height: 100%; object-fit: contain; }
  .renderPreviewHint {
    position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
    font-size: var(--text-xs); color: var(--text-muted); background: oklch(14% 0.015 var(--tone-hue) / 0.55);
    pointer-events: none;
  }
  .renderStatusLine { margin-top: var(--space-2); font-size: var(--text-xs); color: var(--text-muted); }
  /* Result dialog: the finished image dominates; the same checkerboard backs transparent renders. */
  .appModalDialog .confirmDialogPanel.renderResultPanel { width: calc(100vw - 32px); height: calc(100vh - 32px); max-height: none; max-width: none; display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-4); }
  #renderResultDialog .renderResultImageFrame { flex: 1 1 auto; min-height: 0; }
  .renderResultImageFrame { margin-top: var(--space-2); display: flex; align-items: center; justify-content: center; min-width: 0; }
  /* the media itself carries the border and the checkerboard, sized to the render's shape by fitRenderResultMedia — the checkerboard is only ever seen THROUGH a transparent render, never around it */
  .renderResultImageFrame img, .renderResultImageFrame video {
    display: block; width: auto; height: auto; max-width: 100%; max-height: 100%; object-fit: contain;
    border-radius: var(--radius-lg); border: 1px solid var(--control-border); box-sizing: border-box;
    background:
      repeating-conic-gradient(oklch(32% 0.01 var(--tone-hue)) 0% 25%, oklch(24% 0.01 var(--tone-hue)) 0% 50%) 0 0 / 16px 16px;
  }
  .renderResultNameInput { flex: 1; margin-top: 0; }
  .renderResultNameSuffix { color: var(--text-muted); flex: 0 0 auto; }
  /* The render dialog scrolls once lighting + gradient rows are showing; the colour pickers are
     top-level fixed popovers, so they are never clipped by this. */
  .renderSettingsGroupLabel {
    margin-top: var(--space-3); font-size: var(--text-2xs); font-weight: 700;
    letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-muted);
  }
  .renderImageOptionRow .colorSwatchButton { flex: 1; height: 26px; }

  /* Text-entry dialog field (Save .clay as…). The accept button is the affirmative action here, so it
     takes the accent rather than the danger colour the confirmation dialog's accept button uses. */
  .textEntryInput {
    width: 100%; font: inherit; font-size: var(--text-sm); color: var(--text-primary);
    padding: var(--space-2_5) var(--space-3); border-radius: var(--radius-lg);
    background: var(--surface-inset); border: 1px solid var(--control-border); outline: none;
    margin-top: var(--space-1);
  }
  .textEntryInput:focus { border-color: var(--accent-500); box-shadow: 0 0 0 1px var(--accent-500); }
  .appModalDialog .textEntryAcceptButton {
    background: var(--accent-dim); border-color: color-mix(in oklab, var(--accent-500) 55%, transparent);
    color: var(--accent-strong);
  }
  .appModalDialog .textEntryAcceptButton:hover {
    background: var(--accent-dim); border-color: var(--accent-500); color: var(--accent-strong);
  }

  /* Help ▸ Report a problem dialog: wider than the confirmation panel so the diagnostics read without wrapping every
     line; the panel scrolls on short screens, and the diagnostics box scrolls on its own. */
  .appModalDialog .confirmDialogPanel.reportProblemPanel { width: min(600px, 94vw); max-height: 90vh; overflow-y: auto; }
  .reportProblemFieldLabel { margin-top: var(--space-2); font-size: var(--text-xs); color: var(--text-secondary); }
  .reportProblemDescriptionInput { resize: vertical; min-height: 96px; line-height: 1.45; }
  .reportProblemDiagnosticsText {
    margin: 0; max-height: 200px; overflow: auto; white-space: pre-wrap; overflow-wrap: anywhere;
    font-family: var(--font-mono); font-size: var(--text-2xs); line-height: 1.5; color: var(--text-muted);
    padding: var(--space-2) var(--space-3); border-radius: var(--radius-lg);
    background: var(--surface-inset); border: 1px solid var(--control-border);
  }
  .reportProblemStatus { min-height: 1.2em; font-size: var(--text-xs); color: var(--text-muted); }
  .reportProblemStatus.reportProblemStatusError { color: var(--danger, #e5484d); }

  /* In-place layer rename field — sized exactly like the name button it temporarily replaces. */
  .layerNameInput {
    flex: 1; min-width: 0; text-align: left; color: var(--text-primary);
    background: var(--surface-inset); border-color: var(--accent-500); outline: none;
  }

  /* ＋ Layer primitive chooser: the same panel, widened for a four-across row of primitive tiles.
     .lightboxTile is a fixed 140px square built for the Projects window's scrolling row; here the four
     tiles must SHARE the panel width instead, so the fixed size is dropped and aspect-ratio keeps them
     square. minmax(0, 1fr) lets a column shrink below its content's min-width (plain 1fr will not,
     which is what pushed the last tile outside the panel). */
  #primitiveChoiceDialog .confirmDialogPanel { width: min(520px, 94vw); }
  #proLimitDialog { z-index: 170; }   /* above any other dialog: a limit can be reached from inside one (Share, Render) */
  #proLimitDialog .proLimitDialogPanel { width: min(600px, 94vw); }
  .proLimitDialogUnlocks { display: flex; align-items: flex-start; gap: var(--space-2); font-size: var(--text-sm); line-height: 1.5; color: var(--text-primary); }
  .proLimitDialogUnlocks .proLimitDialogBadge { margin: 2px 0 0 0; flex: none; }
  .appModalDialog .proLimitDialogPrice { color: var(--text-secondary); font-weight: 600; }
  .appModalDialog .proLimitDialogNothingLost { font-size: var(--text-xs); }
  .appModalDialog .proLimitDialogButtonRow { flex-wrap: wrap; }
  #crashRecoveryDialog .confirmDialogPanel { width: min(500px, 94vw); }   /* the crash notice's three buttons on one line (2026-09-27) */
  .primitiveChoiceRow {
    display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-2); margin-top: var(--space-2);   /* 3 columns: six shapes sit in two even rows */
  }
  .primitiveChoiceRow .lightboxTile { width: auto; height: auto; aspect-ratio: 1; min-width: 0; min-height: 0; }
  /* The 68px art is fixed-size in the Projects window; here it must shrink with the tile, or on a narrow
     panel the art's own height wins over aspect-ratio and the "squares" become tall rectangles. */
  .primitiveChoiceRow .lightboxTileArt { width: min(68px, 62%); height: auto; aspect-ratio: 1; }

  /* Account panel (sign up / sign in) — additive, sits below the New / Open .clay / Recover actions.
     Those actions and the local .clay path are unchanged and work signed-in OR signed-out. */
  #startScreen .startAuth { width: 100%; display: flex; flex-direction: column; gap: var(--space-2_5); }
  #startScreen .startAuthDivider {
    display: flex; align-items: center; gap: var(--space-3); margin-top: var(--space-2);
    color: var(--text-muted); font-size: var(--text-2xs); letter-spacing: 0.08em; text-transform: uppercase;
  }
  #startScreen .startAuthDivider::before, #startScreen .startAuthDivider::after {
    content: ''; flex: 1; height: 1px; background: var(--control-border);
  }
  #startScreen .startAuthForm, #startScreen .startAuthSignedIn {
    display: flex; flex-direction: column; gap: var(--space-2_5); width: 100%;
  }
  #startScreen .startAuthInput {
    width: 100%; font: inherit; color: var(--text-primary);
    padding: var(--space-2_5) var(--space-3); border-radius: var(--radius-project-box);
    background: var(--surface-raised); border: 1px solid var(--control-border);
    transition: border-color var(--dur-fast) var(--ease-standard);
  }
  #startScreen .startAuthInput::placeholder { color: var(--text-muted); }
  #startScreen .startAuthInput:focus,
  #startScreen .startAuthPrimary:focus-visible {
    outline: none; border-color: var(--accent-500);
    box-shadow: 0 0 0 1px var(--accent-500), 0 0 22px oklch(66% 0.150 calc(var(--tone-hue) - 4) / 0.28);
  }
  #startScreen .startAuthRow { display: flex; gap: var(--space-2_5); }
  #startScreen .startAuthButton {
    flex: 1; cursor: pointer; font: inherit; font-size: var(--text-sm); font-weight: 600;
    padding: var(--space-2_5) var(--space-3); border-radius: var(--radius-project-box);
    background: var(--surface-raised); border: 1px solid var(--control-border); color: var(--text-primary);
    transition: background var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard);
  }
  #startScreen .startAuthButton:hover { background: var(--surface-hover); border-color: var(--control-border-hover); }
  /* Google sign-in: the four-colour G logo sits inline before the label (Google's sign-in branding). */
  #googleSignInButton { display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2, 8px); }
  #googleSignInButton .googleLogoIcon { flex-shrink: 0; }
  #startScreen .startAuthButton:disabled { opacity: 0.55; cursor: default; }
  #startScreen .startAuthButton:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 2px; }
  #startScreen .startAuthPrimary {
    background: var(--accent-dim); color: var(--accent-strong);
    border-color: color-mix(in oklab, var(--accent-500) 60%, transparent);
  }
  #startScreen .startAuthPrimary:hover { background: color-mix(in oklab, var(--accent-dim) 78%, var(--accent-600)); }
  #startScreen .startAuthMessage {
    min-height: 1.1em; margin: 0; text-align: left; font-size: var(--text-xs); color: var(--text-muted);
  }
  #startScreen .startAuthMessage.isError { color: var(--danger-strong, oklch(72% 0.18 25)); }

  /* ── Editorial gate — elevate the first impression ─────────────────────────────── */
  /* Divider reads as a monospace instrument label. */
  #startScreen .startAuthDivider {
    font-family: var(--font-mono); font-size: var(--text-2xs); letter-spacing: 0.24em; color: var(--text-muted);
  }
  /* Primary CTA (now "Create account") = a solid molten-amber fill: the single warm object on screen. */
  #startScreen .startAuthPrimary {
    background: var(--accent-600); color: oklch(99% 0.005 var(--tone-hue)); border-color: transparent; font-weight: 600;
    box-shadow: 0 1px 0 oklch(100% 0 0 / 0.16) inset, 0 6px 18px oklch(52% 0.130 calc(var(--tone-hue) - 4) / 0.35);
  }
  #startScreen .startAuthPrimary:hover {
    background: var(--accent-500); border-color: transparent;
    box-shadow: 0 1px 0 oklch(100% 0 0 / 0.22) inset, 0 9px 26px oklch(52% 0.130 calc(var(--tone-hue) - 4) / 0.45);
  }
  /* Secondary ("Sign in") = a clean ghost outline. */
  #startScreen .startAuthRow .startAuthButton:not(.startAuthPrimary) {
    background: transparent; border-color: var(--hairline-strong); color: var(--text-secondary);
  }
  #startScreen .startAuthRow .startAuthButton:not(.startAuthPrimary):hover {
    background: var(--surface-hover); border-color: var(--text-muted); color: var(--text-primary);
  }
  #startScreen .startAuthInput { background: var(--surface-inset); }
  #startScreen .startAuthForgot {
    align-self: flex-end; margin-top: calc(-1 * var(--space-1_5));
    background: none; border: none; cursor: pointer; font: inherit; padding: 2px 0;
    font-size: var(--text-xs); color: var(--text-muted);
    transition: color var(--dur-fast) var(--ease-standard);
  }
  #startScreen .startAuthForgot:hover { color: var(--accent-strong); }
  #startScreen .startAuthForgot:disabled { opacity: 0.5; cursor: default; }
  /* Dev-only sign-in bypass — deliberately quiet (dashed border, muted text) so it reads as a
     development control, not a product button. Hidden unless auth-ui.js confirms a dev host. */
  #startScreen .devBypassSignInButton {
    margin-top: var(--space-2_5);
    font-size: var(--text-sm);
    padding: var(--space-2) var(--space-3);
    border-style: dashed;
    opacity: 0.75;
  }
  #startScreen .devBypassSignInButton:hover { opacity: 1; }
  /* Paint brush family (Stage D): while the Paint tool is active the flyout carries
     .paintFamilyActive and shows ONLY the paint group; otherwise the paint group is hidden and the
     sculpt groups show as always. */
  #brushFlyout.paintFamilyActive .brushGroup:not(.brushGroupPaint):not(.brushGroupSaved) { display: none; }
  #brushFlyout.paintFamilyActive .brushGroupColumns { display: none; }
  #brushFlyout.paintFamilyActive .brushGroupStacks { display: none; }   /* the two stacks of sculpt groups go the same way */   /* both sculpt columns go; the paint group sits below them */
  #brushFlyout.paintFamilyActive .brushGroupRow { display: none; }   /* the shared row of small sculpt groups (Texture / Move) goes with them */
  #brushFlyout:not(.paintFamilyActive) .brushGroupPaint { display: none; }

  /* ── Signup consent rows (billing/legal) — ADDITIVE. Two separate checkboxes above the
     Create-account row; auth-ui.js gates the account-creating buttons on the terms one. */
  #startScreen .startAuthConsentRow {
    display: flex; align-items: flex-start; gap: var(--space-2_5);
    font-size: var(--text-xs); color: var(--text-secondary); line-height: 1.5;
    text-align: left; cursor: pointer; user-select: none;
  }
  #startScreen .startAuthConsentRow input[type="checkbox"] {
    accent-color: var(--accent-500); width: 16px; height: 16px;
    margin-top: 2px; flex: 0 0 auto; cursor: pointer;
  }
  #startScreen .startAuthConsentRow a { color: var(--accent-strong); }
  #startScreen .consentOptionalTag { color: var(--text-muted); }

  /* ── Sign-in card modes (2026-11-03): create an account, or sign in. auth-ui.js sets data-auth-mode on #startAuth,
     and each mode shows only its own pieces. */
  #startScreen #startAuth[data-auth-mode="create"] .authSignInOnly,
  #startScreen #startAuth[data-auth-mode="sign-in"] .authCreateOnly { display: none; }
  /* Continue with Google leads the card: the quickest way in. */
  #startScreen .startAuthGoogle {
    flex: 0 0 auto; padding-block: var(--space-3);
    background: var(--surface-raised); border-color: var(--hairline-strong); color: var(--text-primary);
  }
  #startScreen .startAuthOrLine {
    display: flex; align-items: center; gap: var(--space-3);
    color: var(--text-muted); font-size: var(--text-xs);
  }
  #startScreen .startAuthOrLine::before, #startScreen .startAuthOrLine::after {
    content: ''; flex: 1; height: 1px; background: var(--control-border);
  }
  #startScreen .startAuthPasswordField { position: relative; width: 100%; }
  #startScreen .startAuthPasswordField .startAuthInput { padding-right: calc(var(--space-3) + 32px); }
  #startScreen .startAuthShowPassword {
    position: absolute; top: 50%; right: var(--space-1_5); transform: translateY(-50%);
    width: 32px; height: 32px; display: inline-flex; align-items: center; justify-content: center;
    background: none; border: none; border-radius: var(--radius-project-box); cursor: pointer; color: var(--text-muted);
  }
  #startScreen .startAuthShowPassword:hover, #startScreen .startAuthShowPassword[aria-pressed="true"] { color: var(--accent-strong); }
  #startScreen .startAuthShowPassword:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 1px; }
  #startScreen .startAuthPrimary { flex: 0 0 auto; }
  #startScreen .startAuthTermsLine, #startScreen .startAuthModeSwitch {
    margin: 0; font-size: var(--text-xs); line-height: 1.5; color: var(--text-muted); text-align: center;
  }
  #startScreen .startAuthTermsLine a { color: var(--accent-strong); }
  #startScreen .startAuthModeSwitch { color: var(--text-secondary); }
  #startScreen .startAuthModeSwitchButton {
    background: none; border: none; padding: 2px 0; cursor: pointer; font: inherit; font-weight: 600; color: var(--accent-strong);
  }
  #startScreen .startAuthModeSwitchButton:hover { text-decoration: underline; }
  #startScreen .startAuthModeSwitchButton:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 2px; }
  #startScreen .reacceptanceExplainer {
    font-size: var(--text-xs); color: var(--text-secondary); line-height: 1.55; text-align: left; margin: 0;
  }

  /* ── Tier picker + tier chips (billing) — ADDITIVE; shown/driven by src/billing-ui.js ─────────
     The picker cards deliberately mirror the LANDING PAGE pricing section (owner request): same
     card anatomy (name / big price / description / call-to-action), same borders, and the same
     blue→purple gradient on the Pro action. The grid is wider than the start card and centers
     over it; on narrow screens auto-fit stacks the cards. Only 'Free'/'Pro' text is ever set by JS. */
  #startTierPicker { width: min(880px, 92vw); display: flex; flex-direction: column; gap: var(--space-3); align-self: center; }
  #startTierPicker .tierCardGrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px; }
  #startTierPicker .tierCard {
    background: rgba(150,160,240,.05); border: 1px solid rgba(150,160,240,.16); border-radius: var(--radius-lg);
    padding: 22px 20px; display: flex; flex-direction: column; text-align: left;
  }
  #startTierPicker .tierCardHighlighted {
    border-color: rgba(120,150,255,.5);
    box-shadow: 0 24px 60px rgba(0,0,0,.35), 0 0 60px rgba(111,155,255,.10);
  }
  #startTierPicker .tierCardName { font-weight: 700; font-size: 17px; color: #fff; letter-spacing: -0.01em; }
  #startTierPicker .tierCardPrice { font-weight: 800; font-size: 30px; color: #fff; letter-spacing: -0.03em; margin-top: 8px; }
  #startTierPicker .tierCardPriceSuffix { font-weight: 500; font-size: 13px; color: #8b90b5; letter-spacing: 0; }
  #startTierPicker .tierCardDescription { font-size: 13.5px; color: #8b90b5; line-height: 1.6; margin-top: 8px; flex: 1; }
  #startTierPicker .tierPromoCodeHint { font-size: 12.5px; color: #8b90b5; text-align: center; margin-top: 10px; }   /* v0.8.8: checkout takes promotion codes */
  #startTierPicker .tierCardAction {
    display: flex; align-items: center; justify-content: center; margin-top: 18px; padding: 12px 16px;
    border-radius: var(--radius-lg); font: inherit; font-weight: 700; font-size: 14px; letter-spacing: -0.01em;
    cursor: pointer; text-decoration: none;
    background: rgba(150,160,240,.10); color: #f2f3fb; border: 1px solid rgba(150,160,240,.22);
    transition: filter .16s ease, transform .16s ease;
  }
  #startTierPicker .tierCardAction:hover { filter: brightness(1.12); transform: translateY(-1px); }
  #startTierPicker .tierCardActionPrimary {
    background: linear-gradient(100deg, #6f9bff, #b48cff); color: #07080d; border: none;
    box-shadow: 0 8px 30px rgba(111,155,255,.32);
  }
  #startTierPicker .tierCardAction:disabled { opacity: var(--disabled-opacity); cursor: default; transform: none; filter: none; }
  /* Monthly/Yearly segmented toggle on the Pro card — same border/pill vocabulary as the cards. */
  #startTierPicker .tierIntervalToggle {
    display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin-top: 14px;
    padding: 4px; border-radius: var(--radius-lg); background: rgba(150,160,240,.07);
    border: 1px solid rgba(150,160,240,.16);
  }
  #startTierPicker .tierIntervalButton {
    font: inherit; font-weight: 600; font-size: 12.5px; letter-spacing: -0.01em;
    padding: 8px 10px; border-radius: var(--radius-md); cursor: pointer;
    background: transparent; color: #8b90b5; border: 1px solid transparent;
    transition: background .16s ease, color .16s ease, border-color .16s ease;
  }
  #startTierPicker .tierIntervalButton:hover { color: #f2f3fb; }
  #startTierPicker .tierIntervalButtonActive {
    background: rgba(111,155,255,.16); color: #f2f3fb; border-color: rgba(120,150,255,.45);
  }
  #startTierPicker .tierIntervalButton:disabled { opacity: var(--disabled-opacity); cursor: default; }
  #startTierPicker .tierMostPopularPill {
    display: inline-block; margin-left: 10px; padding: 3px 10px; border-radius: var(--radius-sm);
    font-family: var(--font-mono); font-size: 10.5px; letter-spacing: 0.14em; text-transform: uppercase;
    color: #9cc0ff; border: 1px solid rgba(120,150,255,.45); background: rgba(111,155,255,.12);
    vertical-align: middle;
  }
  /* The tier chips are BUTTONS: clicking one changes the plan (Free → the picker / checkout,
     Pro → the Stripe billing portal). Styled as pills either way. */
  .authBadgeTier, .headerUserTier {
    font: inherit; font-size: var(--text-2xs); font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
    padding: 2px var(--space-2); border-radius: var(--radius-sm); cursor: pointer;
    background: var(--accent-soft); color: var(--accent-strong);
    border: 1px solid color-mix(in oklab, var(--accent-500) 45%, transparent);
    white-space: nowrap;
    transition: background var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard);
  }
  .authBadgeTier:hover, .headerUserTier:hover { background: var(--accent-dim); border-color: var(--accent-strong); }
  /* The picker is the 6th .startCard child — give it the next rise-in stagger slot so the
     existing delays on children 1-5 stay exactly as they are. */
  @media (prefers-reduced-motion: no-preference) {
    #startScreen .startCard > *:nth-child(6) { animation-delay: 0.30s; }
  }

  /* Stop the browser's autofill from forcing a white/yellow input background — keep it on-theme dark. */
  #startScreen .startAuthInput:-webkit-autofill,
  #startScreen .startAuthInput:-webkit-autofill:hover,
  #startScreen .startAuthInput:-webkit-autofill:focus,
  #startScreen .startAuthInput:-webkit-autofill:active {
    -webkit-text-fill-color: var(--text-primary);
    -webkit-box-shadow: 0 0 0 1000px var(--surface-inset) inset;
    box-shadow: 0 0 0 1000px var(--surface-inset) inset;
    caret-color: var(--text-primary);
    transition: background-color 99999s ease-in-out 0s;
  }
  /* Larger, more present gate block (owner request). */
  #startScreen .startAuthInput { padding: var(--space-3) var(--space-3_5); font-size: var(--text-md); }
  #startScreen .startAuthButton { padding: var(--space-3) var(--space-4); font-size: var(--text-md); }
  /* (the old `gap: var(--space-3)` override that lived here crushed the three sections to 12px apart —
     removed 2026-07-31 so the intended clamp(28px, 4.5vh, 44px) section gap actually applies) */
  /* Signature: a staggered rise-in on the card as the gate appears. */
  @media (prefers-reduced-motion: no-preference) {
    #startScreen .startCard > * { animation: reformGateRise 0.62s var(--ease-out) both; }
    #startScreen .startCard > *:nth-child(2) { animation-delay: 0.06s; }
    #startScreen .startCard > *:nth-child(3) { animation-delay: 0.12s; }
    #startScreen .startCard > *:nth-child(4) { animation-delay: 0.18s; }
    #startScreen .startCard > *:nth-child(5) { animation-delay: 0.24s; }
  }
  @keyframes reformGateRise { from { opacity: 0; transform: translateY(11px); } to { opacity: 1; transform: none; } }

  /* ── Hide dev/perf footguns from normal users (reveal with ?debug in the URL) ──────────────────
     These are jargon to a visitor and make the app look unfinished; the GPU→CPU toggle is an actual
     trap (a user flips to CPU, the app crawls, they leave). The elements stay in the DOM so all the
     existing JS wiring keeps working — they are only hidden. */
  html:not([data-debug]) #compactionMenuItem,
  html:not([data-debug]) #backfaceCullToggleMenuItem,
  html:not([data-debug]) #viewportMsaaToggleMenuItem,
  html:not([data-debug]) #fpsToggleMenuItem,
  html:not([data-debug]) #engineToggleMenuItem,
  html:not([data-debug]) #engineBadge { display: none !important; }

  /* ── Monospace instrumentation: the signature of the "control surface" concept ──────────────────
     Live numeric/technical data reads like a machine panel — the vert/tri overlay and the footer
     status bar in Space Mono with tabular figures. Cosmetic only. */
  #viewportStats { font-family: var(--font-mono); letter-spacing: 0.01em; font-feature-settings: "tnum"; }
  #appFooter { font-family: var(--font-mono); letter-spacing: 0.01em; }
  /* Signed-in user badge — fixed top-right, ABOVE the start screen (z 160) and the app chrome, and OUTSIDE
     #startScreen so it persists after the start screen is dismissed. Shown only when signed in (display
     toggled by auth-ui.js). */
  #authUserBadge {
    position: fixed; top: var(--space-3); right: calc(var(--space-3) + 44px); z-index: 160;   /* (to the left of the fullscreen button, 2026-10-29) */
    display: none; align-items: center; gap: var(--space-2_5);
    padding: var(--space-1_5) var(--space-1_5) var(--space-1_5) var(--space-3);
    border-radius: var(--radius-sm); background: var(--surface-raised); border: 1px solid var(--control-border);
    box-shadow: var(--shadow-md); font-size: var(--text-xs); color: var(--text-primary);
  }
  #authUserBadge .authBadgeEmail { max-width: 190px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* NO EMAIL, A FULLSCREEN BUTTON IN ITS PLACE (2026-10-20):
 the address is no longer shown (auth-ui.js still fills the element, which stays hidden); the button beside the plan
 runs the studio's own fullscreen toggle (view-menu-wiring.js). */
  #authUserBadge .authBadgeEmail { display: none; }
  /* Fullscreen at the start screen's top right, always (2026-10-29); the signed-in badge keeps to its left. */
  #startScreen .startFullscreenButton {
    position: fixed; top: var(--space-3); right: var(--space-3); z-index: 160;
    cursor: pointer; font: inherit; font-size: var(--text-sm); line-height: 1; padding: var(--space-1_5) var(--space-2);
    border-radius: var(--radius-sm); background: var(--surface-raised); border: 1px solid var(--control-border); color: var(--text-primary); box-shadow: none;   /* no shadow (2026-10-29) */
    transition: background var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard);
  }
  #startScreen .startFullscreenButton:hover { background: var(--accent-dim); border-color: var(--accent-strong); }
  #startScreen.dismissed .startFullscreenButton { display: none; }
  #authUserBadge .authBadgeSignOut {
    cursor: pointer; font: inherit; font-size: var(--text-2xs); font-weight: 600; letter-spacing: 0.02em;
    padding: var(--space-1_5) var(--space-2_5); border-radius: var(--radius-sm);
    background: var(--surface-hover); border: 1px solid var(--control-border); color: var(--text-primary);
    transition: background var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard);
  }
  #authUserBadge .authBadgeSignOut:hover { background: var(--accent-dim); border-color: var(--accent-strong); }
  #authUserBadge .authBadgeSignOut:disabled { opacity: 0.55; cursor: default; }

  /* Signed-in user chip in the app header (top-right of #appHeader's actions). Shown only when signed in. */
  .headerActions .headerUser {
    display: inline-flex; align-items: center; gap: var(--space-2); height: 28px;
    padding: 0 4px 0 var(--space-2_5); border-radius: var(--radius-sm);
    background: var(--surface-raised); border: 1px solid var(--control-border);
    font-size: var(--text-2xs); color: var(--text-muted); white-space: nowrap;
  }
  .headerActions .headerUserEmail { max-width: 130px; overflow: hidden; text-overflow: ellipsis; }
  .headerActions .headerUserSignOut {
    cursor: pointer; font: inherit; font-size: var(--text-2xs); font-weight: 600; line-height: 1;
    padding: 5px 9px; border-radius: var(--radius-sm);
    background: var(--surface-hover); border: 1px solid var(--control-border); color: var(--text-primary);
    transition: background var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard);
  }
  .headerActions .headerUserSignOut:hover { background: var(--accent-dim); border-color: var(--accent-strong); }
  .headerActions .headerUserSignOut:disabled { opacity: 0.55; cursor: default; }

  /* Honour reduced-motion: keep the UI legible, drop the non-essential transitions/animations. */
  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
      animation-duration: 0.01ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 0.01ms !important;
    }
  }

  /* ── Interface density (owner 2026-07-16: the chrome read too big at default browser zoom) ──────
     ONE knob scales every persistent chrome surface via standard CSS zoom (Chrome/Edge, Firefox 126+,
     Safari) — set it back to 1.0 to restore the original look exactly. Deliberately NOT scaled:
     the render canvases; the pointer-space stroke overlays (#strokeCursorDot, #dragTrailLine, #dragInertiaLine,
     #strokeGesturePreview), which are placed from raw clientX/clientY; the JS-positioned transient
     floaters (#appTooltip, .colorPickerPopover), whose viewport-pixel placement math must stay 1:1;
     and the startup loading screen. applyViewportFraming (app.js) multiplies the layout-skeleton
     variables by this factor so the camera keeps centring the mesh in the VISUAL viewport gap.
     Everything in the zoom list scales by the SAME factor, so every CSS offset expressed in terms of
     another chrome surface (rail→flyout, panel→toggle, header→gizmo) stays aligned by construction. */
  /* --interface-size-scale is the user's Edit ▸ Preferences ▸ General ▸ Interface size (owner 2026-09-14), 0.8–1.3,
     written on the root element by app.js. It MULTIPLIES the density, so the touch layout's larger base still applies. */
  :root { --interface-density: 0.8; --interface-size-scale: 1; }
  #appHeader, #quickBar, #toolRail, #brushFlyout, #propertiesPanel, #panelToggle, #appFooter, #shortcutBar, #shortcutBarFoldButton,
  #viewportStats, #timelapseIndicator, #perfReadout, #bvhBuildingToast, #processingOverlay .processingCard,
  .appModalDialog .renderSettingsColumn,
  #navigationGizmo, #authUserBadge,
  #startScreen .startCard, #startScreen .startFooter,
  #projectLightbox .projectLightboxPanel {
    zoom: calc(var(--interface-density) * var(--interface-size-scale));
  }
  /* The header account chip is hidden (2026-09-14). It stays in the page
 because the Account menu and Preferences ▸ Account read its text and forward their clicks to its buttons
 (account-menu.js), and auth-ui.js / billing-ui.js still fill it — so plan and sign-out keep working from there. */
  #authHeaderUser { display: none !important; }
