/* Motion timing — deliberately NOT part of the per-theme var block below
   (themes never touch these), so one toggle controls every menu/dropdown/
   flyout/panel/tab transition in the app at once. `html.motion-off`
   (Settings' "Smooth transitions" toggle, index.ts) zeroes them out rather
   than removing the transition rules themselves — elements keep their
   final resting state instantly instead of needing separate non-animated
   code paths. Any NEW popup/panel/tab transition should reuse these vars,
   not hardcode its own duration, so it's covered by the toggle for free. */
:root{
  --pop-dur: 130ms; --pop-ease: cubic-bezier(.22,.85,.32,1);
  --panel-dur: 160ms; --tab-dur: 110ms;
  /* Swipe-mode slides (html.motion-swipe): a directional push travels a whole
     pane width, which reads as a jerk on the built-in ease-out or the lighter
     --pop-ease at --tab-dur's 110ms. It gets the large-surface duration and a
     strong ease-out (cubic-bezier(0.23, 1, 0.32, 1)) that front-loads the
     travel and decelerates into place. index.ts/view.ts read --swipe-dur back
     out of the computed style (shared-ui.ts's transitionMsOf) so the JS swap
     delay can never drift from the CSS transition the way the old hardcoded
     100ms did against a 110ms --tab-dur. */
  --swipe-dur: 160ms; --swipe-ease: cubic-bezier(0.23, 1, 0.32, 1);
}
html.motion-off{ --pop-dur: 0ms; --panel-dur: 0ms; --tab-dur: 0ms; --swipe-dur: 0ms; }

/* Tab/sidebar-pane transition (index.ts's switchTab()) — Datasets/Editing
   Stats/the right-sidebar Master Tag Control swap all animate OUT, THEN swap
   display, THEN animate IN (sequential, not overlapping — avoids a layout
   collision with #galleryTab's own `display:contents`, which can't
   participate in a transition itself since it has no box of its own).
   `.tab-fading` is added/removed by switchTab(), never by CSS alone. Fade
   mode (default) just fades opacity; Swipe mode (`html.motion-swipe`) slides
   each pane fully off toward the edge it "lives" on instead — Datasets is
   the leftmost tab so it slides to/from the left, Stats is the rightmost so
   it slides to/from the right, and Master Tag Control slides in from the
   right over the normal right-sidebar tools (which slide out the same way,
   reading as "pushed off by the panel covering it") since the spec calls for
   it to visually cover them, not swap places arbitrarily. */
#datasetManagerTab, #statsTab, #normalRightTools, #masterTagPanel{
  transition: opacity var(--tab-dur) var(--pop-ease), transform var(--tab-dur) var(--pop-ease);
}
/* Which of #normalRightTools/#masterTagPanel is showing is a class now
   (switchTab(), index.ts), not an inline style — see index.ts's own
   comment on that. Base resting display matches what was previously set
   inline; `.rt-hidden` always wins regardless of any other display rule
   elsewhere (including the mobile breakpoint's `display:flex`). */
#normalRightTools, #masterTagPanel{ display: block; }
.rt-hidden{ display: none !important; }
#datasetManagerTab.tab-fading, #statsTab.tab-fading, #normalRightTools.tab-fading, #masterTagPanel.tab-fading{
  opacity: 0;
}
html.motion-swipe #datasetManagerTab.tab-fading{ opacity: 1; transform: translateX(-100%); }
html.motion-swipe #statsTab.tab-fading,
html.motion-swipe #normalRightTools.tab-fading,
html.motion-swipe #masterTagPanel.tab-fading{ opacity: 1; transform: translateX(100%); }
/* Swipe slides travel further than a fade, so in Swipe mode the panes take the
   large-surface duration and the strong --swipe-ease instead of the fade's
   --tab-dur/--pop-ease. Only the timing changes; the transition property list
   above stays shared. */
html.motion-swipe #datasetManagerTab, html.motion-swipe #statsTab,
html.motion-swipe #normalRightTools, html.motion-swipe #masterTagPanel{
  transition-duration: var(--swipe-dur); transition-timing-function: var(--swipe-ease);
}

/* Gallery view mode (Grid/Compact/Single/Disabled) and single-mode
   prev/next paging — same sequential animate-out/swap/animate-in shape as
   the tab panes above, driven by view.ts's switchView()/swipeSingleIndex().
   Direction is per-transition (which view is to the left/right of which),
   so it's carried as inline classes rather than fixed per-element rules. */
#galleryGrid, #compactGrid, #singleView{
  transition: opacity var(--tab-dur) var(--pop-ease), transform var(--tab-dur) var(--pop-ease);
}
.view-fade-out{ opacity: 0; }
html.motion-swipe .view-swipe-out-left{ opacity: 1; transform: translateX(-100%); }
html.motion-swipe .view-swipe-out-right{ opacity: 1; transform: translateX(100%); }
html.motion-swipe .view-swipe-in-right{ opacity: 1; transform: translateX(100%); }
html.motion-swipe .view-swipe-in-left{ opacity: 1; transform: translateX(-100%); }
html.motion-swipe #galleryGrid, html.motion-swipe #compactGrid, html.motion-swipe #singleView{
  transition-duration: var(--swipe-dur); transition-timing-function: var(--swipe-ease);
}

/* ---------- Swipe map ----------
   Swipe mode treats the app as one map (mapPan(), shared-ui.ts): tabs in
   tab-bar order along the x-axis, Gallery's views as a strip inside the
   Gallery chunk, images as a strip inside Single view and the image modal,
   and the modal itself on the depth axis — lifted out of its card.
   A pan is a View Transition over the ONE region that differs
   (view-transition-name: map-pane, set per move from JS): the outgoing and
   incoming snapshots travel together by exactly one region width, so the
   two chunks read as a single sheet sliding under a fixed frame. The group
   clips to the region's box, so a right-column pan never paints over the
   gallery beside it. Everything outside the region (topbar, tab bar, shared
   panels) stays in the root snapshot and holds still.
   Timing: iOS push curve (--ease-drawer), 260ms for tab/view chunks, 200ms
   for an image-to-image step (a smaller semantic move). Keyboard paging
   and Escape skip the pan entirely (view.ts). */
:root{ --map-dur: 260ms; --map-ease: cubic-bezier(0.32, 0.72, 0, 1); }
html[data-map-kind="page"]{ --map-dur: 200ms; }
::view-transition-group(map-pane){ animation: none; overflow: clip; }
::view-transition-image-pair(map-pane){ isolation: auto; }
::view-transition-old(map-pane), ::view-transition-new(map-pane){
  animation-duration: var(--map-dur); animation-timing-function: var(--map-ease);
  animation-fill-mode: both; mix-blend-mode: normal;
  height: 100%; object-fit: none; object-position: left top;
}
html[data-map-dir="fwd"]::view-transition-old(map-pane){ animation-name: mapOutToLeft; }
html[data-map-dir="fwd"]::view-transition-new(map-pane){ animation-name: mapInFromRight; }
html[data-map-dir="back"]::view-transition-old(map-pane){ animation-name: mapOutToRight; }
html[data-map-dir="back"]::view-transition-new(map-pane){ animation-name: mapInFromLeft; }
@keyframes mapOutToLeft{ from{ transform: translateX(0); } to{ transform: translateX(-100%); } }
@keyframes mapInFromRight{ from{ transform: translateX(100%); } to{ transform: translateX(0); } }
@keyframes mapOutToRight{ from{ transform: translateX(0); } to{ transform: translateX(100%); } }
@keyframes mapInFromLeft{ from{ transform: translateX(-100%); } to{ transform: translateX(0); } }
/* Chrome that changed with the move (active tab, active view button)
   settles in the same beat as the pan instead of the UA's slower fade. */
html[data-map-dir]::view-transition-old(root), html[data-map-dir]::view-transition-new(root){
  animation-duration: var(--map-dur); animation-timing-function: var(--map-ease);
}

/* Image card modal — depth axis. view.ts's aimModalAt() sets --from-x/-y/-s
   to the source card's thumbnail, so under Swipe the modal grows out of the
   card it was opened from and shrinks back into it on close; with no
   visible source the defaults give a short 32px rise at 0.96.
   Compositing: translate3d() + scale keep the flight a compositor move of
   an already-rasterized layer. will-change is NOT static — view.ts adds it
   for the transition only (applied-at-start/reset-at-end), so the large
   modal isn't a permanent resident of GPU memory. */
html.motion-swipe .modal-card{
  transform: translate3d(var(--from-x, 0px), var(--from-y, 32px), 0) scale(var(--from-s, 0.96));
  transition-duration: var(--map-dur); transition-timing-function: var(--map-ease);
}
html.motion-swipe .modal-backdrop{ transition-duration: var(--map-dur); transition-timing-function: var(--map-ease); }
html.motion-swipe .modal-backdrop.modal-visible .modal-card{ transform: translate3d(0, 0, 0) scale(1); }
  /* Transition-only layer promotion (see view.ts openImageCardModal). */
  html.motion-swipe #imageCardModal.modal-anim-layers .modal-card{ will-change: transform; }

/* Swipe mode is opt-in, but prefers-reduced-motion still wins: the map pan
   and the modal's flight drop their movement and keep a plain crossfade
   (fewer/gentler, not none — the change stays marked). The fallback
   out-then-in classes (engines without View Transitions) do the same. */
@keyframes mapFadeOut{ to{ opacity: 0; } }
@keyframes mapFadeIn{ from{ opacity: 0; } }
@media (prefers-reduced-motion: reduce){
  html[data-map-dir]::view-transition-old(map-pane){ animation-name: mapFadeOut; animation-duration: 150ms; }
  html[data-map-dir]::view-transition-new(map-pane){ animation-name: mapFadeIn; animation-duration: 150ms; }
  html.motion-swipe #datasetManagerTab.tab-fading,
  html.motion-swipe #statsTab.tab-fading,
  html.motion-swipe #normalRightTools.tab-fading,
  html.motion-swipe #masterTagPanel.tab-fading{ transform: none; opacity: 0; }
  html.motion-swipe .view-swipe-out-left,
  html.motion-swipe .view-swipe-out-right,
  html.motion-swipe .view-swipe-in-right,
  html.motion-swipe .view-swipe-in-left{ transform: none; opacity: 0; }
  html.motion-swipe .modal-card{ transform: scale(0.96); }
  html.motion-swipe .modal-backdrop.modal-visible .modal-card{ transform: scale(1); }
}

:root, html[data-theme="studio"]{
    --bg-base:#16151c;
    --bg-panel:#1c1a24;
    --bg-elevated:#252230;
    --bg-elevated-2:#2d2a38;
    --border:#38344400;
    --border-soft:#37324277;
    --border-strong:#4a4459;
    --text-primary:#ece8f0;
    --text-muted:#9791a6;
    --text-faint:#6b6578;
    --accent-auto:#e8a33d;
    --accent-auto-dim:#4a3c22;
    --accent-manual:#6fb8d1;
    --accent-manual-dim:#213842;
    --accent-danger:#e2637a;
    --accent-success:#7fbf8f;
    --accent-flair:#b98fd6;
    --mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
    --sans: 'Schibsted Grotesk', 'Segoe UI', system-ui, sans-serif;
    --radius: 6px;

    /* ---- Element grammar ----
       Non-color tokens every theme may set, consumed by the shared component
       rules further down (button, .chip, .card, .tool-section, .brand,
       .tab-btn, headings, checkbox, scrollbar). Declared here once, on the
       same element as every theme block, so a theme that sets none of them
       (Custom) inherits Studio's grammar. These are NOT in THEME_VARS: they
       are shapes and faces, not colors, so night mode's HSL inversion has
       nothing to do with them. */
    --display: var(--sans);   /* brand lockup */
    --head-font: var(--sans); /* dock/section titles */
    --tab-font: var(--sans);
    --brand-size: 17px; --brand-weight: 800; --brand-track: -0.015em; --brand-case: none;
    --tab-size: 12.5px; --tab-track: 0; --tab-case: none;
    --head-size: 12.5px; --head-weight: 700; --head-track: 0; --head-case: none;
    --head-color: var(--text-primary);
    --btn-weight: 500; --btn-track: 0; --btn-case: none;
    --r-ctl: var(--radius); --r-chip: 5px; --r-card: 8px; --r-panel: 10px;
    --r-check: 4px; --r-scroll: 6px;
    --card-shadow: 0 1px 2px rgba(0,0,0,0.25);
    /* Icon grammar (the sprite in index.html, src/renderer/icons.ts): one
       drawing set, restroked per theme. */
    --icon-stroke: 1.75; --icon-cap: round; --icon-join: round;
    /* Premium hover-fill + card-lift (epic/legendary themes, or any theme
       bought up via Refine Theme). Each theme restates these in its own
       material, so a refined theme gets ITS version of the effect, not a
       generic one. --fx-w is the hover resting fill (a scaleX factor); the
       click flash (flairClickFlash) always floods to 1. */
    --fx-fill: var(--accent-flair); --fx-o: 0.22; --fx-top: 0; --fx-h: 100%; --fx-w: 0.75;
    --fx-card-t: translateY(-3px); --fx-card-s: 0 10px 22px rgba(0,0,0,0.4);
  }
  /* Studio — the workbench. Machined panels (a lit top edge, a darker
     floor), labels that read like drawer tags: tight grotesk heads, tags
     set as square-cornered mono labels instead of pills. */
  html[data-theme="studio"] .tool-section,
  html[data-theme="studio"] .settings-section{
    box-shadow: inset 0 1px 0 color-mix(in srgb, var(--text-primary) 5%, transparent), 0 1px 0 rgba(0,0,0,0.25);
  }
  html[data-theme="studio"] #topbar{ border-bottom: 1px solid var(--border-strong); box-shadow: inset 0 -2px 0 color-mix(in srgb, var(--accent-flair) 55%, transparent); }
  html[data-theme="studio"] .tab-btn.active{ color: var(--text-primary); border-bottom-color: var(--accent-flair); }
  html[data-theme="studio"] button.primary{ box-shadow: inset 0 1px 0 color-mix(in srgb, var(--accent-manual) 30%, transparent); }
  html[data-theme="studio"] .brand span.tag{ border: 1px solid color-mix(in srgb, var(--accent-auto) 35%, transparent); }

  /* ================= Themes =================
     Each theme is a material, not a tint: it keeps its own color tokens
     (untouched by this layer) and restates the element grammar above in the
     vocabulary of the world its name already names — its own faces
     (renderer/fonts, OFL), control and tag shapes, active-tab marker, panel
     surface, and premium-effect tokens.
     Rules:
     - Ground textures go on #gallery (the one pane that shows its own
       ground), never on body — every pane is opaque, so a body texture
       never renders.
     - Textures derive from theme vars through color-mix(), never a literal
       rgba, so night mode's HSL inversion carries them too.
     - Never set border-color/background/box-shadow in a theme's bare `.chip`
       or `.card` rule — at (0,2,1) that beats the state classes
       (.chip-match/.chip-isolated/.chip-flagged-review, .card.dirty/
       .untagged) and silently hides them. Selected chips use
       `.chip.selected:not(.chip-match,.chip-isolated,.chip-flagged-review)`.
     - Never set colors in a theme's bare `button` rule: it outranks
       button.primary/.danger-ghost and every classed button.
     - Tab markers use background/box-shadow, never ::after — epic/legendary
       themes use every button's ::after for the premium hover-fill.
     Every theme carries its own --accent-flair (a third accent hue, distinct
     from --accent-auto/--accent-manual) used for navigational chrome —
     never hardcode it, always add it to new themes (it's in THEME_VARS).
     A handful of shared keyframes below are reused across themes with
     different colors via each theme's own var(...) references. */

  @keyframes flairPulse{ 0%,100%{ opacity:0.5; } 50%{ opacity:1; } }
  @keyframes flairDrift{ 0%{ background-position: 0% 0%, 100% 100%; } 100%{ background-position: 100% 100%, 0% 0%; } }
  @keyframes auroraDrift{ 0%{ background-position: 0% 0%, 100% 100%, 0 0; } 100%{ background-position: 100% 100%, 0% 0%, 0 0; } }
  @keyframes flairSweep{ 0%{ transform: translateX(-140%) skewX(-12deg); } 100%{ transform: translateX(140%) skewX(-12deg); } }
  @keyframes flairShimmer{ 0%{ background-position: -200% 0; } 100%{ background-position: 200% 0; } }
  @keyframes flairSlide{ 0%{ background-position: 0% 0%; } 100%{ background-position: 200% 0%; } }
  /* `--flair-play` gates every ambient flair* animation below in one place:
     each such rule ends with `animation-play-state: var(--flair-play);`
     instead of only the shorthand, so the Settings "Disable ambient
     animations" toggle (`html.no-flourish-ambient`, for motion-sensitive
     users who just want a theme's colors) can freeze all of them at once
     without touching each selector individually — any NEW flair*-animated
     rule just needs to add that same line to stay covered. This is one of
     three independent flourish toggles (see "Visual flourishes" near the
     end of this section) — ambient animations, hover-fill, and card-tilt
     can each be turned off on their own. */
  :root{ --flair-play: running; }
  html.no-flourish-ambient{ --flair-play: paused; }
  /* Idle GPU suspend (index.ts adds/removes `gpu-idle` on/no input): pausing
     every animation while nothing is happening lets the compositor sleep,
     which is what takes the hardware-accelerated app's at-rest GPU load from
     "constantly compositing" down to ~nothing. Only animations are frozen —
     transitions never run at rest, so popups/panels/menus behave normally
     the instant input resumes. */
  html.gpu-idle *, html.gpu-idle *::before, html.gpu-idle *::after{ animation-play-state: paused !important; }
  /* Anchor-restore (view.ts restoreAnchor): while the post-rebuild
     scroll alignment runs, drop the content-visibility skip so every
     built card lays out at its REAL height; the placeholder heights
     couldn't be trusted for the restore math. Back off after two RAFs
     (the virtualization is then free again). */
  #galleryGrid.anchor-resolving .card, #galleryGrid.anchor-resolving .compact-card,
  #compactGrid.anchor-resolving .compact-card{ content-visibility: visible !important; }

  /* ---- Cyberpunk — street HUD. Oxanium throughout, hard square corners,
     HUD corner brackets on every panel instead of a border box, and a
     chromatic-split brand lockup. ---- */
  html[data-theme="cyberpunk"]{
    --bg-base:#05060f;
    --bg-panel:#0b0e1e;
    --bg-elevated:#121633;
    --bg-elevated-2:#1a1f45;
    --border-soft:#2e2c6688;
    --border-strong:#4b3fb0;
    --text-primary:#eafcff;
    --text-muted:#8fa3c9;
    --text-faint:#566089;
    --accent-auto:#ff2fb0;
    --accent-auto-dim:#3d0e2c;
    --accent-manual:#22e6ff;
    --accent-manual-dim:#08303a;
    --accent-danger:#ff3860;
    --accent-success:#39ff9c;
    --accent-flair:#ff2fb0;
    --mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
    --sans: 'Oxanium', 'Segoe UI', system-ui, sans-serif;
    --radius: 0px;
    --brand-weight: 700; --brand-track: 0.14em; --brand-case: uppercase;
    --tab-case: uppercase; --tab-track: 0.12em;
    --head-case: uppercase; --head-track: 0.14em; --head-color: var(--accent-manual);
    --btn-weight: 600; --btn-track: 0.04em;
    --r-chip: 0px; --r-card: 0px; --r-panel: 0px; --r-check: 0px; --r-scroll: 0px;
    --card-shadow: none;
    --fx-fill: repeating-linear-gradient(0deg, var(--accent-auto) 0 1px, transparent 1px 3px); --fx-o: 0.55; --fx-w: 1;
    --fx-card-t: translateY(-2px); --fx-card-s: -2px 0 0 var(--accent-manual), 2px 0 0 var(--accent-auto), 0 12px 26px rgba(0,0,0,0.55);
    --icon-stroke: 1.75; --icon-cap: square; --icon-join: miter;
  }
  html[data-theme="cyberpunk"] .brand{
    text-shadow: 1.5px 0 0 color-mix(in srgb, var(--accent-auto) 80%, transparent), -1.5px 0 0 color-mix(in srgb, var(--accent-manual) 80%, transparent);
  }
  html[data-theme="cyberpunk"] .brand span.tag{ text-shadow: none; }
  html[data-theme="cyberpunk"] #topbar{ border-bottom: 1px solid var(--accent-auto); box-shadow: 0 3px 0 -2px var(--accent-manual); }
  /* HUD brackets: four L-shaped corner ticks painted as background layers,
     so the panel keeps its own box and nothing is clipped. */
  html[data-theme="cyberpunk"] .tool-section,
  html[data-theme="cyberpunk"] .settings-section{
    --hud: color-mix(in srgb, var(--accent-manual) 70%, transparent);
    background:
      linear-gradient(var(--hud), var(--hud)) top left / 10px 1px no-repeat,
      linear-gradient(var(--hud), var(--hud)) top left / 1px 10px no-repeat,
      linear-gradient(var(--hud), var(--hud)) top right / 10px 1px no-repeat,
      linear-gradient(var(--hud), var(--hud)) top right / 1px 10px no-repeat,
      linear-gradient(var(--hud), var(--hud)) bottom left / 10px 1px no-repeat,
      linear-gradient(var(--hud), var(--hud)) bottom left / 1px 10px no-repeat,
      linear-gradient(var(--hud), var(--hud)) bottom right / 10px 1px no-repeat,
      linear-gradient(var(--hud), var(--hud)) bottom right / 1px 10px no-repeat,
      var(--bg-elevated);
    border-color: color-mix(in srgb, var(--border-strong) 45%, transparent);
  }
  html[data-theme="cyberpunk"] .tab-btn.active{
    color: var(--accent-manual); border-bottom-color: var(--accent-auto);
    background: linear-gradient(to top, color-mix(in srgb, var(--accent-manual) 12%, transparent), transparent 70%);
  }
  html[data-theme="cyberpunk"] button.primary{ background: color-mix(in srgb, var(--accent-manual) 14%, var(--bg-panel)); }
  html[data-theme="cyberpunk"] .chip.selected:not(.chip-match,.chip-isolated,.chip-flagged-review){
    box-shadow: inset 3px 0 0 var(--accent-auto);
  }
  html[data-theme="cyberpunk"] .card{ transition: transform .12s ease, box-shadow .12s ease; }
  html[data-theme="cyberpunk"] .card:hover{ box-shadow: 0 0 0 1px var(--accent-manual); }

  /* ---- Oriental — lacquerware. Shippori Mincho lockup and section titles
     over Zen Kaku Gothic body text, a double gold-leaf rule under the
     topbar, a cinnabar seal for the brand tag, and a seigaiha wave lattice
     pressed faintly into the ground. ---- */
  html[data-theme="oriental"]{
    --bg-base:#140d0a;
    --bg-panel:#1c1210;
    --bg-elevated:#261714;
    --bg-elevated-2:#301c17;
    --border-soft:#5a2f1f88;
    --border-strong:#8a4a2e;
    --text-primary:#f5e6d3;
    --text-muted:#c9a98a;
    --text-faint:#8a6f57;
    --accent-auto:#d4a24e;
    --accent-auto-dim:#4a3a1a;
    --accent-manual:#c1443c;
    --accent-manual-dim:#3a1614;
    --accent-danger:#b5333f;
    --accent-success:#8fae6b;
    --accent-flair:#7a9e6e;
    --mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
    --sans: 'Zen Kaku Gothic New', 'Segoe UI', system-ui, sans-serif;
    --display: 'Shippori Mincho', Georgia, serif;
    --head-font: 'Shippori Mincho', Georgia, serif;
    --tab-font: 'Shippori Mincho', Georgia, serif;
    --radius: 2px;
    --brand-weight: 700; --brand-track: 0.04em;
    --tab-track: 0.04em;
    --head-track: 0.04em; --head-color: var(--accent-auto);
    --r-chip: 1px; --r-card: 2px; --r-panel: 2px; --r-check: 1px; --r-scroll: 0px;
    --card-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent-auto) 22%, transparent);
    --fx-fill: var(--accent-manual); --fx-o: 0.3;
    --fx-card-t: translateY(-2px); --fx-card-s: inset 0 0 0 1px var(--accent-auto), 0 10px 20px rgba(0,0,0,0.45);
  }
  html[data-theme="oriental"] #gallery{
    --wave: color-mix(in srgb, var(--accent-auto) 11%, transparent);
    background-image:
      radial-gradient(circle at 50% 100%, transparent 0 30%, var(--wave) 30% 34%, transparent 34% 50%, var(--wave) 50% 54%, transparent 54% 70%, var(--wave) 70% 74%, transparent 74%),
      radial-gradient(circle at 50% 100%, transparent 0 30%, var(--wave) 30% 34%, transparent 34% 50%, var(--wave) 50% 54%, transparent 54% 70%, var(--wave) 70% 74%, transparent 74%);
    background-size: 56px 28px, 56px 28px;
    background-position: 0 0, 28px 14px;
  }
  html[data-theme="oriental"] #topbar{
    border-bottom: 1px solid var(--accent-auto);
    box-shadow: 0 3px 0 -1px var(--bg-panel), 0 4px 0 -1px color-mix(in srgb, var(--accent-auto) 60%, transparent);
  }
  html[data-theme="oriental"] .brand span.tag{
    background: var(--accent-manual); color: var(--text-primary); border-radius: 1px;
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--text-primary) 35%, transparent);
  }
  html[data-theme="oriental"] button.danger-ghost{ color: color-mix(in srgb, var(--accent-danger) 55%, var(--text-primary)); font-weight: 700; }
  html[data-theme="oriental"] .tool-section,
  html[data-theme="oriental"] .inspector-box,
  html[data-theme="oriental"] .single-panel{
    background: linear-gradient(180deg, var(--bg-elevated), color-mix(in srgb, var(--bg-elevated) 70%, var(--bg-base)));
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent-auto) 26%, transparent);
  }
  html[data-theme="oriental"] .tab-btn.active{ color: var(--text-primary); border-bottom-color: var(--accent-manual); }
  html[data-theme="oriental"] button.primary{ box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent-auto) 50%, transparent); }
  html[data-theme="oriental"] .chip.selected:not(.chip-match,.chip-isolated,.chip-flagged-review){
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent-auto) 50%, transparent);
  }

  /* ---- Subway — transit signage. Overpass (the highway-sign face), a
     white header carrying the four route lines as a hard-edged strip,
     route-pill tabs, and station-sign section titles. ---- */
  html[data-theme="subway"]{
    --bg-base:#eef6ff;
    --bg-panel:#ffffff;
    --bg-elevated:#f4f8ff;
    --bg-elevated-2:#e8f0ff;
    --border-soft:#d3e3fa;
    --border-strong:#b7cff2;
    --text-primary:#14213d;
    --text-muted:#5b6b8c;
    --text-faint:#93a3c2;
    --accent-auto:#ff9f1c;
    --accent-auto-dim:#ffe3b8;
    --accent-manual:#2ec4f1;
    --accent-manual-dim:#d3f3fd;
    --accent-danger:#ff3f6c;
    --accent-success:#20b876;
    --accent-flair:#7a5cff;
    --mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
    --sans: 'Overpass', 'Trebuchet MS', 'Segoe UI', sans-serif;
    --radius: 14px; --r-ctl: 999px;
    --brand-weight: 900; --brand-track: -0.02em;
    --tab-track: 0.01em;
    --head-weight: 800; --head-case: uppercase; --head-track: 0.08em;
    --btn-weight: 600;
    --r-chip: 999px; --r-card: 14px; --r-panel: 14px; --r-check: 5px; --r-scroll: 999px;
    --card-shadow: 0 4px 14px color-mix(in srgb, var(--text-primary) 8%, transparent);
    --fx-fill: linear-gradient(90deg, var(--accent-auto) 0 25%, var(--accent-danger) 25% 50%, var(--accent-manual) 50% 75%, var(--accent-success) 75%);
    --fx-o: 1; --fx-top: calc(100% - 3px); --fx-h: 3px; --fx-w: 1;
    --fx-card-t: translateY(-4px); --fx-card-s: 0 14px 28px color-mix(in srgb, var(--text-primary) 16%, transparent);
    --icon-stroke: 2.25;
  }
  html[data-theme="subway"] #topbar{
    background: var(--bg-panel);
    border-bottom: none;
    background-image: linear-gradient(90deg, var(--accent-auto) 0 25%, var(--accent-danger) 25% 50%, var(--accent-manual) 50% 75%, var(--accent-success) 75%);
    background-size: 100% 6px; background-position: bottom; background-repeat: no-repeat;
  }
  html[data-theme="subway"] .brand span.tag{ background: var(--text-primary); color: var(--bg-panel); border-radius: 999px; padding: 2px 8px; }
  html[data-theme="subway"] .tab-btn{ border-radius: 999px; border-bottom: none; }
  /* White stays literal: it sits on a saturated accent fill, which night
     mode only darkens, so it keeps its contrast in both modes. */
  html[data-theme="subway"] .tab-btn.active{ background: var(--accent-flair); color: #ffffff; }
  html[data-theme="subway"] .tab-btn:hover:not(:disabled):not(.active){ background: var(--bg-elevated-2); }
  /* Station markers: each dock's status dot becomes a ringed stop on the line. */
  html[data-theme="subway"] .dot{ width: 9px; height: 9px; box-shadow: 0 0 0 2px var(--bg-elevated), 0 0 0 3.5px var(--text-primary); margin: 0 3px; }
  html[data-theme="subway"] .tool-section{ border-color: var(--border-soft); box-shadow: 0 2px 8px color-mix(in srgb, var(--text-primary) 5%, transparent); }
  html[data-theme="subway"] .card .thumbwrap{ border-radius: 13px 13px 0 0; }
  html[data-theme="subway"] .chip{ font-weight: 600; }
  html[data-theme="subway"] button.primary{ background: var(--accent-manual); color: var(--text-primary); border-color: var(--accent-manual); }
  html[data-theme="subway"] button.primary:hover:not(:disabled){ background: color-mix(in srgb, var(--accent-manual) 82%, var(--text-primary)); }

  /* ---- Osmium — the app's own brand (same tokens and faces as the GitHub
     Pages landing page: off-white ground, near-black ink, red/green/blue
     triad; Chakra Petch lockup, IBM Plex Sans body, JetBrains Mono tags).
     Deliberately restrained: ink-solid primary buttons, ink-inverted
     selected tags and tabs, and one signature — the hard-edged RGB strip
     under the topbar, echoing the landing mark's own stripe. ---- */
  html[data-theme="osmium"]{
    --bg-base:#f4f4f1;
    --bg-panel:#ffffff;
    --bg-elevated:#ececea;
    --bg-elevated-2:#e3e3db;
    --border-soft:#deded5;
    --border-strong:#b7b7ae;
    --text-primary:#141412;
    --text-muted:#4a4a45;
    --text-faint:#7a7a70;
    --accent-auto:#e14b3a;
    --accent-auto-dim:#f8ddd9;
    --accent-manual:#3b76d6;
    --accent-manual-dim:#dce8f9;
    --accent-danger:#c9372a;
    --accent-success:#3f9d4f;
    --accent-flair:#3f9d4f;
    --mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
    --sans: 'IBM Plex Sans', 'Segoe UI', system-ui, sans-serif;
    --display: 'Chakra Petch', 'Segoe UI', sans-serif;
    --head-font: 'Chakra Petch', 'Segoe UI', sans-serif;
    --tab-font: 'Chakra Petch', 'Segoe UI', sans-serif;
    --radius: 3px;
    --brand-weight: 700; --brand-track: 0.01em;
    --tab-case: uppercase; --tab-track: 0.08em;
    --head-weight: 700; --head-case: uppercase; --head-track: 0.08em;
    --r-chip: 3px; --r-card: 4px; --r-panel: 4px; --r-check: 2px; --r-scroll: 2px;
    --card-shadow: none;
    --fx-fill: linear-gradient(90deg, var(--accent-auto) 0 33.33%, var(--accent-success) 33.33% 66.66%, var(--accent-manual) 66.66%);
    --fx-o: 1; --fx-top: calc(100% - 2px); --fx-h: 2px; --fx-w: 1;
    --fx-card-t: translateY(-2px); --fx-card-s: 0 8px 18px color-mix(in srgb, var(--text-primary) 14%, transparent);
    --icon-stroke: 1.6; --icon-cap: square; --icon-join: miter;
  }
  html[data-theme="osmium"] #topbar{ border-bottom: 1px solid var(--border-strong); position: relative; }
  html[data-theme="osmium"] #topbar::after{
    content:'';
    position:absolute; left:0; right:0; bottom:-2px; height:2px;
    background: linear-gradient(90deg,
      var(--accent-auto) 0%, var(--accent-auto) 33.33%,
      var(--accent-success) 33.33%, var(--accent-success) 66.66%,
      var(--accent-manual) 66.66%, var(--accent-manual) 100%);
    pointer-events: none;
  }
  html[data-theme="osmium"] .brand span.tag{ background: var(--text-primary); color: var(--bg-panel); }
  html[data-theme="osmium"] .tab-btn.active{ color: var(--text-primary); border-bottom-color: var(--text-primary); }
  html[data-theme="osmium"] button.primary{ background: var(--text-primary); color: var(--bg-panel); border-color: var(--text-primary); }
  html[data-theme="osmium"] button.primary:hover:not(:disabled){ background: var(--accent-manual); border-color: var(--accent-manual); }
  html[data-theme="osmium"] .chip.selected:not(.chip-match,.chip-isolated,.chip-flagged-review){
    background: var(--text-primary); color: var(--bg-panel); border-color: var(--text-primary);
  }
  html[data-theme="osmium"] .tool-section{ background: var(--bg-panel); border-color: var(--border-strong); }

  /* ---- Terminal — a phosphor CRT. VT323 for the lockup, tabs and section
     prompts (each dock title reads as a `>` prompt), JetBrains Mono for
     everything else; zero radius; selected tabs and tags render inverted
     like a TUI highlight; faint scanlines. ---- */
  html[data-theme="terminal"]{
    --bg-base:#050805; --bg-panel:#0a120a; --bg-elevated:#0f1c0f; --bg-elevated-2:#142614;
    --border-soft:#1f4a1f88; --border-strong:#2f6b2f; --text-primary:#ccffcc; --text-muted:#6fbf6f;
    --text-faint:#3f7f3f; --accent-auto:#ffcc00; --accent-auto-dim:#4a3c00; --accent-manual:#00ff66;
    --accent-manual-dim:#003b16; --accent-danger:#ff5555; --accent-success:#00ff66; --accent-flair:#ffcc00;
    --mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
    --sans: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
    --display: 'VT323', 'JetBrains Mono', monospace;
    --head-font: 'VT323', 'JetBrains Mono', monospace;
    --tab-font: 'VT323', 'JetBrains Mono', monospace;
    --radius: 0px;
    --brand-weight: 400; --brand-track: 0.02em;
    --tab-case: uppercase; --tab-track: 0.04em;
    --head-weight: 400; --head-case: uppercase; --head-track: 0.04em; --head-color: var(--accent-manual);
    --btn-weight: 500;
    --r-chip: 0px; --r-card: 0px; --r-panel: 0px; --r-check: 0px; --r-scroll: 0px;
    --card-shadow: none;
    --fx-fill: var(--accent-manual); --fx-o: 0.25; --fx-w: 1;
    --fx-card-t: none; --fx-card-s: 0 0 0 1px var(--accent-manual);
    --icon-stroke: 2; --icon-cap: square; --icon-join: miter;
  }
  html[data-theme="terminal"] #gallery{
    /* Pure black, so the matrix glyphs (a canvas, ground-scroll.ts, drawn only
       while the gallery scroll option is on) are the only thing that moves. */
    background: #000;
  }
  html[data-theme="terminal"] .brand,
  html[data-theme="terminal"] .sec-head .t{ text-shadow: 0 0 6px color-mix(in srgb, var(--accent-manual) 45%, transparent); }
  html[data-theme="terminal"] .sec-head .t::before{ content: '> '; color: var(--accent-auto); }
  html[data-theme="terminal"] #topbar{ border-bottom: 1px dashed var(--border-strong); }
  html[data-theme="terminal"] #tabBar{ border-bottom: 1px dashed var(--border-strong); }
  html[data-theme="terminal"] .tab-btn.active{ background: var(--accent-manual); color: var(--bg-base); border-bottom-color: var(--accent-manual); }
  html[data-theme="terminal"] .tool-section{ border-style: dashed; border-color: var(--border-strong); }
  html[data-theme="terminal"] button.primary{ background: var(--accent-manual); color: var(--bg-base); }
  html[data-theme="terminal"] button.primary:hover:not(:disabled){ background: var(--accent-auto); border-color: var(--accent-auto); }
  html[data-theme="terminal"] .chip.selected:not(.chip-match,.chip-isolated,.chip-flagged-review){
    background: var(--accent-manual); color: var(--bg-base); border-color: var(--accent-manual);
  }
  html[data-theme="terminal"] ::selection{ background: var(--accent-auto); color: var(--bg-base); }

  /* ---- Sakura Dusk — washi and petals. Zen Maru Gothic (rounded), paper
     fibre ground, pill tags, petal-shaped tag selection, soft pill tabs. ---- */
  html[data-theme="sakura"]{
    --bg-base:#241a20; --bg-panel:#2e2027; --bg-elevated:#3a2830; --bg-elevated-2:#46313a;
    --border-soft:#6b455688; --border-strong:#8a5a72; --text-primary:#ffe3ef; --text-muted:#d99cb8;
    --text-faint:#a06a84; --accent-auto:#ffb8d9; --accent-auto-dim:#4a2c38; --accent-manual:#c9a6ff;
    --accent-manual-dim:#352840; --accent-danger:#ff6e91; --accent-success:#a6e3b8; --accent-flair:#ffe3ef;
    --mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
    --sans: 'Zen Maru Gothic', 'Segoe UI', system-ui, sans-serif;
    --radius: 16px; --r-ctl: 999px;
    --brand-weight: 700; --brand-track: 0.02em;
    --head-color: var(--accent-auto);
    --r-chip: 999px; --r-card: 18px; --r-panel: 18px; --r-check: 6px; --r-scroll: 999px;
    --card-shadow: 0 6px 16px rgba(0,0,0,0.2);
    --fx-fill: var(--accent-auto); --fx-o: 0.22;
    --fx-card-t: translateY(-3px) rotate(0.4deg); --fx-card-s: 0 12px 24px rgba(0,0,0,0.35);
    --icon-stroke: 1.6;
  }
  html[data-theme="sakura"] #gallery{
    background-image:
      repeating-linear-gradient(33deg, color-mix(in srgb, var(--accent-auto) 7%, transparent) 0 1px, transparent 1px 7px),
      repeating-linear-gradient(-58deg, color-mix(in srgb, var(--text-primary) 4%, transparent) 0 1px, transparent 1px 11px);
  }
  html[data-theme="sakura"] #topbar{ border-bottom: 1px solid var(--border-strong); }
  html[data-theme="sakura"] #tabBar{ gap: 6px; border-bottom-color: var(--border-strong); }
  html[data-theme="sakura"] .tab-btn{ border-radius: 999px; border-bottom: none; }
  html[data-theme="sakura"] .tab-btn.active{ background: var(--accent-auto-dim); color: var(--accent-auto); }
  /* Petal cut: the selected tag drops its pill for a petal (two round
     lobes, two pointed tips). */
  html[data-theme="sakura"] .chip.selected:not(.chip-match,.chip-isolated,.chip-flagged-review){ border-radius: 14px 2px 14px 2px; }
  html[data-theme="sakura"] .card .thumbwrap{ border-radius: 17px 17px 0 0; }
  html[data-theme="sakura"] .card{ transition: transform 0.18s ease, box-shadow 0.18s ease; }
  html[data-theme="sakura"] .card:hover{ transform: translateY(-2px); }

  /* ---- Bioluminescent Deep — the abyss, lit from below. Sora, panels
     lit from their floor, and a photophore (a lit dot) marking the active
     tab instead of an underline. ---- */
  html[data-theme="bioluminescent"]{
    --bg-base:#03080d; --bg-panel:#061119; --bg-elevated:#0a1a26; --bg-elevated-2:#0f2432;
    --border-soft:#164a5a88; --border-strong:#1f6b80; --text-primary:#d6fbff; --text-muted:#6fc9d6;
    --text-faint:#3f7f8c; --accent-auto:#7b5bff; --accent-auto-dim:#241a4a; --accent-manual:#26e0c9;
    --accent-manual-dim:#0a3a34; --accent-danger:#ff5f8f; --accent-success:#26e0c9; --accent-flair:#4fd6ff;
    --mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
    --sans: 'Sora', 'Segoe UI', system-ui, sans-serif;
    --radius: 11px;
    --brand-weight: 600; --brand-track: -0.01em;
    --head-weight: 600; --head-color: var(--accent-flair);
    --btn-weight: 400;
    --r-chip: 999px; --r-card: 14px; --r-panel: 14px; --r-check: 5px; --r-scroll: 999px;
    --card-shadow: 0 8px 20px rgba(0,0,0,0.45);
    --fx-fill: linear-gradient(to top, var(--accent-manual), transparent); --fx-o: 0.35; --fx-w: 1;
    --fx-card-t: translateY(-3px); --fx-card-s: 0 12px 28px color-mix(in srgb, var(--accent-manual) 22%, transparent);
  }
  html[data-theme="bioluminescent"] #gallery{
    /* The glow, then plankton: one irregular scattered SVG tile (not a grid of dots), so the gallery scroll
       option has something to carry. Fixed colours, like Copper Forge's tile. */
    background-image: linear-gradient(to top, color-mix(in srgb, var(--accent-manual) 11%, transparent), transparent 45%),
      url("data:image/svg+xml,%3Csvg xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22 width%3D%22653%22 height%3D%22577%22 viewBox%3D%220 0 653 577%22%3E%3Cdefs%3E%3CradialGradient id%3D%22h%22%3E%3Cstop offset%3D%220%22 stop-color%3D%22%2326e0c9%22 stop-opacity%3D%22.16%22%2F%3E%3Cstop offset%3D%22.35%22 stop-color%3D%22%2326e0c9%22 stop-opacity%3D%22.05%22%2F%3E%3Cstop offset%3D%221%22 stop-color%3D%22%2326e0c9%22 stop-opacity%3D%220%22%2F%3E%3C%2FradialGradient%3E%3C%2Fdefs%3E%3Ccircle cx%3D%2273.6%22 cy%3D%2253.2%22 r%3D%221.4%22 fill%3D%22%2326e0c9%22 fill-opacity%3D%220.106%22%2F%3E%3Ccircle cx%3D%22377.5%22 cy%3D%22304.9%22 r%3D%220.8%22 fill%3D%22%2326e0c9%22 fill-opacity%3D%220.184%22%2F%3E%3Ccircle cx%3D%22454.9%22 cy%3D%2228.7%22 r%3D%220.9%22 fill%3D%22%2326e0c9%22 fill-opacity%3D%220.149%22%2F%3E%3Ccircle cx%3D%22154.8%22 cy%3D%22419.4%22 r%3D%2212.4%22 fill%3D%22url(%23h)%22%2F%3E%3Ccircle cx%3D%22154.8%22 cy%3D%22419.4%22 r%3D%221.6%22 fill%3D%22%2326e0c9%22 fill-opacity%3D%22.3%22%2F%3E%3Ccircle cx%3D%22113.9%22 cy%3D%22256.2%22 r%3D%220.9%22 fill%3D%22%234fd6ff%22 fill-opacity%3D%220.159%22%2F%3E%3Ccircle cx%3D%22583.4%22 cy%3D%22150%22 r%3D%221%22 fill%3D%22%2326e0c9%22 fill-opacity%3D%220.152%22%2F%3E%3Ccircle cx%3D%22558%22 cy%3D%22351.5%22 r%3D%220.8%22 fill%3D%22%2326e0c9%22 fill-opacity%3D%220.122%22%2F%3E%3Ccircle cx%3D%22244.4%22 cy%3D%2221.2%22 r%3D%220.8%22 fill%3D%22%2326e0c9%22 fill-opacity%3D%220.12300000000000001%22%2F%3E%3Ccircle cx%3D%22341.6%22 cy%3D%22441%22 r%3D%221%22 fill%3D%22%2326e0c9%22 fill-opacity%3D%220.096%22%2F%3E%3Ccircle cx%3D%22312.9%22 cy%3D%22150.4%22 r%3D%220.9%22 fill%3D%22%2326e0c9%22 fill-opacity%3D%220.129%22%2F%3E%3Ccircle cx%3D%22427.6%22 cy%3D%22409.8%22 r%3D%229.7%22 fill%3D%22url(%23h)%22%2F%3E%3Ccircle cx%3D%22427.6%22 cy%3D%22409.8%22 r%3D%221.8%22 fill%3D%22%2326e0c9%22 fill-opacity%3D%22.3%22%2F%3E%3Ccircle cx%3D%22623.4%22 cy%3D%22536%22 r%3D%221%22 fill%3D%22%234fd6ff%22 fill-opacity%3D%220.174%22%2F%3E%3Ccircle cx%3D%22488.9%22 cy%3D%22210.7%22 r%3D%220.6%22 fill%3D%22%2326e0c9%22 fill-opacity%3D%220.233%22%2F%3E%3Ccircle cx%3D%22254.5%22 cy%3D%22312.3%22 r%3D%221%22 fill%3D%22%2326e0c9%22 fill-opacity%3D%220.086%22%2F%3E%3Ccircle cx%3D%2238.9%22 cy%3D%22394.7%22 r%3D%221%22 fill%3D%22%2326e0c9%22 fill-opacity%3D%220.18600000000000003%22%2F%3E%3Ccircle cx%3D%22350.1%22 cy%3D%2244%22 r%3D%221%22 fill%3D%22%2326e0c9%22 fill-opacity%3D%220.14400000000000002%22%2F%3E%3Ccircle cx%3D%22558.3%22 cy%3D%22448.6%22 r%3D%220.6%22 fill%3D%22%2326e0c9%22 fill-opacity%3D%220.16699999999999998%22%2F%3E%3Ccircle cx%3D%2222.7%22 cy%3D%22235.6%22 r%3D%2211.9%22 fill%3D%22url(%23h)%22%2F%3E%3Ccircle cx%3D%2222.7%22 cy%3D%22235.6%22 r%3D%221.3%22 fill%3D%22%2326e0c9%22 fill-opacity%3D%22.3%22%2F%3E%3Ccircle cx%3D%22561.8%22 cy%3D%2228%22 r%3D%221.4%22 fill%3D%22%234fd6ff%22 fill-opacity%3D%220.231%22%2F%3E%3Ccircle cx%3D%22122.8%22 cy%3D%22521.9%22 r%3D%220.8%22 fill%3D%22%2326e0c9%22 fill-opacity%3D%220.212%22%2F%3E%3Ccircle cx%3D%22153.3%22 cy%3D%22153.8%22 r%3D%221.2%22 fill%3D%22%234fd6ff%22 fill-opacity%3D%220.19899999999999998%22%2F%3E%3Ccircle cx%3D%22304.8%22 cy%3D%22528.5%22 r%3D%220.7%22 fill%3D%22%2326e0c9%22 fill-opacity%3D%220.23399999999999999%22%2F%3E%3Ccircle cx%3D%2250.6%22 cy%3D%22153.4%22 r%3D%221.1%22 fill%3D%22%2326e0c9%22 fill-opacity%3D%220.133%22%2F%3E%3Ccircle cx%3D%2240.1%22 cy%3D%22487.7%22 r%3D%221.4%22 fill%3D%22%2326e0c9%22 fill-opacity%3D%220.121%22%2F%3E%3Ccircle cx%3D%22219.2%22 cy%3D%22380.2%22 r%3D%229.8%22 fill%3D%22url(%23h)%22%2F%3E%3Ccircle cx%3D%22219.2%22 cy%3D%22380.2%22 r%3D%221.9%22 fill%3D%22%2326e0c9%22 fill-opacity%3D%22.3%22%2F%3E%3Ccircle cx%3D%22400.1%22 cy%3D%22158.6%22 r%3D%220.7%22 fill%3D%22%2326e0c9%22 fill-opacity%3D%220.09%22%2F%3E%3Ccircle cx%3D%22516.9%22 cy%3D%22112.7%22 r%3D%220.7%22 fill%3D%22%2326e0c9%22 fill-opacity%3D%220.08900000000000001%22%2F%3E%3Ccircle cx%3D%22377%22 cy%3D%22229.2%22 r%3D%220.6%22 fill%3D%22%2326e0c9%22 fill-opacity%3D%220.225%22%2F%3E%3Ccircle cx%3D%22577.4%22 cy%3D%22250.6%22 r%3D%220.6%22 fill%3D%22%234fd6ff%22 fill-opacity%3D%220.102%22%2F%3E%3Ccircle cx%3D%22264.3%22 cy%3D%22453.7%22 r%3D%220.9%22 fill%3D%22%2326e0c9%22 fill-opacity%3D%220.22399999999999998%22%2F%3E%3C%2Fsvg%3E");
    background-size: 100% 100%, 653px 577px;
  }
  html[data-theme="bioluminescent"] #topbar{ border-bottom: 1px solid var(--border-soft); }
  html[data-theme="bioluminescent"] .tool-section{
    background: linear-gradient(to top, color-mix(in srgb, var(--accent-manual) 16%, var(--bg-elevated)), var(--bg-elevated) 70%);
    box-shadow: inset 0 -1px 0 color-mix(in srgb, var(--accent-manual) 55%, transparent);
  }
  html[data-theme="bioluminescent"] .tab-btn.active{
    color: var(--text-primary); border-bottom-color: transparent;
    background: radial-gradient(circle at 50% calc(100% - 4px), var(--accent-flair) 0 2.5px, color-mix(in srgb, var(--accent-flair) 30%, transparent) 3.5px, transparent 9px);
  }
  html[data-theme="bioluminescent"] .brand span.tag{ color: var(--text-primary); animation: flairPulse 2.6s ease-in-out infinite; animation-play-state: var(--flair-play); }
  html[data-theme="bioluminescent"] .chip.selected:not(.chip-match,.chip-isolated,.chip-flagged-review){
    box-shadow: inset 0 0 8px color-mix(in srgb, var(--accent-manual) 35%, transparent);
  }

  /* ---- Royal Amethyst (epic) — a gilded jewel box. Cinzel capitals for
     the lockup, tabs and titles over Figtree, a double gilt rule under the
     topbar, gold-framed panels and tags. ---- */
  html[data-theme="amethyst"]{
    --bg-base:#170b26; --bg-panel:#1f0f31; --bg-elevated:#28143d; --bg-elevated-2:#331a4b;
    --border-soft:#4a2a6688; --border-strong:#6b3d94; --text-primary:#f1e6ff; --text-muted:#b79cd6;
    --text-faint:#7d68a0; --accent-auto:#e8c468; --accent-auto-dim:#4a3a1a; --accent-manual:#c9a6ff;
    --accent-manual-dim:#3a2a50; --accent-danger:#ff6e91; --accent-success:#a6e3b8; --accent-flair:#e8c468;
    --mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
    --sans: 'Figtree', 'Segoe UI', system-ui, sans-serif;
    --display: 'Cinzel', Georgia, serif;
    --head-font: 'Cinzel', Georgia, serif;
    --tab-font: 'Cinzel', Georgia, serif;
    --radius: 3px;
    --brand-weight: 700; --brand-track: 0.06em;
    --tab-track: 0.06em;
    --head-track: 0.06em; --head-color: var(--accent-flair);
    --r-chip: 3px; --r-card: 4px; --r-panel: 4px; --r-check: 2px; --r-scroll: 2px;
    --card-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent-flair) 20%, transparent);
    --fx-fill: linear-gradient(90deg, color-mix(in srgb, var(--accent-flair) 40%, transparent), var(--accent-flair));
    --fx-o: 1; --fx-top: calc(100% - 2px); --fx-h: 2px; --fx-w: 1;
    --fx-card-t: translateY(-3px); --fx-card-s: inset 0 0 0 1px var(--accent-flair), 0 12px 26px rgba(0,0,0,0.45);
    --icon-stroke: 1.4;
  }
  html[data-theme="amethyst"] .brand{ color: var(--accent-flair); }
  html[data-theme="amethyst"] #topbar{
    border-bottom: 1px solid var(--accent-flair);
    box-shadow: 0 3px 0 -1px var(--bg-panel), 0 4px 0 -1px color-mix(in srgb, var(--accent-flair) 55%, transparent);
  }
  html[data-theme="amethyst"] .tool-section,
  html[data-theme="amethyst"] .settings-section{
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent-flair) 20%, transparent), inset 0 0 0 4px var(--bg-elevated), inset 0 0 0 5px color-mix(in srgb, var(--accent-flair) 9%, transparent);
  }
  html[data-theme="amethyst"] .tab-btn.active{ color: var(--accent-flair); border-bottom-color: var(--accent-flair); }
  html[data-theme="amethyst"] button.primary{ box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent-flair) 45%, transparent); }
  html[data-theme="amethyst"] .chip.selected:not(.chip-match,.chip-isolated,.chip-flagged-review){
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent-flair) 55%, transparent);
  }

  /* ---- Solar Flare (legendary) — photosphere. Anybody at heavy weight,
     uppercase, a heat-gradient rule under the header, primary buttons that
     glow from the core outward, a corona breaking over the top edge. ---- */
  html[data-theme="solarflare"]{
    --bg-base:#1a0800; --bg-panel:#231000; --bg-elevated:#2e1600; --bg-elevated-2:#3a1d00;
    --border-soft:#5c2c0088; --border-strong:#7a3d00; --text-primary:#fff1e0; --text-muted:#e0a675;
    --text-faint:#a67650; --accent-auto:#ffd166; --accent-auto-dim:#4a3200; --accent-manual:#ff6a1f;
    --accent-manual-dim:#4a2200; --accent-danger:#ff4d4d; --accent-success:#8fbf7f; --accent-flair:#ffd166;
    --mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
    --sans: 'Anybody', 'Segoe UI', system-ui, sans-serif;
    --radius: 4px;
    --brand-weight: 900; --brand-track: 0.02em; --brand-case: uppercase;
    --tab-case: uppercase; --tab-track: 0.08em;
    --head-weight: 800; --head-case: uppercase; --head-track: 0.06em; --head-color: var(--accent-flair);
    --btn-weight: 600;
    --r-chip: 3px; --r-card: 6px; --r-panel: 6px; --r-check: 3px; --r-scroll: 3px;
    --card-shadow: 0 2px 6px rgba(0,0,0,0.35);
    --fx-fill: linear-gradient(90deg, var(--accent-manual), var(--accent-flair)); --fx-o: 0.35; --fx-w: 1;
    --fx-card-t: translateY(-4px); --fx-card-s: 0 14px 30px color-mix(in srgb, var(--accent-manual) 28%, transparent);
    --icon-stroke: 2.1;
  }
  html[data-theme="solarflare"] #gallery{
    /* The glow, then embers: one irregular scattered SVG tile (not a grid of dots), so the gallery scroll
       option has something to carry. Fixed colours, like Copper Forge's tile. */
    background-image: radial-gradient(ellipse 80% 45% at 50% -8%, color-mix(in srgb, var(--accent-manual) 14%, transparent), transparent 70%),
      url("data:image/svg+xml,%3Csvg xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22 width%3D%22641%22 height%3D%22563%22 viewBox%3D%220 0 641 563%22%3E%3Crect x%3D%22327.6%22 y%3D%22297.5%22 width%3D%221%22 height%3D%2210.7%22 rx%3D%22.5%22 fill%3D%22%23ff6a1f%22 fill-opacity%3D%220.113%22%2F%3E%3Ccircle cx%3D%2261%22 cy%3D%22464.1%22 r%3D%221.5%22 fill%3D%22%23ff6a1f%22 fill-opacity%3D%220.149%22%2F%3E%3Ccircle cx%3D%2290.9%22 cy%3D%22211.5%22 r%3D%220.9%22 fill%3D%22%23ff6a1f%22 fill-opacity%3D%220.235%22%2F%3E%3Ccircle cx%3D%22412.9%22 cy%3D%22546.6%22 r%3D%220.9%22 fill%3D%22%23ff6a1f%22 fill-opacity%3D%220.16%22%2F%3E%3Ccircle cx%3D%22247.5%22 cy%3D%22110.9%22 r%3D%221.4%22 fill%3D%22%23ff6a1f%22 fill-opacity%3D%220.195%22%2F%3E%3Ccircle cx%3D%2253%22 cy%3D%2259.4%22 r%3D%227.6%22 fill%3D%22%23ff6a1f%22 fill-opacity%3D%22.035%22%2F%3E%3Ccircle cx%3D%22555.6%22 cy%3D%22355.9%22 r%3D%220.9%22 fill%3D%22%23ff6a1f%22 fill-opacity%3D%220.124%22%2F%3E%3Ccircle cx%3D%22485%22 cy%3D%22149.2%22 r%3D%221.1%22 fill%3D%22%23ffd166%22 fill-opacity%3D%220.12%22%2F%3E%3Ccircle cx%3D%22622.7%22 cy%3D%22200.3%22 r%3D%221.1%22 fill%3D%22%23ff6a1f%22 fill-opacity%3D%220.157%22%2F%3E%3Crect x%3D%22215.7%22 y%3D%22394.1%22 width%3D%221%22 height%3D%2212.1%22 rx%3D%22.5%22 fill%3D%22%23ff6a1f%22 fill-opacity%3D%220.11900000000000001%22%2F%3E%3Ccircle cx%3D%22277.5%22 cy%3D%22521.8%22 r%3D%221.4%22 fill%3D%22%23ff6a1f%22 fill-opacity%3D%220.284%22%2F%3E%3Ccircle cx%3D%22452.9%22 cy%3D%22417.9%22 r%3D%221%22 fill%3D%22%23ff6a1f%22 fill-opacity%3D%220.28800000000000003%22%2F%3E%3Ccircle cx%3D%22397.6%22 cy%3D%22101.9%22 r%3D%221%22 fill%3D%22%23ff6a1f%22 fill-opacity%3D%220.10300000000000001%22%2F%3E%3Ccircle cx%3D%22539.8%22 cy%3D%22265.4%22 r%3D%221.2%22 fill%3D%22%23ff6a1f%22 fill-opacity%3D%220.271%22%2F%3E%3Ccircle cx%3D%2291.7%22 cy%3D%22351.1%22 r%3D%220.9%22 fill%3D%22%23ff6a1f%22 fill-opacity%3D%220.2%22%2F%3E%3Ccircle cx%3D%22174.7%22 cy%3D%2246.8%22 r%3D%221%22 fill%3D%22%23ffd166%22 fill-opacity%3D%220.259%22%2F%3E%3Ccircle cx%3D%22212.1%22 cy%3D%22291.1%22 r%3D%2210.7%22 fill%3D%22%23ff6a1f%22 fill-opacity%3D%22.035%22%2F%3E%3Ccircle cx%3D%22596.4%22 cy%3D%2291.2%22 r%3D%220.6%22 fill%3D%22%23ff6a1f%22 fill-opacity%3D%220.185%22%2F%3E%3Crect x%3D%2220.5%22 y%3D%22322.6%22 width%3D%221%22 height%3D%2213.4%22 rx%3D%22.5%22 fill%3D%22%23ff6a1f%22 fill-opacity%3D%220.121%22%2F%3E%3Ccircle cx%3D%22410.4%22 cy%3D%22335%22 r%3D%221.3%22 fill%3D%22%23ff6a1f%22 fill-opacity%3D%220.11699999999999999%22%2F%3E%3Ccircle cx%3D%22439.9%22 cy%3D%22247.4%22 r%3D%221%22 fill%3D%22%23ff6a1f%22 fill-opacity%3D%220.141%22%2F%3E%3Ccircle cx%3D%22326.1%22 cy%3D%22384.4%22 r%3D%220.7%22 fill%3D%22%23ffd166%22 fill-opacity%3D%220.14%22%2F%3E%3Ccircle cx%3D%22519%22 cy%3D%2218%22 r%3D%220.7%22 fill%3D%22%23ff6a1f%22 fill-opacity%3D%220.129%22%2F%3E%3Ccircle cx%3D%22155.4%22 cy%3D%22122.3%22 r%3D%221.4%22 fill%3D%22%23ffd166%22 fill-opacity%3D%220.157%22%2F%3E%3Ccircle cx%3D%22586.4%22 cy%3D%22474.9%22 r%3D%221.2%22 fill%3D%22%23ffd166%22 fill-opacity%3D%220.151%22%2F%3E%3Ccircle cx%3D%22102.3%22 cy%3D%22548.8%22 r%3D%221.4%22 fill%3D%22%23ffd166%22 fill-opacity%3D%220.212%22%2F%3E%3Ccircle cx%3D%22369.1%22 cy%3D%22479.9%22 r%3D%221.1%22 fill%3D%22%23ff6a1f%22 fill-opacity%3D%220.207%22%2F%3E%3Crect x%3D%22355.6%22 y%3D%2238%22 width%3D%221%22 height%3D%2211.9%22 rx%3D%22.5%22 fill%3D%22%23ff6a1f%22 fill-opacity%3D%220.154%22%2F%3E%3Ccircle cx%3D%22115.7%22 cy%3D%2274%22 r%3D%220.8%22 fill%3D%22%23ff6a1f%22 fill-opacity%3D%220.27699999999999997%22%2F%3E%3Ccircle cx%3D%22264.9%22 cy%3D%22197%22 r%3D%221.1%22 fill%3D%22%23ffd166%22 fill-opacity%3D%220.297%22%2F%3E%3Ccircle cx%3D%22620.7%22 cy%3D%22271.6%22 r%3D%220.6%22 fill%3D%22%23ffd166%22 fill-opacity%3D%220.272%22%2F%3E%3Ccircle cx%3D%22157.2%22 cy%3D%22402.3%22 r%3D%220.8%22 fill%3D%22%23ff6a1f%22 fill-opacity%3D%220.292%22%2F%3E%3Ccircle cx%3D%22508.3%22 cy%3D%22504.6%22 r%3D%221.2%22 fill%3D%22%23ff6a1f%22 fill-opacity%3D%220.135%22%2F%3E%3Ccircle cx%3D%22545.8%22 cy%3D%22167.2%22 r%3D%221.5%22 fill%3D%22%23ffd166%22 fill-opacity%3D%220.223%22%2F%3E%3C%2Fsvg%3E");
    background-size: 100% 100%, 641px 563px;
  }
  html[data-theme="solarflare"] #topbar{
    border-bottom: none;
    background-image: linear-gradient(90deg, var(--accent-manual), var(--accent-flair) 50%, var(--accent-manual));
    background-size: 100% 2px; background-position: bottom; background-repeat: no-repeat;
  }
  /* Flare: the active tab burns up from its edge. */
  html[data-theme="solarflare"] .tab-btn.active{
    color: var(--accent-flair); border-bottom-color: var(--accent-manual);
    background: radial-gradient(ellipse 70% 90% at 50% 100%, color-mix(in srgb, var(--accent-manual) 35%, transparent), transparent 70%);
  }
  html[data-theme="solarflare"] button.primary{
    background: radial-gradient(ellipse at 50% 120%, color-mix(in srgb, var(--accent-manual) 45%, var(--accent-manual-dim)), var(--accent-manual-dim) 70%);
  }
  html[data-theme="solarflare"] .tool-section{ border-top-color: color-mix(in srgb, var(--accent-manual) 45%, var(--border-soft)); }

  /* ---- 20 additional shop themes ---- */

  /* ---- Midnight Ocean — the nautical chart. Barlow Semi Condensed,
     chart-grid ground, and every panel framed by a double neatline the way
     a chart's border is. ---- */
  html[data-theme="midnight-ocean"]{
    --bg-base:#040c14; --bg-panel:#071827; --bg-elevated:#0c2436; --bg-elevated-2:#123247;
    --border-soft:#1c4a6688; --border-strong:#2c6e94; --text-primary:#e3f4ff; --text-muted:#7fa8c2;
    --text-faint:#4d7086; --accent-auto:#f2a65a; --accent-auto-dim:#4a2f14; --accent-manual:#3ddbd9;
    --accent-manual-dim:#0f3a3a; --accent-danger:#ff6b6b; --accent-success:#5fd88f; --accent-flair:#f2a65a;
    --mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
    --sans: 'Barlow Semi Condensed', 'Segoe UI', system-ui, sans-serif;
    --radius: 2px;
    --brand-weight: 600; --brand-track: 0.03em;
    --tab-case: uppercase; --tab-track: 0.1em;
    --head-weight: 600; --head-case: uppercase; --head-track: 0.12em; --head-color: var(--accent-manual);
    --r-chip: 2px; --r-card: 2px; --r-panel: 2px; --r-check: 1px; --r-scroll: 0px;
    --card-shadow: none;
    --fx-fill: var(--accent-flair); --fx-o: 0.22;
    --fx-card-t: translateY(-2px); --fx-card-s: 0 0 0 1px var(--accent-manual), 0 10px 22px rgba(0,0,0,0.45);
    --icon-stroke: 1.6;
  }
  html[data-theme="midnight-ocean"] #gallery{
    background-image:
      linear-gradient(color-mix(in srgb, var(--accent-manual) 9%, transparent) 1px, transparent 1px),
      linear-gradient(90deg, color-mix(in srgb, var(--accent-manual) 9%, transparent) 1px, transparent 1px);
    background-size: 96px 96px;
  }
  html[data-theme="midnight-ocean"] .tool-section,
  html[data-theme="midnight-ocean"] .settings-section{
    box-shadow: inset 0 0 0 3px var(--bg-elevated), inset 0 0 0 4px color-mix(in srgb, var(--border-strong) 55%, transparent);
  }
  html[data-theme="midnight-ocean"] #topbar{ border-bottom: 1px solid var(--border-strong); box-shadow: 0 3px 0 -2px var(--border-strong); }
  html[data-theme="midnight-ocean"] input::placeholder, html[data-theme="midnight-ocean"] textarea::placeholder{ color: var(--text-muted); }
  /* Sounding line: the active tab's rule carries a plumb tick. */
  html[data-theme="midnight-ocean"] .tab-btn.active{
    color: var(--accent-flair); border-bottom-color: var(--accent-flair);
    background: linear-gradient(var(--accent-flair), var(--accent-flair)) bottom center / 1px 7px no-repeat;
  }

  /* ---- Arctic Frost — pack ice. Albert Sans, crisp hairlines, a lit
     top edge on every surface like the face of a snow cut, a faint
     crystalline hatch in the ground. No glass blur. ---- */
  html[data-theme="arctic-frost"]{
    --bg-base:#eef5fb; --bg-panel:#ffffff; --bg-elevated:#f3f9fd; --bg-elevated-2:#e6f1fa;
    --border-soft:#cfe3f2; --border-strong:#a9c9e0; --text-primary:#173049; --text-muted:#4d6d89;
    --text-faint:#8aa5bc; --accent-auto:#5aa9e6; --accent-auto-dim:#dcefff; --accent-manual:#2f7fb8;
    --accent-manual-dim:#d6ecfa; --accent-danger:#e15668; --accent-success:#3fae7c; --accent-flair:#8fd6ff;
    --mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
    --sans: 'Albert Sans', 'Segoe UI', system-ui, sans-serif;
    --radius: 6px;
    --brand-weight: 800; --brand-track: -0.01em;
    --head-weight: 700; --head-case: uppercase; --head-track: 0.14em; --head-color: var(--accent-manual);
    --r-chip: 4px; --r-card: 8px; --r-panel: 8px; --r-check: 3px; --r-scroll: 4px;
    --card-shadow: 0 1px 0 var(--border-soft), 0 4px 12px color-mix(in srgb, var(--accent-manual) 7%, transparent);
    --fx-fill: linear-gradient(90deg, var(--accent-flair), color-mix(in srgb, var(--accent-flair) 20%, transparent)); --fx-o: 0.5;
    --fx-card-t: translateY(-3px); --fx-card-s: 0 12px 26px color-mix(in srgb, var(--accent-manual) 16%, transparent);
  }
  html[data-theme="arctic-frost"] #gallery{
    background-image:
      repeating-linear-gradient(60deg, color-mix(in srgb, var(--accent-auto) 9%, transparent) 0 1px, transparent 1px 22px),
      repeating-linear-gradient(-60deg, color-mix(in srgb, var(--accent-auto) 9%, transparent) 0 1px, transparent 1px 22px);
  }
  html[data-theme="arctic-frost"] .tool-section,
  html[data-theme="arctic-frost"] .theme-panel{
    background: var(--bg-panel);
    box-shadow: inset 0 2px 0 var(--accent-flair), 0 2px 8px color-mix(in srgb, var(--accent-manual) 6%, transparent);
  }
  html[data-theme="arctic-frost"] #topbar{ border-bottom: 1px solid var(--border-strong); box-shadow: 0 1px 0 var(--bg-panel); }
  html[data-theme="arctic-frost"] .tab-btn.active{ color: var(--accent-manual); border-bottom-color: var(--accent-flair); box-shadow: inset 0 -1px 0 var(--accent-flair); }

  /* ---- Forest Moss — the field guide. Alegreya titles over Alegreya
     Sans, leaf-cut asymmetric corners, topographic contour lines in the
     ground. ---- */
  html[data-theme="forest-moss"]{
    --bg-base:#0e130d; --bg-panel:#151d13; --bg-elevated:#1c271a; --bg-elevated-2:#243322;
    --border-soft:#3d5a3688; --border-strong:#547a4c; --text-primary:#e6f0e0; --text-muted:#a3bd97;
    --text-faint:#708a65; --accent-auto:#d7b568; --accent-auto-dim:#463a1c; --accent-manual:#7cb454;
    --accent-manual-dim:#243a1a; --accent-danger:#d9694f; --accent-success:#7cb454; --accent-flair:#d7b568;
    --mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
    --sans: 'Alegreya Sans', 'Segoe UI', system-ui, sans-serif;
    --display: 'Alegreya', Georgia, serif;
    --head-font: 'Alegreya', Georgia, serif;
    --tab-font: 'Alegreya', Georgia, serif;
    --radius: 9px;
    --brand-weight: 700; --brand-track: 0;
    --head-weight: 700; --head-color: var(--accent-flair);
    --r-ctl: 10px 2px 10px 2px; --r-chip: 9px 1px 9px 1px; --r-card: 18px 3px 18px 3px; --r-panel: 18px 3px 18px 3px;
    --r-check: 5px 1px 5px 1px; --r-scroll: 6px 2px;
    --card-shadow: 0 4px 12px rgba(0,0,0,0.3);
    --fx-fill: var(--accent-manual); --fx-o: 0.25;
    --fx-card-t: translateY(-3px) rotate(-0.5deg); --fx-card-s: 0 12px 24px rgba(0,0,0,0.45);
  }
  html[data-theme="forest-moss"] #gallery{
    /* Slowly flowing contour lines: one SVG tile whose lines drift sideways and sway at different speeds, so
       they warp and twist against each other. Animated inside the image (CSS in the SVG, off under reduced motion);
       the gallery scroll option also carries the tile diagonally. Fixed colours, like Copper Forge's tile. */
    background-image: url("data:image/svg+xml,%3Csvg xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22 width%3D%22640%22 height%3D%22560%22 viewBox%3D%220 0 640 560%22%3E%3Cstyle%3E%40keyframes x%7Bto%7Btransform%3AtranslateX(640px)%7D%7D%40keyframes y%7Bfrom%7Btransform%3AtranslateY(-5px)%7Dto%7Btransform%3AtranslateY(5px)%7D%7D%40media (prefers-reduced-motion%3Areduce)%7B*%7Banimation%3Anone!important%7D%7D%3C%2Fstyle%3E%3Cg style%3D%22animation%3Ay 31s ease-in-out -4s infinite alternate%22%3E%3Cpath d%3D%22M-640 17.4L-624 20.6L-608 24.0L-592 26.8L-576 28.7L-560 29.5L-544 29.3L-528 28.3L-512 27.0L-496 25.9L-480 25.5L-464 26.1L-448 27.7L-432 30.2L-416 33.1L-400 35.9L-384 38.1L-368 39.1L-352 38.9L-336 37.3L-320 34.6L-304 31.4L-288 28.0L-272 25.2L-256 23.3L-240 22.5L-224 22.7L-208 23.7L-192 25.0L-176 26.1L-160 26.5L-144 25.9L-128 24.3L-112 21.8L-96 18.9L-80 16.1L-64 13.9L-48 12.9L-32 13.1L-16 14.7L0 17.4L16 20.6L32 24.0L48 26.8L64 28.7L80 29.5L96 29.3L112 28.3L128 27.0L144 25.9L160 25.5L176 26.1L192 27.7L208 30.2L224 33.1L240 35.9L256 38.1L272 39.1L288 38.9L304 37.3L320 34.6L336 31.4L352 28.0L368 25.2L384 23.3L400 22.5L416 22.7L432 23.7L448 25.0L464 26.1L480 26.5L496 25.9L512 24.3L528 21.8L544 18.9L560 16.1L576 13.9L592 12.9L608 13.1L624 14.7L640 17.4L656 20.6L672 24.0L688 26.8L704 28.7L720 29.5L736 29.3L752 28.3L768 27.0L784 25.9L800 25.5L816 26.1L832 27.7L848 30.2L864 33.1L880 35.9L896 38.1L912 39.1L928 38.9L944 37.3L960 34.6L976 31.4L992 28.0L1008 25.2L1024 23.3L1040 22.5L1056 22.7L1072 23.7L1088 25.0L1104 26.1L1120 26.5L1136 25.9L1152 24.3L1168 21.8L1184 18.9L1200 16.1L1216 13.9L1232 12.9L1248 13.1L1264 14.7L1280 17.4%22 fill%3D%22none%22 stroke%3D%22%237cb454%22 stroke-opacity%3D%220.17%22 stroke-width%3D%221%22 style%3D%22animation%3Ax 296s linear -282s infinite reverse%22%2F%3E%3C%2Fg%3E%3Cg style%3D%22animation%3Ay 38s ease-in-out -21s infinite alternate%22%3E%3Cpath d%3D%22M-640 59.0L-624 60.2L-608 59.9L-592 58.6L-576 57.4L-560 57.4L-544 59.0L-528 62.4L-512 66.7L-496 70.9L-480 73.9L-464 75.1L-448 74.3L-432 72.3L-416 70.0L-400 68.5L-384 68.5L-368 69.9L-352 72.1L-336 74.1L-320 74.8L-304 73.6L-288 70.5L-272 66.3L-256 62.0L-240 58.6L-224 57.0L-208 57.0L-192 58.2L-176 59.5L-160 59.9L-144 58.7L-128 56.1L-112 52.6L-96 49.4L-80 47.4L-64 47.5L-48 49.5L-32 52.8L-16 56.3L0 59.0L16 60.2L32 59.9L48 58.6L64 57.4L80 57.4L96 59.0L112 62.4L128 66.7L144 70.9L160 73.9L176 75.1L192 74.3L208 72.3L224 70.0L240 68.5L256 68.5L272 69.9L288 72.1L304 74.1L320 74.8L336 73.6L352 70.5L368 66.3L384 62.0L400 58.6L416 57.0L432 57.0L448 58.2L464 59.5L480 59.9L496 58.7L512 56.1L528 52.6L544 49.4L560 47.4L576 47.5L592 49.5L608 52.8L624 56.3L640 59.0L656 60.2L672 59.9L688 58.6L704 57.4L720 57.4L736 59.0L752 62.4L768 66.7L784 70.9L800 73.9L816 75.1L832 74.3L848 72.3L864 70.0L880 68.5L896 68.5L912 69.9L928 72.1L944 74.1L960 74.8L976 73.6L992 70.5L1008 66.3L1024 62.0L1040 58.6L1056 57.0L1072 57.0L1088 58.2L1104 59.5L1120 59.9L1136 58.7L1152 56.1L1168 52.6L1184 49.4L1200 47.4L1216 47.5L1232 49.5L1248 52.8L1264 56.3L1280 59.0%22 fill%3D%22none%22 stroke%3D%22%237cb454%22 stroke-opacity%3D%220.12%22 stroke-width%3D%221%22 style%3D%22animation%3Ax 278s linear -139s infinite normal%22%2F%3E%3C%2Fg%3E%3Cg style%3D%22animation%3Ay 47s ease-in-out -34s infinite alternate%22%3E%3Cpath d%3D%22M-640 101.5L-624 103.7L-608 106.1L-592 108.2L-576 109.6L-560 110.2L-544 109.7L-528 108.1L-512 105.6L-496 102.6L-480 99.5L-464 96.8L-448 94.8L-432 93.9L-416 94.2L-400 95.5L-384 97.7L-368 100.3L-352 103.0L-336 105.3L-320 107.0L-304 108.0L-288 108.1L-272 107.5L-256 106.4L-240 105.2L-224 103.9L-208 102.9L-192 102.1L-176 101.5L-160 101.1L-144 100.7L-128 100.2L-112 99.6L-96 99.0L-80 98.3L-64 97.9L-48 97.9L-32 98.5L-16 99.7L0 101.5L16 103.7L32 106.1L48 108.2L64 109.6L80 110.2L96 109.7L112 108.1L128 105.6L144 102.6L160 99.5L176 96.8L192 94.8L208 93.9L224 94.2L240 95.5L256 97.7L272 100.3L288 103.0L304 105.3L320 107.0L336 108.0L352 108.1L368 107.5L384 106.4L400 105.2L416 103.9L432 102.9L448 102.1L464 101.5L480 101.1L496 100.7L512 100.2L528 99.6L544 99.0L560 98.3L576 97.9L592 97.9L608 98.5L624 99.7L640 101.5L656 103.7L672 106.1L688 108.2L704 109.6L720 110.2L736 109.7L752 108.1L768 105.6L784 102.6L800 99.5L816 96.8L832 94.8L848 93.9L864 94.2L880 95.5L896 97.7L912 100.3L928 103.0L944 105.3L960 107.0L976 108.0L992 108.1L1008 107.5L1024 106.4L1040 105.2L1056 103.9L1072 102.9L1088 102.1L1104 101.5L1120 101.1L1136 100.7L1152 100.2L1168 99.6L1184 99.0L1200 98.3L1216 97.9L1232 97.9L1248 98.5L1264 99.7L1280 101.5%22 fill%3D%22none%22 stroke%3D%22%23d7b568%22 stroke-opacity%3D%220.10%22 stroke-width%3D%221%22 style%3D%22animation%3Ax 243s linear -26s infinite reverse%22%2F%3E%3C%2Fg%3E%3Cg style%3D%22animation%3Ay 47s ease-in-out -17s infinite alternate%22%3E%3Cpath d%3D%22M-640 151.3L-624 151.3L-608 151.3L-592 151.1L-576 150.5L-560 149.2L-544 147.2L-528 144.4L-512 141.1L-496 137.7L-480 134.8L-464 133.0L-448 132.8L-432 134.3L-416 137.7L-400 142.4L-384 148.0L-368 153.5L-352 158.2L-336 161.3L-320 162.3L-304 161.0L-288 157.6L-272 152.7L-256 147.1L-240 141.5L-224 136.8L-208 133.6L-192 132.2L-176 132.8L-160 134.9L-144 138.0L-128 141.6L-112 145.1L-96 148.1L-80 150.1L-64 151.3L-48 151.8L-32 151.8L-16 151.5L0 151.3L16 151.3L32 151.3L48 151.1L64 150.5L80 149.2L96 147.2L112 144.4L128 141.1L144 137.7L160 134.8L176 133.0L192 132.8L208 134.3L224 137.7L240 142.4L256 148.0L272 153.5L288 158.2L304 161.3L320 162.3L336 161.0L352 157.6L368 152.7L384 147.1L400 141.5L416 136.8L432 133.6L448 132.2L464 132.8L480 134.9L496 138.0L512 141.6L528 145.1L544 148.1L560 150.1L576 151.3L592 151.8L608 151.8L624 151.5L640 151.3L656 151.3L672 151.3L688 151.1L704 150.5L720 149.2L736 147.2L752 144.4L768 141.1L784 137.7L800 134.8L816 133.0L832 132.8L848 134.3L864 137.7L880 142.4L896 148.0L912 153.5L928 158.2L944 161.3L960 162.3L976 161.0L992 157.6L1008 152.7L1024 147.1L1040 141.5L1056 136.8L1072 133.6L1088 132.2L1104 132.8L1120 134.9L1136 138.0L1152 141.6L1168 145.1L1184 148.1L1200 150.1L1216 151.3L1232 151.8L1248 151.8L1264 151.5L1280 151.3%22 fill%3D%22none%22 stroke%3D%22%237cb454%22 stroke-opacity%3D%220.19%22 stroke-width%3D%221%22 style%3D%22animation%3Ax 192s linear -59s infinite normal%22%2F%3E%3C%2Fg%3E%3Cg style%3D%22animation%3Ay 46s ease-in-out -25s infinite alternate%22%3E%3Cpath d%3D%22M-640 184.0L-624 184.2L-608 184.4L-592 184.5L-576 184.4L-560 184.0L-544 183.2L-528 182.0L-512 180.6L-496 179.0L-480 177.3L-464 175.8L-448 174.6L-432 173.8L-416 173.6L-400 174.1L-384 175.3L-368 177.2L-352 179.7L-336 182.7L-320 185.9L-304 189.3L-288 192.5L-272 195.4L-256 197.8L-240 199.6L-224 200.6L-208 200.8L-192 200.3L-176 199.2L-160 197.5L-144 195.4L-128 193.1L-112 190.9L-96 188.8L-80 187.0L-64 185.6L-48 184.6L-32 184.1L-16 183.9L0 184.0L16 184.2L32 184.4L48 184.5L64 184.4L80 184.0L96 183.2L112 182.0L128 180.6L144 179.0L160 177.3L176 175.8L192 174.6L208 173.8L224 173.6L240 174.1L256 175.3L272 177.2L288 179.7L304 182.7L320 185.9L336 189.3L352 192.5L368 195.4L384 197.8L400 199.6L416 200.6L432 200.8L448 200.3L464 199.2L480 197.5L496 195.4L512 193.1L528 190.9L544 188.8L560 187.0L576 185.6L592 184.6L608 184.1L624 183.9L640 184.0L656 184.2L672 184.4L688 184.5L704 184.4L720 184.0L736 183.2L752 182.0L768 180.6L784 179.0L800 177.3L816 175.8L832 174.6L848 173.8L864 173.6L880 174.1L896 175.3L912 177.2L928 179.7L944 182.7L960 185.9L976 189.3L992 192.5L1008 195.4L1024 197.8L1040 199.6L1056 200.6L1072 200.8L1088 200.3L1104 199.2L1120 197.5L1136 195.4L1152 193.1L1168 190.9L1184 188.8L1200 187.0L1216 185.6L1232 184.6L1248 184.1L1264 183.9L1280 184.0%22 fill%3D%22none%22 stroke%3D%22%237cb454%22 stroke-opacity%3D%220.19%22 stroke-width%3D%221%22 style%3D%22animation%3Ax 188s linear -84s infinite normal%22%2F%3E%3C%2Fg%3E%3Cg style%3D%22animation%3Ay 35s ease-in-out -31s infinite alternate%22%3E%3Cpath d%3D%22M-640 215.6L-624 213.9L-608 213.0L-592 213.0L-576 213.9L-560 215.3L-544 217.0L-528 218.2L-512 218.6L-496 218.0L-480 216.2L-464 213.5L-448 210.2L-432 207.0L-416 204.2L-400 202.4L-384 201.7L-368 202.3L-352 203.9L-336 206.0L-320 208.2L-304 209.9L-288 210.8L-272 210.8L-256 209.9L-240 208.4L-224 206.8L-208 205.6L-192 205.2L-176 205.8L-160 207.6L-144 210.3L-128 213.6L-112 216.8L-96 219.6L-80 221.4L-64 222.1L-48 221.5L-32 219.9L-16 217.8L0 215.6L16 213.9L32 213.0L48 213.0L64 213.9L80 215.3L96 217.0L112 218.2L128 218.6L144 218.0L160 216.2L176 213.5L192 210.2L208 207.0L224 204.2L240 202.4L256 201.7L272 202.3L288 203.9L304 206.0L320 208.2L336 209.9L352 210.8L368 210.8L384 209.9L400 208.4L416 206.8L432 205.6L448 205.2L464 205.8L480 207.6L496 210.3L512 213.6L528 216.8L544 219.6L560 221.4L576 222.1L592 221.5L608 219.9L624 217.8L640 215.6L656 213.9L672 213.0L688 213.0L704 213.9L720 215.3L736 217.0L752 218.2L768 218.6L784 218.0L800 216.2L816 213.5L832 210.2L848 207.0L864 204.2L880 202.4L896 201.7L912 202.3L928 203.9L944 206.0L960 208.2L976 209.9L992 210.8L1008 210.8L1024 209.9L1040 208.4L1056 206.8L1072 205.6L1088 205.2L1104 205.8L1120 207.6L1136 210.3L1152 213.6L1168 216.8L1184 219.6L1200 221.4L1216 222.1L1232 221.5L1248 219.9L1264 217.8L1280 215.6%22 fill%3D%22none%22 stroke%3D%22%237cb454%22 stroke-opacity%3D%220.18%22 stroke-width%3D%221%22 style%3D%22animation%3Ax 207s linear -72s infinite reverse%22%2F%3E%3C%2Fg%3E%3Cg style%3D%22animation%3Ay 32s ease-in-out -1s infinite alternate%22%3E%3Cpath d%3D%22M-640 235.7L-624 236.8L-608 237.6L-592 238.0L-576 238.2L-560 238.2L-544 238.0L-528 237.7L-512 237.6L-496 237.7L-480 238.1L-464 239.0L-448 240.3L-432 242.0L-416 244.1L-400 246.3L-384 248.7L-368 251.0L-352 253.0L-336 254.6L-320 255.5L-304 255.8L-288 255.3L-272 254.1L-256 252.1L-240 249.6L-224 246.7L-208 243.5L-192 240.3L-176 237.2L-160 234.5L-144 232.3L-128 230.7L-112 229.7L-96 229.4L-80 229.7L-64 230.5L-48 231.6L-32 233.0L-16 234.4L0 235.7L16 236.8L32 237.6L48 238.0L64 238.2L80 238.2L96 238.0L112 237.7L128 237.6L144 237.7L160 238.1L176 239.0L192 240.3L208 242.0L224 244.1L240 246.3L256 248.7L272 251.0L288 253.0L304 254.6L320 255.5L336 255.8L352 255.3L368 254.1L384 252.1L400 249.6L416 246.7L432 243.5L448 240.3L464 237.2L480 234.5L496 232.3L512 230.7L528 229.7L544 229.4L560 229.7L576 230.5L592 231.6L608 233.0L624 234.4L640 235.7L656 236.8L672 237.6L688 238.0L704 238.2L720 238.2L736 238.0L752 237.7L768 237.6L784 237.7L800 238.1L816 239.0L832 240.3L848 242.0L864 244.1L880 246.3L896 248.7L912 251.0L928 253.0L944 254.6L960 255.5L976 255.8L992 255.3L1008 254.1L1024 252.1L1040 249.6L1056 246.7L1072 243.5L1088 240.3L1104 237.2L1120 234.5L1136 232.3L1152 230.7L1168 229.7L1184 229.4L1200 229.7L1216 230.5L1232 231.6L1248 233.0L1264 234.4L1280 235.7%22 fill%3D%22none%22 stroke%3D%22%237cb454%22 stroke-opacity%3D%220.19%22 stroke-width%3D%221%22 style%3D%22animation%3Ax 198s linear -91s infinite normal%22%2F%3E%3C%2Fg%3E%3Cg style%3D%22animation%3Ay 34s ease-in-out -8s infinite alternate%22%3E%3Cpath d%3D%22M-640 281.7L-624 278.3L-608 274.9L-592 271.8L-576 269.3L-560 267.7L-544 267.0L-528 267.4L-512 268.9L-496 271.2L-480 274.2L-464 277.5L-448 280.9L-432 284.0L-416 286.5L-400 288.2L-384 288.8L-368 288.4L-352 287.0L-336 284.7L-320 281.7L-304 278.3L-288 274.9L-272 271.8L-256 269.3L-240 267.7L-224 267.0L-208 267.4L-192 268.9L-176 271.2L-160 274.2L-144 277.5L-128 280.9L-112 284.0L-96 286.5L-80 288.2L-64 288.8L-48 288.4L-32 287.0L-16 284.7L0 281.7L16 278.3L32 274.9L48 271.8L64 269.3L80 267.7L96 267.0L112 267.4L128 268.9L144 271.2L160 274.2L176 277.5L192 280.9L208 284.0L224 286.5L240 288.2L256 288.8L272 288.4L288 287.0L304 284.7L320 281.7L336 278.3L352 274.9L368 271.8L384 269.3L400 267.7L416 267.0L432 267.4L448 268.9L464 271.2L480 274.2L496 277.5L512 280.9L528 284.0L544 286.5L560 288.2L576 288.8L592 288.4L608 287.0L624 284.7L640 281.7L656 278.3L672 274.9L688 271.8L704 269.3L720 267.7L736 267.0L752 267.4L768 268.9L784 271.2L800 274.2L816 277.5L832 280.9L848 284.0L864 286.5L880 288.2L896 288.8L912 288.4L928 287.0L944 284.7L960 281.7L976 278.3L992 274.9L1008 271.8L1024 269.3L1040 267.7L1056 267.0L1072 267.4L1088 268.9L1104 271.2L1120 274.2L1136 277.5L1152 280.9L1168 284.0L1184 286.5L1200 288.2L1216 288.8L1232 288.4L1248 287.0L1264 284.7L1280 281.7%22 fill%3D%22none%22 stroke%3D%22%23d7b568%22 stroke-opacity%3D%220.10%22 stroke-width%3D%221%22 style%3D%22animation%3Ax 174s linear -62s infinite reverse%22%2F%3E%3C%2Fg%3E%3Cg style%3D%22animation%3Ay 29s ease-in-out -23s infinite alternate%22%3E%3Cpath d%3D%22M-640 307.2L-624 303.3L-608 300.8L-592 300.5L-576 302.3L-560 305.8L-544 309.8L-528 313.4L-512 315.7L-496 316.5L-480 316.2L-464 315.4L-448 315.0L-432 315.2L-416 316.3L-400 317.5L-384 318.2L-368 317.6L-352 315.3L-336 311.6L-320 307.2L-304 303.3L-288 300.8L-272 300.5L-256 302.3L-240 305.8L-224 309.8L-208 313.4L-192 315.7L-176 316.5L-160 316.2L-144 315.4L-128 315.0L-112 315.2L-96 316.3L-80 317.5L-64 318.2L-48 317.6L-32 315.3L-16 311.6L0 307.2L16 303.3L32 300.8L48 300.5L64 302.3L80 305.8L96 309.8L112 313.4L128 315.7L144 316.5L160 316.2L176 315.4L192 315.0L208 315.2L224 316.3L240 317.5L256 318.2L272 317.6L288 315.3L304 311.6L320 307.2L336 303.3L352 300.8L368 300.5L384 302.3L400 305.8L416 309.8L432 313.4L448 315.7L464 316.5L480 316.2L496 315.4L512 315.0L528 315.2L544 316.3L560 317.5L576 318.2L592 317.6L608 315.3L624 311.6L640 307.2L656 303.3L672 300.8L688 300.5L704 302.3L720 305.8L736 309.8L752 313.4L768 315.7L784 316.5L800 316.2L816 315.4L832 315.0L848 315.2L864 316.3L880 317.5L896 318.2L912 317.6L928 315.3L944 311.6L960 307.2L976 303.3L992 300.8L1008 300.5L1024 302.3L1040 305.8L1056 309.8L1072 313.4L1088 315.7L1104 316.5L1120 316.2L1136 315.4L1152 315.0L1168 315.2L1184 316.3L1200 317.5L1216 318.2L1232 317.6L1248 315.3L1264 311.6L1280 307.2%22 fill%3D%22none%22 stroke%3D%22%237cb454%22 stroke-opacity%3D%220.12%22 stroke-width%3D%221%22 style%3D%22animation%3Ax 207s linear -43s infinite reverse%22%2F%3E%3C%2Fg%3E%3Cg style%3D%22animation%3Ay 38s ease-in-out -2s infinite alternate%22%3E%3Cpath d%3D%22M-640 339.1L-624 338.5L-608 338.5L-592 339.2L-576 340.4L-560 342.0L-544 343.9L-528 345.9L-512 347.8L-496 349.4L-480 350.5L-464 351.1L-448 351.1L-432 350.5L-416 349.3L-400 347.7L-384 345.8L-368 343.8L-352 341.9L-336 340.3L-320 339.1L-304 338.5L-288 338.5L-272 339.2L-256 340.4L-240 342.0L-224 343.9L-208 345.9L-192 347.8L-176 349.4L-160 350.5L-144 351.1L-128 351.1L-112 350.5L-96 349.3L-80 347.7L-64 345.8L-48 343.8L-32 341.9L-16 340.3L0 339.1L16 338.5L32 338.5L48 339.2L64 340.4L80 342.0L96 343.9L112 345.9L128 347.8L144 349.4L160 350.5L176 351.1L192 351.1L208 350.5L224 349.3L240 347.7L256 345.8L272 343.8L288 341.9L304 340.3L320 339.1L336 338.5L352 338.5L368 339.2L384 340.4L400 342.0L416 343.9L432 345.9L448 347.8L464 349.4L480 350.5L496 351.1L512 351.1L528 350.5L544 349.3L560 347.7L576 345.8L592 343.8L608 341.9L624 340.3L640 339.1L656 338.5L672 338.5L688 339.2L704 340.4L720 342.0L736 343.9L752 345.9L768 347.8L784 349.4L800 350.5L816 351.1L832 351.1L848 350.5L864 349.3L880 347.7L896 345.8L912 343.8L928 341.9L944 340.3L960 339.1L976 338.5L992 338.5L1008 339.2L1024 340.4L1040 342.0L1056 343.9L1072 345.9L1088 347.8L1104 349.4L1120 350.5L1136 351.1L1152 351.1L1168 350.5L1184 349.3L1200 347.7L1216 345.8L1232 343.8L1248 341.9L1264 340.3L1280 339.1%22 fill%3D%22none%22 stroke%3D%22%237cb454%22 stroke-opacity%3D%220.19%22 stroke-width%3D%221%22 style%3D%22animation%3Ax 218s linear -167s infinite normal%22%2F%3E%3C%2Fg%3E%3Cg style%3D%22animation%3Ay 40s ease-in-out -24s infinite alternate%22%3E%3Cpath d%3D%22M-640 374.6L-624 373.4L-608 372.3L-592 371.3L-576 370.3L-560 369.6L-544 369.0L-528 368.7L-512 368.7L-496 369.1L-480 369.8L-464 371.0L-448 372.6L-432 374.5L-416 376.7L-400 379.1L-384 381.6L-368 384.0L-352 386.4L-336 388.6L-320 390.4L-304 391.9L-288 393.0L-272 393.6L-256 393.7L-240 393.5L-224 392.8L-208 391.9L-192 390.7L-176 389.3L-160 387.8L-144 386.3L-128 384.8L-112 383.3L-96 381.9L-80 380.5L-64 379.3L-48 378.1L-32 376.9L-16 375.7L0 374.6L16 373.4L32 372.3L48 371.3L64 370.3L80 369.6L96 369.0L112 368.7L128 368.7L144 369.1L160 369.8L176 371.0L192 372.6L208 374.5L224 376.7L240 379.1L256 381.6L272 384.0L288 386.4L304 388.6L320 390.4L336 391.9L352 393.0L368 393.6L384 393.7L400 393.5L416 392.8L432 391.9L448 390.7L464 389.3L480 387.8L496 386.3L512 384.8L528 383.3L544 381.9L560 380.5L576 379.3L592 378.1L608 376.9L624 375.7L640 374.6L656 373.4L672 372.3L688 371.3L704 370.3L720 369.6L736 369.0L752 368.7L768 368.7L784 369.1L800 369.8L816 371.0L832 372.6L848 374.5L864 376.7L880 379.1L896 381.6L912 384.0L928 386.4L944 388.6L960 390.4L976 391.9L992 393.0L1008 393.6L1024 393.7L1040 393.5L1056 392.8L1072 391.9L1088 390.7L1104 389.3L1120 387.8L1136 386.3L1152 384.8L1168 383.3L1184 381.9L1200 380.5L1216 379.3L1232 378.1L1248 376.9L1264 375.7L1280 374.6%22 fill%3D%22none%22 stroke%3D%22%237cb454%22 stroke-opacity%3D%220.19%22 stroke-width%3D%221%22 style%3D%22animation%3Ax 201s linear -192s infinite normal%22%2F%3E%3C%2Fg%3E%3Cg style%3D%22animation%3Ay 37s ease-in-out -33s infinite alternate%22%3E%3Cpath d%3D%22M-640 420.1L-624 419.8L-608 419.8L-592 420.0L-576 420.4L-560 421.0L-544 421.7L-528 422.4L-512 423.2L-496 423.8L-480 424.3L-464 424.6L-448 424.7L-432 424.5L-416 424.1L-400 423.5L-384 422.8L-368 422.0L-352 421.3L-336 420.6L-320 420.1L-304 419.8L-288 419.8L-272 420.0L-256 420.4L-240 421.0L-224 421.7L-208 422.4L-192 423.2L-176 423.8L-160 424.3L-144 424.6L-128 424.7L-112 424.5L-96 424.1L-80 423.5L-64 422.8L-48 422.0L-32 421.3L-16 420.6L0 420.1L16 419.8L32 419.8L48 420.0L64 420.4L80 421.0L96 421.7L112 422.4L128 423.2L144 423.8L160 424.3L176 424.6L192 424.7L208 424.5L224 424.1L240 423.5L256 422.8L272 422.0L288 421.3L304 420.6L320 420.1L336 419.8L352 419.8L368 420.0L384 420.4L400 421.0L416 421.7L432 422.4L448 423.2L464 423.8L480 424.3L496 424.6L512 424.7L528 424.5L544 424.1L560 423.5L576 422.8L592 422.0L608 421.3L624 420.6L640 420.1L656 419.8L672 419.8L688 420.0L704 420.4L720 421.0L736 421.7L752 422.4L768 423.2L784 423.8L800 424.3L816 424.6L832 424.7L848 424.5L864 424.1L880 423.5L896 422.8L912 422.0L928 421.3L944 420.6L960 420.1L976 419.8L992 419.8L1008 420.0L1024 420.4L1040 421.0L1056 421.7L1072 422.4L1088 423.2L1104 423.8L1120 424.3L1136 424.6L1152 424.7L1168 424.5L1184 424.1L1200 423.5L1216 422.8L1232 422.0L1248 421.3L1264 420.6L1280 420.1%22 fill%3D%22none%22 stroke%3D%22%237cb454%22 stroke-opacity%3D%220.19%22 stroke-width%3D%221%22 style%3D%22animation%3Ax 272s linear -75s infinite reverse%22%2F%3E%3C%2Fg%3E%3Cg style%3D%22animation%3Ay 46s ease-in-out -35s infinite alternate%22%3E%3Cpath d%3D%22M-640 461.2L-624 458.1L-608 455.6L-592 453.9L-576 453.0L-560 452.7L-544 452.9L-528 453.5L-512 454.3L-496 455.2L-480 456.4L-464 458.0L-448 459.9L-432 462.1L-416 464.6L-400 467.1L-384 469.4L-368 470.9L-352 471.4L-336 470.7L-320 468.5L-304 465.2L-288 460.9L-272 456.3L-256 451.9L-240 448.4L-224 446.3L-208 446.1L-192 447.6L-176 450.8L-160 455.2L-144 460.2L-128 465.1L-112 469.2L-96 472.0L-80 473.2L-64 472.8L-48 471.0L-32 468.1L-16 464.7L0 461.2L16 458.1L32 455.6L48 453.9L64 453.0L80 452.7L96 452.9L112 453.5L128 454.3L144 455.2L160 456.4L176 458.0L192 459.9L208 462.1L224 464.6L240 467.1L256 469.4L272 470.9L288 471.4L304 470.7L320 468.5L336 465.2L352 460.9L368 456.3L384 451.9L400 448.4L416 446.3L432 446.1L448 447.6L464 450.8L480 455.2L496 460.2L512 465.1L528 469.2L544 472.0L560 473.2L576 472.8L592 471.0L608 468.1L624 464.7L640 461.2L656 458.1L672 455.6L688 453.9L704 453.0L720 452.7L736 452.9L752 453.5L768 454.3L784 455.2L800 456.4L816 458.0L832 459.9L848 462.1L864 464.6L880 467.1L896 469.4L912 470.9L928 471.4L944 470.7L960 468.5L976 465.2L992 460.9L1008 456.3L1024 451.9L1040 448.4L1056 446.3L1072 446.1L1088 447.6L1104 450.8L1120 455.2L1136 460.2L1152 465.1L1168 469.2L1184 472.0L1200 473.2L1216 472.8L1232 471.0L1248 468.1L1264 464.7L1280 461.2%22 fill%3D%22none%22 stroke%3D%22%23d7b568%22 stroke-opacity%3D%220.12%22 stroke-width%3D%221%22 style%3D%22animation%3Ax 270s linear -74s infinite normal%22%2F%3E%3C%2Fg%3E%3Cg style%3D%22animation%3Ay 32s ease-in-out -2s infinite alternate%22%3E%3Cpath d%3D%22M-640 498.3L-624 501.0L-608 503.6L-592 505.9L-576 507.7L-560 508.8L-544 509.2L-528 508.7L-512 507.4L-496 505.5L-480 503.1L-464 500.5L-448 497.9L-432 495.6L-416 493.8L-400 492.7L-384 492.3L-368 492.8L-352 494.1L-336 496.0L-320 498.3L-304 501.0L-288 503.6L-272 505.9L-256 507.7L-240 508.8L-224 509.2L-208 508.7L-192 507.4L-176 505.5L-160 503.1L-144 500.5L-128 497.9L-112 495.6L-96 493.8L-80 492.7L-64 492.3L-48 492.8L-32 494.1L-16 496.0L0 498.3L16 501.0L32 503.6L48 505.9L64 507.7L80 508.8L96 509.2L112 508.7L128 507.4L144 505.5L160 503.1L176 500.5L192 497.9L208 495.6L224 493.8L240 492.7L256 492.3L272 492.8L288 494.1L304 496.0L320 498.3L336 501.0L352 503.6L368 505.9L384 507.7L400 508.8L416 509.2L432 508.7L448 507.4L464 505.5L480 503.1L496 500.5L512 497.9L528 495.6L544 493.8L560 492.7L576 492.3L592 492.8L608 494.1L624 496.0L640 498.3L656 501.0L672 503.6L688 505.9L704 507.7L720 508.8L736 509.2L752 508.7L768 507.4L784 505.5L800 503.1L816 500.5L832 497.9L848 495.6L864 493.8L880 492.7L896 492.3L912 492.8L928 494.1L944 496.0L960 498.3L976 501.0L992 503.6L1008 505.9L1024 507.7L1040 508.8L1056 509.2L1072 508.7L1088 507.4L1104 505.5L1120 503.1L1136 500.5L1152 497.9L1168 495.6L1184 493.8L1200 492.7L1216 492.3L1232 492.8L1248 494.1L1264 496.0L1280 498.3%22 fill%3D%22none%22 stroke%3D%22%237cb454%22 stroke-opacity%3D%220.17%22 stroke-width%3D%221%22 style%3D%22animation%3Ax 300s linear -152s infinite normal%22%2F%3E%3C%2Fg%3E%3Cg style%3D%22animation%3Ay 38s ease-in-out -25s infinite alternate%22%3E%3Cpath d%3D%22M-640 540.3L-624 536.5L-608 531.0L-592 525.3L-576 521.0L-560 519.3L-544 520.8L-528 524.8L-512 530.3L-496 535.7L-480 539.5L-464 541.1L-448 540.6L-432 539.0L-416 537.4L-400 536.8L-384 537.7L-368 539.5L-352 541.3L-336 541.9L-320 540.3L-304 536.5L-288 531.0L-272 525.3L-256 521.0L-240 519.3L-224 520.8L-208 524.8L-192 530.3L-176 535.7L-160 539.5L-144 541.1L-128 540.6L-112 539.0L-96 537.4L-80 536.8L-64 537.7L-48 539.5L-32 541.3L-16 541.9L0 540.3L16 536.5L32 531.0L48 525.3L64 521.0L80 519.3L96 520.8L112 524.8L128 530.3L144 535.7L160 539.5L176 541.1L192 540.6L208 539.0L224 537.4L240 536.8L256 537.7L272 539.5L288 541.3L304 541.9L320 540.3L336 536.5L352 531.0L368 525.3L384 521.0L400 519.3L416 520.8L432 524.8L448 530.3L464 535.7L480 539.5L496 541.1L512 540.6L528 539.0L544 537.4L560 536.8L576 537.7L592 539.5L608 541.3L624 541.9L640 540.3L656 536.5L672 531.0L688 525.3L704 521.0L720 519.3L736 520.8L752 524.8L768 530.3L784 535.7L800 539.5L816 541.1L832 540.6L848 539.0L864 537.4L880 536.8L896 537.7L912 539.5L928 541.3L944 541.9L960 540.3L976 536.5L992 531.0L1008 525.3L1024 521.0L1040 519.3L1056 520.8L1072 524.8L1088 530.3L1104 535.7L1120 539.5L1136 541.1L1152 540.6L1168 539.0L1184 537.4L1200 536.8L1216 537.7L1232 539.5L1248 541.3L1264 541.9L1280 540.3%22 fill%3D%22none%22 stroke%3D%22%237cb454%22 stroke-opacity%3D%220.20%22 stroke-width%3D%221%22 style%3D%22animation%3Ax 204s linear -47s infinite normal%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
    background-size: 640px 560px;
  }
  /* forest-moss still tile */
  /* The base rule's lines animate inside the image, which page CSS cannot pause, so when the gallery scroll option
     is off or animations are suppressed the same lines are served frozen. (The site has neither switch and keeps the
     base rule: its themes.json extraction only matches the exact `html[data-theme] #gallery` selector.) */
  html:not(.ground-scroll-on)[data-theme="forest-moss"] #gallery,
  html.no-flourish-ambient[data-theme="forest-moss"] #gallery{
    background-image: url("data:image/svg+xml,%3Csvg xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22 width%3D%22640%22 height%3D%22560%22 viewBox%3D%220 0 640 560%22%3E%3Cg%3E%3Cpath d%3D%22M-640 17.4L-624 20.6L-608 24.0L-592 26.8L-576 28.7L-560 29.5L-544 29.3L-528 28.3L-512 27.0L-496 25.9L-480 25.5L-464 26.1L-448 27.7L-432 30.2L-416 33.1L-400 35.9L-384 38.1L-368 39.1L-352 38.9L-336 37.3L-320 34.6L-304 31.4L-288 28.0L-272 25.2L-256 23.3L-240 22.5L-224 22.7L-208 23.7L-192 25.0L-176 26.1L-160 26.5L-144 25.9L-128 24.3L-112 21.8L-96 18.9L-80 16.1L-64 13.9L-48 12.9L-32 13.1L-16 14.7L0 17.4L16 20.6L32 24.0L48 26.8L64 28.7L80 29.5L96 29.3L112 28.3L128 27.0L144 25.9L160 25.5L176 26.1L192 27.7L208 30.2L224 33.1L240 35.9L256 38.1L272 39.1L288 38.9L304 37.3L320 34.6L336 31.4L352 28.0L368 25.2L384 23.3L400 22.5L416 22.7L432 23.7L448 25.0L464 26.1L480 26.5L496 25.9L512 24.3L528 21.8L544 18.9L560 16.1L576 13.9L592 12.9L608 13.1L624 14.7L640 17.4L656 20.6L672 24.0L688 26.8L704 28.7L720 29.5L736 29.3L752 28.3L768 27.0L784 25.9L800 25.5L816 26.1L832 27.7L848 30.2L864 33.1L880 35.9L896 38.1L912 39.1L928 38.9L944 37.3L960 34.6L976 31.4L992 28.0L1008 25.2L1024 23.3L1040 22.5L1056 22.7L1072 23.7L1088 25.0L1104 26.1L1120 26.5L1136 25.9L1152 24.3L1168 21.8L1184 18.9L1200 16.1L1216 13.9L1232 12.9L1248 13.1L1264 14.7L1280 17.4%22 fill%3D%22none%22 stroke%3D%22%237cb454%22 stroke-opacity%3D%220.17%22 stroke-width%3D%221%22%2F%3E%3C%2Fg%3E%3Cg%3E%3Cpath d%3D%22M-640 59.0L-624 60.2L-608 59.9L-592 58.6L-576 57.4L-560 57.4L-544 59.0L-528 62.4L-512 66.7L-496 70.9L-480 73.9L-464 75.1L-448 74.3L-432 72.3L-416 70.0L-400 68.5L-384 68.5L-368 69.9L-352 72.1L-336 74.1L-320 74.8L-304 73.6L-288 70.5L-272 66.3L-256 62.0L-240 58.6L-224 57.0L-208 57.0L-192 58.2L-176 59.5L-160 59.9L-144 58.7L-128 56.1L-112 52.6L-96 49.4L-80 47.4L-64 47.5L-48 49.5L-32 52.8L-16 56.3L0 59.0L16 60.2L32 59.9L48 58.6L64 57.4L80 57.4L96 59.0L112 62.4L128 66.7L144 70.9L160 73.9L176 75.1L192 74.3L208 72.3L224 70.0L240 68.5L256 68.5L272 69.9L288 72.1L304 74.1L320 74.8L336 73.6L352 70.5L368 66.3L384 62.0L400 58.6L416 57.0L432 57.0L448 58.2L464 59.5L480 59.9L496 58.7L512 56.1L528 52.6L544 49.4L560 47.4L576 47.5L592 49.5L608 52.8L624 56.3L640 59.0L656 60.2L672 59.9L688 58.6L704 57.4L720 57.4L736 59.0L752 62.4L768 66.7L784 70.9L800 73.9L816 75.1L832 74.3L848 72.3L864 70.0L880 68.5L896 68.5L912 69.9L928 72.1L944 74.1L960 74.8L976 73.6L992 70.5L1008 66.3L1024 62.0L1040 58.6L1056 57.0L1072 57.0L1088 58.2L1104 59.5L1120 59.9L1136 58.7L1152 56.1L1168 52.6L1184 49.4L1200 47.4L1216 47.5L1232 49.5L1248 52.8L1264 56.3L1280 59.0%22 fill%3D%22none%22 stroke%3D%22%237cb454%22 stroke-opacity%3D%220.12%22 stroke-width%3D%221%22%2F%3E%3C%2Fg%3E%3Cg%3E%3Cpath d%3D%22M-640 101.5L-624 103.7L-608 106.1L-592 108.2L-576 109.6L-560 110.2L-544 109.7L-528 108.1L-512 105.6L-496 102.6L-480 99.5L-464 96.8L-448 94.8L-432 93.9L-416 94.2L-400 95.5L-384 97.7L-368 100.3L-352 103.0L-336 105.3L-320 107.0L-304 108.0L-288 108.1L-272 107.5L-256 106.4L-240 105.2L-224 103.9L-208 102.9L-192 102.1L-176 101.5L-160 101.1L-144 100.7L-128 100.2L-112 99.6L-96 99.0L-80 98.3L-64 97.9L-48 97.9L-32 98.5L-16 99.7L0 101.5L16 103.7L32 106.1L48 108.2L64 109.6L80 110.2L96 109.7L112 108.1L128 105.6L144 102.6L160 99.5L176 96.8L192 94.8L208 93.9L224 94.2L240 95.5L256 97.7L272 100.3L288 103.0L304 105.3L320 107.0L336 108.0L352 108.1L368 107.5L384 106.4L400 105.2L416 103.9L432 102.9L448 102.1L464 101.5L480 101.1L496 100.7L512 100.2L528 99.6L544 99.0L560 98.3L576 97.9L592 97.9L608 98.5L624 99.7L640 101.5L656 103.7L672 106.1L688 108.2L704 109.6L720 110.2L736 109.7L752 108.1L768 105.6L784 102.6L800 99.5L816 96.8L832 94.8L848 93.9L864 94.2L880 95.5L896 97.7L912 100.3L928 103.0L944 105.3L960 107.0L976 108.0L992 108.1L1008 107.5L1024 106.4L1040 105.2L1056 103.9L1072 102.9L1088 102.1L1104 101.5L1120 101.1L1136 100.7L1152 100.2L1168 99.6L1184 99.0L1200 98.3L1216 97.9L1232 97.9L1248 98.5L1264 99.7L1280 101.5%22 fill%3D%22none%22 stroke%3D%22%23d7b568%22 stroke-opacity%3D%220.10%22 stroke-width%3D%221%22%2F%3E%3C%2Fg%3E%3Cg%3E%3Cpath d%3D%22M-640 151.3L-624 151.3L-608 151.3L-592 151.1L-576 150.5L-560 149.2L-544 147.2L-528 144.4L-512 141.1L-496 137.7L-480 134.8L-464 133.0L-448 132.8L-432 134.3L-416 137.7L-400 142.4L-384 148.0L-368 153.5L-352 158.2L-336 161.3L-320 162.3L-304 161.0L-288 157.6L-272 152.7L-256 147.1L-240 141.5L-224 136.8L-208 133.6L-192 132.2L-176 132.8L-160 134.9L-144 138.0L-128 141.6L-112 145.1L-96 148.1L-80 150.1L-64 151.3L-48 151.8L-32 151.8L-16 151.5L0 151.3L16 151.3L32 151.3L48 151.1L64 150.5L80 149.2L96 147.2L112 144.4L128 141.1L144 137.7L160 134.8L176 133.0L192 132.8L208 134.3L224 137.7L240 142.4L256 148.0L272 153.5L288 158.2L304 161.3L320 162.3L336 161.0L352 157.6L368 152.7L384 147.1L400 141.5L416 136.8L432 133.6L448 132.2L464 132.8L480 134.9L496 138.0L512 141.6L528 145.1L544 148.1L560 150.1L576 151.3L592 151.8L608 151.8L624 151.5L640 151.3L656 151.3L672 151.3L688 151.1L704 150.5L720 149.2L736 147.2L752 144.4L768 141.1L784 137.7L800 134.8L816 133.0L832 132.8L848 134.3L864 137.7L880 142.4L896 148.0L912 153.5L928 158.2L944 161.3L960 162.3L976 161.0L992 157.6L1008 152.7L1024 147.1L1040 141.5L1056 136.8L1072 133.6L1088 132.2L1104 132.8L1120 134.9L1136 138.0L1152 141.6L1168 145.1L1184 148.1L1200 150.1L1216 151.3L1232 151.8L1248 151.8L1264 151.5L1280 151.3%22 fill%3D%22none%22 stroke%3D%22%237cb454%22 stroke-opacity%3D%220.19%22 stroke-width%3D%221%22%2F%3E%3C%2Fg%3E%3Cg%3E%3Cpath d%3D%22M-640 184.0L-624 184.2L-608 184.4L-592 184.5L-576 184.4L-560 184.0L-544 183.2L-528 182.0L-512 180.6L-496 179.0L-480 177.3L-464 175.8L-448 174.6L-432 173.8L-416 173.6L-400 174.1L-384 175.3L-368 177.2L-352 179.7L-336 182.7L-320 185.9L-304 189.3L-288 192.5L-272 195.4L-256 197.8L-240 199.6L-224 200.6L-208 200.8L-192 200.3L-176 199.2L-160 197.5L-144 195.4L-128 193.1L-112 190.9L-96 188.8L-80 187.0L-64 185.6L-48 184.6L-32 184.1L-16 183.9L0 184.0L16 184.2L32 184.4L48 184.5L64 184.4L80 184.0L96 183.2L112 182.0L128 180.6L144 179.0L160 177.3L176 175.8L192 174.6L208 173.8L224 173.6L240 174.1L256 175.3L272 177.2L288 179.7L304 182.7L320 185.9L336 189.3L352 192.5L368 195.4L384 197.8L400 199.6L416 200.6L432 200.8L448 200.3L464 199.2L480 197.5L496 195.4L512 193.1L528 190.9L544 188.8L560 187.0L576 185.6L592 184.6L608 184.1L624 183.9L640 184.0L656 184.2L672 184.4L688 184.5L704 184.4L720 184.0L736 183.2L752 182.0L768 180.6L784 179.0L800 177.3L816 175.8L832 174.6L848 173.8L864 173.6L880 174.1L896 175.3L912 177.2L928 179.7L944 182.7L960 185.9L976 189.3L992 192.5L1008 195.4L1024 197.8L1040 199.6L1056 200.6L1072 200.8L1088 200.3L1104 199.2L1120 197.5L1136 195.4L1152 193.1L1168 190.9L1184 188.8L1200 187.0L1216 185.6L1232 184.6L1248 184.1L1264 183.9L1280 184.0%22 fill%3D%22none%22 stroke%3D%22%237cb454%22 stroke-opacity%3D%220.19%22 stroke-width%3D%221%22%2F%3E%3C%2Fg%3E%3Cg%3E%3Cpath d%3D%22M-640 215.6L-624 213.9L-608 213.0L-592 213.0L-576 213.9L-560 215.3L-544 217.0L-528 218.2L-512 218.6L-496 218.0L-480 216.2L-464 213.5L-448 210.2L-432 207.0L-416 204.2L-400 202.4L-384 201.7L-368 202.3L-352 203.9L-336 206.0L-320 208.2L-304 209.9L-288 210.8L-272 210.8L-256 209.9L-240 208.4L-224 206.8L-208 205.6L-192 205.2L-176 205.8L-160 207.6L-144 210.3L-128 213.6L-112 216.8L-96 219.6L-80 221.4L-64 222.1L-48 221.5L-32 219.9L-16 217.8L0 215.6L16 213.9L32 213.0L48 213.0L64 213.9L80 215.3L96 217.0L112 218.2L128 218.6L144 218.0L160 216.2L176 213.5L192 210.2L208 207.0L224 204.2L240 202.4L256 201.7L272 202.3L288 203.9L304 206.0L320 208.2L336 209.9L352 210.8L368 210.8L384 209.9L400 208.4L416 206.8L432 205.6L448 205.2L464 205.8L480 207.6L496 210.3L512 213.6L528 216.8L544 219.6L560 221.4L576 222.1L592 221.5L608 219.9L624 217.8L640 215.6L656 213.9L672 213.0L688 213.0L704 213.9L720 215.3L736 217.0L752 218.2L768 218.6L784 218.0L800 216.2L816 213.5L832 210.2L848 207.0L864 204.2L880 202.4L896 201.7L912 202.3L928 203.9L944 206.0L960 208.2L976 209.9L992 210.8L1008 210.8L1024 209.9L1040 208.4L1056 206.8L1072 205.6L1088 205.2L1104 205.8L1120 207.6L1136 210.3L1152 213.6L1168 216.8L1184 219.6L1200 221.4L1216 222.1L1232 221.5L1248 219.9L1264 217.8L1280 215.6%22 fill%3D%22none%22 stroke%3D%22%237cb454%22 stroke-opacity%3D%220.18%22 stroke-width%3D%221%22%2F%3E%3C%2Fg%3E%3Cg%3E%3Cpath d%3D%22M-640 235.7L-624 236.8L-608 237.6L-592 238.0L-576 238.2L-560 238.2L-544 238.0L-528 237.7L-512 237.6L-496 237.7L-480 238.1L-464 239.0L-448 240.3L-432 242.0L-416 244.1L-400 246.3L-384 248.7L-368 251.0L-352 253.0L-336 254.6L-320 255.5L-304 255.8L-288 255.3L-272 254.1L-256 252.1L-240 249.6L-224 246.7L-208 243.5L-192 240.3L-176 237.2L-160 234.5L-144 232.3L-128 230.7L-112 229.7L-96 229.4L-80 229.7L-64 230.5L-48 231.6L-32 233.0L-16 234.4L0 235.7L16 236.8L32 237.6L48 238.0L64 238.2L80 238.2L96 238.0L112 237.7L128 237.6L144 237.7L160 238.1L176 239.0L192 240.3L208 242.0L224 244.1L240 246.3L256 248.7L272 251.0L288 253.0L304 254.6L320 255.5L336 255.8L352 255.3L368 254.1L384 252.1L400 249.6L416 246.7L432 243.5L448 240.3L464 237.2L480 234.5L496 232.3L512 230.7L528 229.7L544 229.4L560 229.7L576 230.5L592 231.6L608 233.0L624 234.4L640 235.7L656 236.8L672 237.6L688 238.0L704 238.2L720 238.2L736 238.0L752 237.7L768 237.6L784 237.7L800 238.1L816 239.0L832 240.3L848 242.0L864 244.1L880 246.3L896 248.7L912 251.0L928 253.0L944 254.6L960 255.5L976 255.8L992 255.3L1008 254.1L1024 252.1L1040 249.6L1056 246.7L1072 243.5L1088 240.3L1104 237.2L1120 234.5L1136 232.3L1152 230.7L1168 229.7L1184 229.4L1200 229.7L1216 230.5L1232 231.6L1248 233.0L1264 234.4L1280 235.7%22 fill%3D%22none%22 stroke%3D%22%237cb454%22 stroke-opacity%3D%220.19%22 stroke-width%3D%221%22%2F%3E%3C%2Fg%3E%3Cg%3E%3Cpath d%3D%22M-640 281.7L-624 278.3L-608 274.9L-592 271.8L-576 269.3L-560 267.7L-544 267.0L-528 267.4L-512 268.9L-496 271.2L-480 274.2L-464 277.5L-448 280.9L-432 284.0L-416 286.5L-400 288.2L-384 288.8L-368 288.4L-352 287.0L-336 284.7L-320 281.7L-304 278.3L-288 274.9L-272 271.8L-256 269.3L-240 267.7L-224 267.0L-208 267.4L-192 268.9L-176 271.2L-160 274.2L-144 277.5L-128 280.9L-112 284.0L-96 286.5L-80 288.2L-64 288.8L-48 288.4L-32 287.0L-16 284.7L0 281.7L16 278.3L32 274.9L48 271.8L64 269.3L80 267.7L96 267.0L112 267.4L128 268.9L144 271.2L160 274.2L176 277.5L192 280.9L208 284.0L224 286.5L240 288.2L256 288.8L272 288.4L288 287.0L304 284.7L320 281.7L336 278.3L352 274.9L368 271.8L384 269.3L400 267.7L416 267.0L432 267.4L448 268.9L464 271.2L480 274.2L496 277.5L512 280.9L528 284.0L544 286.5L560 288.2L576 288.8L592 288.4L608 287.0L624 284.7L640 281.7L656 278.3L672 274.9L688 271.8L704 269.3L720 267.7L736 267.0L752 267.4L768 268.9L784 271.2L800 274.2L816 277.5L832 280.9L848 284.0L864 286.5L880 288.2L896 288.8L912 288.4L928 287.0L944 284.7L960 281.7L976 278.3L992 274.9L1008 271.8L1024 269.3L1040 267.7L1056 267.0L1072 267.4L1088 268.9L1104 271.2L1120 274.2L1136 277.5L1152 280.9L1168 284.0L1184 286.5L1200 288.2L1216 288.8L1232 288.4L1248 287.0L1264 284.7L1280 281.7%22 fill%3D%22none%22 stroke%3D%22%23d7b568%22 stroke-opacity%3D%220.10%22 stroke-width%3D%221%22%2F%3E%3C%2Fg%3E%3Cg%3E%3Cpath d%3D%22M-640 307.2L-624 303.3L-608 300.8L-592 300.5L-576 302.3L-560 305.8L-544 309.8L-528 313.4L-512 315.7L-496 316.5L-480 316.2L-464 315.4L-448 315.0L-432 315.2L-416 316.3L-400 317.5L-384 318.2L-368 317.6L-352 315.3L-336 311.6L-320 307.2L-304 303.3L-288 300.8L-272 300.5L-256 302.3L-240 305.8L-224 309.8L-208 313.4L-192 315.7L-176 316.5L-160 316.2L-144 315.4L-128 315.0L-112 315.2L-96 316.3L-80 317.5L-64 318.2L-48 317.6L-32 315.3L-16 311.6L0 307.2L16 303.3L32 300.8L48 300.5L64 302.3L80 305.8L96 309.8L112 313.4L128 315.7L144 316.5L160 316.2L176 315.4L192 315.0L208 315.2L224 316.3L240 317.5L256 318.2L272 317.6L288 315.3L304 311.6L320 307.2L336 303.3L352 300.8L368 300.5L384 302.3L400 305.8L416 309.8L432 313.4L448 315.7L464 316.5L480 316.2L496 315.4L512 315.0L528 315.2L544 316.3L560 317.5L576 318.2L592 317.6L608 315.3L624 311.6L640 307.2L656 303.3L672 300.8L688 300.5L704 302.3L720 305.8L736 309.8L752 313.4L768 315.7L784 316.5L800 316.2L816 315.4L832 315.0L848 315.2L864 316.3L880 317.5L896 318.2L912 317.6L928 315.3L944 311.6L960 307.2L976 303.3L992 300.8L1008 300.5L1024 302.3L1040 305.8L1056 309.8L1072 313.4L1088 315.7L1104 316.5L1120 316.2L1136 315.4L1152 315.0L1168 315.2L1184 316.3L1200 317.5L1216 318.2L1232 317.6L1248 315.3L1264 311.6L1280 307.2%22 fill%3D%22none%22 stroke%3D%22%237cb454%22 stroke-opacity%3D%220.12%22 stroke-width%3D%221%22%2F%3E%3C%2Fg%3E%3Cg%3E%3Cpath d%3D%22M-640 339.1L-624 338.5L-608 338.5L-592 339.2L-576 340.4L-560 342.0L-544 343.9L-528 345.9L-512 347.8L-496 349.4L-480 350.5L-464 351.1L-448 351.1L-432 350.5L-416 349.3L-400 347.7L-384 345.8L-368 343.8L-352 341.9L-336 340.3L-320 339.1L-304 338.5L-288 338.5L-272 339.2L-256 340.4L-240 342.0L-224 343.9L-208 345.9L-192 347.8L-176 349.4L-160 350.5L-144 351.1L-128 351.1L-112 350.5L-96 349.3L-80 347.7L-64 345.8L-48 343.8L-32 341.9L-16 340.3L0 339.1L16 338.5L32 338.5L48 339.2L64 340.4L80 342.0L96 343.9L112 345.9L128 347.8L144 349.4L160 350.5L176 351.1L192 351.1L208 350.5L224 349.3L240 347.7L256 345.8L272 343.8L288 341.9L304 340.3L320 339.1L336 338.5L352 338.5L368 339.2L384 340.4L400 342.0L416 343.9L432 345.9L448 347.8L464 349.4L480 350.5L496 351.1L512 351.1L528 350.5L544 349.3L560 347.7L576 345.8L592 343.8L608 341.9L624 340.3L640 339.1L656 338.5L672 338.5L688 339.2L704 340.4L720 342.0L736 343.9L752 345.9L768 347.8L784 349.4L800 350.5L816 351.1L832 351.1L848 350.5L864 349.3L880 347.7L896 345.8L912 343.8L928 341.9L944 340.3L960 339.1L976 338.5L992 338.5L1008 339.2L1024 340.4L1040 342.0L1056 343.9L1072 345.9L1088 347.8L1104 349.4L1120 350.5L1136 351.1L1152 351.1L1168 350.5L1184 349.3L1200 347.7L1216 345.8L1232 343.8L1248 341.9L1264 340.3L1280 339.1%22 fill%3D%22none%22 stroke%3D%22%237cb454%22 stroke-opacity%3D%220.19%22 stroke-width%3D%221%22%2F%3E%3C%2Fg%3E%3Cg%3E%3Cpath d%3D%22M-640 374.6L-624 373.4L-608 372.3L-592 371.3L-576 370.3L-560 369.6L-544 369.0L-528 368.7L-512 368.7L-496 369.1L-480 369.8L-464 371.0L-448 372.6L-432 374.5L-416 376.7L-400 379.1L-384 381.6L-368 384.0L-352 386.4L-336 388.6L-320 390.4L-304 391.9L-288 393.0L-272 393.6L-256 393.7L-240 393.5L-224 392.8L-208 391.9L-192 390.7L-176 389.3L-160 387.8L-144 386.3L-128 384.8L-112 383.3L-96 381.9L-80 380.5L-64 379.3L-48 378.1L-32 376.9L-16 375.7L0 374.6L16 373.4L32 372.3L48 371.3L64 370.3L80 369.6L96 369.0L112 368.7L128 368.7L144 369.1L160 369.8L176 371.0L192 372.6L208 374.5L224 376.7L240 379.1L256 381.6L272 384.0L288 386.4L304 388.6L320 390.4L336 391.9L352 393.0L368 393.6L384 393.7L400 393.5L416 392.8L432 391.9L448 390.7L464 389.3L480 387.8L496 386.3L512 384.8L528 383.3L544 381.9L560 380.5L576 379.3L592 378.1L608 376.9L624 375.7L640 374.6L656 373.4L672 372.3L688 371.3L704 370.3L720 369.6L736 369.0L752 368.7L768 368.7L784 369.1L800 369.8L816 371.0L832 372.6L848 374.5L864 376.7L880 379.1L896 381.6L912 384.0L928 386.4L944 388.6L960 390.4L976 391.9L992 393.0L1008 393.6L1024 393.7L1040 393.5L1056 392.8L1072 391.9L1088 390.7L1104 389.3L1120 387.8L1136 386.3L1152 384.8L1168 383.3L1184 381.9L1200 380.5L1216 379.3L1232 378.1L1248 376.9L1264 375.7L1280 374.6%22 fill%3D%22none%22 stroke%3D%22%237cb454%22 stroke-opacity%3D%220.19%22 stroke-width%3D%221%22%2F%3E%3C%2Fg%3E%3Cg%3E%3Cpath d%3D%22M-640 420.1L-624 419.8L-608 419.8L-592 420.0L-576 420.4L-560 421.0L-544 421.7L-528 422.4L-512 423.2L-496 423.8L-480 424.3L-464 424.6L-448 424.7L-432 424.5L-416 424.1L-400 423.5L-384 422.8L-368 422.0L-352 421.3L-336 420.6L-320 420.1L-304 419.8L-288 419.8L-272 420.0L-256 420.4L-240 421.0L-224 421.7L-208 422.4L-192 423.2L-176 423.8L-160 424.3L-144 424.6L-128 424.7L-112 424.5L-96 424.1L-80 423.5L-64 422.8L-48 422.0L-32 421.3L-16 420.6L0 420.1L16 419.8L32 419.8L48 420.0L64 420.4L80 421.0L96 421.7L112 422.4L128 423.2L144 423.8L160 424.3L176 424.6L192 424.7L208 424.5L224 424.1L240 423.5L256 422.8L272 422.0L288 421.3L304 420.6L320 420.1L336 419.8L352 419.8L368 420.0L384 420.4L400 421.0L416 421.7L432 422.4L448 423.2L464 423.8L480 424.3L496 424.6L512 424.7L528 424.5L544 424.1L560 423.5L576 422.8L592 422.0L608 421.3L624 420.6L640 420.1L656 419.8L672 419.8L688 420.0L704 420.4L720 421.0L736 421.7L752 422.4L768 423.2L784 423.8L800 424.3L816 424.6L832 424.7L848 424.5L864 424.1L880 423.5L896 422.8L912 422.0L928 421.3L944 420.6L960 420.1L976 419.8L992 419.8L1008 420.0L1024 420.4L1040 421.0L1056 421.7L1072 422.4L1088 423.2L1104 423.8L1120 424.3L1136 424.6L1152 424.7L1168 424.5L1184 424.1L1200 423.5L1216 422.8L1232 422.0L1248 421.3L1264 420.6L1280 420.1%22 fill%3D%22none%22 stroke%3D%22%237cb454%22 stroke-opacity%3D%220.19%22 stroke-width%3D%221%22%2F%3E%3C%2Fg%3E%3Cg%3E%3Cpath d%3D%22M-640 461.2L-624 458.1L-608 455.6L-592 453.9L-576 453.0L-560 452.7L-544 452.9L-528 453.5L-512 454.3L-496 455.2L-480 456.4L-464 458.0L-448 459.9L-432 462.1L-416 464.6L-400 467.1L-384 469.4L-368 470.9L-352 471.4L-336 470.7L-320 468.5L-304 465.2L-288 460.9L-272 456.3L-256 451.9L-240 448.4L-224 446.3L-208 446.1L-192 447.6L-176 450.8L-160 455.2L-144 460.2L-128 465.1L-112 469.2L-96 472.0L-80 473.2L-64 472.8L-48 471.0L-32 468.1L-16 464.7L0 461.2L16 458.1L32 455.6L48 453.9L64 453.0L80 452.7L96 452.9L112 453.5L128 454.3L144 455.2L160 456.4L176 458.0L192 459.9L208 462.1L224 464.6L240 467.1L256 469.4L272 470.9L288 471.4L304 470.7L320 468.5L336 465.2L352 460.9L368 456.3L384 451.9L400 448.4L416 446.3L432 446.1L448 447.6L464 450.8L480 455.2L496 460.2L512 465.1L528 469.2L544 472.0L560 473.2L576 472.8L592 471.0L608 468.1L624 464.7L640 461.2L656 458.1L672 455.6L688 453.9L704 453.0L720 452.7L736 452.9L752 453.5L768 454.3L784 455.2L800 456.4L816 458.0L832 459.9L848 462.1L864 464.6L880 467.1L896 469.4L912 470.9L928 471.4L944 470.7L960 468.5L976 465.2L992 460.9L1008 456.3L1024 451.9L1040 448.4L1056 446.3L1072 446.1L1088 447.6L1104 450.8L1120 455.2L1136 460.2L1152 465.1L1168 469.2L1184 472.0L1200 473.2L1216 472.8L1232 471.0L1248 468.1L1264 464.7L1280 461.2%22 fill%3D%22none%22 stroke%3D%22%23d7b568%22 stroke-opacity%3D%220.12%22 stroke-width%3D%221%22%2F%3E%3C%2Fg%3E%3Cg%3E%3Cpath d%3D%22M-640 498.3L-624 501.0L-608 503.6L-592 505.9L-576 507.7L-560 508.8L-544 509.2L-528 508.7L-512 507.4L-496 505.5L-480 503.1L-464 500.5L-448 497.9L-432 495.6L-416 493.8L-400 492.7L-384 492.3L-368 492.8L-352 494.1L-336 496.0L-320 498.3L-304 501.0L-288 503.6L-272 505.9L-256 507.7L-240 508.8L-224 509.2L-208 508.7L-192 507.4L-176 505.5L-160 503.1L-144 500.5L-128 497.9L-112 495.6L-96 493.8L-80 492.7L-64 492.3L-48 492.8L-32 494.1L-16 496.0L0 498.3L16 501.0L32 503.6L48 505.9L64 507.7L80 508.8L96 509.2L112 508.7L128 507.4L144 505.5L160 503.1L176 500.5L192 497.9L208 495.6L224 493.8L240 492.7L256 492.3L272 492.8L288 494.1L304 496.0L320 498.3L336 501.0L352 503.6L368 505.9L384 507.7L400 508.8L416 509.2L432 508.7L448 507.4L464 505.5L480 503.1L496 500.5L512 497.9L528 495.6L544 493.8L560 492.7L576 492.3L592 492.8L608 494.1L624 496.0L640 498.3L656 501.0L672 503.6L688 505.9L704 507.7L720 508.8L736 509.2L752 508.7L768 507.4L784 505.5L800 503.1L816 500.5L832 497.9L848 495.6L864 493.8L880 492.7L896 492.3L912 492.8L928 494.1L944 496.0L960 498.3L976 501.0L992 503.6L1008 505.9L1024 507.7L1040 508.8L1056 509.2L1072 508.7L1088 507.4L1104 505.5L1120 503.1L1136 500.5L1152 497.9L1168 495.6L1184 493.8L1200 492.7L1216 492.3L1232 492.8L1248 494.1L1264 496.0L1280 498.3%22 fill%3D%22none%22 stroke%3D%22%237cb454%22 stroke-opacity%3D%220.17%22 stroke-width%3D%221%22%2F%3E%3C%2Fg%3E%3Cg%3E%3Cpath d%3D%22M-640 540.3L-624 536.5L-608 531.0L-592 525.3L-576 521.0L-560 519.3L-544 520.8L-528 524.8L-512 530.3L-496 535.7L-480 539.5L-464 541.1L-448 540.6L-432 539.0L-416 537.4L-400 536.8L-384 537.7L-368 539.5L-352 541.3L-336 541.9L-320 540.3L-304 536.5L-288 531.0L-272 525.3L-256 521.0L-240 519.3L-224 520.8L-208 524.8L-192 530.3L-176 535.7L-160 539.5L-144 541.1L-128 540.6L-112 539.0L-96 537.4L-80 536.8L-64 537.7L-48 539.5L-32 541.3L-16 541.9L0 540.3L16 536.5L32 531.0L48 525.3L64 521.0L80 519.3L96 520.8L112 524.8L128 530.3L144 535.7L160 539.5L176 541.1L192 540.6L208 539.0L224 537.4L240 536.8L256 537.7L272 539.5L288 541.3L304 541.9L320 540.3L336 536.5L352 531.0L368 525.3L384 521.0L400 519.3L416 520.8L432 524.8L448 530.3L464 535.7L480 539.5L496 541.1L512 540.6L528 539.0L544 537.4L560 536.8L576 537.7L592 539.5L608 541.3L624 541.9L640 540.3L656 536.5L672 531.0L688 525.3L704 521.0L720 519.3L736 520.8L752 524.8L768 530.3L784 535.7L800 539.5L816 541.1L832 540.6L848 539.0L864 537.4L880 536.8L896 537.7L912 539.5L928 541.3L944 541.9L960 540.3L976 536.5L992 531.0L1008 525.3L1024 521.0L1040 519.3L1056 520.8L1072 524.8L1088 530.3L1104 535.7L1120 539.5L1136 541.1L1152 540.6L1168 539.0L1184 537.4L1200 536.8L1216 537.7L1232 539.5L1248 541.3L1264 541.9L1280 540.3%22 fill%3D%22none%22 stroke%3D%22%237cb454%22 stroke-opacity%3D%220.20%22 stroke-width%3D%221%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
  }
  html[data-theme="forest-moss"] #topbar{ border-bottom: 1px solid var(--border-strong); }
  html[data-theme="forest-moss"] .tab-btn.active{ color: var(--accent-flair); border-bottom-color: var(--accent-manual); }

  /* ---- Vintage Paper — a letterpress catalogue. Libre Caslon body, IM
     Fell small caps for lockup/titles, Courier Prime typewriter tags,
     index-card tabs, double-ruled section heads. ---- */
  html[data-theme="vintage-paper"]{
    --bg-base:#f2e8d5; --bg-panel:#f9f2e2; --bg-elevated:#efe3c9; --bg-elevated-2:#e8d9b8;
    --border-soft:#c9b48a; --border-strong:#a68e5f; --text-primary:#3b2c1a; --text-muted:#6b5738;
    --text-faint:#8f7c5c; --accent-auto:#a3742f; --accent-auto-dim:#ecd9ad; --accent-manual:#6b4a2f;
    --accent-manual-dim:#e6d3b3; --accent-danger:#a5432f; --accent-success:#5c7a44; --accent-flair:#a5432f;
    --mono: 'Courier Prime', 'Courier New', monospace;
    --sans: 'Libre Caslon Text', Georgia, serif;
    --display: 'IM Fell English SC', Georgia, serif;
    --head-font: 'IM Fell English SC', Georgia, serif;
    --tab-font: 'IM Fell English SC', Georgia, serif;
    --radius: 2px;
    --brand-weight: 400; --brand-track: 0.02em;
    --head-weight: 400; --head-track: 0.02em;
    --btn-weight: 400;
    --r-chip: 1px; --r-card: 2px; --r-panel: 2px; --r-check: 1px; --r-scroll: 0px;
    --card-shadow: 0 1px 2px color-mix(in srgb, var(--text-primary) 14%, transparent);
    --fx-fill: var(--accent-flair); --fx-o: 1; --fx-top: calc(100% - 2px); --fx-h: 2px; --fx-w: 1;
    --fx-card-t: rotate(-0.6deg) translateY(-2px); --fx-card-s: 0 6px 14px color-mix(in srgb, var(--text-primary) 20%, transparent);
    --icon-stroke: 1.4;
  }
  html[data-theme="vintage-paper"] #gallery{
    background-image: repeating-linear-gradient(115deg, color-mix(in srgb, var(--accent-manual) 7%, transparent) 0px, color-mix(in srgb, var(--accent-manual) 7%, transparent) 1px, transparent 1px, transparent 3px);
  }
  html[data-theme="vintage-paper"] #topbar{ border-bottom: 3px double var(--border-strong); }
  /* Lockup as a printed masthead with a rubber-stamped tag. */
  html[data-theme="vintage-paper"] .brand span.tag{
    background: transparent; color: var(--accent-flair); border: 1px solid var(--accent-flair);
    transform: rotate(-2deg); font-family: var(--mono); font-weight: 700;
  }
  html[data-theme="vintage-paper"] #tabBar{ gap: 3px; border-bottom-color: var(--border-strong); background: var(--bg-base); }
  html[data-theme="vintage-paper"] .tab-btn{
    border: 1px solid transparent; border-bottom: none; border-radius: 4px 4px 0 0;
    margin-bottom: -1px;
  }
  html[data-theme="vintage-paper"] .tab-btn.active{
    background: var(--bg-panel); border-color: var(--border-strong); color: var(--accent-flair); font-weight: 400;
  }
  html[data-theme="vintage-paper"] .tool-section .sec-head{ border-bottom: 3px double var(--border-soft); }
  html[data-theme="vintage-paper"] button{ box-shadow: inset 0 1px 0 color-mix(in srgb, var(--bg-panel) 70%, transparent); }
  html[data-theme="vintage-paper"] .chip{ border-style: dashed; }
  html[data-theme="vintage-paper"] .chip.selected:not(.chip-match,.chip-isolated,.chip-flagged-review){ border-style: solid; }
  html[data-theme="vintage-paper"] .card{ transition: transform 0.15s ease, box-shadow 0.15s ease; }
  html[data-theme="vintage-paper"] .card:hover{ transform: rotate(-0.6deg) translateY(-2px); box-shadow: 0 6px 14px color-mix(in srgb, var(--text-primary) 18%, transparent); }

  /* ---- Obsidian — knapped volcanic glass. Michroma (wide) for lockup,
     tabs and titles over Archivo; chamfered controls and tags; a single
     gold facet catching light along each panel's top edge. ---- */
  html[data-theme="obsidian"]{
    --bg-base:#0a0a0c; --bg-panel:#111114; --bg-elevated:#18181c; --bg-elevated-2:#212126;
    --border-soft:#33333a88; --border-strong:#4a4a54; --text-primary:#f0f0f2; --text-muted:#9c9ca6;
    --text-faint:#65656e; --accent-auto:#c9c9d4; --accent-auto-dim:#2a2a30; --accent-manual:#7c9cff;
    --accent-manual-dim:#1c2440; --accent-danger:#ff5c5c; --accent-success:#5cd68f; --accent-flair:#e0b060;
    --mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
    --sans: 'Archivo', 'Segoe UI', system-ui, sans-serif;
    --display: 'Michroma', 'Segoe UI', sans-serif;
    --head-font: 'Michroma', 'Segoe UI', sans-serif;
    --tab-font: 'Michroma', 'Segoe UI', sans-serif;
    --radius: 0px;
    --brand-weight: 400; --brand-track: 0.06em; --brand-case: uppercase;
    --tab-case: uppercase; --tab-track: 0.1em;
    --head-weight: 400; --head-case: uppercase; --head-track: 0.05em;
    --r-chip: 0px; --r-card: 0px; --r-panel: 0px; --r-check: 0px; --r-scroll: 0px;
    --card-shadow: inset 0 1px 0 color-mix(in srgb, var(--text-primary) 7%, transparent);
    --fx-fill: linear-gradient(115deg, transparent 25%, var(--accent-flair) 50%, transparent 75%); --fx-o: 0.3; --fx-w: 1;
    --fx-card-t: translateY(-2px); --fx-card-s: inset 0 1px 0 var(--accent-flair), 0 12px 24px rgba(0,0,0,0.55);
    --icon-stroke: 1.5; --icon-cap: square; --icon-join: miter;
  }
  html[data-theme="obsidian"] button:not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.compare-img-cell button):not(.chip button):not(.exclude-badge button):not(.lang-common-chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell){
    clip-path: polygon(6px 0, 100% 0, 100% calc(100% - 6px), calc(100% - 6px) 100%, 0 100%, 0 6px);
    background-image:
      linear-gradient(to top left, transparent 50%, var(--border-strong) 50% calc(50% + 1.3px), transparent calc(50% + 1.3px)) top left / 6px 6px no-repeat,
      linear-gradient(to top left, transparent 50%, var(--border-strong) 50% calc(50% + 1.3px), transparent calc(50% + 1.3px)) bottom right / 6px 6px no-repeat;
  }
  /* The clip eats an outside focus outline — draw it inside instead. */
  html[data-theme="obsidian"] button:not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.compare-img-cell button):not(.chip button):not(.exclude-badge button):not(.lang-common-chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell):focus-visible{ outline: none; box-shadow: inset 0 0 0 2px var(--accent-manual); }
  html[data-theme="obsidian"] .chip.selected:not(.chip-match,.chip-isolated,.chip-flagged-review){ box-shadow: inset 0 0 0 1px var(--accent-manual); }
  html[data-theme="obsidian"] .chip{ clip-path: polygon(5px 0, 100% 0, 100% calc(100% - 5px), calc(100% - 5px) 100%, 0 100%, 0 5px); }
  html[data-theme="obsidian"] .tool-section,
  html[data-theme="obsidian"] .settings-section{ box-shadow: inset 0 1px 0 color-mix(in srgb, var(--accent-flair) 40%, transparent); }
  html[data-theme="obsidian"] #topbar{ border-bottom: 1px solid var(--border-strong); }
  /* Facet: a knapped notch of gold under the active tab. */
  html[data-theme="obsidian"] .tab-btn.active{
    color: var(--text-primary); border-bottom-color: var(--accent-flair);
    background: conic-gradient(from 135deg at 50% 0%, var(--accent-flair) 0 90deg, transparent 0) bottom center / 12px 6px no-repeat;
  }
  html[data-theme="obsidian"] button.primary{ box-shadow: none; border-color: var(--accent-manual); }

  /* ---- Retro Wave — 1984 horizon. Audiowide lockup and titles over
     Exo 2, sunset bars stepping down under the header, chamfered
     controls, a synth-grid floor. ---- */
  html[data-theme="retrowave"]{
    --bg-base:#170826; --bg-panel:#1f0e33; --bg-elevated:#2a1442; --bg-elevated-2:#361a54;
    --border-soft:#5a2c8888; --border-strong:#7b3fb5; --text-primary:#fbeaff; --text-muted:#c99ee0;
    --text-faint:#8f6bab; --accent-auto:#00e5ff; --accent-auto-dim:#0a3540; --accent-manual:#ff3ec8;
    --accent-manual-dim:#40122f; --accent-danger:#ff4d6d; --accent-success:#39ff9c; --accent-flair:#00e5ff;
    --mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
    --sans: 'Exo 2', 'Segoe UI', system-ui, sans-serif;
    --display: 'Audiowide', 'Segoe UI', sans-serif;
    --head-font: 'Audiowide', 'Segoe UI', sans-serif;
    --radius: 1px;
    --brand-weight: 400; --brand-track: 0.04em; --brand-case: uppercase;
    --tab-case: uppercase; --tab-track: 0.08em;
    --head-weight: 400; --head-case: uppercase; --head-track: 0.08em; --head-color: var(--accent-flair);
    --btn-weight: 600;
    --r-chip: 1px; --r-card: 2px; --r-panel: 2px; --r-check: 1px; --r-scroll: 0px;
    --card-shadow: none;
    --fx-fill: linear-gradient(90deg, var(--accent-manual), var(--accent-flair)); --fx-o: 0.32; --fx-w: 1;
    --fx-card-t: translateY(-3px); --fx-card-s: 0 12px 26px color-mix(in srgb, var(--accent-manual) 25%, transparent);
    --icon-stroke: 1.75; --icon-cap: square; --icon-join: bevel;
  }
  html[data-theme="retrowave"] #gallery{
    background-image:
      linear-gradient(to bottom, transparent 55%, color-mix(in srgb, var(--accent-manual) 13%, transparent)),
      repeating-linear-gradient(0deg, color-mix(in srgb, var(--accent-flair) 11%, transparent) 0px, transparent 1px, transparent 38px, color-mix(in srgb, var(--accent-flair) 11%, transparent) 39px);
  }
  html[data-theme="retrowave"] #topbar{
    border-bottom: none;
    background-image:
      linear-gradient(var(--accent-manual), var(--accent-manual)),
      linear-gradient(color-mix(in srgb, var(--accent-manual) 70%, var(--accent-flair)), color-mix(in srgb, var(--accent-manual) 70%, var(--accent-flair))),
      linear-gradient(color-mix(in srgb, var(--accent-manual) 35%, var(--accent-flair)), color-mix(in srgb, var(--accent-manual) 35%, var(--accent-flair)));
    background-size: 100% 3px, 100% 2px, 100% 1px;
    background-position: 0 100%, 0 calc(100% - 6px), 0 calc(100% - 11px);
    background-repeat: no-repeat;
  }
  html[data-theme="retrowave"] button:not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.compare-img-cell button):not(.chip button):not(.exclude-badge button):not(.lang-common-chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell):not(.tab-btn){
    clip-path: polygon(5px 0, 100% 0, 100% calc(100% - 5px), calc(100% - 5px) 100%, 0 100%, 0 5px);
    background-image:
      linear-gradient(to top left, transparent 50%, var(--border-strong) 50% calc(50% + 1.3px), transparent calc(50% + 1.3px)) top left / 5px 5px no-repeat,
      linear-gradient(to top left, transparent 50%, var(--border-strong) 50% calc(50% + 1.3px), transparent calc(50% + 1.3px)) bottom right / 5px 5px no-repeat;
  }
  html[data-theme="retrowave"] button:not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.compare-img-cell button):not(.chip button):not(.exclude-badge button):not(.lang-common-chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell):not(.tab-btn):focus-visible{ outline: none; box-shadow: inset 0 0 0 2px var(--accent-flair); }
  html[data-theme="retrowave"] .tab-btn.active{
    color: var(--text-primary); border-bottom-color: transparent;
    background: linear-gradient(90deg, var(--accent-manual), var(--accent-flair)) bottom / 100% 2px no-repeat;
  }

  /* ---- Rose Gold — an Art Deco jeweller. Poiret One lockup over Jost,
     letterspaced capitals for titles, pill controls, stepped deco corners
     on panels, a polished-metal shimmer on the primary button. ---- */
  html[data-theme="rose-gold"]{
    --bg-base:#fbeef0; --bg-panel:#fff6f6; --bg-elevated:#fbe4e6; --bg-elevated-2:#f6d6d9;
    --border-soft:#eec2c6; --border-strong:#dba2a8; --text-primary:#4a2126; --text-muted:#8a5459;
    --text-faint:#b0838a; --accent-auto:#e0a45c; --accent-auto-dim:#fbe6c8; --accent-manual:#c47a6f;
    --accent-manual-dim:#f5ded9; --accent-danger:#c1495a; --accent-success:#6ba07d; --accent-flair:#e0a45c;
    --mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
    --sans: 'Jost', 'Segoe UI', system-ui, sans-serif;
    --display: 'Poiret One', 'Jost', sans-serif;
    --radius: 18px; --r-ctl: 999px;
    --brand-weight: 400; --brand-track: 0.04em;
    --tab-case: uppercase; --tab-track: 0.16em;
    --head-weight: 600; --head-case: uppercase; --head-track: 0.2em; --head-color: color-mix(in srgb, var(--accent-manual) 50%, var(--text-primary));
    --r-chip: 999px; --r-card: 3px; --r-panel: 3px; --r-check: 4px; --r-scroll: 999px;
    --card-shadow: 0 2px 10px color-mix(in srgb, var(--accent-manual) 10%, transparent);
    --fx-fill: linear-gradient(110deg, var(--accent-manual), var(--accent-flair) 55%, var(--accent-manual)); --fx-o: 0.3; --fx-w: 1;
    --fx-card-t: translateY(-3px); --fx-card-s: 0 12px 26px color-mix(in srgb, var(--accent-manual) 22%, transparent);
    --icon-stroke: 1.35;
  }
  html[data-theme="rose-gold"] #topbar{ border-bottom: 1px solid var(--accent-flair); box-shadow: 0 3px 0 -2px var(--accent-manual); }
  html[data-theme="rose-gold"] .tool-section,
  html[data-theme="rose-gold"] .settings-section{
    --deco: color-mix(in srgb, var(--accent-flair) 70%, transparent);
    background:
      linear-gradient(var(--deco), var(--deco)) top 4px left 4px / 12px 1px no-repeat,
      linear-gradient(var(--deco), var(--deco)) top 4px left 4px / 1px 12px no-repeat,
      linear-gradient(var(--deco), var(--deco)) top 4px right 4px / 12px 1px no-repeat,
      linear-gradient(var(--deco), var(--deco)) top 4px right 4px / 1px 12px no-repeat,
      var(--bg-elevated);
  }
  html[data-theme="rose-gold"] .chip.selected:not(.chip-match,.chip-isolated,.chip-flagged-review){ color: color-mix(in srgb, var(--accent-manual) 45%, var(--text-primary)); }
  html[data-theme="rose-gold"] .tab-btn.active{ color: var(--accent-manual); border-bottom-color: var(--accent-flair); }
  html[data-theme="rose-gold"] button.primary{
    background-image: linear-gradient(110deg, transparent 30%, color-mix(in srgb, var(--bg-panel) 55%, transparent) 48%, transparent 66%);
    background-size: 220% 100%;
  }
  html[data-theme="rose-gold"] button.primary:hover:not(:disabled){ animation: flairShimmer 1.4s ease infinite; animation-play-state: var(--flair-play); }

  /* ---- Coral Reef — the tide pool. Nunito (rounded terminals), organic
     asymmetric corners, pill tabs, a drift of bubbles in the ground. ---- */
  html[data-theme="coral-reef"]{
    --bg-base:#04191c; --bg-panel:#082428; --bg-elevated:#0d3237; --bg-elevated-2:#134147;
    --border-soft:#1f636b88; --border-strong:#2c8994; --text-primary:#e3fbfa; --text-muted:#7fc7c4;
    --text-faint:#4f8f8c; --accent-auto:#ffb27a; --accent-auto-dim:#4a2f18; --accent-manual:#ff7f6b;
    --accent-manual-dim:#4a1e18; --accent-danger:#ff5c5c; --accent-success:#4fd6a8; --accent-flair:#4fd6a8;
    --mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
    --sans: 'Nunito', 'Segoe UI', system-ui, sans-serif;
    --radius: 12px; --r-ctl: 999px;
    --brand-weight: 900; --brand-track: -0.01em;
    --head-weight: 800; --head-color: var(--accent-auto);
    --btn-weight: 700;
    --r-chip: 999px; --r-card: 24px 4px 24px 4px; --r-panel: 24px 4px 24px 4px; --r-check: 6px; --r-scroll: 999px;
    --card-shadow: 0 6px 16px rgba(0,0,0,0.25);
    --fx-fill: var(--accent-manual); --fx-o: 0.25;
    --fx-card-t: translateY(-4px); --fx-card-s: 0 14px 28px rgba(0,0,0,0.4);
    --icon-stroke: 2;
  }
  html[data-theme="coral-reef"] #gallery{
    --bub: color-mix(in srgb, var(--accent-flair) 22%, transparent);
    --bub2: color-mix(in srgb, var(--accent-auto) 16%, transparent);
    background-image:
      radial-gradient(circle at 23% 67%, transparent 0 7px, var(--bub) 7px 8.5px, transparent 9px),
      radial-gradient(circle at 71% 18%, transparent 0 4px, var(--bub2) 4px 5px, transparent 5.5px),
      radial-gradient(circle at 44% 41%, transparent 0 2.5px, var(--bub) 2.5px 3.5px, transparent 4px),
      radial-gradient(circle at 86% 83%, transparent 0 10px, var(--bub2) 10px 11px, transparent 11.5px);
    background-size: 373px 541px, 457px 389px, 211px 307px, 613px 701px;
  }
  html[data-theme="coral-reef"] #topbar{ border-bottom: 1px solid var(--border-strong); }
  html[data-theme="coral-reef"] #tabBar{ gap: 6px; }
  html[data-theme="coral-reef"] .tab-btn{ border-radius: 999px; border-bottom: none; }
  html[data-theme="coral-reef"] .tab-btn.active{ background: var(--accent-manual-dim); color: var(--accent-manual); }
  html[data-theme="coral-reef"] .card .thumbwrap{ border-radius: 23px 3px 0 0; }

  /* ---- Toxic Waste — hazard placards. Saira Stencil lockup and titles
     over Barlow Condensed, hazard tape under the header and along each
     dock's top edge, placard-solid primary buttons. ---- */
  html[data-theme="toxic-waste"]{
    --bg-base:#0a0f04; --bg-panel:#111c08; --bg-elevated:#182a0c; --bg-elevated-2:#213a10;
    --border-soft:#4a7a1e88; --border-strong:#649c2c; --text-primary:#e6ffcc; --text-muted:#a8d975;
    --text-faint:#749c4d; --accent-auto:#f5ff3d; --accent-auto-dim:#4a4a0a; --accent-manual:#8aff29;
    --accent-manual-dim:#2a4a0c; --accent-danger:#ff4d4d; --accent-success:#8aff29; --accent-flair:#f5ff3d;
    --mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
    --sans: 'Barlow Condensed', 'Segoe UI', system-ui, sans-serif;
    --display: 'Saira Stencil One', 'Barlow Condensed', sans-serif;
    --head-font: 'Saira Stencil One', 'Barlow Condensed', sans-serif;
    --radius: 2px;
    --brand-weight: 400; --brand-track: 0.04em; --brand-case: uppercase;
    --tab-case: uppercase; --tab-track: 0.06em;
    --head-weight: 400; --head-case: uppercase; --head-track: 0.06em; --head-color: var(--accent-flair);
    --btn-weight: 600; --btn-track: 0.03em;
    --r-chip: 2px; --r-card: 2px; --r-panel: 2px; --r-check: 1px; --r-scroll: 0px;
    --card-shadow: inset 0 -2px 0 color-mix(in srgb, var(--accent-manual) 20%, transparent);
    --fx-fill: repeating-linear-gradient(-45deg, var(--accent-flair) 0 6px, transparent 6px 12px); --fx-o: 0.3; --fx-w: 1;
    --fx-card-t: translateY(-3px); --fx-card-s: 0 0 0 1px var(--accent-flair), 0 12px 24px rgba(0,0,0,0.5);
    --icon-stroke: 2.25; --icon-cap: square; --icon-join: miter;
  }
  html[data-theme="toxic-waste"] #topbar{
    border-bottom: none;
    background-image: repeating-linear-gradient(-45deg, var(--accent-flair) 0 8px, var(--bg-base) 8px 16px);
    background-size: 100% 5px; background-position: bottom; background-repeat: no-repeat;
  }
  html[data-theme="toxic-waste"] .tool-section{
    background:
      repeating-linear-gradient(-45deg, color-mix(in srgb, var(--accent-flair) 55%, transparent) 0 5px, transparent 5px 10px) top / 100% 3px no-repeat,
      var(--bg-elevated);
  }
  html[data-theme="toxic-waste"] .tab-btn.active{ color: var(--accent-flair); border-bottom-color: var(--accent-manual); }
  html[data-theme="toxic-waste"] button.primary{ background: var(--accent-manual); color: var(--bg-base); }
  html[data-theme="toxic-waste"] button.primary:hover:not(:disabled){ background: var(--accent-flair); border-color: var(--accent-flair); }

  /* ---- Lavender Fields — a herbarium sheet. Gilda Display lockup and
     specimen-label titles over Karla, planted-row stripes in the ground,
     a short centred mark under the active tab. ---- */
  html[data-theme="lavender-fields"]{
    --bg-base:#f2eefb; --bg-panel:#faf8ff; --bg-elevated:#ece5f9; --bg-elevated-2:#e0d6f4;
    --border-soft:#d1c1ec; --border-strong:#b39fdb; --text-primary:#332648; --text-muted:#6b5a8a;
    --text-faint:#9689b3; --accent-auto:#c98ed6; --accent-auto-dim:#f0dff5; --accent-manual:#7b5ea8;
    --accent-manual-dim:#e5daf5; --accent-danger:#c95a7a; --accent-success:#5c9a7a; --accent-flair:#c98ed6;
    --mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
    --sans: 'Karla', 'Segoe UI', system-ui, sans-serif;
    --display: 'Gilda Display', Georgia, serif;
    --head-font: 'Gilda Display', Georgia, serif;
    --radius: 8px;
    --brand-weight: 400; --brand-track: 0.01em;
    --head-weight: 400; --head-color: var(--accent-manual);
    --r-chip: 999px; --r-card: 10px; --r-panel: 10px; --r-check: 4px; --r-scroll: 999px;
    --card-shadow: 0 2px 8px color-mix(in srgb, var(--accent-manual) 9%, transparent);
    --fx-fill: var(--accent-flair); --fx-o: 0.28;
    --fx-card-t: translateY(-3px); --fx-card-s: 0 12px 24px color-mix(in srgb, var(--accent-manual) 18%, transparent);
    --icon-stroke: 1.5;
  }
  html[data-theme="lavender-fields"] #gallery{
    background-image: repeating-linear-gradient(90deg, color-mix(in srgb, var(--accent-flair) 13%, transparent) 0 14px, transparent 14px 44px);
  }
  html[data-theme="lavender-fields"] #topbar{ border-bottom: 1px solid var(--border-strong); }
  html[data-theme="lavender-fields"] .tool-section{ background: var(--bg-panel); }
  html[data-theme="lavender-fields"] .tool-section .sec-head{ border-bottom: 1px solid var(--border-soft); }
  html[data-theme="lavender-fields"] .tab-btn.active{
    color: var(--accent-manual); border-bottom-color: transparent;
    background: linear-gradient(var(--accent-flair), var(--accent-flair)) bottom center / 36% 2px no-repeat;
  }

  /* ---- Candy Pop — the sweet shop. Fredoka, bouncy pill controls,
     candy-stripe selected tags, fat pill tabs. ---- */
  html[data-theme="candy-pop"]{
    --bg-base:#fff5fa; --bg-panel:#ffffff; --bg-elevated:#ffe9f4; --bg-elevated-2:#ffdcee;
    --border-soft:#ffc2e0; --border-strong:#ff9fce; --text-primary:#3a1930; --text-muted:#8a4d72;
    --text-faint:#b385a0; --accent-auto:#ffd23f; --accent-auto-dim:#fff3cf; --accent-manual:#ff5fa2;
    --accent-manual-dim:#ffdcee; --accent-danger:#ff4d6d; --accent-success:#3fd9c4; --accent-flair:#ffd23f;
    --mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
    --sans: 'Fredoka', 'Segoe UI', system-ui, sans-serif;
    --radius: 20px; --r-ctl: 999px;
    --brand-weight: 700; --brand-track: -0.01em;
    --head-weight: 600; --head-color: var(--accent-manual);
    --r-chip: 999px; --r-card: 22px; --r-panel: 22px; --r-check: 7px; --r-scroll: 999px;
    --card-shadow: 0 4px 14px color-mix(in srgb, var(--accent-manual) 12%, transparent);
    --fx-fill: var(--accent-flair); --fx-o: 0.5;
    --fx-card-t: translateY(-4px) rotate(-1deg); --fx-card-s: 0 14px 26px color-mix(in srgb, var(--accent-manual) 24%, transparent);
    --icon-stroke: 2.25;
  }
  html[data-theme="candy-pop"] #topbar{ border-bottom: 3px solid var(--accent-manual); }
  html[data-theme="candy-pop"] #tabBar{ gap: 6px; }
  html[data-theme="candy-pop"] .tab-btn{ border-radius: 999px; border-bottom: none; }
  html[data-theme="candy-pop"] .tab-btn.active{ background: var(--accent-manual); color: #ffffff; }
  /* Candy stripe on the wrapper, solid under the text. */
  html[data-theme="candy-pop"] .chip.selected:not(.chip-match,.chip-isolated,.chip-flagged-review){
    color: var(--text-primary); border: 2px solid transparent; padding: 1px 3px 1px 8px;
    background:
      linear-gradient(var(--accent-manual-dim), var(--accent-manual-dim)) padding-box,
      repeating-linear-gradient(-45deg, var(--accent-manual) 0 3px, var(--bg-panel) 3px 6px) border-box;
  }
  html[data-theme="candy-pop"] button:not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.chip button):not(.dm-menu-btn){ transition: transform 0.12s cubic-bezier(.34,1.56,.64,1), background 0.12s ease, border-color 0.12s ease; }
  html[data-theme="candy-pop"] button:not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.chip button):not(.dm-menu-btn):hover:not(:disabled){ transform: scale(1.06) translateY(-1px); }
  html[data-theme="candy-pop"] button:not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.chip button):not(.dm-menu-btn):active:not(:disabled){ transform: scale(0.96); }

  /* ---- Copper Forge — the foundry. Big Shoulders stamped capitals over
     Barlow, brushed-metal panels with four rivets, chamfered plates. ---- */
  html[data-theme="copper-forge"]{
    --bg-base:#120e0b; --bg-panel:#1c1611; --bg-elevated:#261e17; --bg-elevated-2:#33281f;
    --border-soft:#6b4a2e88; --border-strong:#8f6640; --text-primary:#f0e2d0; --text-muted:#c2a184;
    --text-faint:#8f755c; --accent-auto:#c98a4a; --accent-auto-dim:#4a3018; --accent-manual:#b0703f;
    --accent-manual-dim:#3a2414; --accent-danger:#c1493c; --accent-success:#8fae6b; --accent-flair:#c98a4a;
    --mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
    --sans: 'Barlow', 'Segoe UI', system-ui, sans-serif;
    --display: 'Big Shoulders Display', 'Barlow', sans-serif;
    --head-font: 'Big Shoulders Display', 'Barlow', sans-serif;
    --tab-font: 'Big Shoulders Display', 'Barlow', sans-serif;
    --radius: 1px;
    --brand-weight: 800; --brand-track: 0.04em; --brand-case: uppercase;
    --tab-case: uppercase; --tab-track: 0.06em;
    --head-weight: 800; --head-case: uppercase; --head-track: 0.06em; --head-color: var(--accent-flair);
    --r-chip: 1px; --r-card: 1px; --r-panel: 1px; --r-check: 1px; --r-scroll: 0px;
    --card-shadow: 0 2px 4px rgba(0,0,0,0.4);
    --fx-fill: linear-gradient(90deg, var(--accent-manual), var(--accent-auto), color-mix(in srgb, var(--accent-auto) 60%, var(--text-primary))); --fx-o: 0.32; --fx-w: 1;
    --fx-card-t: translateY(-2px); --fx-card-s: 0 0 0 1px var(--accent-flair), 0 12px 24px rgba(0,0,0,0.5);
    --icon-stroke: 2; --icon-cap: square; --icon-join: bevel;
  }
  html[data-theme="copper-forge"] #gallery{
    /* Small plus signs on a grid: riveted plate, kept faint. */
    background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%220%200%2040%2040%22%3E%3Cpath%20d%3D%22M16%2020H24M20%2016V24%22%20fill%3D%22none%22%20stroke%3D%22%23c98a4a%22%20stroke-opacity%3D%22.17%22%20stroke-width%3D%221%22%2F%3E%3C%2Fsvg%3E");
    background-size: 40px 40px;
  }
  html[data-theme="copper-forge"] .tool-section,
  html[data-theme="copper-forge"] .settings-section{
    --rivet: radial-gradient(circle, color-mix(in srgb, var(--accent-flair) 75%, var(--text-primary)) 0 1.2px, var(--accent-manual-dim) 1.8px, transparent 2.6px);
    background:
      var(--rivet) top 6px left 6px / 6px 6px no-repeat,
      var(--rivet) top 6px right 6px / 6px 6px no-repeat,
      var(--rivet) bottom 6px left 6px / 6px 6px no-repeat,
      var(--rivet) bottom 6px right 6px / 6px 6px no-repeat,
      repeating-linear-gradient(0deg, color-mix(in srgb, var(--text-primary) 2%, transparent) 0 1px, transparent 1px 3px),
      var(--bg-elevated);
  }
  html[data-theme="copper-forge"] .settings-section{ background-color: var(--bg-elevated); }
  html[data-theme="copper-forge"] #topbar{ border-bottom: 2px solid var(--accent-manual); }
  html[data-theme="copper-forge"] .tab-btn.active{ color: var(--accent-flair); border-bottom-color: var(--accent-flair); }
  html[data-theme="copper-forge"] button:not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.compare-img-cell button):not(.chip button):not(.exclude-badge button):not(.lang-common-chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell):not(.tab-btn){
    clip-path: polygon(0 0, 100% 0, 100% calc(100% - 7px), calc(100% - 7px) 100%, 0 100%);
    background-image:
      linear-gradient(to top left, transparent 50%, var(--border-strong) 50% calc(50% + 1.3px), transparent calc(50% + 1.3px)) bottom right / 7px 7px no-repeat;
  }
  html[data-theme="copper-forge"] button:not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.compare-img-cell button):not(.chip button):not(.exclude-badge button):not(.lang-common-chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell):not(.tab-btn):focus-visible{ outline: none; box-shadow: inset 0 0 0 2px var(--accent-flair); }

  /* ---- Blood Moon — a gothic eclipse. Pirata One blackletter lockup,
     Eczar titles, Alegreya Sans body, a vignette closing in from the
     edges. ---- */
  html[data-theme="blood-moon"]{
    --bg-base:#0e0505; --bg-panel:#1a0808; --bg-elevated:#260c0c; --bg-elevated-2:#331010;
    --border-soft:#6b1f1f88; --border-strong:#8f2c2c; --text-primary:#ffe3e3; --text-muted:#d68a8a;
    --text-faint:#9c5c5c; --accent-auto:#ff8f4d; --accent-auto-dim:#4a2c14; --accent-manual:#ff3b3b;
    --accent-manual-dim:#4a1414; --accent-danger:#ff3b3b; --accent-success:#8fae6b; --accent-flair:#ffb300;
    --mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
    --sans: 'Alegreya Sans', 'Segoe UI', system-ui, sans-serif;
    --display: 'Pirata One', Georgia, serif;
    --head-font: 'Eczar', Georgia, serif;
    --tab-font: 'Eczar', Georgia, serif;
    --radius: 3px;
    --brand-weight: 400; --brand-track: 0.01em;
    --head-weight: 600; --head-color: var(--text-primary);
    --r-chip: 2px; --r-card: 3px; --r-panel: 3px; --r-check: 2px; --r-scroll: 2px;
    --card-shadow: 0 3px 10px rgba(0,0,0,0.5);
    --fx-fill: var(--accent-manual); --fx-o: 0.3;
    --fx-card-t: translateY(-3px); --fx-card-s: 0 0 0 1px var(--accent-manual), 0 14px 28px rgba(0,0,0,0.6);
    --icon-stroke: 1.6;
  }
  html[data-theme="blood-moon"] #gallery{
    /* The vignette, then fine crossing hatching: tiled layers so the gallery scroll option has something to carry. */
    background-image: radial-gradient(ellipse at 50% 45%, transparent 38%, color-mix(in srgb, var(--accent-manual) 10%, color-mix(in srgb, var(--bg-base) 70%, transparent)) 100%),
      repeating-linear-gradient(135deg, color-mix(in srgb, var(--accent-manual) 8%, transparent) 0 1px, transparent 1px 34px),
      repeating-linear-gradient(45deg, color-mix(in srgb, var(--text-primary) 4%, transparent) 0 1px, transparent 1px 58px);
  }
  html[data-theme="blood-moon"] .brand{ color: var(--accent-manual); }
  html[data-theme="blood-moon"] #topbar{ border-bottom: 1px solid var(--accent-manual); box-shadow: 0 1px 12px -4px color-mix(in srgb, var(--accent-manual) 60%, transparent); }
  html[data-theme="blood-moon"] .tab-btn.active{ color: var(--accent-flair); border-bottom-color: var(--accent-manual); }
  html[data-theme="blood-moon"] .sec-head .t::after{
    content: ''; display: inline-block; width: 5px; height: 5px; margin-left: 7px; vertical-align: 2px;
    border-radius: 50%; background: var(--accent-manual);
  }

  /* ---- Twilight Garden (epic) — a night garden. Gloock titles over
     Figtree, arched (trellis) panel tops, fireflies in the ground. ---- */
  html[data-theme="twilight-garden"]{
    --bg-base:#0c1210; --bg-panel:#131f1a; --bg-elevated:#1a2b24; --bg-elevated-2:#22392f;
    --border-soft:#2f6b5588; --border-strong:#3f8f70; --text-primary:#e3fbe9; --text-muted:#8fc7a5;
    --text-faint:#5f8f74; --accent-auto:#c9a6ff; --accent-auto-dim:#2c1a4a; --accent-manual:#4fd68f;
    --accent-manual-dim:#123a26; --accent-danger:#ff6e91; --accent-success:#4fd68f; --accent-flair:#c9a6ff;
    --mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
    --sans: 'Figtree', 'Segoe UI', system-ui, sans-serif;
    --display: 'Gloock', Georgia, serif;
    --head-font: 'Gloock', Georgia, serif;
    --radius: 10px;
    --brand-weight: 400; --brand-track: 0;
    --head-weight: 400; --head-color: var(--accent-flair);
    --r-chip: 999px; --r-card: 30px 30px 6px 6px; --r-panel: 30px 30px 6px 6px; --r-check: 5px; --r-scroll: 999px;
    --card-shadow: 0 4px 14px rgba(0,0,0,0.35);
    --fx-fill: linear-gradient(90deg, var(--accent-manual), var(--accent-flair)); --fx-o: 0.28; --fx-w: 1;
    --fx-card-t: translateY(-4px); --fx-card-s: 0 14px 30px color-mix(in srgb, var(--accent-flair) 18%, transparent);
  }
  html[data-theme="twilight-garden"] #gallery{
    --ff: color-mix(in srgb, var(--accent-flair) 70%, transparent);
    --ff2: color-mix(in srgb, var(--accent-manual) 60%, transparent);
    background-image:
      radial-gradient(circle at 18% 22%, var(--ff) 0 1.5px, color-mix(in srgb, var(--accent-flair) 14%, transparent) 2px, transparent 9px),
      radial-gradient(circle at 62% 58%, var(--ff2) 0 1.5px, color-mix(in srgb, var(--accent-manual) 12%, transparent) 2px, transparent 8px),
      radial-gradient(circle at 37% 83%, var(--ff) 0 1px, color-mix(in srgb, var(--accent-flair) 10%, transparent) 1.5px, transparent 7px);
    background-size: 419px 367px, 353px 461px, 509px 313px;
  }
  html[data-theme="twilight-garden"] #topbar{ border-bottom: 1px solid var(--border-strong); }
  html[data-theme="twilight-garden"] .card .thumbwrap{ border-radius: 29px 29px 0 0; }
  html[data-theme="twilight-garden"] .tab-btn.active{
    color: var(--accent-flair); border-bottom-color: transparent;
    background: radial-gradient(circle at 50% calc(100% - 4px), var(--accent-manual) 0 2.5px, transparent 3.5px);
  }

  /* ---- Aurora Borealis (legendary) — polar night. Epilogue with wide
     tracked capitals for titles, an aurora curtain rippling along the
     header's edge and each dock's top, the sky drifting slowly behind. ---- */
  html[data-theme="aurora-borealis"]{
    --bg-base:#05080f; --bg-panel:#0a0f1c; --bg-elevated:#0f1830; --bg-elevated-2:#152040;
    --border-soft:#2c5a6b88; --border-strong:#3f7f94; --text-primary:#e3f7ff; --text-muted:#8fc7d6;
    --text-faint:#5f8fa0; --accent-auto:#a6ff9c; --accent-auto-dim:#1a3a1a; --accent-manual:#7b5bff;
    --accent-manual-dim:#241a4a; --accent-danger:#ff5f8f; --accent-success:#5cffb0; --accent-flair:#5cffb0;
    --mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
    --sans: 'Epilogue', 'Segoe UI', system-ui, sans-serif;
    --radius: 7px;
    --brand-weight: 800; --brand-track: 0.01em;
    --tab-case: uppercase; --tab-track: 0.12em;
    --head-weight: 700; --head-case: uppercase; --head-track: 0.16em; --head-color: var(--accent-flair);
    --r-chip: 5px; --r-card: 9px; --r-panel: 9px; --r-check: 4px; --r-scroll: 5px;
    --card-shadow: 0 4px 14px rgba(0,0,0,0.4);
    --fx-fill: linear-gradient(90deg, var(--accent-auto), var(--accent-flair), var(--accent-manual)); --fx-o: 0.3; --fx-w: 1;
    --fx-card-t: translateY(-4px); --fx-card-s: 0 14px 30px color-mix(in srgb, var(--accent-flair) 16%, transparent);
    --aurora: linear-gradient(90deg, var(--accent-auto), var(--accent-flair), var(--accent-manual), var(--accent-flair), var(--accent-auto));
  }
  html[data-theme="aurora-borealis"] #gallery{
    /* The glow, then stars: one irregular scattered SVG tile (not a grid of dots), so the gallery scroll
       option has something to carry. Fixed colours, like Copper Forge's tile. */
    background-image: radial-gradient(ellipse at 20% 0%, color-mix(in srgb, var(--accent-auto) 14%, transparent), transparent 55%),
      radial-gradient(ellipse at 80% 10%, color-mix(in srgb, var(--accent-manual) 14%, transparent), transparent 55%),
      url("data:image/svg+xml,%3Csvg xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22 width%3D%22659%22 height%3D%22587%22 viewBox%3D%220 0 659 587%22%3E%3Ccircle cx%3D%22552.4%22 cy%3D%22298.5%22 r%3D%220.9%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.174%22%2F%3E%3Ccircle cx%3D%22160.8%22 cy%3D%2212.6%22 r%3D%220.7%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.16699999999999998%22%2F%3E%3Ccircle cx%3D%22256.5%22 cy%3D%22358.1%22 r%3D%220.7%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.222%22%2F%3E%3Ccircle cx%3D%22572.5%22 cy%3D%2237.7%22 r%3D%220.5%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.29%22%2F%3E%3Ccircle cx%3D%22427.4%22 cy%3D%22506.2%22 r%3D%220.8%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.18899999999999997%22%2F%3E%3Ccircle cx%3D%22424.3%22 cy%3D%22158%22 r%3D%220.9%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.145%22%2F%3E%3Cpath d%3D%22M247.5 173.8H254.9M251.2 170.1V177.5%22 stroke%3D%22%23e3f7ff%22 stroke-opacity%3D%22.28%22 stroke-width%3D%22.8%22 fill%3D%22none%22%2F%3E%3Ccircle cx%3D%2297.7%22 cy%3D%22457%22 r%3D%220.9%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.252%22%2F%3E%3Ccircle cx%3D%22583.6%22 cy%3D%22472.7%22 r%3D%220.9%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.34%22%2F%3E%3Ccircle cx%3D%2274.5%22 cy%3D%22185.1%22 r%3D%220.7%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.14%22%2F%3E%3Ccircle cx%3D%22280.5%22 cy%3D%22522.1%22 r%3D%220.6%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.276%22%2F%3E%3Ccircle cx%3D%22418.9%22 cy%3D%22336.8%22 r%3D%220.9%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.335%22%2F%3E%3Ccircle cx%3D%22104.3%22 cy%3D%22337.5%22 r%3D%220.7%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.205%22%2F%3E%3Ccircle cx%3D%22637.9%22 cy%3D%22329.2%22 r%3D%220.6%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.317%22%2F%3E%3Ccircle cx%3D%22361.6%22 cy%3D%2250.3%22 r%3D%220.9%22 fill%3D%22%23a6ff9c%22 fill-opacity%3D%220.38%22%2F%3E%3Ccircle cx%3D%22553.6%22 cy%3D%22133.3%22 r%3D%220.6%22 fill%3D%22%23a6ff9c%22 fill-opacity%3D%220.231%22%2F%3E%3Ccircle cx%3D%22337.9%22 cy%3D%22265%22 r%3D%220.9%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.26%22%2F%3E%3Ccircle cx%3D%22143.4%22 cy%3D%22105.6%22 r%3D%220.9%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.22699999999999998%22%2F%3E%3Cpath d%3D%22M208.7 267.3H218.2M213.4 262.6V272%22 stroke%3D%22%23e3f7ff%22 stroke-opacity%3D%22.28%22 stroke-width%3D%22.8%22 fill%3D%22none%22%2F%3E%3Ccircle cx%3D%22559.5%22 cy%3D%22209.3%22 r%3D%220.6%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.162%22%2F%3E%3Ccircle cx%3D%2217.8%22 cy%3D%2244.5%22 r%3D%220.4%22 fill%3D%22%23a6ff9c%22 fill-opacity%3D%220.32899999999999996%22%2F%3E%3Ccircle cx%3D%22486.4%22 cy%3D%22405.2%22 r%3D%220.7%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.406%22%2F%3E%3Ccircle cx%3D%22347%22 cy%3D%22400.7%22 r%3D%220.9%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.20199999999999999%22%2F%3E%3Ccircle cx%3D%22629.5%22 cy%3D%22245.6%22 r%3D%220.8%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.37200000000000005%22%2F%3E%3Ccircle cx%3D%22196.9%22 cy%3D%22423.5%22 r%3D%220.8%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.363%22%2F%3E%3Ccircle cx%3D%22343.7%22 cy%3D%22141.9%22 r%3D%220.5%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.307%22%2F%3E%3Ccircle cx%3D%22617.2%22 cy%3D%22164.5%22 r%3D%220.9%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.396%22%2F%3E%3Ccircle cx%3D%2247.5%22 cy%3D%22557.5%22 r%3D%220.4%22 fill%3D%22%23a6ff9c%22 fill-opacity%3D%220.355%22%2F%3E%3Ccircle cx%3D%22153.6%22 cy%3D%22513.3%22 r%3D%221%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.254%22%2F%3E%3Ccircle cx%3D%22477.5%22 cy%3D%2219.1%22 r%3D%220.9%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.19399999999999998%22%2F%3E%3Cpath d%3D%22M207.1 74H215.7M211.4 69.7V78.3%22 stroke%3D%22%23e3f7ff%22 stroke-opacity%3D%22.28%22 stroke-width%3D%22.8%22 fill%3D%22none%22%2F%3E%3Ccircle cx%3D%22468%22 cy%3D%22248.4%22 r%3D%220.7%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.379%22%2F%3E%3Ccircle cx%3D%22283.8%22 cy%3D%2235.2%22 r%3D%221%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.161%22%2F%3E%3Ccircle cx%3D%22487.2%22 cy%3D%22106.6%22 r%3D%220.7%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.138%22%2F%3E%3Ccircle cx%3D%22164%22 cy%3D%22209.7%22 r%3D%220.9%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.413%22%2F%3E%3Ccircle cx%3D%22581.7%22 cy%3D%22401.4%22 r%3D%220.9%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.32299999999999995%22%2F%3E%3Ccircle cx%3D%22359%22 cy%3D%22535%22 r%3D%220.5%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.408%22%2F%3E%3Ccircle cx%3D%22103%22 cy%3D%2247.5%22 r%3D%220.4%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.361%22%2F%3E%3Ccircle cx%3D%22367.7%22 cy%3D%22465.8%22 r%3D%220.4%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.349%22%2F%3E%3Ccircle cx%3D%22198.8%22 cy%3D%22344.9%22 r%3D%220.6%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.22899999999999998%22%2F%3E%3Ccircle cx%3D%22284.9%22 cy%3D%22440.8%22 r%3D%220.7%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.365%22%2F%3E%3Ccircle cx%3D%22347%22 cy%3D%22333.7%22 r%3D%220.9%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.365%22%2F%3E%3Cpath d%3D%22M636.5 498.8H645.8M641.2 494.2V503.5%22 stroke%3D%22%23e3f7ff%22 stroke-opacity%3D%22.28%22 stroke-width%3D%22.8%22 fill%3D%22none%22%2F%3E%3Ccircle cx%3D%2276.2%22 cy%3D%22270.5%22 r%3D%220.8%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.17800000000000002%22%2F%3E%3Ccircle cx%3D%22612.2%22 cy%3D%22556.5%22 r%3D%221%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.248%22%2F%3E%3Ccircle cx%3D%2253.5%22 cy%3D%22403.6%22 r%3D%220.6%22 fill%3D%22%23e3f7ff%22 fill-opacity%3D%220.409%22%2F%3E%3C%2Fsvg%3E");
    background-size: 160% 160%, 160% 160%, 659px 587px;
    /* The star tile stays put under the drifting glows. */
    animation: auroraDrift 18s ease-in-out infinite alternate;
    animation-play-state: var(--flair-play);
  }
  /* The curtain edge is its own 2px strip pinned to the header's bottom
     edge. Animating the header's own background-position let the band ride
     up to mid-height in the packaged app; a pinned pseudo-element can only
     slide sideways. */
  html[data-theme="aurora-borealis"] #topbar{ border-bottom: none; position: relative; }
  html[data-theme="aurora-borealis"] #topbar::after{
    content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
    background: var(--aurora); background-size: 200% 100%;
    animation: flairSlide 14s linear infinite;
    animation-play-state: var(--flair-play);
    pointer-events: none;
  }
  html[data-theme="aurora-borealis"] .tool-section{
    background: var(--aurora) top / 100% 1px no-repeat, var(--bg-elevated);
  }
  /* Curtain: the active tab hangs a short aurora curtain from its edge. */
  html[data-theme="aurora-borealis"] .tab-btn.active{
    color: var(--text-primary); border-bottom-color: transparent;
    background: var(--aurora) bottom / 100% 2px no-repeat,
      linear-gradient(to top, color-mix(in srgb, var(--accent-flair) 18%, transparent), transparent 75%);
  }

  /* ---- Celestial Gold (legendary) — a star atlas. Marcellus small caps
     for lockup and titles over Jost, a starfield, a double hairline
     beneath the active tab like an ecliptic line. ---- */
  html[data-theme="celestial-gold"]{
    --bg-base:#0a0810; --bg-panel:#120e1c; --bg-elevated:#1a1428; --bg-elevated-2:#241c38;
    --border-soft:#4a3a6688; --border-strong:#6b5494; --text-primary:#fff6e0; --text-muted:#d6c088;
    --text-faint:#9c8a5c; --accent-auto:#ffd166; --accent-auto-dim:#4a3a14; --accent-manual:#e8c468;
    --accent-manual-dim:#4a3a1a; --accent-danger:#ff6e6e; --accent-success:#a6e3b8; --accent-flair:#ffd166;
    --mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, monospace;
    --sans: 'Jost', 'Segoe UI', system-ui, sans-serif;
    --display: 'Marcellus SC', Georgia, serif;
    --head-font: 'Marcellus SC', Georgia, serif;
    --tab-font: 'Marcellus SC', Georgia, serif;
    --radius: 12px; --r-ctl: 999px;
    --brand-weight: 400; --brand-track: 0.2em;
    --tab-track: 0.06em;
    --head-weight: 400; --head-track: 0.08em; --head-color: var(--accent-flair);
    --r-chip: 999px; --r-card: 12px; --r-panel: 12px; --r-check: 4px; --r-scroll: 999px;
    --card-shadow: 0 4px 14px rgba(0,0,0,0.4);
    --fx-fill: radial-gradient(ellipse at 0% 50%, var(--accent-flair), transparent 75%); --fx-o: 0.35; --fx-w: 1;
    --fx-card-t: translateY(-4px); --fx-card-s: 0 0 0 1px color-mix(in srgb, var(--accent-flair) 60%, transparent), 0 14px 30px rgba(0,0,0,0.5);
    --icon-stroke: 1.35;
  }
  html[data-theme="celestial-gold"] #gallery{
    background-image: radial-gradient(circle at 50% 0%, color-mix(in srgb, var(--accent-flair) 14%, transparent), transparent 60%),
      radial-gradient(circle at 10% 20%, color-mix(in srgb, var(--text-primary) 60%, transparent) 0 1px, transparent 1.5px),
      radial-gradient(circle at 70% 60%, color-mix(in srgb, var(--text-primary) 50%, transparent) 0 1px, transparent 1.5px),
      radial-gradient(circle at 40% 80%, color-mix(in srgb, var(--text-primary) 40%, transparent) 0 1px, transparent 1.5px),
      radial-gradient(circle at 90% 30%, color-mix(in srgb, var(--text-primary) 50%, transparent) 0 1px, transparent 1.5px);
    background-size: 100% 100%, 240px 240px, 300px 300px, 200px 200px, 260px 260px;
  }
  html[data-theme="celestial-gold"] .brand span.tag{ background: transparent; border: 1px solid color-mix(in srgb, var(--accent-flair) 55%, transparent); }
  html[data-theme="celestial-gold"] #topbar{ border-bottom: 1px solid color-mix(in srgb, var(--accent-flair) 60%, transparent); }
  html[data-theme="celestial-gold"] .tool-section,
  html[data-theme="celestial-gold"] .settings-section{
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent-flair) 16%, transparent);
  }
  html[data-theme="celestial-gold"] .tab-btn.active{
    color: var(--accent-flair); border-bottom-color: transparent;
    background:
      linear-gradient(var(--accent-flair), var(--accent-flair)) bottom 4px center / 70% 1px no-repeat,
      linear-gradient(var(--accent-flair), var(--accent-flair)) bottom 1px center / 40% 1px no-repeat;
  }

  /* ---- Windows 7 (legendary) — Aero. Segoe UI (Selawik where Windows
     isn't), a glass title bar with its gloss line and diagonal streaks, a
     command bar of flat buttons that light up as blue glass, two-tone grey
     push buttons that take the blue hover quickly and let it go slowly, a
     default button that breathes, green progress bars with a running glint,
     Harmony-blue desktop behind the gallery. Every gradient is mixed from
     the theme vars, so night mode gets a dark Aero instead of a broken one. ---- */
  html[data-theme="windows-7"]{
    --bg-base:#f0f0f0; --bg-panel:#ffffff; --bg-elevated:#f7f9fc; --bg-elevated-2:#e9eef4;
    --border-soft:#d5dfe5; --border-strong:#8e9aa8; --text-primary:#1b1b1b; --text-muted:#4d5d72;
    --text-faint:#7f8b99; --accent-auto:#b35f00; --accent-auto-dim:#fdf0dc; --accent-manual:#0066cc;
    --accent-manual-dim:#dcecfc; --accent-danger:#c42b1c; --accent-success:#1e8f2e; --accent-flair:#3a8ee6;
    --mono: 'JetBrains Mono', Consolas, monospace;
    --sans: 'Selawik', 'Segoe UI', Tahoma, system-ui, sans-serif;
    --display: 'Selawik', 'Segoe UI', Tahoma, sans-serif; --head-font: 'Selawik', 'Segoe UI', Tahoma, sans-serif; --tab-font: var(--sans);
    --radius: 3px; --r-ctl: 3px; --r-chip: 3px; --r-card: 4px; --r-panel: 6px; --r-check: 2px; --r-scroll: 3px;
    --brand-weight: 400; --brand-track: 0;
    --head-weight: 400; --head-color: color-mix(in srgb, var(--accent-manual) 45%, var(--text-primary));
    --btn-weight: 400;
    --card-shadow: 0 1px 3px rgba(0,0,0,0.16);
    --icon-stroke: 1.5;
    /* The Aero glass and the two button faces, mixed once here. */
    --w7-hi: color-mix(in srgb, var(--bg-panel) 72%, transparent);
    --w7-glass: linear-gradient(to bottom, color-mix(in srgb, var(--accent-flair) 34%, var(--bg-panel)), color-mix(in srgb, var(--accent-flair) 56%, var(--bg-panel)));
    --w7-hover: linear-gradient(to bottom, color-mix(in srgb, var(--accent-manual-dim) 45%, var(--bg-panel)) 0%,
      var(--accent-manual-dim) 49%, color-mix(in srgb, var(--accent-flair) 24%, var(--bg-panel)) 50%,
      color-mix(in srgb, var(--accent-flair) 38%, var(--bg-panel)) 100%);
    --w7-press: linear-gradient(to bottom, color-mix(in srgb, var(--accent-flair) 18%, var(--bg-panel)) 0%,
      color-mix(in srgb, var(--accent-flair) 28%, var(--bg-panel)) 49%, color-mix(in srgb, var(--accent-flair) 46%, var(--bg-panel)) 50%,
      color-mix(in srgb, var(--accent-flair) 58%, var(--bg-panel)) 100%);
    --w7-ring: color-mix(in srgb, var(--accent-manual) 55%, var(--bg-panel));
    --btn-bg: linear-gradient(to bottom, color-mix(in srgb, var(--bg-panel) 82%, var(--bg-base)) 0%,
      color-mix(in srgb, var(--bg-base) 85%, var(--bg-panel)) 49%, color-mix(in srgb, var(--bg-base) 90%, var(--border-strong)) 50%,
      color-mix(in srgb, var(--bg-base) 76%, var(--border-strong)) 100%);
    --btn-bg-hover: var(--w7-hover);
    --btn-bd-hover: var(--w7-ring);
    --btn-shadow: inset 0 0 0 1px var(--w7-hi);
    /* The hover-fill IS the blue glass: full width, faded rather than swept
       (the transition is restated below). The card lift is Explorer's
       hover box. */
    --fx-fill: var(--w7-hover); --fx-o: 1; --fx-w: 1;
    --fx-card-t: translateY(-2px);
    --fx-card-s: 0 0 0 1px var(--w7-ring), 0 0 0 3px color-mix(in srgb, var(--accent-flair) 22%, transparent), 0 8px 20px rgba(0,0,0,0.16);
    --mat: linear-gradient(to bottom, color-mix(in srgb, var(--accent-flair) 9%, transparent), transparent 60%) 0 0 / 100% 100% no-repeat;
  }
  /* The desktop: Harmony blue, a bloom of light behind where the logo
     would be, and the wallpaper's long curved swooshes. Every layer is
     100%-sized and --ground-still keeps the gallery scroll option off it:
     a wallpaper doesn't drift. */
  html[data-theme="windows-7"] #gallery{
    --ground-still: 1;
    background-image:
      radial-gradient(ellipse 34% 40% at 58% 40%, color-mix(in srgb, var(--bg-panel) 50%, transparent), transparent 70%),
      radial-gradient(ellipse 95% 46% at 40% 108%, transparent 62%, color-mix(in srgb, var(--bg-panel) 30%, transparent) 63% 64%, transparent 67%),
      radial-gradient(ellipse 110% 58% at 62% 112%, transparent 64%, color-mix(in srgb, var(--bg-panel) 22%, transparent) 65% 66.5%, transparent 69%),
      radial-gradient(ellipse 120% 80% at 20% 120%, color-mix(in srgb, var(--accent-flair) 30%, transparent), transparent 60%),
      linear-gradient(160deg, color-mix(in srgb, var(--accent-flair) 34%, var(--bg-base)), color-mix(in srgb, var(--accent-manual) 24%, var(--bg-base)) 55%, color-mix(in srgb, var(--accent-flair) 44%, var(--bg-base)));
    background-size: 100% 100%;
  }
  /* Title bar: Aero glass with the gloss line across its middle and the
     faint diagonal light streaks; the caption glows white behind it. */
  html[data-theme="windows-7"] #topbar{
    border-bottom: 1px solid color-mix(in srgb, var(--accent-flair) 50%, var(--text-primary));
    background:
      linear-gradient(to bottom, color-mix(in srgb, var(--bg-panel) 46%, transparent), color-mix(in srgb, var(--bg-panel) 14%, transparent) 50%, transparent 50%),
      repeating-linear-gradient(115deg, transparent 0 70px, color-mix(in srgb, var(--bg-panel) 16%, transparent) 70px 76px,
        transparent 76px 130px, color-mix(in srgb, var(--bg-panel) 10%, transparent) 130px 150px, transparent 150px 260px),
      radial-gradient(ellipse 40% 160% at 14% 0%, color-mix(in srgb, var(--bg-panel) 45%, transparent), transparent 70%),
      var(--w7-glass);
    box-shadow: inset 0 1px 0 var(--w7-hi), inset 0 -1px 0 color-mix(in srgb, var(--bg-panel) 35%, transparent);
  }
  html[data-theme="windows-7"] .brand,
  html[data-theme="windows-7"] .app-version{
    text-shadow: 0 0 6px var(--bg-panel), 0 0 12px var(--bg-panel), 0 0 18px color-mix(in srgb, var(--bg-panel) 70%, transparent);
  }
  html[data-theme="windows-7"] .brand span.tag{
    text-shadow: none; color: var(--text-primary);
    background: var(--w7-hover); border: 1px solid var(--w7-ring);
  }
  /* Command bar: Explorer's pale gradient strip. Its buttons are flat until
     the pointer finds them, then a rounded blue glass box (::before, inset
     so it floats inside the bar) fades in fast and out slowly. */
  html[data-theme="windows-7"] #tabBar{
    background: linear-gradient(to bottom, color-mix(in srgb, var(--bg-panel) 82%, var(--accent-manual-dim)), color-mix(in srgb, var(--accent-manual-dim) 70%, var(--bg-base)));
    border-bottom: 1px solid color-mix(in srgb, var(--accent-flair) 30%, var(--border-strong));
    box-shadow: inset 0 1px 0 var(--w7-hi);
  }
  html[data-theme="windows-7"] .tab-btn{ position: relative; isolation: isolate; color: var(--text-primary); border-bottom-color: transparent; }
  html[data-theme="windows-7"] .tab-btn:hover:not(:disabled){ background: transparent; }
  html[data-theme="windows-7"] .tab-btn::before{
    content: ''; position: absolute; inset: 5px 1px; border-radius: 3px; z-index: -1; pointer-events: none;
    background: var(--w7-hover); box-shadow: 0 0 0 1px var(--w7-ring), inset 0 0 0 1px var(--w7-hi);
    opacity: 0; transition: opacity 0.6s ease-out;
  }
  html[data-theme="windows-7"] .tab-btn:hover:not(:disabled)::before{ opacity: 1; transition-duration: 0.1s; }
  html[data-theme="windows-7"] .tab-btn.active{ color: var(--text-primary); border-bottom-color: transparent; }
  html[data-theme="windows-7"] .tab-btn.active::before{
    opacity: 1; background: var(--w7-press);
    box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent-manual) 70%, var(--text-primary)), inset 0 1px 2px color-mix(in srgb, var(--text-primary) 25%, transparent);
  }
  /* The tabs have their own glass; the button-wide hover fill would double it. */
  html[data-theme="windows-7"] .tab-btn::after{ content: none !important; }
  /* Push buttons: the blue arrives in 0.1s and drains over 0.7s (the fill
     fades at full width instead of sweeping); pressed is the deeper glass. */
  html[data-theme="windows-7"] button:not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell)::after{
    transform: scaleX(1) !important; opacity: 0 !important;
    transition: opacity 0.7s ease-out !important;
  }
  html[data-theme="windows-7"] button:not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell):hover:not(:disabled):not(.active):not(.on):not(.selected):not([aria-pressed="true"])::after{
    opacity: 1 !important; transition-duration: 0.1s !important;
  }
  html[data-theme="windows-7"] button:active:not(:disabled){ --btn-bg-hover: var(--w7-press); --fx-fill: var(--w7-press); transform: none; }
  /* The default button: a blue-ringed face; the one in a dialog breathes. */
  html[data-theme="windows-7"] button.primary{
    background: var(--btn-bg); color: var(--text-primary); font-weight: 400;
    border-color: color-mix(in srgb, var(--accent-manual) 70%, var(--text-primary));
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent-flair) 55%, transparent);
  }
  html[data-theme="windows-7"] button.primary:hover:not(:disabled){ background: var(--w7-hover); }
  html[data-theme="windows-7"] .modal-card button.primary,
  html[data-theme="windows-7"] .confirm-btn-row button.primary{
    animation: w7DefaultPulse 1.6s ease-in-out infinite alternate; animation-play-state: var(--flair-play);
  }
  @keyframes w7DefaultPulse{
    from{ box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent-flair) 35%, transparent); }
    to{ box-shadow: inset 0 0 0 1px var(--accent-flair), inset 0 0 7px 1px color-mix(in srgb, var(--accent-flair) 45%, transparent); }
  }
  /* Panels: Control Panel's task panes, a pale blue head fading to white. */
  html[data-theme="windows-7"] .tool-section,
  html[data-theme="windows-7"] .settings-section{
    background: linear-gradient(to bottom, color-mix(in srgb, var(--accent-manual-dim) 60%, var(--bg-panel)), var(--bg-panel) 44px);
    border-color: color-mix(in srgb, var(--accent-flair) 28%, var(--border-soft));
    box-shadow: inset 0 1px 0 var(--w7-hi), 0 1px 2px rgba(0,0,0,0.06);
  }
  /* Selection: Explorer's blue glass. */
  html[data-theme="windows-7"] .chip.selected:not(.chip-match,.chip-isolated,.chip-flagged-review){
    background: var(--w7-hover); border-color: var(--w7-ring); color: var(--text-primary);
  }
  html[data-theme="windows-7"] .pdrop-item:hover, html[data-theme="windows-7"] .pdrop-item:focus-visible,
  html[data-theme="windows-7"] .ctx-item:hover, html[data-theme="windows-7"] .ctx-item:focus-visible{
    background: var(--w7-hover); box-shadow: inset 0 0 0 1px var(--w7-ring);
  }
  html[data-theme="windows-7"] .pdrop-item.active{ color: var(--text-primary); }
  /* Menus: the classic grey sheet with its icon gutter. */
  html[data-theme="windows-7"] .ctx-menu,
  html[data-theme="windows-7"] .pdrop-menu,
  html[data-theme="windows-7"] .header-cat-flyout{
    background: linear-gradient(90deg, transparent 27px, color-mix(in srgb, var(--border-soft) 90%, var(--text-faint)) 27px 28px, var(--bg-panel) 28px 29px, transparent 29px), var(--bg-base);
    border-color: color-mix(in srgb, var(--border-strong) 80%, var(--text-faint)); border-radius: 0;
    box-shadow: 3px 3px 4px rgba(0,0,0,0.22);
  }
  /* Windows: dialogs wear the glass frame. */
  html[data-theme="windows-7"] .modal-card{
    border-radius: 6px; border-color: color-mix(in srgb, var(--accent-flair) 40%, var(--text-primary));
    box-shadow: 0 0 0 7px color-mix(in srgb, var(--accent-flair) 48%, transparent),
      0 0 0 8px color-mix(in srgb, var(--text-primary) 45%, transparent), 0 18px 48px rgba(0,0,0,0.45);
  }
  /* Checkboxes: a grey well inside a white rim; checked is a dark tick on
     it, not a filled square; hover is blue. */
  html[data-theme="windows-7"] :where(input[type="checkbox"]){
    border: 1px solid color-mix(in srgb, var(--border-strong) 80%, var(--text-faint));
    background: linear-gradient(135deg, color-mix(in srgb, var(--border-strong) 32%, var(--bg-panel)), var(--bg-panel));
    box-shadow: inset 0 0 0 2px var(--bg-panel);
  }
  html[data-theme="windows-7"] :where(input[type="checkbox"]:hover:not(:disabled)){
    border-color: var(--w7-ring);
    background: linear-gradient(135deg, color-mix(in srgb, var(--accent-flair) 40%, var(--bg-panel)), color-mix(in srgb, var(--accent-flair) 8%, var(--bg-panel)));
  }
  html[data-theme="windows-7"] :where(input[type="checkbox"]:checked)::after{
    border-color: color-mix(in srgb, var(--accent-manual) 40%, var(--text-primary));
  }
  /* Scrollbars: the bevelled grey thumb with its grip, blue under the pointer. */
  html[data-theme="windows-7"] ::-webkit-scrollbar-track{ background: linear-gradient(90deg, color-mix(in srgb, var(--bg-base) 85%, var(--border-strong)), var(--bg-base) 30%); }
  html[data-theme="windows-7"] ::-webkit-scrollbar-thumb{
    border: 1px solid color-mix(in srgb, var(--border-strong) 85%, var(--text-faint)); border-radius: 3px;
    background: repeating-linear-gradient(to bottom, color-mix(in srgb, var(--text-faint) 70%, transparent) 0 1px, var(--bg-panel) 1px 2px) center / 7px 8px no-repeat,
      linear-gradient(90deg, var(--bg-panel), color-mix(in srgb, var(--bg-base) 70%, var(--border-strong)));
  }
  html[data-theme="windows-7"] ::-webkit-scrollbar-thumb:horizontal{
    background: repeating-linear-gradient(90deg, color-mix(in srgb, var(--text-faint) 70%, transparent) 0 1px, var(--bg-panel) 1px 2px) center / 8px 7px no-repeat,
      linear-gradient(to bottom, var(--bg-panel), color-mix(in srgb, var(--bg-base) 70%, var(--border-strong)));
  }
  html[data-theme="windows-7"] ::-webkit-scrollbar-thumb:hover{ border-color: var(--w7-ring); }
  /* Progress: the green bar and the glint that runs along it. */
  html[data-theme="windows-7"] .wd14-progress-track{
    background: linear-gradient(to bottom, color-mix(in srgb, var(--bg-base) 85%, var(--border-strong)), var(--bg-panel));
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--border-strong) 70%, transparent);
  }
  html[data-theme="windows-7"] .wd14-progress-fill{
    position: relative; overflow: hidden;
    background: linear-gradient(to bottom, color-mix(in srgb, var(--accent-success) 45%, var(--bg-panel)), var(--accent-success) 50%, color-mix(in srgb, var(--accent-success) 80%, var(--bg-panel)));
  }
  html[data-theme="windows-7"] .wd14-progress-fill::after{
    content: ''; position: absolute; inset: 0; width: 40%; pointer-events: none;
    background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--bg-panel) 65%, transparent), transparent);
    animation: w7Glint 2.2s linear infinite; animation-play-state: var(--flair-play);
  }
  @keyframes w7Glint{ from{ transform: translateX(-100%); } to{ transform: translateX(260%); } }
  @media (prefers-reduced-motion: reduce){
    html[data-theme="windows-7"] .wd14-progress-fill::after,
    html[data-theme="windows-7"] .modal-card button.primary,
    html[data-theme="windows-7"] .confirm-btn-row button.primary{ animation: none; }
  }

  /* ---- Frutiger Aero (epic) — the glossy 2000s: sky over grass, bubbles,
     jelly. Nunito for the lockup and titles over Selawik, pill-shaped
     aqua-jelly buttons with a wet highlight across their top half, a sky
     that fades into a meadow behind the gallery with bubbles rising
     through it, and a light glint that sweeps a button on hover. ---- */
  html[data-theme="frutiger-aero"]{
    --bg-base:#eef9f1; --bg-panel:#ffffff; --bg-elevated:#f2fbf8; --bg-elevated-2:#def3f7;
    --border-soft:#bfe3e8; --border-strong:#7fbfcc; --text-primary:#0f3346; --text-muted:#3d6b7d;
    --text-faint:#7ea3b0; --accent-auto:#c86400; --accent-auto-dim:#fff0d9; --accent-manual:#0082c8;
    --accent-manual-dim:#d7f0fb; --accent-danger:#d63a4a; --accent-success:#2f9a2a; --accent-flair:#5cc93b;
    --mono: 'JetBrains Mono', Consolas, monospace;
    --sans: 'Selawik', 'Segoe UI', system-ui, sans-serif;
    --display: 'Nunito', 'Selawik', sans-serif; --head-font: 'Nunito', 'Selawik', sans-serif; --tab-font: var(--sans);
    --radius: 10px; --r-ctl: 999px; --r-chip: 999px; --r-card: 14px; --r-panel: 14px; --r-check: 5px; --r-scroll: 999px;
    --brand-weight: 800; --brand-track: -0.01em;
    --head-weight: 800; --head-color: var(--accent-manual);
    --btn-weight: 600;
    --card-shadow: 0 2px 8px color-mix(in srgb, var(--accent-manual) 14%, transparent);
    --icon-stroke: 1.75;
    /* The buttons' jelly (face + wet highlight) runs along this; night mode
       turns it over so the gloss sits at the foot (the user's call). Bars,
       panels and the sky keep their fixed direction. */
    --fa-dir: to bottom;
    --fa-wet: linear-gradient(to bottom, color-mix(in srgb, var(--bg-panel) 85%, transparent), color-mix(in srgb, var(--bg-panel) 25%, transparent) 50%, transparent 52%);
    --fa-btn-wet: linear-gradient(var(--fa-dir), color-mix(in srgb, var(--bg-panel) 85%, transparent), color-mix(in srgb, var(--bg-panel) 25%, transparent) 50%, transparent 52%);
    --btn-bg: var(--fa-btn-wet), linear-gradient(var(--fa-dir), color-mix(in srgb, var(--accent-manual) 10%, var(--bg-panel)), color-mix(in srgb, var(--accent-manual) 26%, var(--bg-panel)));
    --btn-bg-hover: var(--fa-btn-wet), linear-gradient(var(--fa-dir), color-mix(in srgb, var(--accent-manual) 18%, var(--bg-panel)), color-mix(in srgb, var(--accent-manual) 40%, var(--bg-panel)));
    --btn-bd-hover: var(--accent-manual);
    --btn-shadow: inset 0 -1px 2px color-mix(in srgb, var(--accent-manual) 25%, transparent), 0 1px 2px color-mix(in srgb, var(--accent-manual) 18%, transparent);
    --fx-fill: linear-gradient(100deg, transparent 20%, color-mix(in srgb, var(--bg-panel) 90%, transparent) 50%, transparent 80%); --fx-o: 0.8; --fx-w: 1;
    --fx-card-t: translateY(-4px) scale(1.01);
    --fx-card-s: 0 0 0 2px color-mix(in srgb, var(--accent-manual) 45%, transparent), 0 12px 26px color-mix(in srgb, var(--accent-manual) 24%, transparent);
    --mat: radial-gradient(circle at 30% 30%, color-mix(in srgb, var(--accent-manual) 9%, transparent) 0 6px, transparent 7px) 0 0 / 34px 34px,
      linear-gradient(to bottom, color-mix(in srgb, var(--accent-manual) 7%, transparent), color-mix(in srgb, var(--accent-flair) 7%, transparent));
  }
  html.night-mode[data-theme="frutiger-aero"]{ --fa-dir: to top; }
  /* A Bliss-style hillside (painted, not the photo): blue sky with soft
     clouds over a rolling green hill, nothing in front of it (no bubbles:
     the user's call). A picture, so it never drifts (--ground-still), and
     Settings can swap in the user's own image. */
  html[data-theme="frutiger-aero"] #gallery{
    --ground-still: 1;
    background-image:
      radial-gradient(ellipse 60% 40% at 34% 64%, color-mix(in srgb, var(--bg-panel) 22%, transparent), transparent 70%),
      radial-gradient(ellipse 85% 48% at 34% 108%, color-mix(in srgb, var(--accent-flair) 72%, var(--bg-base)) 0 97%, transparent 100%),
      radial-gradient(ellipse 70% 42% at 96% 112%, color-mix(in srgb, var(--accent-flair) 80%, var(--text-primary)) 0 97%, transparent 100%),
      radial-gradient(ellipse 16% 6% at 20% 16%, color-mix(in srgb, var(--bg-panel) 75%, transparent), transparent 70%),
      radial-gradient(ellipse 22% 7% at 30% 20%, color-mix(in srgb, var(--bg-panel) 55%, transparent), transparent 70%),
      radial-gradient(ellipse 20% 6% at 74% 11%, color-mix(in srgb, var(--bg-panel) 65%, transparent), transparent 70%),
      linear-gradient(to bottom, color-mix(in srgb, var(--accent-manual) 55%, var(--bg-panel)), color-mix(in srgb, var(--accent-manual) 18%, var(--bg-panel)) 70%);
    background-size: 100% 100%;
    background-position: 0 0;
  }
  /* The user's own picture (Settings ▸ Frutiger Aero background,
     theme-background.ts), filling the gallery like a desktop wallpaper. */
  html.fa-user-bg[data-theme="frutiger-aero"] #gallery{
    background: var(--fa-user-bg) center / cover no-repeat, var(--bg-base);
  }
  /* A glossy aqua bar with the wet highlight; a grass line beneath. */
  html[data-theme="frutiger-aero"] #topbar{
    background: var(--fa-wet), linear-gradient(to bottom, color-mix(in srgb, var(--accent-manual) 22%, var(--bg-panel)), color-mix(in srgb, var(--accent-manual) 42%, var(--bg-panel)));
    border-bottom: 3px solid var(--accent-flair);
    box-shadow: inset 0 -1px 0 color-mix(in srgb, var(--bg-panel) 50%, transparent);
  }
  html[data-theme="frutiger-aero"] .brand{ color: var(--text-primary); text-shadow: 0 1px 0 color-mix(in srgb, var(--bg-panel) 80%, transparent); }
  html[data-theme="frutiger-aero"] .brand span.tag{ background: var(--accent-flair); color: var(--text-primary); text-shadow: none; }
  html[data-theme="frutiger-aero"] #tabBar{ gap: 6px; background: linear-gradient(to bottom, var(--bg-panel), var(--bg-elevated)); }
  html[data-theme="frutiger-aero"] .tab-btn{ border-radius: 999px; border-bottom: none; }
  html[data-theme="frutiger-aero"] .tab-btn.active{
    color: var(--text-primary);
    background: var(--fa-btn-wet), linear-gradient(var(--fa-dir), color-mix(in srgb, var(--accent-flair) 55%, var(--bg-panel)), var(--accent-flair));
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent-flair) 70%, var(--text-primary)), 0 2px 6px color-mix(in srgb, var(--accent-flair) 35%, transparent);
  }
  html[data-theme="frutiger-aero"] button.primary{
    background: var(--fa-btn-wet), linear-gradient(var(--fa-dir), color-mix(in srgb, var(--accent-flair) 60%, var(--bg-panel)), var(--accent-flair));
    border-color: color-mix(in srgb, var(--accent-flair) 65%, var(--text-primary)); color: var(--text-primary);
  }
  html[data-theme="frutiger-aero"] button.primary:hover:not(:disabled){
    background: var(--fa-btn-wet), linear-gradient(var(--fa-dir), color-mix(in srgb, var(--accent-flair) 75%, var(--bg-panel)), color-mix(in srgb, var(--accent-flair) 85%, var(--text-primary)));
  }
  html[data-theme="frutiger-aero"] .tool-section,
  html[data-theme="frutiger-aero"] .settings-section{
    background: linear-gradient(to bottom, var(--bg-panel), var(--bg-elevated));
    border-color: var(--border-soft);
    box-shadow: inset 0 1px 0 var(--bg-panel), 0 3px 10px color-mix(in srgb, var(--accent-manual) 10%, transparent);
  }
  html[data-theme="frutiger-aero"] .chip.selected:not(.chip-match,.chip-isolated,.chip-flagged-review){
    background: var(--fa-wet), color-mix(in srgb, var(--accent-manual) 30%, var(--bg-panel)); border-color: var(--accent-manual); color: var(--text-primary);
  }
  html[data-theme="frutiger-aero"] :where(input[type="checkbox"]:checked){
    background: var(--fa-wet), var(--accent-flair); border-color: color-mix(in srgb, var(--accent-flair) 65%, var(--text-primary));
  }
  html[data-theme="frutiger-aero"] .wd14-progress-fill{
    background: var(--fa-wet), linear-gradient(90deg, var(--accent-manual), var(--accent-flair));
  }
  html[data-theme="frutiger-aero"] ::-webkit-scrollbar-thumb{
    background: var(--fa-wet), linear-gradient(90deg, color-mix(in srgb, var(--accent-manual) 35%, var(--bg-panel)), color-mix(in srgb, var(--accent-manual) 55%, var(--bg-panel)));
  }

  /* Light grounds: accent-auto on accent-auto-dim (the brand tag, a
     flagged-for-review chip) falls under 3:1 on every light palette, so
     those two pick up ink from the theme's own text color instead. */
  html[data-theme="arctic-frost"] .brand span.tag, html[data-theme="rose-gold"] .brand span.tag,
  html[data-theme="lavender-fields"] .brand span.tag, html[data-theme="candy-pop"] .brand span.tag,
  html[data-theme="vintage-paper"] .brand span.tag{ color: var(--text-primary); }
  html[data-theme="subway"] .chip.chip-flagged-review, html[data-theme="osmium"] .chip.chip-flagged-review,
  html[data-theme="arctic-frost"] .chip.chip-flagged-review, html[data-theme="vintage-paper"] .chip.chip-flagged-review,
  html[data-theme="rose-gold"] .chip.chip-flagged-review, html[data-theme="lavender-fields"] .chip.chip-flagged-review,
  html[data-theme="candy-pop"] .chip.chip-flagged-review, html[data-theme="windows-7"] .chip.chip-flagged-review,
  html[data-theme="frutiger-aero"] .chip.chip-flagged-review{ color: color-mix(in srgb, var(--accent-auto) 40%, var(--text-primary)); }

  /* ---- Premium hover-fill effect (epic/legendary tier shop themes only) ----
     A button's ::after fills in with the theme's own --fx-fill on hover
     (its shape, opacity, and resting width come from the --fx-* tokens each
     theme sets: a gilt underline for Amethyst, scanlines for Cyberpunk, the
     route strip for Subway, …) — reserved for the pricier themes as a
     purchase-worthy flourish, not applied everywhere (unobtrusive by
     exclusion). Excludes any button that relies on `position: absolute`
     for its own placement (.img-menu-btn — the 3-dot image-options button,
     the remove button inside .compare-img-cell, and .dm-menu-btn — the
     Dataset tab's per-folder 3-dot button) — forcing `position: relative`
     onto those broke their placement (they'd fall back into normal document
     flow instead of sitting pinned to their card corner), which is exactly
     the "broken button positioning in single mode" bug this exclusion list
     fixes. Also excludes every `all: unset`-based button class in the file
     (.ctx-item, .pdrop-item, .dock-collapse-btn, .dm-icon-picker-cell, and
     the classless-button-inside-a-classed-parent ones: .chip button,
     .exclude-badge button, .lang-common-chip button)
     — layering `position/overflow/isolation` back on top of an `all:
     unset` reset via a much-higher-specificity selector produced
     squashed, overlapping text (first caught on .ctx-item, in the 3-dot
     menu, on epic/legendary themes — see DEVELOPMENT_LOG.txt item 20).
     Add any FUTURE absolutely-positioned OR `all: unset`-based button
     class here too, not just style around it — grep the file for
     `all: unset` before adding another broad theme-wide button rule.

     `html.theme-refined` is OR'd in alongside the five hardcoded theme
     names — Refine Theme (achievements.ts's shop) lets a user unlock this
     effect for ANY theme individually (applyTheme(), themes.ts, toggles
     this class based on the refinedThemes list). A future new epic/
     legendary theme still gets its own `html[data-theme="..."]` line as
     before; theme-refined only covers themes bought UP to this tier.
     Because the fill reads the ACTIVE theme's --fx-* tokens, a refined
     theme gets its own world's version of the effect for free.

     Every selector here (native AND theme-refined) also carries
     `:not(.suppress-theme-flourishes)` — the Settings "Suppress Theme
     Flourishes" toggle (was "Suppress Theme Upgrade") hides the Refine
     Theme button AND blanks this effect everywhere, not just on
     individually-refined themes; a theme that's natively epic/legendary
     is suppressed the same as one bought up to that tier. */
  html[data-theme="amethyst"]:not(.suppress-theme-flourishes) button:not(.ghost-close):not(.danger-ghost):not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.compare-img-cell button):not(.chip button):not(.exclude-badge button):not(.lang-common-chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell),
  html[data-theme="solarflare"]:not(.suppress-theme-flourishes) button:not(.ghost-close):not(.danger-ghost):not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.compare-img-cell button):not(.chip button):not(.exclude-badge button):not(.lang-common-chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell),
  html[data-theme="twilight-garden"]:not(.suppress-theme-flourishes) button:not(.ghost-close):not(.danger-ghost):not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.compare-img-cell button):not(.chip button):not(.exclude-badge button):not(.lang-common-chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell),
  html[data-theme="aurora-borealis"]:not(.suppress-theme-flourishes) button:not(.ghost-close):not(.danger-ghost):not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.compare-img-cell button):not(.chip button):not(.exclude-badge button):not(.lang-common-chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell),
  html[data-theme="celestial-gold"]:not(.suppress-theme-flourishes) button:not(.ghost-close):not(.danger-ghost):not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.compare-img-cell button):not(.chip button):not(.exclude-badge button):not(.lang-common-chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell),
  html[data-theme="windows-7"]:not(.suppress-theme-flourishes) button:not(.ghost-close):not(.danger-ghost):not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.compare-img-cell button):not(.chip button):not(.exclude-badge button):not(.lang-common-chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell),
  html[data-theme="frutiger-aero"]:not(.suppress-theme-flourishes) button:not(.ghost-close):not(.danger-ghost):not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.compare-img-cell button):not(.chip button):not(.exclude-badge button):not(.lang-common-chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell),
  html.theme-refined:not(.suppress-theme-flourishes) button:not(.ghost-close):not(.danger-ghost):not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.compare-img-cell button):not(.chip button):not(.exclude-badge button):not(.lang-common-chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell){
    /* `overflow: clip`, NOT `hidden`: both clip the ::after flood, but
       `hidden` makes the button a scroll container, which drops a flex
       item's automatic min-width/min-height to 0 — so any button in a
       crowded flex row/column (Clear filter, the Tag sorting pill) got
       squashed below its own text, padding eaten, on these themes only. */
    position: relative; overflow: clip; isolation: isolate;
  }
  html[data-theme="amethyst"]:not(.suppress-theme-flourishes) button:not(.ghost-close):not(.danger-ghost):not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.compare-img-cell button):not(.chip button):not(.exclude-badge button):not(.lang-common-chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell)::after,
  html[data-theme="solarflare"]:not(.suppress-theme-flourishes) button:not(.ghost-close):not(.danger-ghost):not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.compare-img-cell button):not(.chip button):not(.exclude-badge button):not(.lang-common-chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell)::after,
  html[data-theme="twilight-garden"]:not(.suppress-theme-flourishes) button:not(.ghost-close):not(.danger-ghost):not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.compare-img-cell button):not(.chip button):not(.exclude-badge button):not(.lang-common-chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell)::after,
  html[data-theme="aurora-borealis"]:not(.suppress-theme-flourishes) button:not(.ghost-close):not(.danger-ghost):not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.compare-img-cell button):not(.chip button):not(.exclude-badge button):not(.lang-common-chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell)::after,
  html[data-theme="celestial-gold"]:not(.suppress-theme-flourishes) button:not(.ghost-close):not(.danger-ghost):not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.compare-img-cell button):not(.chip button):not(.exclude-badge button):not(.lang-common-chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell)::after,
  html[data-theme="windows-7"]:not(.suppress-theme-flourishes) button:not(.ghost-close):not(.danger-ghost):not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.compare-img-cell button):not(.chip button):not(.exclude-badge button):not(.lang-common-chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell)::after,
  html[data-theme="frutiger-aero"]:not(.suppress-theme-flourishes) button:not(.ghost-close):not(.danger-ghost):not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.compare-img-cell button):not(.chip button):not(.exclude-badge button):not(.lang-common-chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell)::after,
  html.theme-refined:not(.suppress-theme-flourishes) button:not(.ghost-close):not(.danger-ghost):not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.compare-img-cell button):not(.chip button):not(.exclude-badge button):not(.lang-common-chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell)::after{
    content: ''; position: absolute; left: 0; top: var(--fx-top); width: 100%; height: var(--fx-h);
    background: var(--fx-fill); opacity: var(--fx-o);
    /* Scaled, not width-animated: the fill runs on the compositor with no
       per-frame layout. */
    transform: scaleX(0); transform-origin: left center;
    transition: transform 0.24s cubic-bezier(.2,.8,.3,1), opacity 0.2s ease;
    z-index: -1; pointer-events: none;
  }
  html[data-theme="amethyst"]:not(.suppress-theme-flourishes) button:not(.ghost-close):not(.danger-ghost):not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.compare-img-cell button):not(.chip button):not(.exclude-badge button):not(.lang-common-chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell):hover:not(:disabled)::after,
  html[data-theme="solarflare"]:not(.suppress-theme-flourishes) button:not(.ghost-close):not(.danger-ghost):not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.compare-img-cell button):not(.chip button):not(.exclude-badge button):not(.lang-common-chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell):hover:not(:disabled)::after,
  html[data-theme="twilight-garden"]:not(.suppress-theme-flourishes) button:not(.ghost-close):not(.danger-ghost):not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.compare-img-cell button):not(.chip button):not(.exclude-badge button):not(.lang-common-chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell):hover:not(:disabled)::after,
  html[data-theme="aurora-borealis"]:not(.suppress-theme-flourishes) button:not(.ghost-close):not(.danger-ghost):not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.compare-img-cell button):not(.chip button):not(.exclude-badge button):not(.lang-common-chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell):hover:not(:disabled)::after,
  html[data-theme="celestial-gold"]:not(.suppress-theme-flourishes) button:not(.ghost-close):not(.danger-ghost):not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.compare-img-cell button):not(.chip button):not(.exclude-badge button):not(.lang-common-chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell):hover:not(:disabled)::after,
  html[data-theme="windows-7"]:not(.suppress-theme-flourishes) button:not(.ghost-close):not(.danger-ghost):not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.compare-img-cell button):not(.chip button):not(.exclude-badge button):not(.lang-common-chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell):hover:not(:disabled)::after,
  html[data-theme="frutiger-aero"]:not(.suppress-theme-flourishes) button:not(.ghost-close):not(.danger-ghost):not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.compare-img-cell button):not(.chip button):not(.exclude-badge button):not(.lang-common-chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell):hover:not(:disabled)::after,
  html.theme-refined:not(.suppress-theme-flourishes) button:not(.ghost-close):not(.danger-ghost):not(.img-menu-btn):not(.ctx-item):not(.pdrop-item):not(.dock-collapse-btn):not(.compare-img-cell button):not(.chip button):not(.exclude-badge button):not(.lang-common-chip button):not(.dm-menu-btn):not(.dm-icon-picker-cell):hover:not(:disabled)::after{
    transform: scaleX(var(--fx-w));
  }
  /* Click flash: on an actual click (not just hover), the hover-fill
     briefly floods to full width, then fades out via opacity — independent
     of whether the pointer is still over the button afterward. `.fx-flash`
     is added/removed by a tiny generic click listener (shared-ui.ts,
     initClickFlash()) on ANY button, app-wide; it's a no-op everywhere
     except these themes' buttons, since only they have the `::after` box
     above for it to animate — no per-theme JS needed. Once the animation
     ends and the class is removed, the normal :hover-driven width rule
     above takes back over via its own transition (settles to --fx-w if
     still hovered, or 0% if not) — that's the "restores naturally when
     mouseoff'd and hovered again" behavior. */
  @keyframes flairClickFlash{
    0%{ transform: scaleX(0); opacity: 0.6; }
    35%{ transform: scaleX(1); opacity: 0.6; }
    100%{ transform: scaleX(1); opacity: 0; }
  }
  .fx-flash::after{ animation: flairClickFlash 0.6s ease forwards; }
  /* Without this, removing `.fx-flash` while still hovering let the ::after
     box transition straight from the flash's end state (full scale, 0
     opacity) to the :hover rule's scale — it barely moves so it read as
     an instant "reappear" rather than a fill. `.fx-flash-reset` (added right
     after `.fx-flash` is removed, then removed itself one frame later —
     initClickFlash(), shared-ui.ts) snaps the fill back to zero with no
     transition first, so the width rule that applies once it's gone
     (--fx-w if still hovered, 0% if not) plays out as a normal fill-in from
     zero, the same motion a fresh mouseenter would produce. `!important`
     because it must beat the `:hover` rule's own specificity
     unconditionally. */
  .fx-flash-reset::after{ transition: none !important; transform: scaleX(0) !important; }
  /* A button that is already lit by its own state (current tab, on toggle,
     selected item) has nothing to fill, so no hover fill and no refill. A
     click flash already running (the click that made it active) is left to
     finish: it floods the whole button, fades, and only then does this rule
     hold the fill at zero. Everything returns once it stops being active. */
  button.active:not(.fx-flash)::after, button.on:not(.fx-flash)::after,
  button.selected:not(.fx-flash)::after, button[aria-pressed="true"]:not(.fx-flash)::after{
    transition: none !important; transform: scaleX(0) !important; opacity: 0 !important;
  }

  /* Card lift: the second Refine Theme reward alongside the button
     hover-fill above — same epic/legendary + `.theme-refined` group, so
     buying one buys both. The motion and shadow come from the active
     theme's --fx-card-t/--fx-card-s (Vintage Paper's tilt, a gilt ring for
     Amethyst, a HUD offset for Cyberpunk, …). */
  html[data-theme="amethyst"]:not(.suppress-theme-flourishes) .card,
  html[data-theme="solarflare"]:not(.suppress-theme-flourishes) .card,
  html[data-theme="twilight-garden"]:not(.suppress-theme-flourishes) .card,
  html[data-theme="aurora-borealis"]:not(.suppress-theme-flourishes) .card,
  html[data-theme="celestial-gold"]:not(.suppress-theme-flourishes) .card,
  html[data-theme="windows-7"]:not(.suppress-theme-flourishes) .card,
  html[data-theme="frutiger-aero"]:not(.suppress-theme-flourishes) .card,
  html.theme-refined:not(.suppress-theme-flourishes) .card{
    transition: transform 0.2s cubic-bezier(.2,.8,.3,1), box-shadow 0.2s ease;
  }
  html[data-theme="amethyst"]:not(.suppress-theme-flourishes) .card:hover,
  html[data-theme="solarflare"]:not(.suppress-theme-flourishes) .card:hover,
  html[data-theme="twilight-garden"]:not(.suppress-theme-flourishes) .card:hover,
  html[data-theme="aurora-borealis"]:not(.suppress-theme-flourishes) .card:hover,
  html[data-theme="celestial-gold"]:not(.suppress-theme-flourishes) .card:hover,
  html[data-theme="windows-7"]:not(.suppress-theme-flourishes) .card:hover,
  html[data-theme="frutiger-aero"]:not(.suppress-theme-flourishes) .card:hover,
  html.theme-refined:not(.suppress-theme-flourishes) .card:hover{
    transform: var(--fx-card-t);
    box-shadow: var(--fx-card-s);
  }

  /* Visual flourishes (Settings): three independent motion-sensitivity
     switches, each killing one NATIVE effect app-wide — regardless of
     whether a theme has it built-in (epic/legendary, Vintage Paper's
     one-off card-tilt) or via Suppress Theme Flourishes/Refine Theme.
     Deliberately does NOT touch each theme's static, non-animated
     material (textures, rules, frames) — those aren't a motion trigger.
     `!important` because these have to beat several more specific
     per-theme `:hover` rules unconditionally. Ambient animations are
     handled separately above via `--flair-play`. */
  html.no-flourish-tilt .card:hover{ transform: none !important; }
  html.no-flourish-hover button::after{ display: none !important; }

  /* ---------- Gallery grounds for the themes that had none ----------
     Same rules as the older grounds above: derived from theme vars through
     color-mix() (night mode inverts them), ink at 3-8% so it reads as texture
     and vanishes behind cards, and built from tiles or straight hatch so the
     optional gallery scroll (ground-scroll.ts) can drift them. Each echoes its
     theme's mat at a larger, quieter scale. */
  /* Royal Amethyst: a wide gilt argyle, diamonds on hairline gold. */
  html[data-theme="amethyst"] #gallery{
    background-image:
      repeating-linear-gradient(45deg, color-mix(in srgb, var(--accent-auto) 6%, transparent) 0 1px, transparent 1px 44px),
      repeating-linear-gradient(-45deg, color-mix(in srgb, var(--accent-auto) 6%, transparent) 0 1px, transparent 1px 44px);
  }
  /* Candy Pop: candy silhouettes (wrapped candy, candy corn, marshmallow), all
     tilted the same 24 degrees. SVG tiles carry literal palette colors at low
     alpha: a data-URI image can't read theme vars, so night mode leaves them be. */
  html[data-theme="candy-pop"] #gallery{
    background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22300%22%20height%3D%22260%22%20viewBox%3D%220%200%20300%20260%22%3E%3Cg%20transform%3D%22translate%2862%2052%29%20rotate%28-24%29%22%3E%3Cg%20fill%3D%22%23ff5fa2%22%20fill-opacity%3D%220.18%22%3E%3Ccircle%20r%3D%2211%22%2F%3E%3Cpath%20d%3D%22M-9%20-1%20L-25%20-10%20Q-29%200%20-25%2010%20L-9%201%20Z%22%2F%3E%3Cpath%20d%3D%22M9%20-1%20L25%20-10%20Q29%200%2025%2010%20L9%201%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fg%3E%3Cg%20transform%3D%22translate%28205%2044%29%20rotate%28-24%29%22%3E%3Cg%20fill%3D%22%23ffb800%22%20fill-opacity%3D%220.26%22%20stroke%3D%22%23ffb800%22%20stroke-opacity%3D%220.26%22%20stroke-width%3D%222%22%20stroke-linejoin%3D%22round%22%3E%3Cpolygon%20points%3D%22-0%2C-18%200%2C-18%204.2%2C-7%20-4.2%2C-7%22%2F%3E%3Cpolygon%20points%3D%22-5.2%2C-4.5%205.2%2C-4.5%209.2%2C6%20-9.2%2C6%22%2F%3E%3Cpolygon%20points%3D%22-10.1%2C8.5%2010.1%2C8.5%2013%2C16%20-13%2C16%22%2F%3E%3C%2Fg%3E%3C%2Fg%3E%3Cg%20transform%3D%22translate%28262%20128%29%20rotate%28-24%29%22%3E%3Crect%20x%3D%22-15%22%20y%3D%22-10%22%20width%3D%2230%22%20height%3D%2220%22%20rx%3D%228%22%20fill%3D%22%232fcfb8%22%20fill-opacity%3D%220.2%22%2F%3E%3C%2Fg%3E%3Cg%20transform%3D%22translate%28150%20140%29%20rotate%28-24%29%22%3E%3Cg%20fill%3D%22%232fcfb8%22%20fill-opacity%3D%220.18%22%3E%3Ccircle%20r%3D%2211%22%2F%3E%3Cpath%20d%3D%22M-9%20-1%20L-25%20-10%20Q-29%200%20-25%2010%20L-9%201%20Z%22%2F%3E%3Cpath%20d%3D%22M9%20-1%20L25%20-10%20Q29%200%2025%2010%20L9%201%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fg%3E%3Cg%20transform%3D%22translate%2852%20196%29%20rotate%28-24%29%22%3E%3Cg%20fill%3D%22%23ff5fa2%22%20fill-opacity%3D%220.17%22%20stroke%3D%22%23ff5fa2%22%20stroke-opacity%3D%220.17%22%20stroke-width%3D%222%22%20stroke-linejoin%3D%22round%22%3E%3Cpolygon%20points%3D%22-0%2C-18%200%2C-18%204.2%2C-7%20-4.2%2C-7%22%2F%3E%3Cpolygon%20points%3D%22-5.2%2C-4.5%205.2%2C-4.5%209.2%2C6%20-9.2%2C6%22%2F%3E%3Cpolygon%20points%3D%22-10.1%2C8.5%2010.1%2C8.5%2013%2C16%20-13%2C16%22%2F%3E%3C%2Fg%3E%3C%2Fg%3E%3Cg%20transform%3D%22translate%28214%20214%29%20rotate%28-24%29%22%3E%3Crect%20x%3D%22-15%22%20y%3D%22-10%22%20width%3D%2230%22%20height%3D%2220%22%20rx%3D%228%22%20fill%3D%22%23ffb800%22%20fill-opacity%3D%220.26%22%2F%3E%3C%2Fg%3E%3Cg%20transform%3D%22translate%28108%20236%29%20rotate%28-24%29%22%3E%3Crect%20x%3D%22-15%22%20y%3D%22-10%22%20width%3D%2230%22%20height%3D%2220%22%20rx%3D%228%22%20fill%3D%22%23ff5fa2%22%20fill-opacity%3D%220.14%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
    background-size: 300px 260px;
  }
  /* Cyberpunk: data rails, a thin vertical line every 96px with a node between. */
  html[data-theme="cyberpunk"] #gallery{
    background-image:
      linear-gradient(90deg, color-mix(in srgb, var(--accent-flair) 7%, transparent) 1px, transparent 1px),
      radial-gradient(circle, color-mix(in srgb, var(--accent-flair) 16%, transparent) 1.2px, transparent 1.8px);
    background-size: 96px 100%, 96px 96px;
    background-position: 47px 0, 0 0;
  }
  /* Obsidian: long glassy facets, the mat's fracture lines opened out. */
  html[data-theme="obsidian"] #gallery{
    background-image:
      repeating-linear-gradient(115deg, color-mix(in srgb, var(--accent-auto) 5%, transparent) 0 1px, transparent 1px 61px),
      repeating-linear-gradient(115deg, color-mix(in srgb, var(--text-primary) 4%, transparent) 0 1px, transparent 1px 97px);
  }
  /* Osmium: the brand dot field, quieter and wider than the mat's. */
  html[data-theme="osmium"] #gallery{
    background-image: radial-gradient(circle at 1px 1px, color-mix(in srgb, var(--text-primary) 8%, transparent) 1px, transparent 1.5px);
    background-size: 26px 26px;
  }
  /* Rose Gold: Art Deco pinstripe, a paired hairline every 72px. */
  html[data-theme="rose-gold"] #gallery{
    background-image: repeating-linear-gradient(90deg,
      color-mix(in srgb, var(--accent-auto) 8%, transparent) 0 1px, transparent 1px 5px,
      color-mix(in srgb, var(--accent-auto) 8%, transparent) 5px 6px, transparent 6px 72px);
  }
  /* Studio: the workbench pegboard, holes far apart. */
  html[data-theme="studio"] #gallery{
    background-image: radial-gradient(circle, color-mix(in srgb, var(--text-primary) 6%, transparent) 1.2px, transparent 1.8px);
    background-size: 32px 32px;
  }
  /* Subway: a fine navigation grid, the sibling of Midnight Ocean's chart grid on a tighter 80px cell. */
  html[data-theme="subway"] #gallery{
    background-image:
      linear-gradient(color-mix(in srgb, var(--text-primary) 12%, transparent) 1px, transparent 1px),
      linear-gradient(90deg, color-mix(in srgb, var(--text-primary) 12%, transparent) 1px, transparent 1px);
    background-size: 80px 80px;
  }
  /* Toxic Waste: hazard bands, wide and barely there. */
  html[data-theme="toxic-waste"] #gallery{
    background-image: repeating-linear-gradient(-45deg, color-mix(in srgb, var(--accent-auto) 4%, transparent) 0 14px, transparent 14px 56px);
  }

  /* ---------- Image mats: the letterbox behind every image ----------
     A fixed-shape frame (the 3:4 card thumb, Single view's preview, the image
     modal's image side) leaves bare ground wherever the image's aspect ratio
     doesn't match. Instead of a flat void, each world lays down its own mat:
     a small, quiet motif drawn from theme vars through color-mix(), so night
     mode inverts it with everything else (the Derived Texture Rule). Mats are
     static material, never motion, so no flourish toggle gates them.
     `--mat` is a background-layer list (image + position/size) painted over
     `--bg-base` by the three frames' own rules further down. Keep the ink
     around 8-14%: it should read as texture at a glance and vanish the moment
     the eye lands on the image. Any theme that sets nothing (Custom
     included) gets Studio's pegboard. */
  :root{
    --mat-ink: color-mix(in srgb, var(--text-primary) 12%, transparent);
    /* Studio: workbench pegboard, a square grid of drilled holes. */
    --mat: radial-gradient(circle, var(--mat-ink) 1.1px, transparent 1.6px) 0 0 / 16px 16px;
  }
  /* Cyberpunk: fine diagonal HUD hatch in the flair neon. */
  html[data-theme="cyberpunk"]{
    --mat: repeating-linear-gradient(-45deg, color-mix(in srgb, var(--accent-flair) 13%, transparent) 0 1px, transparent 1px 7px);
  }
  /* Oriental: nashiji, gold flakes sprinkled into lacquer on two offset lattices. */
  html[data-theme="oriental"]{
    --mat: radial-gradient(circle, color-mix(in srgb, var(--accent-auto) 26%, transparent) 0.9px, transparent 1.4px) 0 0 / 23px 19px,
      radial-gradient(circle, color-mix(in srgb, var(--accent-auto) 16%, transparent) 0.7px, transparent 1.2px) 11px 9px / 17px 23px;
  }
  /* Subway: station wall tile, 2:1 glazed tiles in stacked bond. */
  html[data-theme="subway"]{
    --mat: linear-gradient(color-mix(in srgb, var(--text-primary) 9%, transparent) 1px, transparent 1px) 0 0 / 28px 14px,
      linear-gradient(90deg, color-mix(in srgb, var(--text-primary) 9%, transparent) 1px, transparent 1px) 0 0 / 28px 14px;
  }
  /* Osmium: the brand's own dot field, the same one the website lays under
     its screenshots. */
  html[data-theme="osmium"]{
    --mat: radial-gradient(circle at 1px 1px, color-mix(in srgb, var(--text-primary) 16%, transparent) 1px, transparent 1.5px) 0 0 / 18px 18px;
  }
  /* Terminal: CRT scanlines, the dark field between phosphor rows. */
  html[data-theme="terminal"]{
    --mat: repeating-linear-gradient(0deg, color-mix(in srgb, var(--text-primary) 9%, transparent) 0 1px, transparent 1px 3px);
  }
  /* Sakura Dusk: petals drifting on washi, sparse and uneven. */
  html[data-theme="sakura"]{
    --mat: radial-gradient(ellipse 3px 2px, color-mix(in srgb, var(--accent-flair) 30%, transparent) 60%, transparent 100%) 6px 8px / 41px 37px,
      radial-gradient(ellipse 2px 3px, color-mix(in srgb, var(--accent-flair) 22%, transparent) 60%, transparent 100%) 25px 27px / 53px 47px;
  }
  /* Bioluminescent Deep: marine snow and faint plankton specks. */
  html[data-theme="bioluminescent"]{
    --mat: radial-gradient(circle, color-mix(in srgb, var(--accent-manual) 34%, transparent) 0.9px, transparent 1.8px) 3px 5px / 29px 31px,
      radial-gradient(circle, color-mix(in srgb, var(--accent-success) 20%, transparent) 0.7px, transparent 1.4px) 17px 19px / 43px 37px;
  }
  /* Royal Amethyst: gilt argyle, a diamond lattice of hairline gold. */
  html[data-theme="amethyst"]{
    --mat: repeating-linear-gradient(45deg, color-mix(in srgb, var(--accent-auto) 13%, transparent) 0 1px, transparent 1px 14px),
      repeating-linear-gradient(-45deg, color-mix(in srgb, var(--accent-auto) 13%, transparent) 0 1px, transparent 1px 14px);
  }
  /* Solar Flare: corona rings radiating from the frame's center. */
  html[data-theme="solarflare"]{
    --mat: repeating-radial-gradient(circle at 50% 50%, color-mix(in srgb, var(--accent-auto) 12%, transparent) 0 1px, transparent 1px 11px);
  }
  /* Midnight Ocean: a chart's lat/long grid. */
  html[data-theme="midnight-ocean"]{
    --mat: linear-gradient(color-mix(in srgb, var(--accent-manual) 12%, transparent) 1px, transparent 1px) 0 0 / 22px 22px,
      linear-gradient(90deg, color-mix(in srgb, var(--accent-manual) 12%, transparent) 1px, transparent 1px) 0 0 / 22px 22px;
  }
  /* Arctic Frost: crystalline cross-hatch at 60 degrees, like frost on glass. */
  html[data-theme="arctic-frost"]{
    --mat: repeating-linear-gradient(60deg, var(--mat-ink) 0 1px, transparent 1px 12px),
      repeating-linear-gradient(-60deg, var(--mat-ink) 0 1px, transparent 1px 12px);
  }
  /* Forest Moss: moss stipple, dense and irregular. */
  html[data-theme="forest-moss"]{
    --mat: radial-gradient(circle, color-mix(in srgb, var(--accent-success) 24%, transparent) 0.9px, transparent 1.5px) 0 0 / 9px 11px,
      radial-gradient(circle, color-mix(in srgb, var(--accent-success) 16%, transparent) 0.8px, transparent 1.4px) 4px 6px / 13px 7px;
  }
  /* Vintage Paper: a ledger, ruled lines with one margin rule. */
  html[data-theme="vintage-paper"]{
    --mat: linear-gradient(90deg, transparent 22px, color-mix(in srgb, var(--accent-danger) 22%, transparent) 22px 23px, transparent 23px) 0 0 / 100% 100% no-repeat,
      linear-gradient(transparent 17px, color-mix(in srgb, var(--accent-manual) 16%, transparent) 17px 18px) 0 0 / 100% 18px;
  }
  /* Obsidian: conchoidal fracture, long glassy facets in one direction. */
  html[data-theme="obsidian"]{
    --mat: repeating-linear-gradient(115deg, color-mix(in srgb, var(--accent-auto) 10%, transparent) 0 1px, transparent 1px 19px),
      repeating-linear-gradient(115deg, var(--mat-ink) 0 1px, transparent 1px 31px);
  }
  /* Retro Wave: the synth grid, magenta on the horizon. */
  html[data-theme="retrowave"]{
    --mat: linear-gradient(color-mix(in srgb, var(--accent-flair) 15%, transparent) 1px, transparent 1px) 0 0 / 24px 24px,
      linear-gradient(90deg, color-mix(in srgb, var(--accent-flair) 15%, transparent) 1px, transparent 1px) 0 0 / 24px 24px;
  }
  /* Rose Gold: Art Deco pinstripe, paired hairlines. */
  html[data-theme="rose-gold"]{
    --mat: repeating-linear-gradient(90deg, color-mix(in srgb, var(--accent-auto) 16%, transparent) 0 1px, transparent 1px 4px, color-mix(in srgb, var(--accent-auto) 16%, transparent) 4px 5px, transparent 5px 22px);
  }
  /* Coral Reef: rising bubbles, open rings rather than dots. */
  html[data-theme="coral-reef"]{
    --mat: radial-gradient(circle, transparent 2.4px, color-mix(in srgb, var(--accent-manual) 26%, transparent) 2.6px 3.3px, transparent 3.6px) 4px 6px / 34px 38px,
      radial-gradient(circle, transparent 1.3px, color-mix(in srgb, var(--accent-manual) 20%, transparent) 1.5px 2px, transparent 2.3px) 20px 24px / 26px 44px;
  }
  /* Toxic Waste: hazard stripes, turned right down so they stay a mat. */
  html[data-theme="toxic-waste"]{
    --mat: repeating-linear-gradient(-45deg, color-mix(in srgb, var(--accent-auto) 5%, transparent) 0 4px, transparent 4px 16px);
  }
  /* Lavender Fields: planted rows, dots set close in the row, rows set apart. */
  html[data-theme="lavender-fields"]{
    --mat: radial-gradient(circle, color-mix(in srgb, var(--accent-flair) 28%, transparent) 1px, transparent 1.6px) 0 0 / 18px 7px;
  }
  /* Candy Pop: polka dots, big and soft, on two offset grids. */
  html[data-theme="candy-pop"]{
    --mat: radial-gradient(circle, color-mix(in srgb, var(--accent-flair) 20%, transparent) 3px, transparent 3.6px) 0 0 / 24px 24px,
      radial-gradient(circle, color-mix(in srgb, var(--accent-manual) 16%, transparent) 3px, transparent 3.6px) 12px 12px / 24px 24px;
  }
  /* Copper Forge: tread plate, raised diagonal bars alternating direction. */
  html[data-theme="copper-forge"]{
    --mat: linear-gradient(45deg, transparent 43%, color-mix(in srgb, var(--accent-auto) 11%, transparent) 43% 57%, transparent 57%) 0 0 / 14px 14px,
      linear-gradient(-45deg, transparent 43%, color-mix(in srgb, var(--accent-auto) 11%, transparent) 43% 57%, transparent 57%) 7px 7px / 14px 14px;
  }
  /* Blood Moon: ash drifting under the eclipse, sparse and dim red. */
  html[data-theme="blood-moon"]{
    --mat: radial-gradient(circle, color-mix(in srgb, var(--accent-danger) 26%, transparent) 0.8px, transparent 1.4px) 5px 3px / 31px 27px,
      radial-gradient(circle, var(--mat-ink) 0.7px, transparent 1.3px) 19px 16px / 23px 37px;
  }
  /* Twilight Garden: fireflies, a warm point with a soft halo. */
  html[data-theme="twilight-garden"]{
    --mat: radial-gradient(circle, color-mix(in srgb, var(--accent-auto) 40%, transparent) 0.8px, color-mix(in srgb, var(--accent-auto) 10%, transparent) 2px, transparent 3.2px) 8px 10px / 47px 43px,
      radial-gradient(circle, color-mix(in srgb, var(--accent-auto) 24%, transparent) 0.7px, transparent 1.6px) 30px 29px / 37px 53px;
  }
  /* Aurora Borealis: faint curtain folds hanging from the top. */
  html[data-theme="aurora-borealis"]{
    --mat: linear-gradient(transparent 35%, var(--bg-base)) 0 0 / 100% 100% no-repeat,
      repeating-linear-gradient(90deg, color-mix(in srgb, var(--accent-success) 11%, transparent) 0 2px, transparent 2px 9px, color-mix(in srgb, var(--accent-flair) 9%, transparent) 9px 10px, transparent 10px 17px);
  }
  /* Celestial Gold: a star chart at three magnitudes. */
  html[data-theme="celestial-gold"]{
    --mat: radial-gradient(circle, color-mix(in srgb, var(--accent-flair) 55%, transparent) 0.9px, transparent 1.5px) 7px 11px / 61px 53px,
      radial-gradient(circle, color-mix(in srgb, var(--text-primary) 30%, transparent) 0.6px, transparent 1.1px) 29px 3px / 37px 41px,
      radial-gradient(circle, color-mix(in srgb, var(--text-primary) 18%, transparent) 0.5px, transparent 1px) 13px 23px / 19px 23px;
  }

  /* ---- Custom (Theme Studio) ----
     Custom has no world of its own: Theme Studio (theme-studio.ts
     compileSpec) writes the element grammar above plus these `--c-*` vars
     inline on <html>. Every rule falls back to the plain component look, so
     a Custom saved by an older build (colors only) renders as before. */
  html[data-theme="custom"] #gallery{ background: var(--c-ground, none), var(--bg-base); }
  html[data-theme="custom"] .tool-section,
  html[data-theme="custom"] .settings-section{
    background: var(--c-pad-bg, var(--bg-elevated)); box-shadow: var(--c-pad-shadow, none);
  }
  html[data-theme="custom"] .tab-btn.active{
    background: var(--c-tab-bg, transparent); color: var(--c-tab-fg, var(--accent-flair));
    border-bottom-color: var(--c-tab-line, var(--accent-flair)); box-shadow: var(--c-tab-shadow, none);
    border-radius: var(--c-tab-r, 0);
  }
  html[data-theme="custom"] #topbar{
    border-bottom: var(--c-top-border, 2px solid var(--accent-flair)); border-image: var(--c-top-bimg, none);
  }
  html[data-theme="custom"] button.primary{
    background: var(--c-pri-bg, var(--accent-manual-dim)); color: var(--c-pri-fg, var(--accent-manual));
    border-color: var(--c-pri-bd, var(--accent-manual));
  }
  html[data-theme="custom"] button.primary:hover:not(:disabled){
    background: var(--c-pri-hover, color-mix(in srgb, var(--accent-manual) 35%, var(--bg-panel)));
  }

  /* ================= End themes ================= */

  select#themeSelect{
    font-family: var(--sans);
    font-size: 13px;
    color: var(--text-primary);
    background: var(--bg-elevated);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    padding: 7px 10px;
    cursor: pointer;
  }

  /* ---------- Floating panels: theme customizer + favorites ---------- */

  .theme-panel{
    position: fixed; top: 64px; right: 20px; width: 300px;
    /* Fixed CSS-px width never shrinks on its own — at high enough native
       page zoom (Settings' own font-size slider), 300 CSS-px can exceed the
       viewport's own (zoom-shrunk) CSS-px width, so the box itself would
       overflow past the window edge no matter how well JS repositions it.
       max-width ties it to whatever room actually exists at the current
       zoom, same safety-net shape as max-height below for the vertical
       axis. */
    max-width: calc(100vw - 16px);
    background: var(--bg-elevated-2); border: 1px solid var(--border-strong);
    border-radius: var(--r-panel); padding: 14px; z-index: 455; /* above .modal-backdrop (450) — this class backs every
    floating panel (settings, favorites, theme customizer, log, achievements, shop, tag details), any of which
    can be summoned while the image-card modal is open */
    box-shadow: 0 16px 40px rgba(0,0,0,0.5);
    display: flex; flex-direction: column; gap: 10px;
    max-height: 80vh; overflow-y: scroll;
    opacity: 0; transform: translateY(-8px) scale(0.98);
    transition: opacity var(--panel-dur) var(--pop-ease), transform var(--panel-dur) var(--pop-ease);
    pointer-events: none;
  }
  .theme-panel.panel-visible{ opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }
  .theme-panel-head{ font-size: 12.5px; font-weight: 600; color: var(--text-primary); }
  .theme-panel-actions{ display: flex; gap: 8px; }
  .theme-panel-actions button{ flex: 1; font-size: 12px; }
  .theme-panel-note{ font-size: 10.5px; color: var(--text-faint); line-height: 1.4; }
  /* Frutiger Aero's own background picker: only shown on that theme. */
  .fa-bg-row{ display: none; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 12.5px; }
  html[data-theme="frutiger-aero"] .fa-bg-row{ display: flex; }
  .fa-bg-row > span{ flex: 1 1 100%; color: var(--text-muted); }

  /* ---------- Theme Studio (theme-studio.ts) ----------
     Dim-screen builder for the Custom theme. Its own chrome is the app's
     current theme; only the .ts-sample tiles and the preview iframe render
     in the draft (the tiles get the draft's color vars inline). Grid:
     header / scrolling editor | fixed preview stage / footer. */
  .confirm-box.ts-box{
    width: min(1480px, 96vw); height: min(920px, 93vh); padding: 0;
    display: grid; grid-template-columns: minmax(360px, 424px) minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr) auto;
    grid-template-areas: "head head" "edit stage" "foot foot";
    background: var(--bg-panel); border-radius: var(--r-panel); overflow: hidden;
    opacity: 0; transform: translateY(12px) scale(0.985);
    transition: transform calc(var(--panel-dur) * 1.6) var(--swipe-ease), opacity var(--panel-dur) ease;
  }
  .ts-backdrop.modal-visible .ts-box{ opacity: 1; transform: none; }

  .ts-head{
    grid-area: head; display: flex; align-items: center; gap: 16px;
    padding: 12px 16px 12px 22px; border-bottom: 1px solid var(--border-soft);
  }
  .ts-title-block{ display: flex; align-items: center; gap: 14px; min-width: 0; flex: 1; }
  .ts-title{
    margin: 0; font-family: var(--head-font); font-size: 15px; font-weight: 700;
    letter-spacing: var(--head-track); text-transform: var(--head-case); color: var(--text-primary); white-space: nowrap;
  }
  .ts-name{
    min-width: 0; width: 300px; max-width: 300px; flex: 0 1 300px; font-size: 14px; font-weight: 600; color: var(--text-primary);
    background: transparent; border: 1px solid transparent; border-left-color: var(--border-soft);
    border-radius: 0; padding: 5px 12px;
  }
  .ts-name:hover{ border-color: var(--border-soft); border-radius: var(--r-ctl); }
  .ts-name:focus{ outline: none; border-color: var(--accent-manual); border-radius: var(--r-ctl); background: var(--bg-base); }
  .ts-head-actions{ display: flex; align-items: center; gap: 8px; }
  .ts-close{ padding: 7px; background: transparent; border-color: transparent; color: var(--text-muted); }
  .ts-close:hover:not(:disabled){ color: var(--text-primary); background: var(--bg-elevated); }

  .ts-edit{
    grid-area: edit; overflow-y: auto; position: relative;
    border-right: 1px solid var(--border-soft); overscroll-behavior: contain;
  }
  .ts-jump{
    position: sticky; top: 0; z-index: 3; display: flex; flex-wrap: wrap; gap: 2px;
    padding: 9px 14px; background: var(--bg-panel); border-bottom: 1px solid var(--border-soft);
  }
  .ts-jump-btn{
    font-size: 11.5px; padding: 4px 8px; background: transparent; border-color: transparent; color: var(--text-muted);
  }
  .ts-jump-btn:hover:not(:disabled){ background: var(--bg-elevated); color: var(--text-primary); }
  .ts-jump-btn.on{ background: var(--bg-elevated); border-color: var(--border-soft); color: var(--text-primary); }

  .ts-section{ padding: 20px 20px 24px; border-bottom: 1px solid var(--border-soft); }
  .ts-section:last-child{ border-bottom: 0; padding-bottom: 36px; }
  .ts-sec-title{
    margin: 0; font-family: var(--head-font); font-size: 13.5px; font-weight: 700;
    letter-spacing: var(--head-track); text-transform: var(--head-case); color: var(--text-primary);
  }
  .ts-sec-lede{ margin: 4px 0 0; font-size: 11.5px; line-height: 1.45; color: var(--text-muted); max-width: 58ch; }
  .ts-field{ margin-top: 16px; position: relative; }
  .ts-field-label{ font-size: 11.5px; font-weight: 600; color: var(--text-muted); margin-bottom: 7px; }
  .ts-field-hint{ font-weight: 400; color: var(--text-faint); margin-left: 8px; }
  .ts-sub-title{
    font-size: 10.5px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase;
    color: var(--text-faint); margin: 16px 0 4px;
  }

  /* Colors */
  .ts-color{ display: grid; grid-template-columns: 30px minmax(0, 1fr) auto 88px; align-items: center; gap: 10px; padding: 4px 0; }
  .ts-swatch{
    position: relative; width: 30px; height: 24px; border-radius: 6px; cursor: pointer;
    background: linear-gradient(var(--sw), var(--sw)), conic-gradient(#c8c8c8 25%, #fff 0 50%, #c8c8c8 0 75%, #fff 0) 0 0 / 8px 8px;
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--text-primary) 18%, transparent);
  }
  .ts-swatch:focus-within{ outline: 2px solid var(--accent-manual); outline-offset: 2px; }
  .ts-swatch input{ position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; border: 0; padding: 0; }
  .ts-color-name{ font-size: 12.5px; color: var(--text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .ts-contrast{
    font-family: var(--mono); font-size: 10px; color: var(--text-faint); font-variant-numeric: tabular-nums;
    background: transparent; border: 1px solid transparent; padding: 1px 5px; cursor: default; font-weight: 400;
    text-transform: none; letter-spacing: 0;
  }
  .ts-contrast:hover:not(:disabled){ background: transparent; border-color: transparent; }
  .ts-contrast[hidden]{ display: inline-block; visibility: hidden; } /* keeps its grid cell */
  .ts-contrast.low{ color: var(--accent-danger); }
  .ts-contrast.low[tabindex="0"]{ cursor: pointer; border-color: color-mix(in srgb, var(--accent-danger) 45%, transparent); }
  .ts-contrast.low[tabindex="0"]:hover:not(:disabled){ background: color-mix(in srgb, var(--accent-danger) 12%, transparent); border-color: var(--accent-danger); }
  /* Night palette on Automatic: rows show the computed colors, read-only. */
  .ts-color-auto .ts-swatch{ cursor: default; opacity: 0.8; }
  .ts-color-auto .ts-hex{ color: var(--text-muted); background: transparent; }
  .ts-night{ margin-top: 2px; }
  .ts-night[hidden], .ts-night-note[hidden], .ts-link-btn[hidden]{ display: none; }
  .ts-night-note{ margin-top: 6px; }
  .ts-color-acts{ display: flex; flex-wrap: wrap; gap: 0 18px; }
  /* My themes: a preset tile plus hover actions; the one being edited is ringed. */
  .ts-lib-item{ position: relative; display: flex; }
  .ts-lib-item > .ts-preset{ flex: 1; min-width: 0; }
  .ts-lib-item.editing > .ts-preset, .ts-lib-new.editing{ border-color: var(--accent-manual); box-shadow: inset 0 0 0 1px var(--accent-manual); }
  .ts-lib-badge{
    align-self: flex-start; margin: 0 3px; padding: 1px 6px; border-radius: 999px; font-size: 10px; font-weight: 600;
    color: var(--accent-success); background: color-mix(in srgb, var(--accent-success) 14%, transparent);
  }
  .ts-lib-acts{
    position: absolute; top: 8px; right: 8px; display: flex; gap: 3px;
    opacity: 0; transition: opacity 0.12s ease;
  }
  .ts-lib-item:hover .ts-lib-acts, .ts-lib-item:focus-within .ts-lib-acts{ opacity: 1; }
  .ts-lib-act{
    padding: 3px; border-radius: 5px; background: color-mix(in srgb, #000 58%, transparent); border-color: transparent; color: #fff;
  }
  .ts-lib-act .ic{ width: 13px; height: 13px; }
  .ts-lib-act:hover:not(:disabled){ background: color-mix(in srgb, #000 75%, transparent); border-color: transparent; }
  .ts-lib-act.danger:hover:not(:disabled){ background: var(--accent-danger); }
  .ts-lib-plus{
    display: flex; align-items: center; justify-content: center; font-size: 18px; color: var(--text-muted);
    border: 1px dashed var(--border-strong); background: transparent;
  }
  /* Preview toolbar: tab switcher + hold-to-compare. */
  .ts-stage-bar{ display: flex; align-items: center; justify-content: center; gap: 10px; flex-wrap: wrap; }
  .ts-compare{ font-size: 12px; padding: 6px 12px; user-select: none; touch-action: none; }
  .ts-compare.on{ border-color: var(--accent-manual); color: var(--accent-manual); background: var(--accent-manual-dim); }
  .ts-frame-wrap.ts-comparing{ box-shadow: 0 0 0 2px var(--accent-manual), 0 28px 64px rgba(0,0,0,0.45); }
  .ts-contrast.low::before{ content: ''; display: inline-block; width: 5px; height: 5px; border-radius: 50%; background: currentColor; margin-right: 5px; vertical-align: 1px; }
  .ts-hex{
    width: 88px; font-family: var(--mono); font-size: 11.5px; padding: 4px 7px;
    background: var(--bg-elevated); color: var(--text-primary); border: 1px solid var(--border-soft); border-radius: 5px;
  }
  .ts-hex:focus{ outline: none; border-color: var(--accent-manual); }
  .ts-hex.bad{ border-color: var(--accent-danger); }
  .ts-link-btn{
    margin-top: 12px; padding: 5px 0; background: transparent; border-color: transparent;
    color: var(--accent-manual); font-size: 12px;
  }
  .ts-link-btn:hover:not(:disabled){ background: transparent; border-color: transparent; text-decoration: underline; text-underline-offset: 3px; }

  /* Option tiles */
  .ts-opts{ display: grid; grid-template-columns: repeat(auto-fill, minmax(88px, 1fr)); gap: 8px; }
  .ts-opts-compact{ grid-template-columns: repeat(auto-fill, minmax(74px, 1fr)); }
  .ts-opts-wide{ grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); }
  .ts-opt{
    display: flex; flex-direction: column; align-items: stretch; justify-content: flex-start; gap: 6px; padding: 5px 5px 6px;
    white-space: normal; background: var(--bg-elevated); border: 1px solid var(--border-soft);
    border-radius: var(--r-card); color: var(--text-muted); font-size: 11px; font-weight: 500;
    transition: border-color 0.12s ease, color 0.12s ease, box-shadow 0.12s ease, transform 0.1s ease;
  }
  .ts-opt:hover:not(:disabled){ background: var(--bg-elevated); border-color: var(--border-strong); color: var(--text-primary); }
  .ts-opt:active:not(:disabled){ transform: scale(0.97); }
  .ts-opt.on{ border-color: var(--accent-manual); color: var(--text-primary); box-shadow: inset 0 0 0 1px var(--accent-manual); }
  .ts-opt[hidden]{ display: none; }
  /* Effect unlocks: a locked tile still previews; the badge says what keeping
     it costs (tinted like the shop's rarity gems). The badge sits inside the
     sample, so it's drawn in the app's colors, not the draft's. */
  .ts-price{
    position: absolute; top: 3px; right: 3px; display: inline-flex; align-items: center; gap: 2px;
    padding: 1px 5px 1px 3px; border-radius: 999px; font-family: var(--mono); font-size: 9.5px; line-height: 1.4;
    font-variant-numeric: tabular-nums; background: color-mix(in srgb, #000 62%, transparent); color: #fff;
  }
  .ts-price[hidden]{ display: none; }
  .ts-price .ic{ width: 10px; height: 10px; }
  .ts-price.rarity-epic .ic{ color: #d7b4ff; }
  .ts-price.rarity-legendary .ic{ color: #ffd166; }
  .ts-opt.locked .ts-opt-label{ color: var(--text-faint); }
  .ts-opt.locked.on .ts-opt-label{ color: var(--text-primary); }
  .ts-fx-note{
    display: flex; align-items: flex-start; gap: 0; margin: 10px 0 0; padding: 8px 10px;
    font-size: 11.5px; line-height: 1.45; color: var(--text-muted);
    background: var(--bg-elevated); border: 1px solid var(--border-soft); border-radius: var(--r-ctl);
  }
  .ts-fx-note .ic{ flex: none; margin-top: 2px; color: var(--accent-flair); }
  .ts-wallet{ display: inline-flex; align-items: center; font-size: 12px; color: var(--text-muted); font-variant-numeric: tabular-nums; }
  .ts-wallet .ic{ color: var(--accent-auto); }
  .ts-wallet b{ color: var(--text-primary); font-weight: 600; }
  .ts-opt-label{ text-align: center; line-height: 1.2; }
  .ts-sample{
    position: relative; display: flex; align-items: center; justify-content: center; height: 46px;
    border-radius: max(0px, calc(var(--r-card) - 3px)); overflow: hidden; background: var(--bg-base);
    font-family: var(--sans); color: var(--text-primary); pointer-events: none;
  }
  .ts-s-preset .ic{ width: 20px; height: 20px; color: var(--accent-flair); }
  .ts-s-btn{
    font-size: 11px; font-weight: 500; padding: 4px 11px; background: var(--bg-elevated);
    border: 1px solid var(--border-strong); color: var(--text-primary);
  }
  .ts-s-chip{
    font-family: 'JetBrains Mono', monospace; font-size: 9.5px; padding: 2px 7px;
    background: var(--bg-elevated); border: 1px solid var(--border-soft); color: var(--text-primary);
  }
  .ts-s-check{
    width: 17px; height: 17px; display: flex; align-items: center; justify-content: center;
    background: var(--accent-manual); color: var(--bg-base);
  }
  .ts-s-check .ic{ width: 13px; height: 13px; stroke-width: 2.6; }
  .ts-s-fill{
    position: relative; width: 74%; height: 26px; display: flex; align-items: center; justify-content: center;
    background: var(--bg-elevated); border: 1px solid var(--border-strong); border-radius: 5px; overflow: hidden;
  }
  .ts-s-fill-bar{ position: absolute; left: 0; width: 100%; }
  .ts-s-fill-txt{ position: relative; font-size: 10.5px; }
  .ts-s-card{ width: 34px; height: 26px; background: var(--bg-panel); border: 1px solid var(--border-soft); border-radius: 5px; }
  .ts-s-pad{
    width: 76%; height: 30px; border: 1px solid var(--border-soft); border-radius: 6px;
    display: flex; flex-direction: column; justify-content: center; gap: 4px; padding: 0 8px;
  }
  .ts-s-pad i{ display: block; height: 3px; width: 70%; border-radius: 2px; background: var(--text-faint); opacity: 0.6; }
  .ts-s-pad i + i{ width: 45%; }
  .ts-s-pat{ position: absolute; inset: 0; }
  .ts-s-mat i{
    position: absolute; top: 50%; left: 50%; width: 38%; height: 76%; transform: translate(-50%, -50%);
    border-radius: 2px; background: linear-gradient(135deg, #8a8176, #3f3a35);
  }
  .ts-s-tabs{ display: flex; align-self: stretch; align-items: stretch; gap: 2px; width: 100%; padding: 8px 8px 0; background: var(--bg-panel); }
  .ts-s-tabs span{
    display: flex; align-items: center; font-size: 10.5px; padding: 0 9px; color: var(--text-muted);
    border-bottom: 2px solid transparent;
  }
  .ts-s-top{ width: 100%; height: 24px; align-self: flex-start; background: var(--bg-panel); border-bottom: 2px solid transparent; }

  .ts-seg{
    display: inline-flex; flex-wrap: wrap; gap: 2px; padding: 2px; background: var(--bg-base);
    border: 1px solid var(--border-soft); border-radius: calc(var(--r-ctl) + 2px);
  }
  .ts-seg button{
    font-size: 11.5px; padding: 5px 11px; background: transparent; border-color: transparent; color: var(--text-muted);
  }
  .ts-seg button:hover:not(:disabled){ background: var(--bg-elevated); color: var(--text-primary); }
  .ts-seg button.on{ background: var(--bg-elevated-2); border-color: var(--border-strong); color: var(--text-primary); }

  .ts-slider{ display: flex; align-items: center; gap: 12px; }
  .ts-slider input{ flex: 1; min-width: 0; }
  .ts-slider output{ width: 40px; text-align: right; font-family: var(--mono); font-size: 11px; color: var(--text-muted); font-variant-numeric: tabular-nums; }
  .ts-toggles{ display: flex; flex-wrap: wrap; gap: 8px 18px; }
  .ts-toggle{ font-size: 12.5px; }

  /* Font picker */
  .ts-font-grid{ display: grid; grid-template-columns: 1fr 1fr; gap: 0 12px; }
  .ts-font-grid .ts-field:last-child{ grid-column: 1 / -1; }
  .ts-font-btn{ width: 100%; justify-content: space-between; gap: 8px; padding: 8px 10px; font-size: 14px; }
  .ts-font-face{ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .ts-font-caret{ flex: none; color: var(--text-faint); }
  .ts-font-pop{
    position: absolute; top: calc(100% + 4px); left: 0; z-index: 5; width: max(100%, 250px); max-height: 320px;
    overflow-y: auto; padding: 6px; background: var(--bg-elevated-2); border: 1px solid var(--border-strong);
    border-radius: var(--r-panel); box-shadow: 0 16px 36px rgba(0,0,0,0.45);
    opacity: 0; transform: translateY(-4px); transform-origin: top left;
    transition: opacity var(--pop-dur) var(--pop-ease), transform var(--pop-dur) var(--pop-ease);
  }
  .ts-font-pop.in{ opacity: 1; transform: none; }
  .ts-font-group{
    font-family: var(--sans); font-size: 10px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase;
    color: var(--text-faint); padding: 8px 10px 4px;
  }
  .ts-font-item{
    display: block; width: 100%; text-align: left; justify-content: flex-start; padding: 6px 10px;
    font-size: 15px; font-weight: 400; background: transparent; border-color: transparent; text-transform: none; letter-spacing: 0;
  }
  .ts-font-item:hover:not(:disabled), .ts-font-item:focus-visible{ background: var(--bg-elevated); border-color: transparent; outline: none; }
  .ts-font-item.on{ color: var(--accent-manual); background: var(--accent-manual-dim); }

  /* Start from */
  .ts-presets{ display: grid; grid-template-columns: repeat(auto-fill, minmax(116px, 1fr)); gap: 8px; margin-top: 14px; }
  .ts-preset{
    display: flex; flex-direction: column; align-items: stretch; justify-content: flex-start; gap: 6px; padding: 5px 5px 7px;
    background: var(--bg-elevated); border: 1px solid var(--border-soft); border-radius: var(--r-card);
    white-space: normal; font-size: 11.5px; transition: border-color 0.12s ease, transform 0.1s ease;
  }
  .ts-preset:hover:not(:disabled){ background: var(--bg-elevated); border-color: var(--border-strong); }
  .ts-preset:active:not(:disabled){ transform: scale(0.97); }
  .ts-preset-sw{ display: flex; height: 26px; border-radius: max(0px, calc(var(--r-card) - 3px)); overflow: hidden; }
  .ts-preset-sw i{ flex: 1; }
  .ts-preset-sw i:first-child{ flex: 2.2; }
  .ts-preset-name{ text-align: left; padding: 0 3px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-primary); }
  .ts-presets-more{ grid-column: 1 / -1; margin: 4px 0 0; font-size: 11px; color: var(--text-faint); }

  /* Preview stage */
  .ts-stage{
    grid-area: stage; min-width: 0; min-height: 0; display: flex; flex-direction: column; gap: 12px;
    padding: 22px 26px 14px;
    background: radial-gradient(circle at 1px 1px, color-mix(in srgb, var(--text-primary) 9%, transparent) 1px, transparent 1.5px) 0 0 / 14px 14px, var(--bg-base);
  }
  .ts-stage-frame{ flex: 1; min-height: 0; display: flex; align-items: center; justify-content: center; }
  .ts-frame-wrap{
    position: relative; overflow: hidden; border-radius: 10px;
    box-shadow: 0 0 0 1px var(--border-strong), 0 28px 64px rgba(0,0,0,0.45), 0 4px 12px rgba(0,0,0,0.25);
    opacity: 0; transform: scale(0.985);
    transition: opacity 220ms ease, transform 320ms var(--swipe-ease);
  }
  .ts-frame-wrap.ts-ready{ opacity: 1; transform: none; }
  .ts-frame-wrap.ts-ready.ts-switching{ opacity: 0.25; transition-duration: var(--tab-dur); }
  .ts-frame{ position: absolute; top: 0; left: 0; border: 0; display: block; transform-origin: 0 0; background: transparent; }
  .ts-preview-tabs{
    align-self: center; display: flex; flex-wrap: wrap; justify-content: center; gap: 3px; padding: 3px;
    background: var(--bg-panel); border: 1px solid var(--border-soft); border-radius: calc(var(--r-ctl) + 4px);
  }
  .ts-ptab{ font-size: 12px; padding: 6px 13px; background: transparent; border-color: transparent; color: var(--text-muted); }
  .ts-ptab:hover:not(:disabled){ background: var(--bg-elevated); color: var(--text-primary); }
  .ts-ptab.active{ background: var(--bg-elevated-2); border-color: var(--border-strong); color: var(--text-primary); }
  .ts-stage-note{ text-align: center; font-size: 11px; color: var(--text-faint); }

  .ts-foot{
    grid-area: foot; display: flex; align-items: center; gap: 12px;
    padding: 12px 18px 12px 20px; border-top: 1px solid var(--border-soft);
  }
  .ts-reset{ background: transparent; border-color: transparent; color: var(--text-muted); }
  .ts-reset:hover:not(:disabled){ background: transparent; color: var(--text-primary); }
  .ts-dirty{ flex: 1; text-align: right; font-size: 11.5px; color: var(--accent-auto); }
  .ts-foot-actions{ display: flex; gap: 8px; }

  @media (max-width: 1000px){
    .confirm-box.ts-box{
      grid-template-columns: minmax(0, 1fr); grid-template-rows: auto 42% minmax(0, 1fr) auto;
      grid-template-areas: "head" "stage" "edit" "foot";
    }
    .ts-edit{ border-right: 0; border-top: 1px solid var(--border-soft); }
    .ts-name{ width: auto; flex: 1; }
    .ts-stage-note{ display: none; }
  }
  @media (prefers-reduced-motion: reduce){
    .confirm-box.ts-box, .ts-frame-wrap{ transform: none; }
    .ts-opt:active:not(:disabled), .ts-preset:active:not(:disabled){ transform: none; }
  }

  /* ---------- Collapsible settings sections ---------- */

  .settings-section{
    border: 1px solid var(--border-soft); border-radius: var(--r-panel); overflow: hidden;
    flex-shrink: 0; /* overflow:hidden zeroes a flex item's auto min-size, which would
      otherwise let the panel's flexbox squeeze sections below their content height
      instead of the intended behavior: sections stay full height, the PANEL scrolls. */
  }
  .settings-section-header{
    width: 100%; display: flex; align-items: center; gap: 8px;
    background: var(--bg-elevated); border: none; border-radius: 0;
    padding: 9px 10px; font-size: 12.5px; font-weight: 600; color: var(--text-primary);
    cursor: pointer; text-align: left;
  }
  .settings-section-header:hover{ background: var(--bg-elevated-2); }
  .settings-section-arrow{ transition: transform 0.15s ease; flex-shrink: 0; color: var(--text-faint); }
  .settings-section.expanded .settings-section-arrow{ transform: rotate(90deg); color: var(--accent-flair); }
  .settings-section-body{
    display: flex; flex-direction: column; gap: 10px;
    padding: 10px; background: var(--bg-panel);
  }
  .settings-section:not(.expanded) .settings-section-body{ display: none; }
  .settings-section.danger-section .settings-section-header{ color: var(--accent-danger); }

  .favorites-panel{ right: 340px; width: 280px; }
  .favorites-list{ display: flex; flex-direction: column; }
  .fav-row{ display: flex; align-items: center; gap: 6px; padding: 6px 2px; border-bottom: 1px solid var(--border-soft); }
  .fav-row:last-child{ border-bottom: none; }
  .fav-row .fav-name{
    flex: 1; font-family: var(--mono); font-size: 11.5px; color: var(--text-primary);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .fav-row button{ font-size: 11px; padding: 4px 8px; flex-shrink: 0; }
  .fav-empty{ color: var(--text-faint); font-size: 12px; padding: 6px 2px; }

  .log-panel{
    left: 50%; right: auto;
    width: 480px; max-height: 78vh;
    transform: translate(-50%, -8px) scale(0.98);
  }
  .log-panel.panel-visible{ transform: translate(-50%, 0) scale(1); }
  /* Settings shares .log-panel's wider width/max-height, but NOT its
     horizontal centering — it should anchor directly below the ⚙ Settings
     button instead, wherever that button actually sits (its left/top get
     set in JS at open time, index.ts's btnSettings click handler, the same
     viewport-clamping positionMenu() every dropdown/context-menu uses). */
  #settingsPanel{ left: auto; right: auto; transform: translateY(-8px) scale(0.98); }
  #settingsPanel.panel-visible{ transform: translateY(0) scale(1); }
  .log-list{ display: flex; flex-direction: column; }
  .log-row{ display: flex; flex-direction: column; gap: 5px; padding: 9px 6px; border-bottom: 1px solid var(--border-soft); }
  .log-row:last-child{ border-bottom: none; }
  .log-row .log-meta{ display: flex; justify-content: space-between; align-items: center; gap: 8px; }
  .log-row .log-time{ font-family: var(--mono); font-size: 10.5px; color: var(--text-faint); white-space: nowrap; }
  .log-row .log-type{ font-family: var(--mono); font-size: 10px; color: var(--accent-manual); text-transform: uppercase; letter-spacing: 0.4px; }
  .log-row .log-summary{ font-size: 12px; color: var(--text-primary); line-height: 1.4; }
.log-row .log-actions{ display: flex; gap: 8px; }
.log-row .log-actions button{ font-size: 11px; padding: 4px 9px; }
/* "Image not found in Dataset": a stack item whose image is gone. */
.log-missing-warn{ display: flex; align-items: center; gap: 5px; font-size: 11px; color: var(--accent-danger); }
.log-missing-warn .ic{ width: 13px; height: 13px; }

/* Log panel: the per-entry chevron that opens the edit-stack modal. */
.log-row .log-stack-btn{ font-size: 11px; padding: 4px 9px; }

/* Edit-stack modal (edit-log.ts openLogStackModal): every image in a log
   entry, in processing order, each with its own undo/redo. */
.log-stack-box{ max-width: min(680px, 94vw); }
.log-stack-sub{ font-size: 12px; color: var(--text-muted); margin: -4px 0 10px; }
.log-stack-list{ max-height: 60vh; overflow-y: auto; display: flex; flex-direction: column; gap: 6px; margin-bottom: 12px; }
.log-stack-item{ display: flex; align-items: center; gap: 10px; padding: 6px 8px; border: 1px solid var(--border-soft); border-radius: var(--r-panel); background: var(--bg-elevated); }
.log-stack-item.missing{ border-color: color-mix(in srgb, var(--accent-danger) 50%, transparent); }
.log-stack-order{ font-family: var(--mono); font-size: 11px; color: var(--text-faint); min-width: 22px; text-align: right; flex: none; }
.log-stack-thumb{ width: 44px; height: 44px; flex: none; border-radius: var(--r-check, 4px); overflow: hidden; background: var(--bg-base); display: flex; align-items: center; justify-content: center; color: var(--accent-danger); }
.log-stack-thumb.clickable{ cursor: pointer; }
.log-stack-thumb.clickable:hover{ outline: 1px solid var(--accent-manual); }
.log-stack-thumb img{ width: 100%; height: 100%; object-fit: cover; display: block; }
.log-stack-info{ flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.log-stack-name{ font-size: 12px; color: var(--text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.log-stack-actions{ display: flex; gap: 6px; flex: none; }
.log-stack-actions button{ font-size: 11px; padding: 4px 9px; }
/* In the per-image history modal (openImageEditsModal) the action row sits in
   a stacked .log-row, not the flex stack item — left-align it there. */
.log-row .log-stack-actions{ align-self: flex-start; }
/* Log panel toolbar (edit-type filter + recency toggle) + pagination. */
.log-toolbar{ display: flex; align-items: center; gap: 8px; }
.log-toolbar label{ font-size: 11px; color: var(--text-muted); flex: none; }
/* Themed persistent dropdown (buildPersistentDropdown → .pdrop/.pdrop-btn). Its
   .pdrop-btn already carries the control grammar; just let it fill the row. */
.log-type-dropdown{ flex: 1; min-width: 0; }
.log-type-dropdown .pdrop-btn{ width: 100%; font-size: 11.5px; padding: 4px 8px; }
.log-order-btn{ flex: none; font-size: 11.5px; padding: 4px 9px; }
.log-pager{ display: flex; flex-wrap: wrap; gap: 4px; align-items: center; }
.log-pager .log-page{ min-width: 26px; padding: 3px 7px; font-size: 11px; font-family: var(--mono); background: var(--bg-elevated); color: var(--text-muted); border: 1px solid var(--border-soft); border-radius: var(--r-ctl, 5px); cursor: pointer; }
.log-pager .log-page:hover{ color: var(--text-primary); border-color: var(--border-strong); }
.log-pager .log-page.active{ color: var(--bg-base); background: var(--accent-manual); border-color: var(--accent-manual); }
.log-pager .log-page-gap{ border-color: transparent; background: transparent; }
.log-empty{ color: var(--text-faint); font-size: 12px; padding: 12px 4px; text-align:center; }
  .log-panel-actions{ display: flex; gap: 8px; }
  .log-panel-actions button{ flex: 1; font-size: 12px; }

  /* ---------- Version / tab bar ---------- */

  .brand-block{ display: flex; flex-direction: column; gap: 2px; }
  .app-version{ font-family: var(--mono); font-size: 10px; color: var(--text-faint); letter-spacing: 0.5px; }

  #tabBar{
    display: flex; gap: 4px; padding: 0 20px; background: var(--bg-panel);
    border-bottom: 1px solid var(--border-soft);
    position: relative; z-index: 16;
    flex-shrink: 0; /* see #topbar's comment — same #app column-flex compression risk */
  }
  .tab-btn{
    background: transparent; border: none; border-bottom: 2px solid transparent;
    border-radius: 0; padding: 10px 14px; font-size: var(--tab-size); color: var(--text-muted);
    font-family: var(--tab-font); text-transform: var(--tab-case); letter-spacing: var(--tab-track);
  }
  .tab-btn:hover:not(:disabled){ background: var(--bg-elevated); color: var(--text-primary); }
  .tab-btn.active{ color: var(--accent-flair); border-bottom-color: var(--accent-flair); font-weight: 600; }

  /* ---------- Dataset tab (folder manager) ---------- */

  #datasetManagerTab{ padding: 20px; overflow-y: auto; height: 100%; }
  .dm-toolbar{ display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; gap: 10px; }
  .dm-toolbar-left{ display: flex; gap: 6px; }
  .dm-toolbar-right{ display: flex; align-items: center; gap: 8px; }
  .dm-sort-label{ font-size: 12px; color: var(--text-muted); }
  .dm-view-btn.active{ color: var(--accent-flair); border-color: var(--accent-flair); }

  /* ---------- Dataset folder tabs (groups), incl. the optional password lock ---------- */

  .dm-tab-bar{ display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 14px; }
  .dm-tab{
    display: flex; align-items: center; gap: 6px;
    padding: 6px 10px; border-radius: 999px; font-size: 12.5px;
    background: var(--bg-elevated); border: 1px solid var(--border-soft); color: var(--text-muted);
  }
  .dm-tab.active{ background: var(--accent-manual-dim); border-color: var(--accent-manual); color: var(--accent-manual); font-weight: 600; }
  .dm-tab-lock{ font-size: 11px; }
  .dm-tab-menu-btn{
    padding: 0 2px; min-height: 0; background: transparent; border: none; color: inherit;
    font-size: 13px; line-height: 1; opacity: .7;
  }
  .dm-tab-menu-btn:hover{ opacity: 1; }
  .dm-tab-add{ padding: 6px 12px; font-weight: 600; }

  .dm-lock-screen{
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 10px; padding: 60px 20px; width: 100%; color: var(--text-muted); text-align: center;
  }
  .dm-lock-icon{ font-size: 38px; }
  .dm-lock-msg{ font-size: 13.5px; }

  /* promptText()'s own input (dataset-manager.ts) — reuses .confirm-box's
     layout, just needs a field between the message and button row. */
  .dm-prompt-input{
    width: 100%; margin: 4px 0 4px; padding: 8px 10px; font: inherit; font-size: 13px;
    background: var(--bg-base); border: 1px solid var(--border-soft); border-radius: 6px; color: var(--text-primary);
  }

  .dm-move-tab-list{ display: flex; flex-direction: column; gap: 6px; margin-bottom: 16px; max-height: 40vh; overflow-y: auto; }
  .dm-move-tab-row{ text-align: left; padding: 9px 12px; border-radius: 6px; background: var(--bg-base); border: 1px solid var(--border-soft); }
  .dm-move-tab-row:disabled{ opacity: .5; cursor: default; }
  .dm-move-tab-row.active{ border-color: var(--accent-manual); color: var(--accent-manual); }

  .dm-grid{ display: flex; flex-wrap: wrap; gap: 16px; align-content: flex-start; }
  .dm-grid.dm-list-view{ flex-direction: column; flex-wrap: nowrap; }

  .dm-tile{
    position: relative; display: flex; flex-direction: column; align-items: center;
    width: 150px; padding: 10px 6px; border-radius: var(--radius); cursor: pointer;
    background: transparent; border: 1px solid transparent;
  }
  .dm-tile:hover{ background: var(--bg-elevated); border-color: var(--border-soft); }
  .dm-tile.dm-dragging{ opacity: 0.4; }
  .dm-tile.dm-drop-target{ outline: 2px dashed var(--accent-manual); outline-offset: 2px; }
  .dm-grid.dm-list-view .dm-tile{
    flex-direction: row; width: 100%; align-items: center; gap: 12px; padding: 8px 12px;
  }

  /* Add-tile: a plain <button>, not one of this app's `all: unset` ctx-style
     buttons, so it keeps normal button focus/hover behavior for free.
     Sized to match the actual folder ICON graphic (.dm-folder-icon, 96x72)
     other tiles show, not their full 150x160 tile box — that box is mostly
     padding/label space around a much smaller icon, so a dashed border
     filling the whole thing read as a conspicuously oversized square next
     to the other (visually much smaller) folder icons. */
  .dm-add-tile{
    width: 110px; height: 82px; margin: 10px 6px; display: flex; align-items: center; justify-content: center;
    background: transparent; border: 2px dashed var(--accent-flair); border-radius: var(--radius);
    font-size: 22px; color: var(--accent-flair); line-height: 1;
  }
  .dm-add-tile:hover:not(:disabled){ background: var(--bg-elevated); }
  .dm-grid.dm-list-view .dm-add-tile{ width: 100%; height: 44px; margin: 0; }

  .dm-pin-badge{
    position: absolute; top: 4px; left: 8px; color: var(--accent-auto); font-size: 12px; z-index: 4;
  }

  /* Two stacked inline SVGs (back pocket, front flap) around an optional
     <img> — the image sits between them in z-index so it appears to poke
     out above the front flap's top edge. Stroke color is the plain
     --accent-flair CSS var (not computed in JS), so it re-themes and
     night-mode-inverts automatically, same as every other themed element —
     see CLAUDE.md's Known Pitfalls re: native-vs-custom controls needing no
     new THEME_VARS entry since this reuses an existing var. */
  .dm-folder-icon{ position: relative; width: 96px; height: 72px; flex-shrink: 0; }
  .dm-folder-back, .dm-folder-front{
    position: absolute; inset: 0; width: 100%; height: 100%;
    fill: none; stroke: var(--accent-flair); stroke-width: 2; stroke-linejoin: round;
  }
  .dm-folder-back{ z-index: 1; }
  /* Front flap is opaque (matching the tile's own current background, not
     transparent like the back layer) so an icon-mode image reads as
     physically tucked inside the folder, occluded by the flap where they
     overlap, instead of showing straight through the flap's unfilled
     outline. Tracks .dm-tile's own two background states (plain vs
     :hover's --bg-elevated) so it never looks mismatched either way. */
  .dm-folder-front{ z-index: 3; fill: var(--bg-base); }
  .dm-tile:hover .dm-folder-front{ fill: var(--bg-elevated); }
  /* `bottom: 13%` is a hard floor, not a stylistic choice — it lines up
     with FOLDER_BACK_PATH's own floor edge (y=42 of the 0-48 viewBox,
     i.e. 12.5% up from the bottom; 13% clears it with a hair of margin).
     Because this box's size is fixed in CSS (percentages of the icon's own
     fixed 64x48 dimensions, not the source image's), object-fit: cover
     crops ANY source image — tall, wide, whatever — to exactly this box;
     the rendered image can never extend past the back pocket's floor
     regardless of the source photo's own aspect ratio. Don't compute this
     bottom offset from the image instead — the box defines the limit, the
     image just fills it. */
  .dm-folder-img{
    position: absolute; z-index: 2; left: 17%; right: 17%; bottom: 13%; height: 86%;
    object-fit: cover; border-radius: 2px;
  }

  .dm-tile-label{
    margin-top: 6px; font-size: 11.5px; color: var(--text-primary); text-align: center;
    max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .dm-grid.dm-list-view .dm-tile-label{ margin-top: 0; text-align: left; flex: 1; }

  /* Plain button using position:absolute for placement, like .img-menu-btn —
     must stay in the epic/legendary hover-fill exclusion lists below (grep
     `all: unset` / `position: absolute` before adding any more such buttons,
     per this project's own Known Pitfalls). */
  .dm-menu-btn{
    position: absolute; top: 4px; right: 4px; z-index: 5;
    width: 22px; height: 22px; padding: 0; font-size: 13px; line-height: 1;
    background: rgba(0,0,0,0.55); color: #fff; border: none; border-radius: 5px;
  }
  .dm-menu-btn:hover{ background: rgba(0,0,0,0.75); }
  .dm-grid.dm-list-view .dm-menu-btn{ position: static; margin-left: 8px; background: transparent; color: var(--text-muted); }
  .dm-grid.dm-list-view .dm-menu-btn:hover{ background: var(--bg-elevated); }

  .dm-icon-picker-grid{
    display: grid; grid-template-columns: repeat(auto-fill, minmax(72px, 1fr)); gap: 8px;
    max-height: 320px; overflow-y: auto; margin-bottom: 12px;
  }
  .dm-icon-picker-cell{
    all: unset; cursor: pointer; width: 72px; height: 72px; border-radius: 5px; overflow: hidden;
    border: 2px solid transparent;
  }
  .dm-icon-picker-cell:hover{ border-color: var(--accent-flair); }
  .dm-icon-picker-cell img{ width: 100%; height: 100%; object-fit: cover; display: block; }
  .dm-icon-picker-noimage{
    display: flex; align-items: center; justify-content: center; text-align: center;
    font-size: 10px; line-height: 1.2; padding: 4px; background: var(--bg-elevated); color: var(--text-muted);
  }
  .dm-icon-picker-scanning{ grid-column: 1 / -1; padding: 8px 0; color: var(--text-muted); }

  /* ---------- Editing Stats tab ---------- */

  #statsTab{ padding: 24px; overflow-y: scroll; height: 100%; }
  .stats-header{ display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; }
  .stats-header h2{ margin: 0; font-size: 18px; color: var(--text-primary); }
  .stats-view-toggle{ display: flex; gap: 6px; }
  .stats-view-toggle button.active{ background: var(--accent-manual-dim); border-color: var(--accent-manual); color: var(--accent-manual); }
  .stats-body{ display: flex; gap: 30px; align-items: flex-start; flex-wrap: wrap; }
  /* max-width/min() rather than a hard flex-shrink:0 + 260px: at phone width
     the chart (and its empty state, which sized to its unwrapped sentence)
     outgrew the tab and made the whole Stats tab scroll sideways. */
  .stats-chart-wrap{ flex-shrink: 0; max-width: 100%; min-width: 0; }
  .stats-chart-wrap svg, .stats-chart-wrap canvas{ max-width: 100%; height: auto; }
  .stats-legend{ display: flex; flex-direction: column; gap: 8px; min-width: min(260px, 100%); }
  .stats-header{ flex-wrap: wrap; gap: 10px; }
  .stats-legend-row{ display: flex; align-items: center; gap: 10px; font-size: 12.5px; }
  .stats-legend-swatch{ width: 12px; height: 12px; border-radius: 3px; flex-shrink: 0; }
  .stats-legend-label{ flex: 1; color: var(--text-primary); }
  .stats-legend-value{ font-family: var(--mono); color: var(--text-muted); font-size: 11.5px; }
  .stats-totals{
    margin-top: 26px; display: grid; grid-template-columns: repeat(auto-fill, minmax(150px,1fr));
    gap: 12px;
  }
  .stats-total-card{
    background: var(--bg-elevated); border: 1px solid var(--border-soft); border-radius: 8px; padding: 12px;
  }
  .stats-total-card .num{ font-family: var(--mono); font-size: 22px; font-weight: 600; color: var(--text-primary); }
  .stats-total-card .lbl{ font-size: 11px; color: var(--text-muted); margin-top: 3px; }
  .stats-empty{ color: var(--text-faint); padding: 40px; text-align: center; }

  /* ---------- SynthDat Overseer tab ---------- */

  /* position:relative + a (harmless, no-op) transform makes #synthDatTab
     itself the containing block for any position:fixed descendant (a
     transformed/relatively-positioned ancestor intercepts fixed positioning
     per spec) instead of the viewport — so the prompt-panel overlay and its
     edge tab below can anchor with plain top:0/right:0 rather than a guessed
     pixel offset for the app's own topbar+tab-bar height.
     CRITICAL companion rule: the tab itself does NOT scroll. A transformed
     element that is also the scroll container makes its fixed descendants
     behave layout-absolute — anchored to the content origin and scrolling
     away with it — which is the "edge tab scrolled off-screen" bug. The
     scrolling lives one level down in .synthdat-scroll, so the tab's own
     box (and everything pinned to it) never moves. */
  /* flex:1 + min-height:0, not height:100%: as a flex item of #app's
     column, 100% resolved to the WHOLE window height, stacked under the
     topbar + tab bar — and since this tab is overflow:visible (it doesn't
     scroll, see above) its min-height:auto refused to shrink, so the page
     itself overflowed by the header height and grew a second, whole-UI
     scrollbar. The other tabs escape it only because they scroll themselves. */
  #synthDatTab{ flex: 1 1 0; min-height: 0; position: relative; transform: translateZ(0); }
  .synthdat-scroll{ height: 100%; overflow-y: scroll; padding: 24px; }
  /* Generation (top, full-width, its own internal 2-col settings/preview
     split — see below) above a single column of the setup docks (ComfyUI
     connection/Reference image/Model & LoRA/ControlNet/Reference resize),
     since Generation is what you look at/act on for every single
     generation while the setup docks are configure-once-and-forget. Prompt
     fields lives outside this flow entirely now — a fixed-position overlay
     (#synthDatPromptFieldsDock below) toggled via its own edge tab, so it
     doesn't cost either section any width and stays reachable regardless of
     scroll position. */
  .synthdat-body{ display: flex; flex-direction: column; gap: 24px; }
  .synthdat-cols{ display: flex; gap: 24px; align-items: flex-start; flex-wrap: wrap; }
  .synthdat-col{ flex: 1; min-width: 320px; display: flex; flex-direction: column; gap: 16px; }

  /* Prompt fields' own edge tab — permanently docked to the right edge,
     independent of #synthDatTab's own scroll position (fixed, not
     absolute/sticky), so it's reachable no matter how far down the setup
     docks column you've scrolled. Only ever visible while the SynthDat tab
     itself is showing (#synthDatTab's own display:none, inherited, handles
     that — no extra guard needed here). */
  #btnSynthDatPromptPanelToggle{
    position: fixed; top: 60px; right: 0; z-index: 40;
    padding: 10px 12px; border-radius: var(--radius) 0 0 var(--radius);
    border-right: none;
  }
  /* Overlaps directly over whatever's currently in view underneath —
     deliberately NOT part of the flex column flow (no reserved width,
     nothing reflows when it opens/closes), per direct feedback: the app's
     shared #right sidebar resizes the interface around itself, which reads
     as heavier than warranted for a panel you dip into for a few seconds at
     a time. Slides in from the right edge; closing is either its own header
     arrow or the edge tab underneath it (both call the same close path). */
  #synthDatPromptFieldsDock{
    position: fixed; top: 0; right: 0; bottom: 0; z-index: 41;
    width: min(420px, 92vw); overflow-y: auto;
    background: var(--bg-panel); border-left: 1px solid var(--border-strong);
    box-shadow: -12px 0 32px rgba(0,0,0,0.35); padding: 16px;
  }
  /* Generation's own two columns are deliberately NOT an even split — the
     result/preview is the actual point of interest here (what you're
     generating and deciding accept/reject on), the settings column is
     secondary controls you set up once per session. Same relationship as a
     video player's main view vs. its sidebar: capping the settings column's
     width and giving the preview column the lion's share of extra space
     makes the preview read as the dominant screen element instead of a
     small box squeezed beside a tall list of number fields. */
  .synthdat-gen-top .synthdat-cols > .synthdat-col:first-child{ flex: 1 1 300px; max-width: 360px; }
  .synthdat-gen-top .synthdat-cols > .synthdat-col:last-child{ flex: 2 1 480px; }
  .synthdat-gen-top .synthdat-ref-preview{ max-height: 480px; }
  .synthdat-gen-top .synthdat-ref-preview img{ max-height: 480px; }
  /* The empty-state placeholder ("No image generated yet") used to just
     shrink-wrap its own text (via .synthdat-ref-preview's width:fit-content
     below), reading as a tiny incidental box rather than an actual screen
     element. Large and stable instead — fills the preview column's width at
     a consistent minimum height in BOTH states (empty and loaded), like a
     video player's frame that doesn't resize around whatever's currently
     playing. A loaded image renders centered at its own natural size
     (still capped by max-height/max-width above) rather than stretched to
     fill, so it can letterbox within the frame instead of the frame
     shrinking to match it. */
  .synthdat-gen-top #synthDatPreviewWrap,
  .synthdat-gen-top #synthDatLivePreviewWrap{
    width: 100%; min-height: 420px;
    display: flex; align-items: center; justify-content: center;
  }
  .synthdat-gen-top #synthDatPreviewWrap .stats-empty{ padding: 0; font-size: 14px; }
  /* Shrink-wraps to the image's own rendered size (capped by max-height,
     and by the column's own width) instead of forcing a fixed-aspect box —
     a portrait image inside a wider/shorter forced box (or vice versa)
     used to letterbox with visible background padding on two sides. The
     empty-state placeholder (.stats-empty, shown when no img yet) still
     gets a sensible box from its own padding, so this only affects the
     "image loaded" case. */
  .synthdat-ref-preview{
    display: block; width: fit-content; max-width: 100%; max-height: 340px;
    /* The theme's image mat, like every other image frame — including the
       "No image generated yet" state, which is all mat until a result lands. */
    background: var(--mat), var(--bg-base); border-radius: var(--radius); overflow: hidden; margin: 0 auto; cursor: zoom-in;
  }
  /* Reference image + padded/resized preview side by side (each still sized
     to its own image's natural aspect ratio via .synthdat-ref-preview's own
     width:fit-content) rather than stacked — wraps to stacked automatically
     if the column's too narrow for both. */
  .synthdat-refs-row{ display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; }
  .synthdat-refs-row .synthdat-ref-preview{ margin: 0; }
  .synthdat-ref-preview img{ display: block; max-width: 100%; max-height: 340px; width: auto; height: auto; }
  .synthdat-wd14-result{
    background: var(--bg-elevated); border: 1px solid var(--border-soft); border-radius: var(--radius);
    padding: 8px 10px; font-size: 11.5px; color: var(--text-muted); font-family: var(--mono); word-break: break-word;
  }
  .synthdat-lora-row{ display: flex; gap: 8px; align-items: center; }
  .synthdat-lora-row label{ display: flex; align-items: center; gap: 6px; font-size: 12.5px; }
  .synthdat-lora-row input[readonly]{ cursor: pointer; }
  #btnSynthDatSwapReso{ flex-shrink: 0; padding: 6px 9px; font-size: 13px; }
  .synthdat-warning{ font-size: 11px; color: var(--accent-danger); margin-top: 4px; }
  .synthdat-conn-body{ display: flex; flex-direction: column; gap: 10px; min-width: 0; }
  /* Local ComfyUI's chosen folder: a long path, so it ends in an ellipsis
     and shows in full on hover (title). */
  .synthdat-local-folder{
    flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    font-family: var(--font-mono, monospace); font-size: 11.5px; color: var(--text-muted);
  }
  .synthdat-pass-choice{
    flex: 1; display: flex; flex-direction: column; align-items: center; gap: 4px;
    background: var(--bg-elevated); border: 2px solid var(--border-soft); border-radius: var(--radius);
    padding: 6px; cursor: pointer; font-size: 11px; color: var(--text-muted);
  }
  .synthdat-pass-choice img{ display: block; max-width: 100%; max-height: 110px; width: auto; height: auto; border-radius: 4px; }
  .synthdat-pass-choice:hover{ border-color: var(--border-strong); }
  .synthdat-pass-choice.active{ border-color: var(--accent-manual); color: var(--text-primary); font-weight: 600; }
  .synthdat-section-disabled{ opacity: 0.45; pointer-events: none; }
  /* #synthDatRefImageSection (index.html) is a plain wrapper div added so
     the whole reference-image sub-area can be grayed out via the class
     above when "skip reference image" is checked — being a bare div with no
     layout of its own, everything inside it lost .tool-section's flex/gap
     rhythm (its own children, not this wrapper, count as the flex items),
     which is why its two buttons rendered side-by-side with no spacing at
     all instead of stacked like every other section. Match .tool-section's
     own column-flex-with-gap shape so nesting content one level deeper
     doesn't change its spacing. */
  #synthDatRefImageSection{ display: flex; flex-direction: column; gap: 10px; }
  .synthdat-resized-preview-label{ font-size: 10.5px; color: var(--text-faint); margin-top: 4px; }
  .synthdat-tag-assign{
    display: flex; flex-direction: column; gap: 4px; max-height: 260px; overflow-y: scroll;
  }
  .synthdat-tag-assign-row{
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
    background: var(--bg-elevated); border: 1px solid var(--border-soft); border-radius: var(--radius);
    padding: 4px 6px 4px 9px;
  }
  .synthdat-tag-assign-label{ font-size: 11.5px; color: var(--text-primary); font-family: var(--mono); }
  .synthdat-tag-assign-btns{ display: flex; gap: 4px; flex-shrink: 0; }
  .synthdat-tag-assign-btn{
    padding: 3px 7px; font-size: 10.5px; color: var(--text-muted);
    background: var(--bg-elevated-2); border: 1px solid var(--border-soft); border-radius: var(--radius);
  }
  .synthdat-tag-assign-btn.active{
    background: var(--accent-manual-dim); border-color: var(--accent-manual); color: var(--accent-manual);
  }
  /* Final tag card, right before Accept/Reject — reuses the same .chip/
     .chiprow classes the Gallery card view builds its tag chips from
     (view.ts's buildChip()), so pruning a tag here looks and behaves like
     pruning one anywhere else in the app, just scoped to "what gets saved"
     rather than a real entry's tags yet. */
  .synthdat-tag-card{ display: flex; flex-direction: column; gap: 8px; }
  .chip.synthdat-chip-excluded{ opacity: 0.45; text-decoration: line-through; }
  /* Void-marked/already-void-ruled tags: kept at full opacity (unlike plain
     excluded above) since the strikethrough itself IS the signal here — this
     tag isn't just being dropped from this one image, it's flagged as
     fundamentally unwanted dataset-wide (see synthdat-overseer.ts's
     renderTagCard()/finalTagList()). Danger-tinted border to read as more
     deliberate than a casual per-image exclude. */
  .chip.synthdat-chip-void{
    text-decoration: line-through; border-color: var(--accent-danger); color: var(--accent-danger);
  }
  .synthdat-merge-suggest{
    all: unset; cursor: pointer; font-family: var(--sans); font-size: 10px;
    color: var(--accent-manual); background: var(--accent-manual-dim);
    border-radius: 8px; padding: 1px 6px; margin-left: 2px; white-space: nowrap;
  }
  .synthdat-merge-suggest:hover{ background: var(--accent-manual); color: #fff; }

  /* General field styling scoped to #synthDatTab, but every property here
     that a specific row layout (.synthdat-lora-row's select/number pairs)
     needs to override is deliberately left OFF this rule (no width here) —
     an ID selector always outranks any number of classes on specificity, so
     a bare "#synthDatTab input[type=number]{width:100%}" would silently win
     over ".synthdat-lora-row input[type=number]{width:80px}" regardless of
     which rule comes later in the file. Width is set per-context below
     instead (100% by default via .field-block's own children, 80px/flex:1
     inside .synthdat-lora-row) so there's nothing here for that fight. */
  #synthDatTab select, #synthDatTab input[type="number"], #synthDatTab input[type="text"], #synthDatTab textarea{
    font-family: var(--sans); font-size: 12.5px; color: var(--text-primary); background: var(--bg-elevated);
    border: 1px solid var(--border-strong); border-radius: var(--radius); padding: 6px 9px; box-sizing: border-box;
  }
  #synthDatTab select{ cursor: pointer; }
  #synthDatTab textarea{
    font-family: var(--sans); line-height: 1.4; resize: none; overflow: hidden; min-height: 52px;
    /* Auto-grows via JS (see growTextarea() in synthdat-overseer.ts) instead
       of a manual drag-handle — the user asked these not be hand-resizable,
       only ever taller when content actually overflows. min-height is the
       actual fix for these looking cramped: growTextarea() sets an explicit
       inline height from scrollHeight, and an EMPTY textarea's scrollHeight
       collapses to one line regardless of its `rows` attribute once height
       is driven by JS instead of layout — rows="2" alone stopped mattering
       the moment init() ran growTextarea() over every field once on load. */
  }
  #synthDatTab select:focus, #synthDatTab input[type="number"]:focus, #synthDatTab input[type="text"]:focus, #synthDatTab textarea:focus{
    border-color: var(--accent-manual); outline: none;
  }
  #synthDatTab .field-block > select, #synthDatTab .field-block > textarea,
  #synthDatTab .field-block > input[type="number"], #synthDatTab .field-block > input[type="text"]{
    width: 100%;
  }
  #synthDatTab .synthdat-lora-row select, #synthDatTab .synthdat-lora-row input[type="text"]{ flex: 1; min-width: 0; }
  #synthDatTab .synthdat-lora-row input[type="number"]{ width: 80px; flex-shrink: 0; }
  #synthDatTab .synthdat-lora-row button{ flex-shrink: 0; padding: 6px 10px; }

  /* ---------- Trainflow tab ----------
     Built from the same parts as SynthDat (tool-section docks, field-block,
     synthdat-lora-row, the WD14 progress bar), so every theme restyles it with
     no rules of its own beyond layout. Same scroll structure as #synthDatTab:
     the tab doesn't scroll, .trainflow-scroll does. */
  /* ---------- Curate tab (curate.ts) ----------
     Same scroll structure as Trainflow: the tab doesn't scroll, .curate-scroll does. */
  #curateTab{ flex: 1 1 0; min-height: 0; position: relative; transform: translateZ(0); }
  .curate-scroll{ height: 100%; overflow-y: scroll; padding: 24px; }
  .curate-body{ display: flex; flex-direction: column; gap: 14px; }
  .curate-empty{ color: var(--text-muted); max-width: 560px; line-height: 1.5; display: flex; flex-direction: column; gap: 12px; align-items: flex-start; }
  .curate-toolbar{
    position: sticky; top: -24px; z-index: 3;
    margin: 0 -24px; padding: 10px 24px; background: var(--bg-base); border-bottom: 1px solid var(--border-soft);
  }
  .curate-row{ display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
  .curate-folders{ display: inline-flex; flex-wrap: wrap; gap: 6px; align-items: center; }
  .curate-folder-chip{
    display: inline-flex; align-items: center; gap: 7px; border-radius: 999px; padding: 4px 6px 4px 12px;
    border: 1px solid var(--accent-flair); color: var(--text-primary);
  }
  .curate-folder-chip.all, .curate-folder-chip.none{ border-color: var(--border-strong); }
  .curate-folder-chip.junk{ border-color: var(--accent-danger); color: var(--accent-danger); }
  .curate-folder-chip.active{ background: var(--accent-manual-dim); }
  .curate-folder-chip.junk.active{ background: color-mix(in srgb, var(--accent-danger) 22%, var(--bg-panel)); }
  .curate-folder-n{
    font-family: var(--mono); font-size: 11px; color: var(--text-muted);
    background: var(--bg-elevated); border-radius: 999px; padding: 1px 7px;
  }
  .curate-sep{ width: 1px; align-self: stretch; margin: 2px 4px; background: var(--border-strong); }
  .curate-folder{ font-family: var(--mono); color: var(--text-primary); overflow-wrap: anywhere; margin-right: 4px; }
  .curate-count{ color: var(--text-muted); min-width: 82px; }
  .curate-label{ display: inline-flex; align-items: center; gap: 6px; color: var(--text-muted); }
  .curate-seg{ display: inline-flex; }
  .curate-seg button{ border-radius: 0; }
  .curate-seg button:first-child{ border-radius: var(--r-ctl) 0 0 var(--r-ctl); }
  .curate-seg button:last-child{ border-radius: 0 var(--r-ctl) var(--r-ctl) 0; }
  .curate-seg button.active{ background: var(--accent-manual-dim); color: var(--text-primary); }
  #curateTab select{
    font-family: var(--sans); color: var(--text-primary); background: var(--bg-elevated);
    border: 1px solid var(--border-strong); border-radius: var(--radius); padding: 5px 8px; cursor: pointer;
  }
  #curateTab select:focus{ border-color: var(--accent-manual); outline: none; }
  .curate-junk-btn{ color: var(--accent-danger); }
  .curate-grid{ position: relative; display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 10px; }
  .curate-grid.dragging{ user-select: none; cursor: crosshair; }
  .curate-grid.dragging .curate-card{ cursor: crosshair; }
  /* Duplicates view */
  .curate-dupes{ display: flex; flex-direction: column; gap: 14px; }
  .curate-dupes-head{ gap: 10px; }
  .curate-dupes-sum{ color: var(--text-primary); }
  .curate-note{ color: var(--text-muted); font-size: 12px; line-height: 1.45; max-width: 760px; }
  .curate-scan{ display: flex; align-items: center; gap: 12px; max-width: 640px; }
  .curate-scan-text{ color: var(--text-muted); white-space: nowrap; font-family: var(--mono); font-size: 12px; }
  .curate-scan-track{ flex: 1; height: 6px; border-radius: 999px; background: var(--bg-elevated); overflow: hidden; }
  .curate-scan-fill{ height: 100%; width: 0; background: var(--accent-manual); transition: width .2s ease-out; }
  .curate-dupe-group{
    display: flex; flex-direction: column; gap: 8px; padding: 10px 12px;
    background: var(--bg-panel); border: 1px solid var(--border-soft); border-radius: var(--r-panel);
  }
  .curate-dupe-title{ font-weight: 600; margin-right: 6px; }
  .curate-dupe-row{ display: flex; gap: 10px; overflow-x: auto; padding-bottom: 4px; }
  .curate-dupe-card{ flex: 0 0 170px; }
  .curate-dupe-card:not(.keep){ opacity: .72; }
  .curate-dupe-card.keep{ border-color: var(--accent-success); }
  .curate-keep-tag{
    position: absolute; top: 6px; left: 6px; font-size: 10.5px; padding: 1px 7px; border-radius: 999px;
    background: color-mix(in srgb, var(--bg-base) 82%, transparent); color: var(--accent-danger);
  }
  .curate-dupe-card.keep .curate-keep-tag{ color: var(--accent-success); }
  #curateTab select.curate-dupe-move{ margin: 0 7px 8px; padding: 3px 6px; font-size: 12px; }
  .curate-dupe-meta{ padding: 0 7px 6px; font-family: var(--mono); font-size: 11px; color: var(--text-faint); }
  .curate-marquee{
    position: absolute; z-index: 2; pointer-events: none; border-radius: 3px;
    border: 1px solid var(--accent-manual); background: color-mix(in srgb, var(--accent-manual) 16%, transparent);
  }
  .curate-card{
    position: relative; display: flex; flex-direction: column; min-width: 0; cursor: pointer; user-select: none;
    background: var(--bg-panel); border: 2px solid transparent; border-radius: var(--r-card); overflow: hidden;
  }
  /* A light checkerboard behind the images: a transparent PNG (line art,
     cut-outs) would otherwise show dark lines on the dark theme = "blank". */
  .curate-card img, .curate-stage img{
    background: repeating-conic-gradient(#d9d9d9 0 25%, #f5f5f5 0 50%) 0 0 / 16px 16px;
  }
  .curate-card img{ width: 100%; aspect-ratio: 3 / 4; object-fit: cover; display: block; }
  /* A thumbnail not made yet: an empty tile, not a broken-image icon + alt text. */
  .curate-card img:not([src]){ visibility: hidden; }
  .curate-card:has(img:not([src])){ background: var(--bg-elevated); }
  .curate-card.selected{ border-color: var(--accent-manual); }
  .curate-check{
    position: absolute; top: 6px; left: 6px; width: 20px; height: 20px; border-radius: 50%;
    display: none; align-items: center; justify-content: center; font-size: 12px;
    background: var(--accent-manual); color: var(--bg-base);
  }
  .curate-card.selected .curate-check{ display: flex; }
  .curate-card-foot{ display: flex; gap: 6px; align-items: center; padding: 5px 7px; min-width: 0; }
  .curate-name{ flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-muted); font-size: 11.5px; }
  .curate-res{ flex-shrink: 0; font-family: var(--mono); font-size: 11px; color: var(--text-faint); }
  .curate-res:empty{ display: none; }
  .curate-res.low{
    color: var(--accent-danger); border: 1px solid var(--accent-danger); border-radius: 999px; padding: 0 6px;
  }
  .curate-res.low::before{ content: "⚠ "; }
  .curate-res-card{
    position: absolute; top: 6px; right: 6px; padding: 1px 6px; border-radius: 999px;
    color: var(--text-primary); background: color-mix(in srgb, var(--bg-base) 78%, transparent);
  }
  .curate-res-card.low{ background: color-mix(in srgb, var(--bg-base) 85%, transparent); }
  .curate-res-single{ align-self: flex-start; font-size: 12px; }
  .curate-res-single.low::after{ content: " · low-res"; }
  .curate-badge{
    flex-shrink: 0; font-size: 10.5px; padding: 1px 7px; border-radius: 999px;
    border: 1px solid var(--accent-flair); color: var(--accent-flair);
  }
  .curate-badge.none{ border-color: var(--border-strong); color: var(--text-faint); }
  .curate-badge.junk{ border-color: var(--accent-danger); color: var(--accent-danger); }
  /* Single: the tab stops scrolling and fills its height — image on the
     left as large as it fits, the assign column on the right. */
  #curateTab.curate-single-mode .curate-scroll{ display: flex; flex-direction: column; overflow: hidden; }
  #curateTab.curate-single-mode .curate-body{ flex: 1 1 0; min-height: 0; }
  #curateTab.curate-single-mode .curate-toolbar{ position: static; margin: 0; padding: 0 0 10px; }
  .curate-single{
    flex: 1 1 0; min-height: 0; display: grid; gap: 14px;
    grid-template-columns: minmax(0, 1fr) minmax(200px, 250px);
  }
  .curate-stage{
    min-width: 0; min-height: 0; display: flex; justify-content: center; align-items: center;
    background: var(--bg-panel); border-radius: var(--r-panel); padding: 10px;
  }
  .curate-stage img{ max-width: 100%; max-height: 100%; object-fit: contain; cursor: pointer; border-radius: 4px; }
  .curate-side{ display: flex; flex-direction: column; gap: 8px; min-height: 0; }
  .curate-nav{ display: flex; align-items: center; gap: 8px; }
  .curate-nav button{ flex: 0 0 auto; min-width: 44px; font-size: 18px; line-height: 1; }
  .curate-pos{ flex: 1; text-align: center; font-family: var(--mono); color: var(--text-muted); }
  .curate-single-name{ display: flex; gap: 6px; align-items: center; min-width: 0; }
  .curate-side-title{ margin-top: 4px; color: var(--text-muted); font-size: 11.5px; text-transform: uppercase; letter-spacing: .05em; }
  .curate-stack{ display: flex; flex-direction: column; gap: 6px; overflow-y: auto; min-height: 0; flex: 1 1 auto; }
  .curate-stack-btn{
    display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; flex-shrink: 0;
    border-left: 3px solid var(--accent-flair);
  }
  .curate-stack-btn kbd{
    flex-shrink: 0; min-width: 18px; text-align: center; font-family: var(--mono); font-size: 11px;
    padding: 1px 4px; border-radius: 4px; border: 1px solid var(--border-strong); color: var(--text-muted);
  }
  .curate-stack-btn.none{ border-left-color: var(--border-strong); }
  .curate-stack-btn.junk{ border-left-color: var(--accent-danger); color: var(--accent-danger); }
  .curate-stack-btn.current{ background: var(--accent-manual-dim); color: var(--text-primary); }
  .curate-stack-btn.create{ border-left-color: transparent; border-style: dashed; color: var(--text-muted); }
  .curate-stack-btn.create.junk{ color: var(--accent-danger); }
  .curate-side-add{ display: flex; flex-direction: column; gap: 6px; padding-top: 8px; border-top: 1px solid var(--border-soft); }

  #trainflowTab{ flex: 1 1 0; min-height: 0; position: relative; transform: translateZ(0); }
  .trainflow-scroll{ height: 100%; overflow-y: scroll; padding: 24px; }
  .trainflow-body{ display: flex; flex-direction: column; gap: 24px; }
  .trainflow-col-run{ flex: 3 1 480px; }
  .trainflow-col-setup{ flex: 2 1 340px; max-width: 520px; }
  .trainflow-grid{ display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 10px; }
  /* Extra image folders: path (shrinks), repeats, remove. */
  .tf-extra-folders{ display: flex; flex-direction: column; gap: 6px; margin-bottom: 6px; }
  .tf-extra-row{ align-items: center; }
  .tf-extra-row .synthdat-local-folder{ flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .tf-extra-row .tf-extra-repeats{ width: 64px; flex: none; }

  /* Trainflow ▸ Advanced: a native <details> disclosure, collapsed by default so
     the one-button path stays obvious. Everything reads theme tokens (bg/border/
     accent/radii), so each of the 26 worlds restyles it like any other control. */
  .tf-advanced{
    margin-top: 12px; border: 1px solid var(--border-soft); border-radius: var(--r-panel);
    background: var(--bg-base); padding: 0;
  }
  .tf-advanced > summary{
    cursor: pointer; list-style: none; padding: 8px 12px; font-size: 12px;
    font-family: var(--head-font, var(--sans)); color: var(--text-muted);
    border-radius: var(--r-panel); display: flex; align-items: center; gap: 6px;
  }
  .tf-advanced > summary::before{ content: '▸'; color: var(--text-faint); font-size: 11px; transition: transform 0.12s ease; }
  .tf-advanced[open] > summary::before{ transform: rotate(90deg); }
  .tf-advanced > summary::-webkit-details-marker{ display: none; }
  .tf-advanced > summary:hover{ color: var(--text-primary); }
  .tf-advanced[open] > summary{ border-bottom: 1px solid var(--border-soft); border-radius: var(--r-panel) var(--r-panel) 0 0; }
  .tf-advanced > .trainflow-grid{ padding: 10px 12px 12px; }
  /* Sub-headings inside the disclosure ("LoRA shape", "Precision", …). */
  .tf-adv-head{
    padding: 10px 12px 0; font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.4px;
    text-transform: uppercase; color: var(--accent-manual);
  }
  .tf-adv-head + .trainflow-grid{ padding-top: 6px; }
  /* The checkbox rows inside Advanced reuse .ach-toggle-row; align the box+label. */
  .tf-advanced .field-block .ach-toggle-row{ gap: 6px; font-size: 12px; }
  .trainflow-errors{ display: flex; flex-direction: column; gap: 3px; font-size: 12px; white-space: pre-wrap; }
  .trainflow-progress{ height: 8px; border-radius: 4px; }
  .trainflow-stats{ display: flex; flex-wrap: wrap; gap: 6px 18px; font-family: var(--mono); font-size: 11.5px; color: var(--text-muted); }
  .trainflow-stats b{ color: var(--text-primary); font-weight: 600; }
  .trainflow-bucket-report{ white-space: pre-wrap; line-height: 1.5; }
  .trainflow-mini{ margin-left: auto; padding: 2px 8px; font-size: 11px; }
  .trainflow-log{
    margin: 0; height: 300px; overflow-y: auto; white-space: pre-wrap; word-break: break-word;
    background: var(--bg-elevated); border: 1px solid var(--border-soft); border-radius: var(--radius);
    padding: 8px 10px; font-family: var(--mono); font-size: 11.5px; line-height: 1.35; color: var(--text-muted);
  }
  .trainflow-previews{ display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 10px; }
  .trainflow-previews .stats-empty{ grid-column: 1 / -1; padding: 24px; }
  .trainflow-previews img{
    display: block; width: 100%; height: auto; border-radius: var(--radius); cursor: zoom-in;
    background: var(--mat), var(--bg-base);
  }
  #trainflowTab select, #trainflowTab input[type="number"], #trainflowTab input[type="text"], #trainflowTab textarea{
    font-family: var(--sans); font-size: 12.5px; color: var(--text-primary); background: var(--bg-elevated);
    border: 1px solid var(--border-strong); border-radius: var(--radius); padding: 6px 9px; box-sizing: border-box;
  }
  #trainflowTab select{ cursor: pointer; }
  #trainflowTab textarea{ font-family: var(--sans); line-height: 1.4; resize: vertical; min-height: 52px; }
  #trainflowTab select:focus, #trainflowTab input[type="number"]:focus, #trainflowTab input[type="text"]:focus, #trainflowTab textarea:focus{
    border-color: var(--accent-manual); outline: none;
  }
  #trainflowTab .field-block > select, #trainflowTab .field-block > textarea,
  #trainflowTab .field-block > input[type="number"], #trainflowTab .field-block > input[type="text"]{ width: 100%; }
  #trainflowTab .synthdat-lora-row input[type="text"]{ flex: 1; min-width: 0; }
  #trainflowTab .synthdat-lora-row button{ flex-shrink: 0; padding: 6px 10px; }
  #trainflowTab .sec-head .trainflow-mini{ padding: 2px 8px; }

  @keyframes barGrow{ from{ transform: scaleX(0); } to{ transform: scaleX(1); } }
  .stat-bar-row{ display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
  .stat-bar-label{ width: 130px; font-size: 11.5px; color: var(--text-muted); flex-shrink: 0; }
  .stat-bar-track{ flex: 1; height: 18px; background: var(--bg-elevated); border-radius: 4px; overflow: hidden; }
  .stat-bar-fill{ height: 100%; border-radius: 4px; transform-origin: left; animation: barGrow 0.7s cubic-bezier(.2,.8,.3,1) both; }
  .stat-bar-value{ width: 60px; text-align: right; font-family: var(--mono); font-size: 11px; color: var(--text-faint); }

  @keyframes pieReveal{ from{ stroke-dasharray: 0 1000; } }
  .pie-slice{ transition: opacity 0.15s ease; }
  .pie-slice:hover{ opacity: 0.8; }

  /* ---------- Achievements ---------- */

  /* Every checkbox in the app lives inside one of these — a bordered card
     around the checkbox AND its label text (not just the bare native
     checkbox floating next to plain text), matching the same "boxed row"
     language .ach-row already used for achievement entries. Applies
     app-wide: Settings toggles, the exact-match filter checkbox, every
     SynthDat checkbox, the achievements popup toggle — anywhere a checkbox
     appears, it's wrapped in `<label class="ach-toggle-row">…<span>label
     text</span></label>` (originally just this panel's own class, kept the
     name since renaming it everywhere would've been pure churn). */
  .ach-toggle-row{
    display: flex; align-items: center; gap: 9px; font-size: 11.5px; color: var(--text-muted);
    padding: 8px 10px; border: 1px solid var(--border-soft); border-radius: var(--radius);
    background: var(--bg-base); cursor: pointer;
    transition: border-color 0.12s ease, background 0.12s ease;
  }
  .ach-toggle-row:hover{ border-color: var(--border-strong); background: var(--bg-elevated); }
  /* Custom checkbox — bigger than a native one (18px vs. a browser's usual
     ~13px) and styled to match the app's own controls (rounded, accent-
     colored check) instead of whatever the OS theme happens to render. */
  /* Applied to EVERY checkbox (not just .ach-toggle-row ones) so none falls
     back to the OS look. Wrapped in :where() = zero specificity, so any
     class's own size/position (.master-select-cb, .master-mini-cb, …) still
     wins. The tick is sized in % so it fits any box size. */
  :where(input[type="checkbox"]){
    appearance: none; -webkit-appearance: none; margin: 0;
    width: 15px; height: 15px; flex-shrink: 0; position: relative; vertical-align: middle;
    border: 1.5px solid var(--border-strong); border-radius: var(--r-check);
    background: var(--bg-elevated); cursor: pointer;
    transition: background 0.12s ease, border-color 0.12s ease;
  }
  :where(input[type="checkbox"]:hover:not(:disabled)){ border-color: var(--accent-manual); }
  :where(input[type="checkbox"]:checked){
    background: var(--accent-manual); border-color: var(--accent-manual);
  }
  :where(input[type="checkbox"]:checked)::after{
    content: ''; position: absolute; left: 50%; top: 45%;
    width: 28%; height: 55%; border: solid var(--bg-base); border-width: 0 2px 2px 0;
    transform: translate(-50%, -50%) rotate(45deg);
  }
  :where(input[type="checkbox"]:focus-visible){
    outline: 2px solid var(--accent-manual); outline-offset: 1px;
  }
  :where(input[type="checkbox"]:disabled){ opacity: 0.5; cursor: default; }
  .ach-toggle-row input[type="checkbox"]{ width: 18px; height: 18px; }
  .ach-list{
    display: flex; flex-direction: column; gap: 8px;
    max-height: 340px; overflow-y: scroll; flex-shrink: 0;
  }
  .ach-row{
    display: flex; gap: 10px; align-items: flex-start; padding: 9px 8px; border-radius: 6px;
    background: var(--bg-base); border: 1px solid var(--border-soft);
  }
  .ach-row.unlocked{ border-color: var(--accent-success); }
  .ach-row.locked{ opacity: 0.55; }
  .ach-rarity-icon{ font-size: 18px; flex-shrink: 0; }
  .ach-info{ flex: 1; }
  .ach-title{ font-size: 12.5px; font-weight: 600; color: var(--text-primary); }
  .ach-desc{ font-size: 11px; color: var(--text-muted); margin-top: 2px; }
  .ach-reward{ font-family: var(--mono); font-size: 10.5px; color: var(--accent-auto); margin-top: 3px; }

  @keyframes achPopIn{
    from{ opacity: 0; transform: translateY(16px) scale(0.94); }
    to{ opacity: 1; transform: translateY(0) scale(1); }
  }
  #achievementPopupHost{ position: fixed; bottom: 20px; right: 20px; z-index: 500; display: flex; flex-direction: column; gap: 10px; }
  .ach-popup{
    width: 300px; background: var(--bg-elevated-2); border: 1px solid var(--accent-auto);
    border-radius: 10px; padding: 12px 14px; box-shadow: 0 12px 30px rgba(0,0,0,0.5);
    animation: achPopIn 0.35s cubic-bezier(.2,.8,.3,1) both;
    display: flex; gap: 10px; align-items: flex-start;
  }
  .ach-popup .ach-rarity-icon{ font-size: 22px; }
  .ach-popup .ach-title{ color: var(--accent-auto); }

  /* ---------- Shop ---------- */

  .shop-list{
    display: flex; flex-direction: column; gap: 8px;
    max-height: 340px; overflow-y: scroll; flex-shrink: 0;
  }
  .shop-row{
    display: flex; align-items: center; gap: 10px; padding: 9px 8px; border-radius: 6px;
    background: var(--bg-base); border: 1px solid var(--border-soft);
  }
  /* Refine Theme (achievements.ts) badge — a plain rainbow border marks any
     shop entry whose theme id is in themes.ts's refinedThemes list, so it
     reads as "individually upgraded" distinct from the theme's own native
     rarity styling (which is unaffected — this is layered on top of
     whatever border the row already has). border-image needs an explicit
     border-width/style, hence overriding the plain 1px solid above. */
  .shop-row.refined{
    border: 2px solid transparent;
    border-image: linear-gradient(90deg, #ff3b3b, #ff9f1c, #ffe066, #4fd68f, #4fa8ff, #a67bff, #ff6ec7) 1;
  }
  .shop-swatches{ display: flex; gap: 3px; flex-shrink: 0; }
  .shop-swatch{ width: 12px; height: 12px; border-radius: 3px; }
  .shop-info{ flex: 1; }
  .shop-name{ font-size: 12.5px; font-weight: 600; color: var(--text-primary); }
  .shop-rarity{ font-size: 10px; color: var(--text-faint); text-transform: uppercase; letter-spacing: 0.4px; }
  .shop-row button{ font-size: 11.5px; padding: 6px 10px; flex-shrink: 0; }

  /* ---------- Tag details ---------- */

  .tag-details-body{ display: flex; flex-direction: column; gap: 10px; font-size: 12.5px; }
  /* Tag Wiki window (tag-wiki.ts): the floating-panel look, but its own class
     so closeAllFloatingPanels() never shuts it; it stays until its × is
     clicked. Dragged by its header; the definition scrolls above the field. */
  .tag-wiki-window{
    position: fixed; width: 360px; max-width: calc(100vw - 16px); max-height: 70vh;
    background: var(--bg-elevated-2); border: 1px solid var(--border-strong);
    border-radius: var(--r-panel); padding: 12px 14px 14px; z-index: 460;
    box-shadow: 0 16px 40px rgba(0,0,0,0.5);
    display: flex; flex-direction: column; gap: 10px;
    opacity: 0; transform: translateY(-8px) scale(0.98);
    transition: opacity var(--panel-dur) var(--pop-ease), transform var(--panel-dur) var(--pop-ease);
    pointer-events: none;
  }
  .tag-wiki-window.panel-visible{ opacity: 1; transform: none; pointer-events: auto; }
  .tag-wiki-head{ display: flex; align-items: center; justify-content: space-between; gap: 8px; cursor: move; user-select: none; touch-action: none; }
  .tag-wiki-close{ padding: 4px 6px; line-height: 0; }
  .tag-wiki-body{ display: flex; flex-direction: column; gap: 8px; min-height: 80px; overflow-y: auto; }
  .tag-wiki-tag{ font-weight: 600; font-size: 14px; color: var(--text-primary); }
  /* The main definition sits in a clearly bounded box; See also sits unboxed
     below it (the user's layout). */
  .tag-wiki-def{
    background: var(--bg-elevated); border: 1px solid var(--border-soft);
    border-radius: var(--radius); padding: 10px 12px;
  }
  .tag-wiki-seealso{ display: flex; flex-direction: column; gap: 6px; }
  .tag-wiki-seealso-head{
    font-family: var(--mono); font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.5px;
    color: var(--accent-flair);
  }
  .tag-wiki-seealso-list{ display: flex; flex-wrap: wrap; gap: 4px 12px; }
  .tag-wiki-link{
    all: unset; cursor: pointer; font-size: 12.5px; color: var(--accent-manual);
    text-decoration: underline; text-decoration-color: color-mix(in srgb, var(--accent-manual) 40%, transparent);
    text-underline-offset: 2px;
  }
  .tag-wiki-link:hover, .tag-wiki-link:focus-visible{ text-decoration-color: currentColor; }
  .tag-wiki-plain{ font-size: 12.5px; color: var(--text-muted); }
  .tag-wiki-input{ width: 100%; }
  .tag-details-meta{ display: flex; gap: 10px; font-family: var(--mono); font-size: 11px; color: var(--text-muted); }
  .tag-details-def{ line-height: 1.5; color: var(--text-primary); white-space: pre-wrap; }
  .tag-details-def.greyed{ color: var(--text-faint); font-style: italic; }
  .tag-details-body textarea{
    width: 100%; min-height: 80px; background: var(--bg-elevated); border: 1px solid var(--border-strong);
    border-radius: var(--radius); color: var(--text-primary); padding: 8px 10px; font-family: var(--sans);
    font-size: 12.5px; resize: vertical;
  }

  /* Night/Day mode is now applied via JS (algorithmic HSL lightness-inversion,
     see toggleDayNightMode in app.js) rather than hand-authored per-theme
     overrides — this correctly inverts DARK themes to a light "day" mode too,
     not just darkening already-dark themes further, and works automatically
     for any theme (including future ones) without needing a bespoke rule. */

  .settings-row{ display: flex; align-items: center; gap: 8px; }
  .settings-row input[type="range"]{ flex: 1; }
  .settings-row button{ flex: 1; font-size: 12px; }

  /* ---------- Power-tool highlighting ---------- */
  /* Default: a colored outline in the theme's manual-accent color. Themes can
     override with something more distinctive (see Subway Fresh below). */
  html.power-highlight .power-tool{
    border: 2px solid var(--accent-manual) !important;
    box-shadow: 0 0 0 1px var(--accent-manual-dim);
  }
  html.power-highlight.power-fill .power-tool{
    background: var(--accent-manual-dim) !important;
  }
  html.power-highlight[data-theme="subway"] .power-tool{
    border: 2px solid transparent !important;
    border-image: linear-gradient(90deg, var(--accent-auto) 0%, var(--accent-danger) 33%, var(--accent-manual) 66%, var(--accent-success) 100%) 1 !important;
    box-shadow: none;
  }
  html.power-highlight.power-fill[data-theme="subway"] .power-tool{
    background: linear-gradient(90deg, color-mix(in srgb, var(--accent-auto) 16%, transparent) 0%, color-mix(in srgb, var(--accent-danger) 16%, transparent) 33%, color-mix(in srgb, var(--accent-manual) 16%, transparent) 66%, color-mix(in srgb, var(--accent-success) 16%, transparent) 100%) !important;
  }

  /* ---------- Manual power-tool marking ---------- */

  body.pt-picking, body.pt-picking *{ cursor: crosshair !important; }

  .power-tool-list{
    display: flex; flex-direction: column; gap: 3px;
    background: var(--bg-base); border-radius: 6px; padding: 4px;
  }
  .pt-list-row{
    display: flex; align-items: center; gap: 8px; padding: 5px 7px; border-radius: 5px; font-size: 11.5px;
  }
  .pt-list-row:hover{ background: var(--bg-elevated-2); }
  .pt-list-row input[type="checkbox"]{ flex-shrink: 0; cursor: pointer; }
  .pt-list-row input[type="checkbox"]:disabled{ cursor: default; opacity: 0.6; }
  .pt-list-label{ flex: 1; color: var(--text-primary); }
  .pt-list-tag{ color: var(--text-faint); font-size: 10px; }

  .pt-choice-menu{
    position: fixed; z-index: 455; /* above .modal-backdrop (450) — same reasoning as .theme-panel */
    background: var(--bg-elevated-2); border: 1px solid var(--border-strong); border-radius: 8px;
    padding: 6px; display: flex; flex-direction: column; gap: 2px; min-width: 190px;
    box-shadow: 0 12px 32px rgba(0,0,0,0.5);
  }
  .pt-choice-menu .ctx-header{
    font-family: var(--mono); font-size: 11px; color: var(--text-muted);
    padding: 4px 6px 6px; border-bottom: 1px solid var(--border-soft); margin-bottom: 2px;
  }
  .pt-choice-menu button{ text-align: left; width: 100%; }

  .tooltip-bubble{
    position: fixed; z-index: 600; max-width: 240px;
    background: var(--bg-elevated-2); border: 1px solid var(--border-strong); color: var(--text-primary);
    font-size: 11.5px; line-height: 1.4; padding: 7px 10px; border-radius: 6px;
    box-shadow: 0 8px 20px rgba(0,0,0,0.4); pointer-events: none;
  }
  /* Used when there's no room to place the bubble above/adjacent to its
     trigger (typical for topbar buttons) — index.ts pins it to the bottom
     of the screen instead of directly below the cursor, where a small font
     size or a custom cursor image can hide it entirely. Since it's no
     longer spatially tied to what triggered it, the accent glow + one-shot
     pulse gives the eye somewhere to jump to. */
  .tooltip-bubble.tooltip-bottom-pinned{
    border-color: var(--accent-manual);
    box-shadow: 0 8px 20px rgba(0,0,0,0.4), 0 0 0 2px var(--accent-manual), 0 0 16px 3px var(--accent-manual);
    animation: tooltipHighlightPulse 900ms ease-out 1;
  }
  html.motion-off .tooltip-bubble.tooltip-bottom-pinned{ animation: none; }
  @keyframes tooltipHighlightPulse{
    0%{ box-shadow: 0 8px 20px rgba(0,0,0,0.4), 0 0 0 2px var(--accent-manual), 0 0 0 0 var(--accent-manual); }
    50%{ box-shadow: 0 8px 20px rgba(0,0,0,0.4), 0 0 0 2px var(--accent-manual), 0 0 22px 8px var(--accent-manual); }
    100%{ box-shadow: 0 8px 20px rgba(0,0,0,0.4), 0 0 0 2px var(--accent-manual), 0 0 16px 3px var(--accent-manual); }
  }

  #btnFlagIsolated.active, #btnDiscreteToggle.active{
    background: var(--accent-manual-dim); border-color: var(--accent-manual); color: var(--accent-manual);
  }
  /* The flagged-for-review list toggle glows orange — the app's own review-flag
     color, the same --accent-auto the flagged chip uses — so it's obvious the
     TAGS list has been hijacked. */
  #btnReviewFlagged.active{
    background: var(--accent-auto-dim); border-color: var(--accent-auto); color: var(--accent-auto);
    box-shadow: 0 0 8px var(--accent-auto);
  }

  /* ---------- Image options menu (3-dot) ---------- */

  .img-menu-btn{
    position: absolute; top: 8px; left: 8px; z-index: 5;
    width: 22px; height: 22px; padding: 0; border-radius: 50%;
    background: rgba(0,0,0,0.55); border: 1px solid rgba(255,255,255,0.25); color: #fff;
    display: flex; align-items: center; justify-content: center; font-size: 13px; line-height: 1;
  }
  .img-menu-btn:hover{ background: rgba(0,0,0,0.75); }
  .master-select-cb{
    position: absolute; top: 10px; left: 38px; z-index: 6; width: 18px; height: 18px;
    accent-color: var(--accent-manual); cursor: pointer;
  }
  .flag-badge{
    position: absolute; top: 8px; right: 8px; z-index: 5; width: 12px; height: 12px; border-radius: 50%;
    box-shadow: 0 0 0 2px rgba(0,0,0,0.4);
  }
  .tagcount-badge{
    position: absolute; top: 26px; right: 8px; z-index: 5; font-family: var(--mono); font-size: 10px;
    background: rgba(0,0,0,0.6); color: #fff; border-radius: 9px; padding: 2px 6px;
  }
  .note-badge{
    position: absolute; bottom: 30px; right: 8px; z-index: 5; font-size: 12px;
    background: rgba(0,0,0,0.55); border-radius: 50%; width: 20px; height: 20px;
    display: flex; align-items: center; justify-content: center; cursor: pointer;
  }
  .note-badge:hover{ background: rgba(0,0,0,0.8); }
  .lock-badge{
    color: #fff;
    position: absolute; bottom: 8px; left: 8px; z-index: 5; font-size: 12px;
    background: rgba(0,0,0,0.55); border-radius: 50%; width: 20px; height: 20px;
    display: flex; align-items: center; justify-content: center;
  }
  .card-status-icons{
    position: absolute; top: 38px; left: 8px; z-index: 5;
    display: flex; flex-direction: column; gap: 5px;
  }
  .mv-badges{
    position: absolute; bottom: 8px; left: 34px; z-index: 5;
    display: flex; gap: 4px;
  }
  /* Single view (view.ts buildSinglePreview): one column over the image, top to bottom: 3-dot menu,
     lock, Merge Immunize / Antivoid, then the status pills. Every mark gets the menu button's outline. */
  .single-icon-col{
    position: absolute; top: 10px; left: 10px; z-index: 5;
    display: flex; flex-direction: column; align-items: flex-start; gap: 6px;
  }
  .single-icon-col .img-menu-btn,
  .single-icon-col .lock-badge,
  .single-icon-col .mv-badge{
    position: static; flex: none; width: 22px; height: 22px; border: 1px solid rgba(255,255,255,0.25);
  }
  .single-icon-col .mv-badges{ position: static; }
  .single-icon-col .card-status-icons{ position: static; gap: 6px; }
  .single-icon-col .status-icon-badge{ border: 1px solid rgba(255,255,255,0.25); }
  .mv-badge{
    /* white like the status pills beside it: the badge icon follows currentColor, and on a light theme
       that was near-black on the dark scrim */
    color: #fff;
    font-size: 12px; background: rgba(0,0,0,0.55); border-radius: 50%;
    width: 20px; height: 20px; display: flex; align-items: center; justify-content: center;
  }
  .status-icon-badge{
    font-family: var(--mono); font-size: 15px; line-height: 1;
    background: rgba(0,0,0,0.55); color: #fff; border-radius: 11px;
    padding: 5px 8px; display: flex; align-items: center; gap: 3px;
    width: fit-content;
  }
  .modal-status-row{
    display: flex; flex-wrap: wrap; gap: 6px; margin: 2px 0;
  }
  /* State marks (view.ts statusStateIcon): check = yes, cross = no,
     question = not indicated. On cards they sit on the black photo scrim, so
     they use fixed bright colors (a photo scrim is deliberately literal,
     like the scrim itself); in the modal they sit on the theme's surface and
     use its own success/danger/auto accents. */
  .status-icon-badge .ic{ width: 15px; height: 15px; }
  .status-icon-badge .status-yes{ color: #5fd38a; }
  .status-icon-badge .status-no{ color: #ff6b6b; }
  .status-icon-badge .status-unknown{ color: #ffc44d; }
  .modal-status-badge{
    display: inline-flex; align-items: center; gap: 5px;
    font-size: 11px; color: var(--text-muted); background: var(--bg-elevated);
    border: 1px solid var(--border-soft); border-radius: 11px; padding: 3px 9px 3px 7px;
  }
  .modal-status-badge .ic{ width: 13px; height: 13px; }
  .modal-status-badge .status-topic{ color: var(--text-muted); }
  .modal-status-badge .status-state{ margin-left: -2px; }
  .modal-status-badge .status-yes{ color: var(--accent-success); }
  .modal-status-badge .status-no{ color: var(--accent-danger); }
  .modal-status-badge .status-unknown{ color: var(--accent-auto); }
  .card-note-visible{
    font-size: 10.5px; color: var(--text-muted); background: var(--bg-base); border-radius: 4px;
    padding: 5px 7px; line-height: 1.4; max-height: 54px; overflow-y: auto;
  }
  .card.flagged{ box-shadow: inset 0 0 0 2px var(--card-flag-color, transparent); }
  /* An edit took this image out of the current filter (view.ts
     patchGridCard): it fades out over 150ms before the card is removed, so
     it doesn't blink out from under the pointer. Reduced motion: fade only. */
  .card.card-leaving{
    opacity: 0; transform: scale(0.97); pointer-events: none;
    transition: opacity 150ms ease-out, transform 150ms var(--swipe-ease);
  }
  @media (prefers-reduced-motion: reduce){ .card.card-leaving{ transform: none; } }

  .lang-common-chip{
    display: inline-flex; align-items: center; gap: 5px; background: var(--bg-elevated);
    border: 1px solid var(--border-soft); border-radius: 12px; padding: 3px 4px 3px 9px;
    font-size: 11.5px; margin: 2px 3px 2px 0; transition: background 0.12s ease, border-color 0.12s ease;
  }
  .lang-common-chip.active{
    background: var(--accent-success); border-color: var(--accent-success);
    color: var(--bg-base); font-weight: 600;
  }
  .lang-common-chip.active button{ color: var(--bg-base); opacity: 0.7; }
  .lang-common-chip.active button:hover{ color: var(--accent-danger); opacity: 1; }
  .lang-common-chip button{ all: unset; cursor: pointer; color: var(--text-faint); font-size: 11px; padding: 0 3px; }
  .lang-common-chip button:hover{ color: var(--accent-danger); }

  /* ---------- Floating image card modal (grid click) ---------- */

  .modal-backdrop{
    position: fixed; inset: 0; background: rgba(0,0,0,0.72); z-index: 450;
    display: flex; align-items: center; justify-content: center;
    opacity: 0; transition: opacity var(--panel-dur) ease;
  }
  .modal-backdrop.modal-visible{ opacity: 1; }
  /* Base/desktop state — hidden here (source order BEFORE the mobile media
     query below) so its `display:inline-flex` override there reliably wins
     at narrow widths. A later plain rule at equal specificity beats an
     earlier one even when it's wrapped in a satisfied @media block, so this
     has to come first, not just live outside the block — a stray rule
     placed AFTER the media query silently cancelled it here before. */
  .modal-info-btn{ display: none; }
  .modal-card{
    width: min(1100px, 92vw); height: min(760px, 88vh);
    background: var(--bg-panel); border: 1px solid var(--border-strong); border-radius: 12px;
    box-shadow: 0 16px 44px rgba(0,0,0,0.5); display: flex; overflow: hidden;
    transform: scale(0.96); transition: transform var(--panel-dur) ease;
  }
  .modal-backdrop.modal-visible .modal-card{ transform: scale(1); }
  .modal-close-btn{ position: absolute; top: 12px; right: 12px; z-index: 10; }

  /* ---------- Help & documentation panel ---------- */

  .help-modal-card{ position: relative; }
  /* Base/desktop state — before the mobile media query for the same reason
     .modal-info-btn's own base rule is (see that comment): a later plain
     rule beats an earlier one at equal specificity even inside a satisfied
     @media, so the hide has to come first, not just live outside the block. */
  .help-toc-toggle{ display: none; }
  .help-toc{
    width: 230px; flex-shrink: 0; overflow-y: auto;
    background: var(--bg-elevated); border-right: 1px solid var(--border-soft);
    padding: 16px 10px;
  }
  .help-toc-title{
    font-family: var(--mono); font-size: 10.5px; font-weight: 600; color: var(--text-faint);
    text-transform: uppercase; letter-spacing: 0.04em; padding: 2px 10px 10px;
  }
  .help-toc-item{
    display: block; width: 100%; text-align: left; padding: 8px 10px; margin-bottom: 2px;
    border-radius: var(--radius); font-size: 12.5px; color: var(--text-muted);
    background: transparent; border: 1px solid transparent; cursor: pointer;
    /* Base button{} is white-space:nowrap, which clipped longer section
       titles in the 230px TOC ("Start here: training a LoRA"). Let them wrap. */
    white-space: normal; overflow-wrap: anywhere; line-height: 1.35;
  }
  .help-toc-item:hover{ background: var(--bg-elevated-2); color: var(--text-primary); }
  .help-toc-item.active{
    background: var(--accent-manual-dim); color: var(--accent-manual);
    border-color: var(--accent-manual); font-weight: 600;
  }
  /* Only the selected section's content is ever in the DOM at once — this
     is meant to be read one digestible piece at a time via the TOC on the
     left, not scrolled through as one long page. */
  .help-content{
    flex: 1; overflow-y: auto; padding: 30px 40px 40px; line-height: 1.65;
    font-size: 13.5px; color: var(--text-primary);
  }
  .help-content h2{ margin: 0 0 18px; font-size: 18px; padding-right: 30px; }
  .help-content p{ margin: 0 0 13px; color: var(--text-muted); }
  .help-content p:last-child{ margin-bottom: 0; }
  .help-content ul, .help-content ol{
    margin: 0 0 13px; padding-left: 20px; display: flex; flex-direction: column; gap: 7px;
    color: var(--text-muted);
  }
  .help-content b{ color: var(--text-primary); }
  .help-content code{
    font-family: var(--mono); font-size: 12px; background: var(--bg-elevated);
    padding: 1px 5px; border-radius: 4px; color: var(--text-secondary, var(--accent-manual));
  }
  /* Learning-oriented tables (e.g. "what to collect, by LoRA type") so a long
     set of recommendations reads as a grid instead of a wall of prose. */
  .help-content h3{
    margin: 20px 0 7px; font-size: 14px; color: var(--text-primary); font-weight: 600;
  }
  .help-content table{
    width: 100%; border-collapse: collapse; margin: 0 0 13px; font-size: 12.5px;
  }
  .help-content th, .help-content td{
    border: 1px solid var(--border-soft); padding: 6px 8px; text-align: left; vertical-align: top;
    color: var(--text-muted); line-height: 1.5;
  }
  .help-content th{ color: var(--text-primary); background: var(--bg-elevated); font-weight: 600; }
  .help-content td b{ color: var(--text-primary); }

  /* ---------- Generic image lightbox (shared-ui.ts showImageLightbox()) ---------- */

  /* z-index 650: above .confirm-backdrop/.picker-backdrop (600) and .ctx-menu
     (650). The lightbox can be opened FROM a modal (a log stack / per-image
     edits modal), and it must sit in the foreground so the modal is still
     there, usable, once the image is dismissed. */
  .lightbox-backdrop{
    position: fixed; inset: 0; background: rgba(0,0,0,0.85); z-index: 650;
    display: flex; align-items: center; justify-content: center; cursor: zoom-out;
    opacity: 0; transition: opacity var(--panel-dur) ease;
  }
  .lightbox-backdrop.modal-visible{ opacity: 1; }
  /* "2 / 3 · name · ← →" when the lightbox steps through a set (LightboxNav). */
  .lightbox-caption{
    position: fixed; left: 50%; bottom: 14px; transform: translateX(-50%); pointer-events: none;
    padding: 4px 12px; border-radius: 999px; font-family: var(--mono); font-size: 12px;
    color: #eee; background: rgba(0,0,0,0.6); white-space: nowrap; max-width: 90vw; overflow: hidden; text-overflow: ellipsis;
  }
  .lightbox-backdrop img{
    max-width: 92vw; max-height: 92vh; object-fit: contain;
    box-shadow: 0 16px 44px rgba(0,0,0,0.5); border-radius: 4px;
    /* Without this, the WebView's own touch-scroll/pinch-zoom gesture
       recognizer competes with the pointer-event pan/pinch-zoom handlers
       in shared-ui.ts's showImageLightbox() for the same drag — reported
       as panning feeling clipped/janky on-device. `none` hands the browser
       zero built-in touch behavior on this element, so only our own
       pointermove/attachPinchZoom logic ever sees the gesture. */
    touch-action: none;
  }

  /* ---------- Themed confirm modal ---------- */

  .confirm-backdrop{
    position: fixed; inset: 0; background: rgba(0,0,0,0.62); z-index: 600;
    display: flex; align-items: center; justify-content: center;
    opacity: 0; transition: opacity var(--panel-dur) ease;
  }
  .confirm-backdrop.modal-visible{ opacity: 1; }
  .confirm-box{
    width: min(460px, 92vw); background: var(--bg-elevated-2); border: 1px solid var(--border-strong);
    border-radius: 10px; padding: 18px; box-shadow: 0 20px 50px rgba(0,0,0,0.5);
    transform: scale(0.94); transition: transform var(--panel-dur) ease;
  }
  .confirm-backdrop.modal-visible .confirm-box{ transform: scale(1); }
  .confirm-message{ font-size: 13px; color: var(--text-primary); line-height: 1.5; margin-bottom: 16px; white-space: pre-wrap; }
  /* In-dialog caveat(s) (showConfirmModal's `warning`/`warnings` opts): one
     tinted box each, so they can't be missed, for things a toast would hide
     (e.g. the File System Access "Desktop/Documents are blocked" notice while
     the native picker is open). Two tones: red = danger, blue = info. */
  .confirm-warning{
    display: flex; gap: 8px; align-items: flex-start;
    margin: 0 0 12px; padding: 9px 11px;
    border-radius: 8px; font-size: 12.5px; line-height: 1.5; color: var(--text-primary);
  }
  .confirm-warning:last-of-type{ margin-bottom: 16px; }
  .confirm-warning-icon{ flex-shrink: 0; font-size: 14px; line-height: 1.35; }
  .confirm-warning-danger{
    background: color-mix(in srgb, var(--accent-danger) 14%, var(--bg-elevated));
    border: 1px solid color-mix(in srgb, var(--accent-danger) 45%, var(--border-strong));
  }
  .confirm-warning-danger .confirm-warning-icon{ color: var(--accent-danger); }
  .confirm-warning-info{
    background: color-mix(in srgb, var(--accent-manual) 14%, var(--bg-elevated));
    border: 1px solid color-mix(in srgb, var(--accent-manual) 45%, var(--border-strong));
  }
  .confirm-warning-info .confirm-warning-icon{ color: var(--accent-manual); }
  .confirm-btn-row{ display: flex; gap: 8px; justify-content: flex-end; flex-wrap: wrap; }
  /* showChoiceModal layout 'stack': Save choices stacked full-width (equal
     shape), Cancel on its own row along the bottom-left. */
  .confirm-btn-stack{ display: flex; flex-direction: column; gap: 8px; }
  .confirm-btn-stack button{ width: 100%; }
  .confirm-btn-row-cancel{ justify-content: flex-start; margin-top: 8px; }

  /* ---------- Model/option picker modal (attachPickerModal) ---------- */
  /* Same tap-to-pick UI as Comfy Bridge's model fields, restyled onto this
     app's own tokens. z-index matches .confirm-backdrop (600) — pickers and
     confirms never stack on top of each other in practice. */
  .picker-backdrop{
    position: fixed; inset: 0; background: rgba(0,0,0,0.62); z-index: 600;
    display: flex; align-items: center; justify-content: center;
    opacity: 0; transition: opacity var(--panel-dur) ease;
  }
  .picker-backdrop.modal-visible{ opacity: 1; }
  .picker-box{
    width: min(440px, 92vw); max-height: 78vh;
    background: var(--bg-elevated-2); border: 1px solid var(--border-strong);
    border-radius: 10px; padding: 14px; box-shadow: 0 20px 50px rgba(0,0,0,0.5);
    display: flex; flex-direction: column;
    transform: scale(0.94); transition: transform var(--panel-dur) ease;
  }
  .picker-backdrop.modal-visible .picker-box{ transform: scale(1); }
  .picker-head{ display: flex; align-items: center; gap: 8px; font-weight: 600; font-size: 14px; margin-bottom: 10px; color: var(--text-primary); }
  .picker-head span{ flex: 1; }
  .picker-close{ background: none; border: none; color: var(--text-muted); font-size: 20px; line-height: 1; padding: 2px 6px; cursor: pointer; }
  .picker-close:hover{ color: var(--text-primary); }
  .picker-search{ margin-bottom: 8px; }
  .picker-list{ overflow-y: auto; border-top: 1px solid var(--border-soft); }
  .picker-row{ padding: 9px 8px; font-size: 13px; color: var(--text-primary); border-bottom: 1px solid var(--border-soft); cursor: pointer; overflow-wrap: anywhere; }
  .picker-row:last-child{ border-bottom: none; }
  .picker-row:hover{ background: var(--bg-elevated); }
  .picker-row.picked{ color: var(--accent-manual); font-weight: 600; }
  .picker-clear{ color: var(--text-muted); }
  .picker-empty{ padding: 9px 8px; font-size: 12.5px; color: var(--text-faint); }

  /* A tip's own ⓘ button — sits inline right after the short teaser text,
     opens showInfoModal() with the matching #info<Id>Content <template>'s
     full explanation instead of that text always taking up dock space. */
  .info-btn{
    display: inline-flex; align-items: center; justify-content: center;
    width: 18px; height: 18px; padding: 0; margin-left: 4px;
    border-radius: 50%; font-size: 12px; line-height: 1;
    background: var(--bg-elevated); color: var(--text-muted);
    flex-shrink: 0;
  }
  .info-btn:hover{ color: var(--text-primary); border-color: var(--accent-manual); }
  /* Dock-header info buttons (Tag Pruner/Retroactive Merge-Void/Master Tag
     Control/SynthDat sections) now show on desktop too — the long inline
     description paragraphs they replace (.dock-desc-hide-mobile, hidden
     unconditionally now, not just in the mobile breakpoint) were eating a
     lot of dock space for text most people read once. .info-btn's own base
     rule above already sets display:inline-flex, so no override needed here. */
  /* `.tool-section .sec-head` (base rule, specificity 0-2-0) sets its own
     `display:flex` — a bare `.dock-desc-hide-mobile{display:none}` (0-1-0)
     loses to it despite coming later in the file, same specificity trap as
     the topbar/`.actions` scroll bug elsewhere in this file. Scoped to match
     (or beat) that specificity here, same fix this rule already needed when
     it only applied inside the mobile breakpoint. */
  .tool-section .sec-head.dock-desc-hide-mobile{ display: none; }
  /* openDockListModal() trigger buttons ("🔍 Browse tags" etc.) — mobile
     only, same reasoning .dock-info-btn used to need. Desktop's dock list
     stays inline exactly as before, no trigger needed. */
  .dock-list-open-btn{ display: none; }
  /* Master Tag Control's own grid-pop-out button is available at every
     width, not just the mobile/narrow layout the other dock-list-open-btn
     triggers are scoped to — its thumbnails are genuinely too small to work
     with in the docked panel even on a full desktop window, unlike Tag
     Pruner's plain text list or the other list-only triggers this class
     also covers. */
  .dock-list-open-btn.always-visible{ display: block; margin-bottom: 8px; }

  .info-modal-box{
    width: min(520px, 88vw); max-height: 76vh; overflow-y: auto;
  }
  /* openDockListModal()'s box (shared-ui.ts) — bigger than the plain info
     modal since it holds a real list/grid (Tag Pruner's matches, Master
     Tag Control's thumbnail grid, etc.), not just a paragraph of text. */
  .dock-list-modal-box{
    width: min(640px, 94vw); max-height: 82vh; overflow-y: auto;
  }
  .dock-list-modal-content{ width: 100%; }
  /* Sequential tagging on touch (view.ts beginSequential): the whole run in
     one scrolling sheet — head row, image + "will apply" chips, then the
     form with its sticky Confirm. The box is the scroller, so the panel's
     own overflow is dropped and sticky grounds to the box instead. */
  .seq-modal-box{
    width: min(640px, 96vw); height: 92vh; max-height: 92vh; overflow-y: auto;
    padding: 12px;
  }
  .seq-modal-box .single-wrap{ flex-direction: column; gap: 10px; align-items: stretch; }
  .seq-modal-box .seq-img-col{ display: flex; flex-direction: column; gap: 8px; }
  .seq-modal-box .single-preview{ max-height: 34vh; }
  .seq-modal-box .single-preview img{ max-height: 34vh; object-fit: contain; }
  .seq-modal-box .single-panel.seq-panel{ overflow: visible; max-width: none; width: auto; padding: 0; }
  .seq-modal-head{ position: sticky; top: -12px; z-index: 4; background: var(--bg-panel); padding: 4px 0; }
  .info-modal-title{ font-size: 13px; font-weight: 600; color: var(--text-primary); margin-bottom: 10px; }
  .info-modal-body{ font-size: 12.5px; color: var(--text-muted); line-height: 1.55; }
  .info-modal-body p{ margin: 0 0 12px; }
  .info-modal-body p:last-child{ margin-bottom: 0; }
  .info-modal-body ul{ margin: 0 0 12px; padding-left: 18px; display: flex; flex-direction: column; gap: 8px; }
  .info-modal-body ul:last-child{ margin-bottom: 0; }
  .info-modal-body b{ color: var(--text-primary); }

  html.discrete-mode .card img, html.discrete-mode .compact-card img, html.discrete-mode .single-img-side img, html.discrete-mode .single-preview img, html.discrete-mode .trainflow-previews img{
    filter: blur(24px);
  }
  html.discrete-mode .card.discrete-off img, html.discrete-mode .compact-card.discrete-off img{
    filter: none;
  }
  .card.manually-blurred img, .compact-card.manually-blurred img{ filter: blur(20px); transition: filter 0.15s ease; }
  .card.manually-blurred img:hover, .compact-card.manually-blurred img:hover{ filter: blur(4px); }

  * { box-sizing: border-box; }

  html, body {
    height: 100%;
    margin: 0;
    background: var(--bg-base);
    color: var(--text-primary);
    font-family: var(--sans);
    font-size: 14px;
    overflow: auto;
  }

  #app {
    display: flex;
    flex-direction: column;
    height: 100%;
    width: 100%;
  }

  /* ---------- Topbar ---------- */

  /* Sits OUTSIDE #topbarActions on purpose — that row scales its own content
     down (updateTopbarScale(), index.ts) to fit instead of scrolling, so a
     button living inside it could shrink away with everything else. This
     one stays a fixed sibling at the actual far-right edge of the header,
     same role .brand-block plays at the far left — separated from every
     other topbar control, easy to find when someone's actually looking for
     it, not just another item in a crowded row. */
  .topbar-help-btn{
    flex-shrink: 0;
    border-color: var(--accent-manual); color: var(--accent-manual); font-weight: 600;
  }
  .topbar-help-btn:hover:not(:disabled){ background: var(--accent-manual-dim); }

  #topbar {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 8px 20px;
    min-height: 56px;
    background: var(--bg-panel);
    border-bottom: 2px solid var(--accent-flair);
    z-index: 20;
    /* #app is a column flex container; #galleryTab uses `display: contents`
       when Gallery/Master Tags is active, so its own children (#shell, the
       big gallery layout) become direct flex items of #app right alongside
       #topbar and #tabBar. That's more competing content than when Dataset/
       Stats is active (#galleryTab is display:none, contributing nothing).
       Without flex-shrink:0, #topbar/#tabBar's default flex-shrink:1 let the
       column algorithm compress them slightly under that extra pressure —
       shorter on Gallery/Master, back to their natural size on Dataset/
       Stats. Locking both to flex-shrink:0 makes their height purely a
       function of their own content/padding, never of a sibling tab's. */
    flex-shrink: 0;
  }

  /* ---- Opening flourish (index.html's inline script builds and times it;
     the per-theme mark and particle table lives there). A slab of the
     theme's flair colour led by two disconnected columns — the thinner one
     in front — sweeps across a dimmed window; the theme's Osmium mark is
     set in bg-base ink on the fill. Every colour is a theme var, so night
     mode and Custom themes carry through. Removed from the DOM when done. */
  .of{
    position: fixed; inset: 0; z-index: 2147483000; overflow: hidden;
    contain: strict; pointer-events: auto; cursor: progress;
    --of-fill: var(--accent-flair); --of-ink: var(--bg-base);
  }
  /* Osmium's mark is solid black on its green fill (lines, nodes, wordmark). */
  html[data-theme="osmium"] .of{ --of-ink: #000; }
  .of-scrim{ position: absolute; inset: 0; background: rgba(6, 6, 10, 0.58); }
  .of-parts{ position: absolute; inset: 0; }
  .of-parts i{ position: absolute; opacity: 0; will-change: transform, opacity; box-sizing: border-box; }
  .of-band{
    position: absolute; top: 0; bottom: 0; left: 0; width: 114.5vw;
    display: flex; gap: 1.2vw; transform: translateX(-114.5vw); will-change: transform;
  }
  .of-body{ flex: 0 0 108vw; background: var(--of-fill); }
  /* TPE teeth: tiles buried halfway into the trailing edge (index.html). */
  .of-body{ position: relative; }
  .of-tooth{ position: absolute; box-sizing: border-box; }
  .of-col{ background: var(--of-fill); }
  .of-col-b{ flex: 0 0 3vw; }
  .of-col-a{ flex: 0 0 1.1vw; }
  .of-v .of-band{
    right: 0; bottom: auto; width: auto; height: 114.5vh;
    flex-direction: column; gap: 1.2vh; transform: translateY(-114.5vh);
  }
  .of-v .of-body{ flex-basis: 108vh; }
  .of-v .of-col-b{ flex-basis: 3vh; }
  .of-v .of-col-a{ flex-basis: 1.1vh; }
  /* Worlds with a light source (Native Light Rule): the leading column burns. */
  .of-glow .of-col-a{ box-shadow: 0 0 22px 3px color-mix(in srgb, var(--of-fill) 80%, transparent); }
  .of-mark{ position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; pointer-events: none; }
  .of-mark-in{ display: flex; flex-direction: column; align-items: center; gap: 14px; will-change: transform, opacity; }
  .of-logo{ width: clamp(112px, 23vmin, 220px); height: auto; overflow: visible; }
  .of-logo g{ stroke: var(--of-ink); }
  .of-logo .of-nodes{ fill: var(--of-ink); stroke: none; }
  .of-logo .of-thin{ opacity: 0.55; }
  .of-logo .of-hollow{ fill: var(--of-fill); stroke: var(--of-ink); }
  .of-logo .of-pin{ fill: var(--of-fill); }
  .of-logo .of-split{ opacity: 0.85; }
  .of-logo .of-split-a{ stroke: var(--accent-manual); }
  .of-logo .of-split-b{ stroke: var(--accent-danger); }
  .of-logo .of-seal{ fill: var(--accent-danger); }
  .of-logo .of-moon{ fill: color-mix(in srgb, var(--accent-danger) 55%, var(--of-fill)); }
  .of-word{
    font-family: var(--display); font-weight: var(--brand-weight, 800);
    letter-spacing: var(--brand-track, -0.01em); text-transform: var(--brand-case, none);
    font-size: clamp(19px, 3.3vmin, 34px); line-height: 1.05; white-space: nowrap; color: var(--of-ink);
  }

  .brand {
    font-family: var(--display);
    font-weight: var(--brand-weight);
    font-size: var(--brand-size);
    letter-spacing: var(--brand-track);
    text-transform: var(--brand-case);
    line-height: 1.05;
    color: var(--text-primary);
    white-space: nowrap;
    /* Stacked (tag pill below the name) instead of side-by-side, so the
       "local · no upload" pill no longer adds to this block's horizontal
       footprint — frees up width for #topbar .actions, which needs it most
       at high font-size zoom (its own overflow-x:auto scrollbar is
       untouched by this and still appears whenever the button row itself
       still doesn't fit). */
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
  }
  .brand span.tag {
    font-family: var(--mono);
    font-size: 11px;
    color: var(--accent-auto);
    background: var(--accent-auto-dim);
    padding: 2px 6px;
    border-radius: var(--r-chip);
    font-weight: 400; letter-spacing: 0; text-transform: none;
  }

  #topbar .actions {
    display: flex;
    align-items: center;
    gap: 10px;
    flex: 1;
    flex-wrap: nowrap;
    overflow: hidden;
    min-width: 0;
    /* Themes that make buttons grow on hover (candy-pop's
       scale(1.06) translateY(-1px), etc.) overflow the row's content box by
       a couple px — without slack, overflow:hidden here shears off the
       button's top outline exactly at the moment of hover. Padding gives
       the expansion room to live inside the clip box (both copies:
       desktop + mobile own their styles but the shape is shared). */
    padding: 3px 0;
    /* JS (updateTopbarScale in index.ts) sets a transform: scale(n) here
       instead of letting this row overflow into a scrollbar — scrollWidth/
       clientWidth are unaffected by an element's own transform, so that scale
       is recomputed straight off this row's natural (unscaled) content width
       vs. the space actually available, on every resize/zoom. Scaling
       in-place (transform-origin: left) shrinks every icon/label together
       instead of clipping or scrolling them. */
    transform-origin: left center;
  }
  /* Every direct child must refuse to shrink below its own nowrap content
     width — a flex item's default min-width:auto already prevents this in
     most cases, but a bare div with no explicit flex-shrink could still get
     squeezed enough to visually shove the buttons after it (drifting their
     content rightward inside their own boxes). Real overflow is handled by
     the transform-scale above, not by shrinking children individually. */
  #topbar .actions > *{ flex-shrink: 0; }

  .header-cat{ position: relative; display: inline-block; flex-shrink: 0; }
  .header-cat-btn{ white-space: nowrap; }
  .header-cat-flyout{
    position: fixed; z-index: 350;
    background: var(--bg-elevated-2); border: 1px solid var(--border-strong); border-radius: var(--r-panel);
    padding: 8px; display: flex; flex-direction: column; gap: 6px; min-width: 210px;
    box-shadow: 0 12px 30px rgba(0,0,0,0.5);
  }
  .header-cat-flyout button, .header-cat-flyout select{ width: 100%; }
  /* Themed keyboard-focus ring for arrow-key menu navigation
     (initMenuKeyboardNav(), shared-ui.ts) — these three menu-container
     types use plain, non-`all:unset` buttons, so without this they'd fall
     back to the browser's default (untthemed) focus outline. */
  .header-cat-flyout button:focus-visible,
  .pt-choice-menu button:focus-visible,
  .confirm-btn-row button:focus-visible{
    outline: 2px solid var(--accent-manual); outline-offset: -2px;
  }
  .header-cat-flyout .pdrop{ display: block; width: 100%; }
  .pdrop-menu.theme-pdrop-menu{ max-height: min(60vh, 420px); overflow-y: auto; overflow-x: hidden; }

  button {
    font-family: var(--sans);
    font-size: 13px;
    font-weight: var(--btn-weight);
    letter-spacing: var(--btn-track);
    text-transform: var(--btn-case);
    color: var(--text-primary);
    /* --btn-bg / --btn-bg-hover / --btn-bd-hover / --btn-shadow: an optional
       button MATERIAL (a gradient, a bevel) for themes whose controls are
       not a flat fill (Windows 7's two-tone face). Read here, in the base
       rule, so every classed button (.primary, .danger-ghost, …) that sets
       its own background still wins; unset, they fall back to the flat look. */
    background: var(--btn-bg, var(--bg-elevated));
    box-shadow: var(--btn-shadow, none);
    border: 1px solid var(--border-strong);
    border-radius: var(--r-ctl);
    padding: 7px 13px;
    cursor: pointer;
    transition: background 0.12s ease, border-color 0.12s ease, transform 0.05s ease;
    white-space: nowrap;
    /* Explicit centering (not just relying on the UA default) so icon+text
       content stays centered at any font-size zoom level, even for buttons
       with a nested <span> (#dirtyCount, #walletDisplay) — without this,
       some topbar buttons drifted their content toward the right edge as
       the font-size slider grew, eventually clipping it. `all: unset`-based
       button classes (.ctx-item, .pdrop-item, etc.) reset display back to
       inline via `all: unset` itself, so they're unaffected by this. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  button:hover:not(:disabled) { background: var(--btn-bg-hover, var(--bg-elevated-2)); border-color: var(--btn-bd-hover, var(--text-faint)); }

  /* ---------- Icons ----------
     Every UI icon is <svg class="ic"><use href="#i-…"></svg> from the
     sprite at the top of index.html. Sized in em so an icon tracks its
     label at every font-size zoom; stroke grammar comes from the theme.
     .ic-lead / .ic-trail replace the space that used to separate a glyph
     from its words (a leading space collapses inside a flex button). */
  .ic{
    width: 1.2em; height: 1.2em; flex-shrink: 0; display: inline-block; vertical-align: -0.24em;
    fill: none; stroke: currentColor; stroke-width: var(--icon-stroke);
    stroke-linecap: var(--icon-cap); stroke-linejoin: var(--icon-join); overflow: visible;
  }
  .ic-lead{ margin-inline-end: 0.42em; }
  /* Achievement rarity: one gem drawing, tinted by tier from theme vars (a
     star for legendary). */
  .ic.rarity-common{ color: var(--text-faint); }
  .ic.rarity-uncommon{ color: var(--accent-success); }
  .ic.rarity-rare{ color: var(--accent-manual); }
  .ic.rarity-epic{ color: var(--accent-flair); }
  .ic.rarity-legendary{ color: var(--accent-auto); fill: color-mix(in srgb, var(--accent-auto) 30%, transparent); }
  .ic-trail{ margin-inline-start: 0.36em; }
  /* Icon followed by a label span (Help, the Edibits counter): the source
     space sits between two elements, which a flex button drops. */
  .ic:not(.ic-lead) + span:not(:empty){ margin-inline-start: 0.4em; }
  /* Carets/chevrons read as punctuation, not pictures: a notch smaller. */
  .ic:has(use[href="#i-caret-down"]), .ic:has(use[href="#i-caret-up"]),
  .ic:has(use[href="#i-chevron-right"]), .ic:has(use[href="#i-chevron-left"]){ width: 0.95em; height: 0.95em; vertical-align: -0.12em; }
  button:active:not(:disabled) { transform: translateY(1px); }
  button:disabled { opacity: 0.35; cursor: default; }

  button.primary {
    background: var(--accent-manual-dim);
    border-color: var(--accent-manual);
    color: var(--accent-manual);
    font-weight: 600;
  }
  /* Derived from the theme's own accent, not hardcoded — a hardcoded dark
     teal here read fine as "darker than accent-manual-dim" on every dark
     theme this shipped with, but inverted into a jarring dark flash against
     a light theme's pale accent-manual-dim (subway, osmium). */
  button.primary:hover:not(:disabled) { background: color-mix(in srgb, var(--accent-manual) 35%, var(--bg-panel)); }

  /* Theme-aware, not hardcoded dark: a light theme (subway, osmium) needs a
     light elevated box here too. bg-elevated/border-strong happen to sit
     very close to the old hardcoded #2a1c20/#5a2f38 on studio (the default),
     so this is a no-op there and a real fix everywhere lighter. */
  button.danger-ghost { color: var(--accent-danger); border-color: var(--border-strong); background: var(--bg-elevated); }
  button.danger-ghost:hover:not(:disabled) { background: var(--bg-elevated-2); }

  /* ---------- Shell layout ---------- */

  #galleryTab{ display: contents; }

  #shell {
    flex: 1;
    display: grid;
    /* --right-w (dragged via #rightPanelResizeHandle, see below) overrides
       the fixed 380px default in every layout mode at once. */
    grid-template-columns: 270px 1fr var(--right-w, 380px);
    min-height: 0;
    transition: grid-template-columns var(--panel-dur) var(--pop-ease);
    /* Containing block for #rightPanelResizeHandle's position:absolute —
       #shell's own box already starts/ends exactly where the 3-column
       layout does (below #topbar/#tabBar), so an absolute child anchored
       top:0/bottom:0 gets that for free with no JS height/top math needed,
       unlike position:fixed (viewport-relative, and silently broken by any
       ancestor transform — see the mobile drawer layout's own #right
       transform, which is exactly what went wrong before this). */
    position: relative;
  }
  #left{ order: 1; }
  #gallery{ order: 2; }
  #right{ order: 3; }

  #shell.layout-gallery-left{ grid-template-columns: 1fr 270px var(--right-w, 380px); }
  #shell.layout-gallery-left #gallery{ order: 1; }
  #shell.layout-gallery-left #left{ order: 2; }
  #shell.layout-gallery-left #right{ order: 3; }

  #shell.layout-gallery-right{ grid-template-columns: 270px var(--right-w, 380px) 1fr; }
  #shell.layout-gallery-right #left{ order: 1; }
  #shell.layout-gallery-right #right{ order: 2; }
  #shell.layout-gallery-right #gallery{ order: 3; }

  /* Right panel collapse: shrinks whichever grid track #right currently
     occupies down to a thin strip (just wide enough for the toggle arrow)
     instead of hiding #right outright — an empty grid cell doesn't
     collapse on its own, so the track width has to be overridden per
     layout mode (the right panel isn't always the rightmost column, see
     layout-gallery-right above where it sits next to #left instead). The
     arrow button itself lives at #right's own left edge in normal flow,
     so it naturally ends up wherever #right's leading edge currently is
     in every layout mode, with no separate positioning logic needed. */
  #shell.right-panel-collapsed{ grid-template-columns: 270px 1fr 32px; }
  #shell.right-panel-collapsed.layout-gallery-left{ grid-template-columns: 1fr 270px 32px; }
  #shell.right-panel-collapsed.layout-gallery-right{ grid-template-columns: 270px 32px 1fr; }

  aside, main { min-height: 0; overflow-y: scroll; }

  #left {
    background: var(--bg-panel);
    border-right: 1px solid var(--border-soft);
    padding: 16px;
    display: flex;
    flex-direction: column;
    gap: 18px;
  }

  #right {
    background: var(--bg-panel);
    border-left: 1px solid var(--border-soft);
    padding: 16px;
    display: flex;
    flex-direction: column;
    gap: 20px;
    overflow-x: hidden;
    /* Query container for .icon-fallback-btn's label swap below — keyed to
       this element specifically because it's the one whose width the drag
       handle (index.ts's rightPanelResizeHandle, via --right-w) actually
       controls, not the viewport. */
    container-type: inline-size;
  }

  /* Static, not sticky — sticky was tried here and reverted: a header glued
     to the viewport follows the user down the panel instead of staying at
     its top, which reads as a floating control chasing you. Static keeps it
     docked at the panel's own top edge, scrolling off naturally with
     everything else. (Absolute is out for the older reason below: #right is
     itself the scrolling element, so absolute children place against the
     full content height and land below the fold.)
     The row also holds btnGoToTagOverseer/btnMasterBack — those fill what
     used to be dead space beside a lone 20px-wide arrow in an
     otherwise-empty full-width strip, rather than leaving it empty. */
  #rightPanelHeaderRow{
    position: static;
    display: flex; align-items: center; gap: 8px;
    background: var(--bg-panel);
  }
  .panel-collapse-arrow{
    width: 20px; height: 28px; padding: 0; flex-shrink: 0;
    display: flex; align-items: center; justify-content: center;
    font-size: 13px; line-height: 1; border-radius: var(--radius);
    background: var(--bg-elevated); border: 1px solid var(--border-soft);
    color: var(--text-muted); cursor: pointer;
  }
  .panel-collapse-arrow:hover{ color: var(--text-primary); background: var(--bg-elevated-2); }
  .right-panel-header-action{
    flex: 1; min-width: 0; font-size: 11.5px; padding: 5px 10px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    /* Same centered-clipping risk as .pdrop-btn above — see its comment. */
    text-align: left;
  }
  #right.right-panel-collapsed .right-panel-header-action{ display: none; }

  /* Lets the right panel be dragged wider/narrower (down to a minimum) — a
     sibling of #left/#gallery/#right rather than a child of #right (which
     is itself the scrolling element, see the comment above on
     .panel-collapse-arrow — a child anchored inside it would scroll away
     with its content instead of staying put along the panel's actual edge).
     `position: absolute` against #shell's own box (see #shell's `position:
     relative`) gets top:0/bottom:0 for free with no JS math, unlike the
     `position: fixed` this used originally — viewport-relative, and it
     silently broke (ended up spanning the whole window height, through the
     header) the moment any ANCESTOR got a `transform` (the mobile drawer
     layout's #right does), since that creates a new containing block fixed
     positioning suddenly resolves against instead of the viewport.
     index.ts's repositionRightResizeHandle() still keeps `left` lined up
     with whichever edge is actually draggable (the boundary next to
     #gallery — #right's own left edge normally, but its right edge in
     gallery-right layout, where #right sits directly next to #left instead
     of at the screen's outer edge) any time that edge could have moved.
     Hidden entirely while the panel is collapsed — nothing meaningful to
     resize down to a 32px strip. */
  #rightPanelResizeHandle{
    position: absolute; top: 0; bottom: 0; width: 8px;
    cursor: ew-resize; z-index: 50;
    /* Same "grip" indicator as .dock-resize-handle (vertical dock resize),
       rotated — a repeating stripe pattern that's always faintly visible
       (not just a hover-only tint) so the draggable edge is discoverable,
       not just a proximity-tolerant hit zone that happens to highlight. */
    background: repeating-linear-gradient(180deg, var(--border-strong) 0 6px, transparent 6px 12px);
    opacity: 0.35;
  }
  #rightPanelResizeHandle:hover, #rightPanelResizeHandle.resizing{ opacity: 0.85; }
  #right.right-panel-collapsed + #rightPanelResizeHandle{ display: none; }

  #right.right-panel-collapsed{ padding-left: 0; padding-right: 0; }
  #right.right-panel-collapsed #rightPanelContent{ display: none; }

  h3.panel-title {
    margin: 0;
    font-family: var(--head-font);
    font-size: calc(var(--head-size) - 0.5px);
    font-weight: var(--head-weight);
    color: var(--text-muted);
    letter-spacing: var(--head-track);
    text-transform: var(--head-case);
  }

  input[type="text"], textarea {
    width: 100%;
    background: var(--bg-elevated);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    caret-color: var(--accent-manual);
    color: var(--text-primary);
    padding: 8px 10px;
    font-family: var(--sans);
    font-size: 13px;
    outline: none;
  }
  input[type="text"]:focus, textarea:focus { border-color: var(--accent-manual); }
  input[type="text"]::placeholder { color: var(--text-faint); }

  /* ---------- Left panel: filter + stats ---------- */

  .field-block { display: flex; flex-direction: column; gap: 8px; }
  /* Picker-modal tap-targets (model fields wired to attachPickerModal) are
     marked readonly to stop the native caret/keyboard editing implying free
     text entry — cursor:pointer is the only visual cue left that they're
     still clickable. */
  .field-block input[readonly] { cursor: pointer; }

  /* All/Untagged/Unsaved: a fixed three-button row that must stay one line.
     It used to wrap per theme because the wider theme faces (Terminal's mono,
     Barlow Condensed, the tracked-caps themes) pushed the natural width past
     the panel. Same treatment as .toolbar-row: never wrap — shrink the row's
     buttons in place instead. 11px matches .filter-help-row's buttons, and
     the reduced padding keeps the widest face under the panel width so the
     labels never actually clip (inset outline when shrink does kick in). */
  .quickfilters { display: flex; flex-wrap: nowrap; gap: 5px; }
  .quickfilters button {
    padding: 4px 7px;
    font-size: 11px;
    background: var(--bg-elevated);
    min-width: 0;
    overflow: hidden;
  }
  .quickfilters button:focus-visible { outline-offset: -2px; }
  .quickfilters button.active {
    background: var(--accent-manual-dim);
    border-color: var(--accent-manual);
    color: var(--accent-manual);
  }

  .exclude-badge{
    display:none; align-items:center; gap:8px;
    background: var(--accent-manual-dim);
    color: var(--accent-manual);
    border: 1px solid var(--accent-manual);
    border-radius: 6px;
    padding: 6px 9px;
    font-size: 11.5px;
    font-family: var(--mono);
  }
  .exclude-badge b { font-weight:600; }
  .exclude-badge button{
    all: unset; cursor: pointer; padding: 0 3px; color: var(--accent-manual);
    margin-left: auto;
  }
  .exclude-badge button:hover{ color: var(--accent-danger); }

  .stat-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
  .stat-card {
    background: var(--bg-elevated);
    border: 1px solid var(--border-soft);
    border-radius: var(--radius);
    padding: 10px 12px;
  }
  .stat-card .num { font-family: var(--mono); font-size: 20px; font-weight: 600; color: var(--text-primary); }
  .stat-card .lbl { font-size: 11px; color: var(--text-muted); margin-top: 2px; }

  .left-sort-row{ display: flex; align-items: center; gap: 6px; margin-bottom: 6px; }
  .left-sort-row .panel-title{ flex: 1; margin: 0; }
  /* .pdrop-btn's label is white-space:nowrap, so a flex item wrapping it can't
     shrink below the full label width by default (flex-shrink's min-width:auto
     floor) — at larger zoom/font sizes that overflows this row's fixed-width
     column and pushes #btnResetFamilyOrder off the edge. Let the dropdown
     shrink and truncate its own label instead; keep the icon buttons fixed
     size so they're never the ones that get squeezed. */
  .left-sort-row .pdrop{ min-width: 0; flex: 0 1 auto; }
  .left-sort-row .pdrop-btn{ overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
  .left-sort-row #leftSortDirBtn, .left-sort-row #btnResetFamilyOrder{ flex-shrink: 0; }
  .left-sort-row select{
    font-family: var(--sans); font-size: 11px; color: var(--text-primary); background: var(--bg-elevated);
    border: 1px solid var(--border-strong); border-radius: var(--radius); padding: 4px 6px; cursor: pointer;
  }
  #leftSortDirBtn{ font-size: 11px; padding: 4px 7px; }
  .freq-family-header{
    font-family: var(--mono); font-size: 10.5px; color: var(--accent-manual); padding: 6px 8px 2px;
    text-transform: uppercase; letter-spacing: 0.4px; cursor: grab;
    display: flex; align-items: center; gap: 4px;
  }
  .freq-family-header .family-drag-handle{ color: var(--text-faint); font-size: 11px; }
  .freq-family-header.family-dragging{ opacity: 0.4; }
  .freq-family-header.family-drop-target{ background: var(--accent-manual-dim); border-radius: 4px; }
  /* Empty-state sentence for the TAGS list (no flagged tags, no families).
     Its own plain block, not .freq-family-header: that class is a flex row
     styled as an uppercase mono heading, and once a sentence carried an
     inline icon, flex split it into text | icon | text columns. */
  .freq-empty{ padding: 6px 8px; font-size: 12px; line-height: 1.45; color: var(--text-muted); }

  #tagFrequencyList {
    display: flex; flex-direction: column; gap: 2px;
    max-height: 100%;
    overflow-y: scroll;
  }
  .freq-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 5px 8px;
    border-radius: 4px;
    font-family: var(--mono);
    font-size: 12px;
    color: var(--text-muted);
    cursor: pointer;
  }
  .freq-row:hover { background: var(--bg-elevated); color: var(--text-primary); }
  .freq-row .n { color: var(--text-faint); font-size: 11px; }

  /* Flagged-for-review rows (the hijacked TAGS list): tag name + a Reviewed
     button; a cleared tag stays listed, struck through, for the session. */
  .review-flag-row .review-flag-tag { color: var(--text-primary); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .review-flag-row.reviewed .review-flag-tag { text-decoration: line-through; color: var(--text-faint); }
  .review-flag-row .review-done-btn {
    flex-shrink: 0; font-size: 10.5px; padding: 3px 8px; border-radius: 4px; cursor: pointer;
    background: var(--accent-auto-dim); border: 1px solid var(--accent-auto); color: var(--accent-auto);
  }
  .review-flag-row .review-done-btn:hover:not(:disabled) { background: var(--accent-auto); color: var(--bg-base); }
  .review-flag-row .review-done-btn:disabled { opacity: 0.45; cursor: default; }

  /* Review mode hides the family/count sort controls — they don't apply to a
     flat flagged-tags list. */
  #tagFamilyListArea.review-mode .left-sort-row .pdrop,
  #tagFamilyListArea.review-mode .left-sort-row #leftSortDirBtn,
  #tagFamilyListArea.review-mode .left-sort-row #btnResetFamilyOrder { display: none; }

  /* ---------- Tag inspector (left panel) ---------- */

  .inspector-box{
    background: var(--bg-elevated);
    border: 1px solid var(--accent-manual);
    border-radius: 8px;
    padding: 12px;
    display: flex;
    flex-direction: column;
    gap: 10px;
  }
  .insp-name{ font-family: var(--mono); font-size: 13px; font-weight: 600; color: var(--accent-manual); word-break: break-all; }
  .insp-count{ font-size: 11.5px; color: var(--text-muted); margin-top: -6px; }
  .insp-actions{ display:flex; }
  .insp-actions button{ width:100%; font-size:12px; }
  .insp-label{ font-size: 11px; color: var(--text-faint); }
  .insp-result{ font-size: 12px; color: var(--text-muted); line-height: 1.5; min-height: 16px; }
  .insp-result b{ color: var(--text-primary); }
  .ghost-close{ align-self: flex-start; font-size: 11.5px; padding: 5px 10px; background:transparent; }

  /* Merge Immunize / Antivoid / Antimmunize toggle pairs (Master Tags) —
     smaller and less visually loud than the app's normal buttons, since six
     of these stacked (three do/undo pairs, each with its own icon) read as
     garish at full button size/weight. The "undo" side of each pair
     (.ghost-secondary) is deliberately de-emphasized further — it's the
     less-common action, shouldn't visually compete with its own "do" pair. */
  /* Always stacked (column), not a wrapping row — a width-dependent wrap
     looked inconsistent between pairs at any given zoom level (e.g. one
     pair happens to fit side-by-side while the other two don't, purely from
     incidental label-width differences) and would keep reshuffling itself
     as zoom changes. Stacking is zoom-invariant: the same layout at every
     zoom level, not just "narrow enough to force it." */
  .immunize-row{ display: flex; flex-direction: column; gap: 8px; }
  .immunize-row button{ font-size: 11.5px; padding: 5px 9px; }
  .immunize-row .ghost-secondary{ background: transparent; color: var(--text-muted); }
  .immunize-row .ghost-secondary:hover:not(:disabled){ color: var(--text-primary); background: var(--bg-elevated); }
  .shop-bottom-row{ display: flex; gap: 8px; }
  .shop-bottom-row .ghost-close{ align-self: auto; }

  /* ---------- Gallery ---------- */

  #gallery {
    padding: 0;
    background: var(--bg-base);
  }
  .gallery-content{
    padding: 18px;
    min-height: 100%;
  }

  #dropHint {
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-direction: column;
    gap: 14px;
    color: var(--text-faint);
    text-align: center;
    padding: 40px;
  }
  #dropHint .big { font-size: 16px; color: var(--text-muted); font-weight: 600; }
  #dropHint .small { font-size: 13px; max-width: 440px; line-height: 1.5; }

  #galleryToolbar{
    display: none;
    align-items: center;
    gap: 10px;
    flex-wrap: nowrap;
    /* Kept tight so the toolbar is a single row of controls on a normal
       window and its background doesn't eat gallery height. Icon-only
       buttons (Unlock all / Hide tags / Disabled / Originals) keep it narrow
       enough to stay one row at most widths. */
    margin: 0 0 8px 0;
    padding: 6px 14px;
    position: sticky;
    top: 0;
    z-index: 15;
    background: var(--bg-base);
    border-bottom: 1px solid var(--border-soft);
  }
  /* The toolbar's single control row. JS (updateGalleryToolbarScale) scales
     it down in place at higher zoom / a narrower gallery so the buttons stay
     on one row, like #topbar .actions. Wraps on narrow screens; the phone
     layout below also turns .viewToggle into a horizontal swipe strip. */
  .toolbar-row{
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    flex: 1 1 auto; min-width: 0; flex-wrap: wrap; transform-origin: left center;
  }
  .viewToggle{ display: flex; flex-wrap: wrap; gap: 5px; min-width: 0; }
  @media (min-width: 901px){
    /* Desktop: never wrap — updateGalleryToolbarScale shrinks the row to fit
       instead. The row's own overflow:clip + padding give hover-fill outlines
       room, same as #topbar .actions. */
    .toolbar-row{ flex-wrap: nowrap; overflow: hidden; padding: 3px 0; }
    .toolbar-row > *{ flex-shrink: 0; }
    .viewToggle{ flex-wrap: nowrap; }
  }
  .viewToggle button.active{
    background: var(--accent-manual-dim); border-color: var(--accent-manual); color: var(--accent-manual);
  }
  .viewToggle button.drag-over{
    background: var(--accent-danger); border-color: var(--accent-danger); color: #fff;
  }
  .gallery-sort{ display: flex; gap: 6px; align-items: center; }
  .gallery-sort select{
    font-family: var(--sans); font-size: 12.5px; color: var(--text-primary); background: var(--bg-elevated);
    border: 1px solid var(--border-strong); border-radius: var(--radius); padding: 6px 9px; cursor: pointer;
  }
  #gallerySortDirBtn{ font-size: 12px; padding: 6px 10px; }

  .pdrop{ display: inline-block; }
  .pdrop-btn{
    font-family: var(--sans); font-size: 12.5px; color: var(--text-primary); background: var(--bg-elevated);
    border: 1px solid var(--border-strong); border-radius: var(--radius); padding: 6px 9px; cursor: pointer;
    white-space: nowrap;
    /* Block, not the base button's inline-flex: under flex the label is an
       anonymous flex item that justify-content:center spills off BOTH
       edges (and text-overflow can't reach it), so a too-wide label in a
       wide theme face lost its first letters. */
    display: block; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
    /* <button>'s own UA default is text-align:center — harmless at full
       width, but once a narrow flex context (e.g. .left-sort-row) forces
       this to shrink+ellipsis (below), a centered label clips evenly from
       BOTH edges with no visible ellipsis instead of truncating from the
       right only. */
    text-align: left;
  }
  .pdrop-menu{
    /* Fixed + appended to document.body (buildPersistentDropdown(), shared-ui.ts)
       rather than absolute-inside-its-own-container — a container-relative
       popup gets silently clipped by any scrollable/overflow:hidden ancestor
       (e.g. the Settings panel) once it doesn't fit, with no boundary
       awareness at all. Matches .ctx-menu's own approach; positionMenu()
       clamps it on-screen either way. z-index 650 (matching .ctx-menu) is
       deliberately ABOVE .theme-panel's 455 AND .confirm-backdrop's 600 — a
       pdrop-menu very often opens from a button that lives INSIDE a floating
       panel (e.g. Settings' "Panel arrangement") or, on mobile, inside
       openDockListModal()'s reparented content (the sort dropdown for
       "Browse tags"/"Prune tags" — this was the actual bug: at the old 460 it
       rendered behind that modal's own 600 backdrop, completely unusable),
       so it must render in front of whichever one it's opened from. */
    position: fixed; z-index: 650;
    background: var(--bg-elevated-2); border: 1px solid var(--border-strong); border-radius: 8px;
    padding: 4px; display: flex; flex-direction: column; gap: 2px; min-width: 160px;
    max-width: min(320px, 90vw);
    /* Long lists (Settings ▸ App icon's theme list) scroll inside a short
       flyout instead of running off-screen. */
    max-height: min(300px, 60vh); overflow-y: auto; overscroll-behavior: contain;
    /* The vertical scrollbar eats into the content width; without this the
       items overflow by that much and a horizontal scrollbar appears. */
    overflow-x: hidden;
    box-shadow: 0 12px 30px rgba(0,0,0,0.5);
  }
  .pdrop-item{
    all: unset; flex-shrink: 0; cursor: pointer; padding: 7px 9px; border-radius: 5px; font-size: 12.5px; color: var(--text-primary);
  }
  .pdrop-item:hover, .pdrop-item:focus-visible{ background: var(--bg-elevated); }
  .pdrop-item.active{ color: var(--accent-manual); font-weight: 600; }
  /* A locked theme picked from the theme menu: the row refuses with a short
     head-shake (and a danger tint) while the toast says how to unlock it. */
  .pdrop-item.pdrop-item-denied{ animation: pdropDenied 0.42s cubic-bezier(.36,.07,.19,.97); color: var(--accent-danger); }
  @keyframes pdropDenied{
    10%, 90%{ transform: translateX(-1px); }
    20%, 80%{ transform: translateX(3px); }
    30%, 50%, 70%{ transform: translateX(-5px); }
    40%, 60%{ transform: translateX(5px); }
  }
  @media (prefers-reduced-motion: reduce){ .pdrop-item.pdrop-item-denied{ animation-duration: 0.42s; animation-name: pdropDeniedFade; } }
  @keyframes pdropDeniedFade{ from, to{ transform: none; } }

  .filter-help-row{ display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
  .filter-help-row span{ font-family: var(--mono); font-size: 10.5px; color: var(--text-faint); }
  .filter-help-row button{ font-size: 11px; padding: 5px 9px; }
  /* Lock + Clear filter wrap as ONE unit, so Clear filter never lands on a
     line of its own at high zoom. */
  .filter-lock-clear{ display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
  /* Compact ach-toggle-row, sized to sit in this one-line row. */
  .ach-toggle-row.filter-mode-lock{ gap: 6px; padding: 4px 8px; user-select: none; }
  .ach-toggle-row.filter-mode-lock input[type="checkbox"]{ width: 14px; height: 14px; }

  .filter-suggestions{
    margin-top: 4px;
    background: var(--bg-elevated-2);
    border: 1px solid var(--border-strong);
    border-radius: 8px;
    padding: 6px;
    max-height: 220px;
    overflow-y: auto;
  }
  .filter-suggestion-family{
    padding: 4px 8px 2px;
    font-family: var(--mono); font-size: 10px; color: var(--text-faint);
    text-transform: uppercase; letter-spacing: 0.03em;
  }

  #filterMatchCount{
    font-family: var(--mono); font-size: 12px; color: var(--text-muted);
    white-space: nowrap;
  }

  #singleNav{ display: none; align-items: center; gap: 10px; }
  /* Single view's compact layout (view.ts setSingleCompact): the toolbar folds
     up out of the way (a transition, so gpu-idle can't freeze it half-way),
     and .single-topbar carries Grid (+ Wiki) on the left, over the preview,
     and the moved #singleNav on the right, over the tag card. */
  #galleryToolbar{ max-height: 400px; transition: max-height var(--panel-dur) var(--pop-ease), padding var(--panel-dur) var(--pop-ease), opacity var(--panel-dur) var(--pop-ease), margin var(--panel-dur) var(--pop-ease); }
  #galleryToolbar.toolbar-collapsed{
    max-height: 0; padding-top: 0; padding-bottom: 0; margin-bottom: 0; opacity: 0;
    overflow: hidden; border-bottom-color: transparent; pointer-events: none;
  }
  /* Manual collapse (outside Single view): ▲ hangs just under the toolbar's
     bottom-right border, ▼ is a sticky tab at the gallery's top-right while
     it's collapsed — same 12px/6px offsets, so the arrow flips in place.
     `!important` on position: the premium themes' hover-fill rule forces
     `position: relative` on every button not in its exclusion list, which
     dropped ▼ into flow at the gallery's LEFT edge. */
  .toolbar-collapse-btn{
    position: absolute !important; right: 12px; top: 100%; margin-top: 6px;
    padding: 2px 7px; min-height: 0; line-height: 0;
    box-shadow: 0 4px 12px rgba(0,0,0,0.35);
  }
  #galleryToolbar.toolbar-collapsed .toolbar-collapse-btn{ display: none; }
  .toolbar-expand-row{ position: sticky; top: 0; z-index: 16; height: 0; }
  /* No dataset: the toolbar itself is display:none (index.ts), so a ▼ to bring
     it back has nothing to bring back. */
  #galleryToolbar[style*="display: none"] + .toolbar-expand-row{ display: none !important; }
  .toolbar-expand-btn{
    position: absolute !important; right: 12px; top: 6px;
    padding: 2px 7px; min-height: 0; line-height: 0;
    box-shadow: 0 4px 12px rgba(0,0,0,0.35);
  }
  .single-topbar{
    display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap;
    margin: 0 0 12px;
  }
  .single-topbar-left{ display: flex; gap: 6px; }
  .single-pos{ font-family: var(--mono); font-size: 12px; color: var(--text-muted); min-width: 0; text-align:center; display: inline-flex; align-items: center; justify-content: center; gap: 2px; }
  /* The image-number field in the Single mode toolbar — type a number, Enter to
     jump (view.ts renderSinglePos). */
  /* `input.` prefix is load-bearing: the global `input[type="text"]` rule
     (width: 100%, 8px 10px padding) outranks a bare class selector. */
  input.single-pos-input{
    font-family: var(--mono); font-size: 12px; color: var(--text-primary);
    background: var(--bg-elevated); border: 1px solid var(--border-strong); border-radius: 4px;
    padding: 2px 4px; width: 5ch; min-width: 0; box-sizing: content-box; text-align: center; cursor: text;
  }
  input.single-pos-input:focus{ border-color: var(--accent-manual); outline: none; }
  .single-pos-total{ font-family: var(--mono); font-size: 12px; color: var(--text-faint); }
  /* Sequential mode (view.ts): the Confirm button sticks to the panel's
     visible bottom edge so it never needs a scroll to reach; the panel's
     own scroller is .single-panel's overflow, so sticky grounds to that. */
  .seq-confirm{
    position: sticky; bottom: 0; z-index: 3;
    box-shadow: 0 -6px 14px rgba(0,0,0,0.25);
    background: var(--bg-panel);
  }
  /* And the rest of the fit-without-scrolling compaction: the sequential
     panel drops the normal panel's 85vh scroll cap (its content, compacted
     below, is designed to fit), tightens gaps/padding, and flattens the
     toggle pills to one-line height — every control + the Confirm button
     visible at once at normal window sizes (the image-side chips preview
     has its own modest cap in view.ts). */
  .single-panel.seq-panel{
    max-height: none; padding: 12px 14px; gap: 7px;
    overflow-y: auto;
    /* Fill the space beside the image instead of stopping at the base
       panel's 360px, which left a dead strip to the right. */
    width: auto; flex: 1 1 auto; min-width: 0; max-width: 980px;
  }
  /* Category sections flow as columns: column one fills with its own rows,
     then column two. column-width (not a fixed count) makes it one column
     whenever the panel is narrower than two 230px tracks, which happens at
     high font-size zoom or with a narrow window. A section never splits
     across columns. */
  .seq-sections{ columns: 230px 2; column-gap: 22px; }
  .seq-section{ break-inside: avoid; padding-bottom: 10px; }
  .seq-section > :first-child{ margin-top: 0 !important; }
  .single-panel.seq-panel .ach-toggle-row{
    padding: 4px 8px; gap: 7px;
  }
  .seq-panel .single-name{
    font-size: 13px;
  }
  /* Single view's right panel: the same Sequential form, live (view.ts
     applySingleRight). The panel's own width decides one or two columns. */
  #singleSeqPanel{ padding: 10px 12px 16px; }
  .single-seq-head{ display: flex; flex-direction: column; gap: 2px; margin-bottom: 6px; }
  .single-seq-note{ font-size: 12px; color: var(--text-faint); }
  #singleSeqPanel .ach-toggle-row{ padding: 4px 8px; gap: 7px; }
  /* Prev/Next inside the image card modal (view.ts's renderImageCardModal)
     — same shape as #singleNav above, reused here since mobile drops
     Single view (see the viewSingleBtn mobile rule) in favor of always
     navigating through this modal instead. */
  .modal-card-nav{ display: flex; align-items: center; gap: 10px; margin: 4px 0; }

  #compactGrid{
    display: none;
    grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
    gap: 6px;
  }
  .compact-card{
    position: relative; aspect-ratio: 1/1; background: var(--bg-base); border-radius: 5px; overflow: hidden;
    cursor: zoom-in; border: 1px solid var(--border-soft);
    /* Same offscreen-skip the .card grid gets (renderer rationale above). */
    content-visibility: auto;
    contain-intrinsic-size: auto 240px;
  }
  .compact-card img{ width: 100%; height: 100%; object-fit: cover; display: block; }
  .compact-card.dirty{ border-color: var(--accent-auto); }
  .compact-card.disabled-card{ opacity: 0.45; }
  .compact-card .flag-badge{ top: 4px; right: 4px; width: 9px; height: 9px; }
  .compact-hover-tags{
    position: absolute; left: 0; right: 0; bottom: 0; max-height: 100%;
    background: linear-gradient(transparent, rgba(0,0,0,0.88) 30%);
    color: #eee; font-size: 9.5px; line-height: 1.4; padding: 16px 5px 4px;
    opacity: 0; transition: opacity 0.12s ease; overflow: hidden; pointer-events: none;
  }
  .compact-card:hover .compact-hover-tags{ opacity: 1; }
  .compact-card.stickied{ outline: 2px solid var(--accent-manual); }

  .compact-compare-area{
    margin-top: 20px; border-top: 1px solid var(--border-soft); padding-top: 16px;
  }
  .compact-compare-header{
    display: flex; align-items: center; justify-content: space-between;
    font-size: 12px; color: var(--text-muted); margin-bottom: 10px;
  }
  .compact-compare-table{ display: flex; flex-direction: column; gap: 2px; overflow-x: auto; }
  .compare-row{ display: flex; gap: 2px; align-items: center; }
  .compare-cell{
    flex-shrink: 0; width: 130px; padding: 4px 6px; display: flex; align-items: center; justify-content: center;
    min-height: 34px; background: var(--bg-elevated); border-radius: 4px;
  }
  .compare-label-cell{ justify-content: flex-start; font-family: var(--mono); font-size: 11.5px; color: var(--text-muted); background: transparent; width: 160px; }
  .compare-corner{ width: 160px; background: transparent; }
  .compare-header-row{ margin-bottom: 6px; }
  .compare-img-cell{ flex-direction: column; gap: 4px; height: 90px; position: relative; background: var(--bg-base); }
  .compare-img-cell img{ max-width: 100%; max-height: 60px; object-fit: contain; border-radius: 4px; }
  .compare-img-cell button{ position: absolute; top: 2px; right: 2px; font-size: 10px; padding: 2px 5px; }
  .compare-cell button{ font-size: 11px; padding: 3px 8px; }

  /* Fixed 3 columns in the docked panel — the panel's own width varies with
     the resizable right panel (see #right's container-type), so auto-fill
     used to silently change the column count depending on how wide the
     panel happened to be dragged. A fixed count keeps thumbnails a
     predictable, comparable size regardless of panel width; anyone who
     wants them bigger has the pop-out button (below) for that instead of
     this view trying to be both. */
  .master-mini-grid{
    display: grid; grid-template-columns: repeat(3, 1fr);
    gap: 5px; max-height: 240px; overflow-y: scroll; padding: 4px;
    background: var(--bg-base); border-radius: 6px; border: 1px solid var(--border-soft);
    user-select: none; /* drag-to-select shouldn't also select stray text as the pointer crosses cells */
  }
  /* Popped out into openDockListModal()'s bigger box — same element,
     reparented, so this just needs its own column/height rules for that
     context. auto-fill with a min cell width instead of a fixed count: at
     the modal's own max width (640px, see .dock-list-modal-box) a 56px
     minimum lands at 10 columns, and it naturally shows fewer on a
     narrower window instead of an explicit count needing its own
     responsive breakpoints. */
  .master-mini-grid.dock-list-modal-content{
    grid-template-columns: repeat(auto-fill, minmax(calc(56px * var(--mini-grid-size, 1)), 1fr));
    max-height: 68vh;
  }
  /* Size row (1x-4x) — only meaningful once the grid has real room to grow
     into, so it stays hidden in the docked 3-column panel and only shows
     once popped into the modal. grid-column spans the full row so it reads
     as a toolbar strip above the thumbnails, not one more cell competing
     with them for a column. */
  .mini-grid-size-row{
    display: none; grid-column: 1 / -1;
    align-items: center; gap: 6px; padding: 2px 2px 8px;
    font-size: 11px; color: var(--text-muted);
  }
  .master-mini-grid.dock-list-modal-content .mini-grid-size-row{ display: flex; }
  .mini-grid-size-row button{
    padding: 3px 10px; font-size: 11px; min-height: 0;
  }
  .mini-grid-size-row button.active{
    background: var(--accent-manual); color: #fff; border-color: var(--accent-manual);
  }
  .master-mini-cell{
    position: relative; aspect-ratio: 1/1; border-radius: 4px; overflow: hidden;
    cursor: pointer; border: 2px solid transparent; background: var(--bg-base);
  }
  .master-mini-cell.selected{ border-color: var(--accent-manual); }
  .master-mini-cell img{ width: 100%; height: 100%; object-fit: cover; display: block; }
  .master-mini-cb{
    position: absolute; top: 3px; left: 3px; z-index: 2; width: 14px; height: 14px;
    accent-color: var(--accent-manual); cursor: pointer;
  }

  .multicompare-wrap{ padding: 4px; }
  .multicompare-header{
    display: flex; align-items: center; justify-content: space-between;
    font-size: 13px; color: var(--text-muted); margin-bottom: 14px;
  }
  .multicompare-table{ display: flex; flex-direction: column; gap: 1px; border: 1px solid var(--border-soft); border-radius: 8px; overflow: hidden; }
  .mc-row{ display: flex; }
  .mc-head-row{ background: var(--bg-elevated); font-weight: 600; font-size: 11.5px; color: var(--text-muted); }
  .mc-cell{ padding: 8px 10px; border-bottom: 1px solid var(--border-soft); }
  .mc-row:last-child .mc-cell{ border-bottom: none; }
  .mc-tagname{ width: 220px; flex-shrink: 0; background: var(--bg-panel); }
  .mc-tagname input{
    width: 100%; background: transparent; border: none; color: var(--text-primary);
    font-family: var(--mono); font-size: 12.5px; padding: 2px;
  }
  .mc-tagname input:focus{ outline: 1px solid var(--accent-manual); border-radius: 3px; }
  .mc-owners{ flex: 1; background: var(--bg-base); display: flex; flex-direction: column; gap: 3px; }
  .mc-owner-row{
    font-family: var(--mono); font-size: 11.5px; color: var(--text-primary); cursor: pointer;
    padding: 2px 6px; border-radius: 4px;
  }
  .mc-owner-row:hover{ background: var(--accent-manual-dim); color: var(--accent-manual); }
  .mc-owner-row.mc-owner-missing{ color: var(--text-faint); font-style: italic; }
  .mc-owner-row.mc-owner-missing:hover{ background: var(--accent-auto-dim); color: var(--accent-auto); }

  #galleryGrid {
    display: grid;
    /* --gallery-cols (set from Settings' "Gallery columns" picker) overrides
       this with a fixed repeat(N, 1fr) — auto-fill's column count otherwise
       drifts with whatever width happens to be left over after font-size
       zoom or open side panels eat into it. */
    grid-template-columns: var(--gallery-cols, repeat(auto-fill, minmax(230px, 1fr)));
    gap: 16px;
  }

  html.dynamic-cards #galleryGrid{
    display: block;
    /* Same override, CSS-columns flavor: a fixed --gallery-col-count pairs
       with --gallery-col-width forced down to ~0 (set together in JS) so the
       width constraint can't further reduce the column count underneath it. */
    column-width: var(--gallery-col-width, 240px);
    column-count: var(--gallery-col-count, auto);
    column-gap: 16px;
    /* Deliberately NOT column-fill:auto: with an auto-height container (this
       one grows with content, no fixed/viewport height to paginate against),
       "auto" has nothing to measure a column break against, so every card
       piles into a single column and the rest sit empty — the masonry
       collapses to one column instead of using the available width. The
       default "balance" mode is what actually works here: verified it
       distributes cards across all available columns while still sizing
       each card to its own natural content height (image + tags), not a
       forced uniform one. */
  }
  html.dynamic-cards #galleryGrid .card{
    display: block;
    width: 100%;
    break-inside: avoid;
    margin-bottom: 16px;
  }
  html.dynamic-cards .card .tagbox{
    flex: none; /* card is no longer a flex container in this mode, so the
      normal-mode "stretch to fill the row" flex:1 has to go explicitly —
      otherwise it's a stale, if inert, mismatch with the rest of this block */
  }
  html.dynamic-cards .card .thumbwrap{
    aspect-ratio: unset;
    height: auto;
    display: block;
  }
  html.dynamic-cards .card .thumbwrap img{
    position: static;
    width: 100%;
    height: auto;
    display: block;
    object-fit: unset;
  }

  .card {
    background: var(--bg-panel);
    /* Large-dataset support (view.ts makeChunkedList): `content-visibility:auto`
       skips layout+paint for cards scrolled far out of view; the placeholder
       intrinsic size keeps the scrollbar steady until real heights replace
       it on approach. */
    content-visibility: auto;
    contain-intrinsic-size: auto 320px;
    border: 1px solid var(--border-soft);
    border-radius: var(--r-card);
    box-shadow: var(--card-shadow);
    overflow: hidden;
    display: flex;
    flex-direction: column;
  }
  .card.dirty { border-color: var(--accent-auto); }
  .card.untagged { border-color: color-mix(in srgb, var(--accent-danger) 45%, var(--border-soft)); }
  .card.disabled-card { opacity: 0.55; }

  .card .thumbwrap {
    position: relative;
    width: 100%;
    aspect-ratio: 3 / 4;
    background: var(--mat), var(--bg-base);
    cursor: zoom-in;
  }
  /* Censored/Has-text/Perspective badges: their own strip above the image
     (view.ts buildCard appends them to .card before .thumbwrap), extending
     the card instead of covering the artwork. The phone layout tightens
     this further in the mobile block. */
  .card > .card-status-icons{
    position: static; flex: 0 0 auto; flex-direction: row; flex-wrap: wrap;
    gap: 4px; padding: 5px 6px;
  }
  .card > .card-status-icons .status-icon-badge{ font-size: 13px; padding: 3px 7px; }
  .card > .card-status-icons .status-icon-badge .ic{ width: 13px; height: 13px; }
  /* The Merge Immunize / Antivoid mark rides in this strip, to the right of the status pills. */
  .card > .card-status-icons .mv-badges{ position: static; align-items: center; }
  .card > .card-status-icons .mv-badge{ width: 22px; height: 22px; font-size: 13px; }
  .card img {
    width: 100%; height: 100%;
    /* `contain`, not `cover` — the card's box is a fixed 3:4 rectangle
       regardless of the source image's own aspect ratio, and `cover` crops
       whatever doesn't fit that shape (the reported "images are stuffed
       into the card" bug: a landscape or otherwise off-ratio image had
       real content sliced off, only visible after clicking through to the
       full modal). `contain` always shows the whole image, letterboxed
       into the thumbwrap's own background on whichever axis doesn't match. */
    object-fit: contain;
    display: block;
  }
  .card .filename {
    position: absolute;
    bottom: 0; left: 0; right: 0;
    background: linear-gradient(transparent, rgba(0,0,0,0.75));
    color: #d8d4e0;
    font-family: var(--mono);
    font-size: 10.5px;
    padding: 14px 8px 6px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .card .dirtydot {
    position: absolute; top: 8px; right: 8px;
    width: 8px; height: 8px; border-radius: 50%;
    background: var(--accent-auto);
    box-shadow: 0 0 0 2px rgba(0,0,0,0.4);
  }

  .card .tagbox {
    padding: 9px;
    display: flex;
    flex-direction: column;
    gap: 8px;
    flex: 1;
  }

  .chiprow { display: flex; flex-wrap: wrap; gap: 5px; }

  /* Tag Sorting (view.ts) — Single mode + card modal only. A small pill toggle
     above the chips, and the category segments it reveals. */
  .tagcat-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 8px; }
  .tagcat-toggle {
    font-size: 11px; padding: 4px 10px;
    border-radius: 999px; cursor: pointer;
    background: var(--bg-elevated); border: 1px solid var(--border-strong); color: var(--text-muted);
  }
  .tagcat-toggle:hover { border-color: var(--accent-manual); color: var(--text-primary); }
  .tagcat-toggle.active {
    background: var(--accent-manual-dim); border-color: var(--accent-manual); color: var(--accent-manual);
  }
  .tagcat-groups { display: flex; flex-direction: column; gap: 12px; width: 100%; }
  .tagcat-seg { display: flex; flex-direction: column; gap: 6px; }
  .tagcat-head {
    display: flex; align-items: baseline; gap: 8px;
    border-bottom: 1px solid var(--border-soft); padding-bottom: 3px;
  }
  .tagcat-name {
    font-family: var(--mono); font-size: 10.5px; text-transform: uppercase;
    letter-spacing: 0.5px; color: var(--accent-flair);
  }
  .tagcat-count { font-family: var(--mono); font-size: 10px; color: var(--text-faint); }
  /* Image Quicktagging (quick-tag.ts): in Single view it replaces #left's own
     tools. !important because some of #left's children carry inline display
     (Inline-Styles-Beat-Stylesheet). Slides in from the left on entry; the
     animation only replays when the panel goes from hidden to shown, not on
     every re-render. */
  #quickTagPanel{ display: none; }
  #left.quicktag-on > :not(#quickTagPanel){ display: none !important; }
  #left.quicktag-on > #quickTagPanel{
    display: flex; flex-direction: column; gap: 14px;
    animation: quicktag-in var(--panel-dur) var(--pop-ease) both;
  }
  @keyframes quicktag-in{ from{ opacity: 0; transform: translateX(-28px); } to{ opacity: 1; transform: none; } }
  /* gpu-idle pauses every animation; paused at its first frame this one-shot
     slide-in would leave the whole panel invisible, so it always runs. */
  html.gpu-idle #left.quicktag-on > #quickTagPanel{ animation-play-state: running !important; }
  .quicktag-note{ font-size: 11px; color: var(--text-faint); line-height: 1.4; margin-top: -8px; }
  .quicktag-group{ display: flex; flex-direction: column; gap: 6px; }
  .quicktag-group-head{
    font-family: var(--mono); font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.5px;
    color: var(--accent-flair); border-bottom: 1px solid var(--border-soft); padding-bottom: 3px;
  }
  .quicktag-options{ display: flex; flex-direction: column; gap: 4px; }
  /* Custom quicktags: "+" on every category header (and × on custom
     categories), ✎/× on custom items, "+ Add category" at the bottom. */
  .quicktag-group-head{ display: flex; align-items: center; justify-content: space-between; gap: 6px; }
  .quicktag-head-actions, .quicktag-item-actions{ display: flex; gap: 4px; flex-shrink: 0; }
  .quicktag-mini{ padding: 0 7px; min-height: 0; line-height: 18px; font-size: 12px; font-family: var(--mono); }
  .quicktag-mini.quicktag-del:hover{ border-color: var(--accent-danger); color: var(--accent-danger); }
  .quicktag-line{ display: flex; align-items: center; gap: 6px; }
  .quicktag-line > .quicktag-option{ flex: 1; min-width: 0; }
  .quicktag-item-actions{ opacity: 0.55; }
  .quicktag-line:hover .quicktag-item-actions, .quicktag-item-actions:focus-within{ opacity: 1; }
  .quicktag-add-category{ align-self: flex-start; }
  /* Drag reorder (quick-tag.ts): a ⋮⋮ handle on each category and quicktag,
     an accent line where the drop lands. Cursors stay grab/default (the
     user's Windows cursor scheme has them). */
  .quicktag-group-name{ flex: 1; min-width: 0; }
  .quicktag-drag{ cursor: grab; color: var(--text-faint); font-size: 11px; letter-spacing: -2px; padding: 0 4px 0 0; user-select: none; opacity: 0.6; }
  .quicktag-drag:hover{ opacity: 1; color: var(--text-primary); }
  .qt-dragging{ opacity: 0.45; }
  .quicktag-line, .quicktag-group{ position: relative; }
  .qt-drop-before::before, .qt-drop-after::after{
    content: ''; position: absolute; left: 0; right: 0; height: 2px; background: var(--accent-flair); border-radius: 1px; pointer-events: none;
  }
  .qt-drop-before::before{ top: -3px; }
  .qt-drop-after::after{ bottom: -3px; }
  .quicktag-options.qt-drop-into{ outline: 1px dashed var(--accent-flair); outline-offset: 2px; border-radius: 4px; }
  .quicktag-edit-box{ width: min(460px, 92vw); display: flex; flex-direction: column; gap: 10px; }
  .quicktag-edit-field{ display: flex; flex-direction: column; gap: 3px; }
  .quicktag-edit-label{ font-size: 12px; font-weight: 600; color: var(--text-primary); }
  .quicktag-edit-hint{ font-size: 10.5px; color: var(--text-faint); line-height: 1.35; }
  /* Per-category "+" (buildCategoryAddButton): sits at the header's right end
     and opens an add field under the header. Empty categories keep a dimmed
     header so they can be started too. */
  .tagcat-add {
    margin-left: auto; padding: 0 7px; min-height: 0; line-height: 18px; font-size: 13px;
    font-family: var(--mono); align-self: center;
  }
  .tagcat-seg-empty .tagcat-name { opacity: 0.55; }
  .tagcat-seg-empty .tagcat-count { opacity: 0.55; }
  .tagcat-add-input { width: 100%; }

  /* Character sections (Tag Sorting, opt-in): empty named sections under the
     Character category. Tags move in by dragging a chip or a whole category
     header onto one, or shift-clicking chips then "Move tags to:". */
  .tagsub-addsubject {
    align-self: flex-start; font-size: 11px; padding: 4px 10px; border-radius: 999px;
  }
  .tagsub-subject {
    display: flex; flex-direction: column; gap: 8px;
    border: 1px solid var(--border-soft); border-radius: 8px; padding: 8px 10px; background: var(--bg-base);
  }
  .tagsub-subject.drop-hover { border-color: var(--accent-manual); background: var(--accent-manual-dim); }
  .tagsub-subject-head { display: flex; align-items: center; gap: 8px; }
  .tagsub-name {
    font-family: var(--mono); font-size: 12px; font-weight: 600; color: var(--text-primary);
    background: var(--bg-elevated); border: 1px solid var(--border-strong); border-radius: 4px;
    padding: 3px 6px; min-width: 90px; max-width: 220px; cursor: text;
  }
  .tagsub-name:focus { border-color: var(--accent-manual); outline: none; }
  .tagsub-head-actions { margin-left: auto; display: flex; gap: 6px; }
  .tagsub-head-actions button { font-size: 11px; padding: 3px 8px; }
  .tagsub-del { color: var(--accent-danger); }
  .tagsub-sub { margin-left: 18px; display: flex; flex-direction: column; gap: 6px; }
  .tagsub-sub-head {
    display: flex; align-items: baseline; gap: 8px;
    border-bottom: 1px solid var(--border-soft); padding-bottom: 2px;
  }
  .tagsub-cat { font-family: var(--mono); font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--accent-flair); }
  .tagsub-count { font-family: var(--mono); font-size: 10px; color: var(--text-faint); }
  .tagsub-empty { font-size: 11px; color: var(--text-faint); font-style: italic; }
  .tagsub-chip { cursor: grab; }
  .tagsub-subject { margin-left: 18px; }
  .tagsub-draghead { cursor: grab; }
  .chip.tagsub-offer {
    border-style: dashed; opacity: 0.7; background: transparent; cursor: pointer;
    font: inherit; font-size: 12px; padding: 3px 9px;
  }
  .chip.tagsub-offer:hover { opacity: 1; border-color: var(--accent-manual); }
  .chip.tagsub-selected { outline: 2px solid var(--accent-manual); outline-offset: 1px; }
  .tagsub-movetoolbar {
    position: sticky; top: 0; z-index: 2; display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
    padding: 6px 8px; border: 1px solid var(--accent-manual); border-radius: 6px; background: var(--bg-elevated);
  }
  .tagsub-movecount { font-family: var(--mono); font-size: 11px; color: var(--accent-manual); }
  .tagsub-moveflyout { display: flex; gap: 6px; flex-wrap: wrap; }
  .tagsub-moveflyout button { font-size: 11px; padding: 3px 8px; }
  /* Read-only chip (no × button) — e.g. the WD14 transfer-list viewer.
     The base .chip's asymmetric padding (9px left / 4px right + 5px gap)
     budgets room for the remove button; without it the label sits visibly
     off-center, so re-balance and tighten line spacing. */
  .chip.chip-static{ padding: 3px 9px; gap: 0; line-height: 1.2; cursor: default; }
  .chip {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    background: var(--bg-elevated);
    border: 1px solid var(--border-soft);
    border-radius: var(--r-chip);
    padding: 2px 4px 2px 9px;
    font-family: var(--mono);
    font-size: 11px;
    color: var(--text-primary);
    cursor: default;
  }
  .chip.selected { border-color: var(--accent-manual); background: var(--accent-manual-dim); color: var(--accent-manual); }
  .chip.chip-match {
    border-color: var(--accent-manual); box-shadow: 0 0 8px var(--accent-manual);
    animation: chipGlow 1.4s ease-in-out infinite alternate;
  }
  .chip.chip-isolated {
    border-color: var(--accent-danger); box-shadow: 0 0 8px var(--accent-danger); color: var(--accent-danger);
  }
  .chip.chip-flagged-review {
    border-color: var(--accent-auto); background: var(--accent-auto-dim); color: var(--accent-auto);
    box-shadow: 0 0 6px var(--accent-auto);
  }
  @keyframes chipGlow{ from{ box-shadow: 0 0 3px var(--accent-manual); } to{ box-shadow: 0 0 10px var(--accent-manual); } }
  .chip button {
    all: unset;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 14px; height: 14px;
    border-radius: 50%;
    color: var(--text-faint);
    font-size: 12px;
    line-height: 1;
    cursor: pointer;
  }
  .chip button:hover { background: var(--accent-danger); color: #fff; }
  /* Past-tag preview (view.ts buildGhostChip): what a merge/void rule took
     off this image. Recedes behind the real tags — dashed, see-through,
     no fill — and comes forward on hover. `.chiprow` prefix outranks a
     theme's bare `.chip` rule so no theme repaints a ghost as a live tag. */
  .chiprow .chip.chip-ghost{
    background: transparent; border-style: dashed; border-color: var(--border-strong);
    color: var(--text-muted); opacity: 0.62; transition: opacity 0.12s ease;
  }
  .chiprow .chip.chip-ghost:hover{ opacity: 1; }
  .chip-ghost-void .chip-ghost-label{
    text-decoration: line-through; text-decoration-color: var(--accent-danger); text-decoration-thickness: 1.5px;
  }
  .chip-ghost .chip-ghost-ic{ width: 11px; height: 11px; margin-right: -1px; color: var(--accent-auto); flex: none; }

  .addtag-input {
    background: transparent;
    border: 1px dashed var(--border-strong);
    border-radius: 12px;
    padding: 8px;
    font-family: var(--mono);
    font-size: 11.5px;
    color: var(--text-muted);
  }
  .addtag-input:focus { border-style: solid; border-color: var(--accent-manual); }
  /* Mobile grid cards only (view.ts's buildCard()) — a plain label instead
     of a non-functional `<input>`, since tapping anywhere in the tagbox
     already opens the edit modal there. No border/dashed-box styling, so
     it doesn't read as a text field that isn't really one. */
  .addtag-ghost{
    padding: 8px; font-family: var(--mono); font-size: 11.5px;
    color: var(--text-faint); text-align: center;
  }

  .card-btn-row button, .single-btn-row button { font-size: 11.5px; padding: 6px; }
  .card-btn-row button { width: 100%; }

  /* ---------- Tag context menu ---------- */

  /* Menu/dropdown/flyout pop-in — one mechanism for every popup that funnels
     through positionMenu() (ctx-menu, pt-choice-menu, and every dm/master/
     view.ts context menu built on top of it), buildPersistentDropdown()'s
     pdrop-menu, and the File/Personalization header flyouts. transform-origin
     stays top-left since all of these are already positioned so their
     top-left corner sits at (or very near) the point the user clicked to
     open them — see shared-ui.ts's positionMenu()/buildPersistentDropdown()
     and index.ts's setupHeaderCategory(). Toggling `.menu-in` on/off (rather
     than swapping display) is what lets the transition actually run. */
  .ctx-menu, .pdrop-menu, .pt-choice-menu, .header-cat-flyout{
    transform-origin: top left;
    opacity: 0; transform: scale(0.94) translateY(-4px);
    transition: opacity var(--pop-dur) var(--pop-ease), transform var(--pop-dur) var(--pop-ease);
  }
  .ctx-menu.menu-in, .pdrop-menu.menu-in, .pt-choice-menu.menu-in, .header-cat-flyout.menu-in{
    opacity: 1; transform: none;
  }

  .ctx-menu{
    position: fixed;
    background: var(--bg-elevated-2);
    border: 1px solid var(--border-strong);
    border-radius: 8px;
    padding: 6px;
    min-width: 240px;
    max-height: min(440px, 65vh);
    overflow-y: scroll;
    box-shadow: 0 12px 32px rgba(0,0,0,0.5);
    z-index: 650; /* above .modal-backdrop (450) AND .confirm-backdrop (600) — a
      tag/image context menu opened while the image card modal (or, on mobile, a
      Browse/Prune-tags dock-list-modal) is up must render in front of its dim
      overlay, not behind it (it's a sibling of the backdrop under <body>, not a
      child). At the old 460 this rendered invisibly behind any confirm-backdrop-
      based modal — confirmed as the cause of "Browse tags" sort/context menus
      being unusable on mobile. */
    display: flex;
    flex-direction: column;
    gap: 2px;
  }
  .ctx-header{
    font-family: var(--mono); font-size: 11.5px; color: var(--text-muted);
    padding: 6px 8px 8px; border-bottom: 1px solid var(--border-soft); margin-bottom: 4px;
    word-break: break-all;
  }
  .ctx-item{
    all: unset; cursor: pointer; padding: 7px 8px; border-radius: 5px;
    font-size: 12.5px; color: var(--text-primary);
  }
  .ctx-item:hover, .ctx-item:focus-visible{ background: var(--bg-elevated); }
  /* Destructive ctx-item (e.g. "Delete permanently") — .ctx-item's own
     `all: unset` means a generic .danger-ghost class can't reliably layer
     color on top of it, so this is its own modifier instead. */
  .ctx-item.ctx-item-danger{ color: var(--accent-danger); }
  .ctx-item.ctx-item-danger:hover, .ctx-item.ctx-item-danger:focus-visible{ background: var(--bg-elevated); }
  .ctx-sep{ font-size: 10.5px; color: var(--text-faint); padding: 6px 8px 2px; }
  .ctx-words{ display: flex; flex-wrap: wrap; gap: 5px; padding: 2px 8px 6px; }
  .ctx-words button{ padding: 4px 9px; font-size: 11.5px; }
  /* Change tag category: the tag's current category. */
  .ctx-words button.active{ border-color: var(--accent-manual); color: var(--accent-manual); }
  .ctx-rename-row{ padding: 2px 8px 6px; }
  .ctx-rename-row input{ width: 100%; padding: 5px 7px; font-size: 12px; }
  .chip-rename-input{
    width: 100px; font-size: inherit; font-family: inherit; color: inherit;
    background: var(--bg-elevated); border: 1px solid var(--accent-manual);
    border-radius: 3px; padding: 0 3px;
  }

  /* ---------- Right panel: tag tools ---------- */

  .tool-section {
    background: var(--bg-elevated);
    border: 1px solid var(--border-soft);
    border-radius: var(--r-panel);
    padding: 14px;
    display: flex;
    flex-direction: column;
    gap: 10px;
  }
  /* A dock's buttons never outgrow the dock: in wide theme faces
     (Oxanium, Michroma, VT323…) a nowrap label at a narrow panel width ran
     past the dock's content box and got its right border clipped. Cap at
     the container and let the label wrap instead. */
  #left button, #right button{ max-width: 100%; }
  .tool-section button:not(.dock-collapse-btn):not(.chip button):not(.pdrop-btn){ white-space: normal; text-wrap: balance; }
  .tool-section .sec-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
  }
  .dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
  .dot.auto { background: var(--accent-auto); }
  .dot.manual { background: var(--accent-manual); }
  .sec-head .t {
    font-family: var(--head-font); font-size: var(--head-size); font-weight: var(--head-weight);
    letter-spacing: var(--head-track); text-transform: var(--head-case); color: var(--head-color);
  }
  .sec-head .d { font-size: 11px; color: var(--text-faint); }
  .sec-head .d ul { margin: 0; padding-left: 16px; display: flex; flex-direction: column; gap: 6px; }
  .sec-head .d li { line-height: 1.4; }
  .sec-head .d li b { color: var(--text-secondary); }

  .dock-controls{ display: flex; align-items: center; gap: 6px; margin-left: auto; flex-shrink: 0; }
  .dock-drag-handle{ cursor: grab; color: var(--text-faint); font-size: 12px; user-select: none; }
  .dock-drag-handle:active{ cursor: grabbing; }
  .dock-collapse-btn{
    all: unset; cursor: pointer; font-size: 10px; color: var(--text-faint); padding: 2px 4px;
  }
  .dock-collapse-btn:hover{ color: var(--text-primary); }
  .tool-section[data-dock-id]{ position: relative; transition: opacity 0.15s ease; }
  .tool-section[data-dock-id].dock-dragging{ opacity: 0.4; }
  .tool-section[data-dock-id].dock-drop-target{ outline: 2px dashed var(--accent-manual); outline-offset: 2px; }
  .dock-scroll-body{
    display: flex; flex-direction: column; gap: 10px;
  }
  .dock-resize-handle{
    flex-shrink: 0; flex-grow: 0;
    width: 100%; height: 10px; min-height: 10px; max-height: 10px;
    margin: 6px -14px -14px -14px; width: calc(100% + 28px);
    cursor: ns-resize;
    background: repeating-linear-gradient(90deg, var(--border-strong) 0 6px, transparent 6px 12px);
    opacity: 0.35; border-radius: 0 0 var(--r-panel) var(--r-panel);
  }
  .dock-resize-handle:hover{ opacity: 0.8; }

  .pruner-instance-list {
    max-height: 200px;
    overflow-y: scroll;
    display: flex;
    flex-direction: column;
    gap: 1px;
    background: var(--bg-base);
    border-radius: 6px;
    border: 1px solid var(--border-soft);
  }
  .pruner-instance{ display: flex; flex-direction: column; gap: 6px; margin-bottom: 12px; }
  .pruner-instance-head{ display: flex; gap: 6px; align-items: center; }
  .pruner-instance-head input{ flex: 1; }
  .pruner-remove-btn{ flex-shrink: 0; font-size: 11px; padding: 6px 8px; }
  /* Mirror-to-gallery-search checkbox — deliberately just an emoji, not a
     labeled toggle, to stay compact in an already-busy head row; the title
     tooltip carries the full explanation. Radio-button semantics (only one
     Tag Pruner instance can be checked at a time) are enforced in JS
     (tag-pruner.ts), not CSS. */
  .pruner-mirror-toggle{
    display: flex; align-items: center; gap: 3px; flex-shrink: 0;
    font-size: 13px; cursor: pointer; padding: 4px 6px; border-radius: 6px;
    border: 1px solid var(--border-soft); background: var(--bg-elevated);
  }
  .pruner-mirror-toggle input{ flex: none; margin: 0; accent-color: var(--accent-manual); }
  .pruner-clear-btn{ flex-shrink: 0; font-size: 11px; padding: 6px 8px; }
  .pruner-clear-btn:disabled{ opacity: 0.4; cursor: default; }
  /* One row per Tag Pruner instance with a non-empty selection, in the
     "Unify or void selected tags" dock (unifyVoidRows, tag-pruner.ts's
     renderUnifyVoidRows()) — visually separated since there can be several
     at once, each acting on a completely independent selection. */
  .unify-void-row{
    display: flex; flex-direction: column; gap: 6px; padding: 8px;
    border: 1px solid var(--border-soft); border-radius: 8px; margin-bottom: 8px;
    background: var(--bg-elevated);
  }
  .unify-void-row:last-child{ margin-bottom: 0; }

  .match-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 8px;
    font-family: var(--mono);
    font-size: 12px;
    cursor: pointer;
  }
  .match-row:hover { background: var(--bg-elevated-2); }
  /* A real highlight for a selected tag, not just its checkbox's own tick
     — see tag-pruner.ts's comment on why this needed to stop being solely
     the checkbox: on mobile, the other place selection used to be obvious
     (the mirrored gallery search box) lives in a separate bottom-panel
     sheet, not visible at the same time as Tag Pruner. */
  .match-row.selected{
    background: var(--accent-manual-dim); border-radius: var(--radius);
  }
  .match-row.selected .name{ color: var(--accent-manual); font-weight: 600; }
  .match-row input[type="checkbox"] { accent-color: var(--accent-manual); cursor: pointer; }
  .match-row .name { flex: 1; color: var(--text-primary); }

  .canonical-rule-row{
    display: flex; flex-direction: column; gap: 6px; padding: 8px; margin-bottom: 8px;
    background: var(--bg-elevated); border: 1px solid var(--border-soft); border-radius: 6px;
  }
  .canonical-rule-head{ display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px; }
  .canonical-rule-group{ margin-bottom: 10px; }
  .canonical-rule-group .settings-section-body{ padding: 10px 0 0; background: transparent; }
  .canonical-rule-group-header{
    font-family: var(--mono); font-size: 11px; font-weight: 600; color: var(--text-faint);
    text-transform: uppercase; letter-spacing: 0.03em;
    margin: 4px 0 8px; padding-top: 6px; border-top: 1px solid var(--border-soft);
  }
  .canonical-rule-label{ font-size: 12.5px; font-weight: 600; color: var(--accent-manual); }
  .canonical-rule-label.canonical-rule-void{ color: var(--accent-danger); }
  .canonical-rule-head button{ font-size: 11px; padding: 3px 7px; }
  .canonical-rule-name-input{ flex: 1; font-size: 12.5px; }
  .canonical-rule-add-row{ display: flex; gap: 6px; }
  .canonical-rule-add-row input{ flex: 1; font-size: 12px; }
  .canonical-rule-add-row button{ font-size: 11px; padding: 4px 8px; }
  /* Compact ach-toggle-row — sits in the rule's header row. */
  .ach-toggle-row.canonical-rule-enable-toggle{ gap: 6px; padding: 4px 8px; white-space: nowrap; }
  .ach-toggle-row.canonical-rule-enable-toggle input[type="checkbox"]{ width: 14px; height: 14px; }
  .canonical-rule-row.canonical-rule-disabled{ opacity: 0.55; }
  .canonical-rule-row.canonical-rule-disabled .canonical-rule-label{ text-decoration: line-through; }
  .canonical-chip-disabled{ opacity: 0.5; }
  .canonical-chip-disabled span{ text-decoration: line-through; }

  .qm-variants{ color: var(--text-muted); }
  select.qm-canon{
    color: var(--accent-manual); font-weight: 600; font-family: var(--sans); font-size: 12px;
    background: var(--bg-elevated); border: 1px solid var(--border-strong); border-radius: 5px;
    padding: 3px 6px; cursor: pointer;
  }
  .match-row .cnt { color: var(--text-faint); font-size: 10.5px; }

  /* ---------- Tag autocomplete ("+ add tag" suggestions) ---------- */

  .ac-panel{
    position: fixed;
    background: var(--bg-elevated-2);
    border: 1px solid var(--border-strong);
    border-radius: 8px;
    padding: 6px;
    max-height: min(320px, 60vh);
    overflow-y: auto;
    box-shadow: 0 12px 32px rgba(0,0,0,0.5);
    z-index: 650; /* same reasoning as .ctx-menu: must outrank .modal-backdrop (450) AND .confirm-backdrop (600) */
  }
  .ac-empty{ padding: 8px; font-size: 12px; color: var(--text-faint); }
  .ac-list{ display: flex; flex-direction: column; gap: 1px; }
  .ac-row{
    display: flex; align-items: center; gap: 8px; padding: 6px 8px;
    border-radius: 5px; font-size: 12px; cursor: pointer;
  }
  .ac-row:hover{ background: var(--bg-elevated); }
  .ac-row-name{ flex: 1; color: var(--text-primary); font-family: var(--mono); }
  .ac-row-count{ color: var(--text-faint); font-size: 10.5px; }
  .ac-flash-card{
    margin: 2px 0 4px; padding: 8px; opacity: 0; transform: translateY(-4px);
    background: var(--bg-base); border-radius: 5px; border: 1px solid var(--accent-manual);
    transition: opacity 0.15s ease, transform 0.15s ease;
  }
  .ac-flash-card.show{ opacity: 1; transform: translateY(0); }
  /* Floating hover variant (tags-autocomplete.ts's attachAcChipHover, used
     by the WD14 transfer-list modal): position:fixed by inline styles, so
     strip the in-list flow extras. z-index must clear the info modal's own
     #confirm-backdrop (600) or the card renders behind it. */
  .ac-flash-card.ac-flash-floating{ margin: 0; box-shadow: 0 6px 18px rgba(0,0,0,0.35); z-index: 650; }
  .ac-flash-body{ font-size: 12px; color: var(--text-muted); }
  .ac-flash-def{ line-height: 1.5; color: var(--text-primary); white-space: pre-wrap; }
  .ac-flash-empty{ color: var(--text-faint); margin-bottom: 6px; }
  .ac-flash-body textarea{ margin-bottom: 6px; }

  /* Master Tag Control button rows — one consistent 8px rhythm on both
     axes, in a plain base rule no theme flourish touches (themes restyle
     button fills/borders, never these row boxes). Replaces three inline
     flex rows in index.html that had horizontal gap but zero vertical
     separation, which read as cramped/merged on flat themes.
     flex-wrap:nowrap (not wrap) is load-bearing: a paired row (Select
     all visible/Clear selection, Disable/Delete, Lock/Unlock) must never
     stack, even in the narrow resized dock — wrapping is exactly what
     produced that. min-width:0 on the buttons is the other half of the
     fix: flex items default to min-width:auto, which refuses to shrink
     below the label's own intrinsic width and was what pushed a long
     label (e.g. the old "Delete selected permanently") past the dock's
     right edge instead of actually wrapping its own text. */
  .mtc-btn-row{ display:flex; gap:8px; flex-wrap:nowrap; margin:0 0 8px; }
  .mtc-btn-row button{ flex:1 1 0; min-width:0; }
  /* Long button labels ("Sequential from first/selected", "Select all
     visible", "Clear selection") have no short form that still reads as a
     sentence — unlike "Disable"/"Delete" etc., min-width:0 alone just lets
     the label clip or wrap against #right's own overflow-x:hidden once the
     panel's dragged narrower than the text needs. Below the threshold,
     swap to an icon-only span instead of letting that happen; aria-label
     on the button (set in JS) carries the real meaning in both states, so
     nothing is lost for a screen reader. */
  .label-icon{ display:none; font-size:1.05rem; }
  @container (max-width: 380px){
    .icon-fallback-btn .label-full{ display:none; }
    .icon-fallback-btn .label-icon{ display:inline; }
  }
  .immunize-row{ display:flex; gap:8px; flex-wrap:wrap; margin-bottom:8px; }
  .selection-summary {
    font-size: 11.5px;
    color: var(--text-muted);
    background: var(--bg-base);
    border-radius: 6px;
    padding: 8px 10px;
    line-height: 1.6;
    max-height: 70px;
    overflow-y: auto;
    margin-bottom: 8px;
  }
  .selection-summary .tk {
    display: inline-block;
    font-family: var(--mono);
    color: var(--accent-manual);
    margin-right: 6px;
  }

  .apply-row { display: flex; gap: 8px; }
  .apply-row input { flex: 1; }
  /* A text field + its button stack (field full width, button under it):
     side by side, the button squeezed the field to a few characters in the
     narrow Tag Overseer panel. Rows without a text field (select + icon
     button, the delete tool's actions) stay side by side. */
  .apply-row:has(> input) { flex-direction: column; }
  .apply-row:has(> input) > input { flex: none; width: 100%; }

  /* Tag Overseer "Delete tags": a ticked list of the dataset's tags. */
  .mass-delete-list {
    max-height: 220px;
    overflow-y: auto;
    margin: 6px 0;
    padding: 4px 6px;
    border: 1px solid var(--border-soft);
    border-radius: var(--r-field, 6px);
  }
  .mass-delete-list:empty::before { content: 'No tags in this dataset yet.'; color: var(--muted); font-size: 12px; }
  .mass-delete-row { display: flex; align-items: center; gap: 6px; padding: 2px 0; font-size: 12px; cursor: pointer; }
  .mass-delete-row .n { margin-left: auto; color: var(--muted); font-family: var(--mono); }
  .mass-delete-more { color: var(--muted); font-size: 11px; padding: 4px 0; }
  .mass-delete-actions { align-items: center; }
  .mass-delete-count { flex: 1; color: var(--muted); font-size: 12px; }

  /* Explicit nowrap + shared flex-shrink so these two stay side-by-side at
     any dock width/zoom level instead of dropping onto separate lines —
     they shrink together (min-width:0 lets `white-space:nowrap` button text
     actually compress) rather than one wrapping under the other. */
  /* Wrap rather than force an equal 1:1 flex split between these two —
     equal-split shrinks both buttons to the SAME width regardless of label
     length, which squeezed "Clear selection" (the longer label) down below
     its own readable width and clipped its text (some themes give buttons
     `overflow:hidden` for their hover-fill flourish, which then clips
     instead of just visually bleeding). Buttons keep their natural size and
     drop to a second line if the panel's too narrow for both. */
  .selection-actions-row{ display: flex; flex-wrap: wrap; gap: 8px; }

  /* Paired fields stack one per line (the button that runs them sits under
     the last one): two-up, each field showed only a few characters. */
  .gtt-row{ display: flex; flex-direction: column; gap: 8px; }
  .gtt-row input{ width: 100%; min-width: 0; }
  .gtt-divider{ height: 1px; background: var(--border-soft); margin: 2px 0; }
  .gtt-label{ font-size: 11px; color: var(--text-faint); }
  .gtt-result{ font-size: 11.5px; color: var(--text-muted); min-height: 14px; line-height: 1.4; }

  /* ---------- WD14 Autotagger (Tag Overseer) ---------- */
  .wd14-settings{
    border: 1px solid var(--border-soft); border-radius: var(--radius);
    padding: 8px 10px; display: flex; flex-direction: column; gap: 6px;
  }
  .wd14-settings summary{
    cursor: pointer; font-size: 11.5px; color: var(--text-muted); font-weight: 600;
    padding: 2px 0; user-select: none;
  }
  .wd14-settings > *:not(summary){ margin-top: 2px; }
  .wd14-settings select, .wd14-settings input[type="number"]{
    font-family: var(--sans); font-size: 12.5px; color: var(--text-primary); background: var(--bg-elevated);
    border: 1px solid var(--border-strong); border-radius: var(--radius); padding: 6px 9px; cursor: pointer;
  }
  .wd14-settings select{ width: 100%; }
  .wd14-settings input[type="number"]{ width: 100%; cursor: text; }
  .wd14-settings select:focus, .wd14-settings input[type="number"]:focus{ border-color: var(--accent-manual); outline: none; }
  .wd14-settings input[type="text"]{
    font-family: var(--sans); font-size: 12.5px; color: var(--text-primary); background: var(--bg-elevated);
    border: 1px solid var(--border-strong); border-radius: var(--radius); padding: 6px 9px; width: 100%;
  }
  .wd14-settings input[type="text"]:focus{ border-color: var(--accent-manual); outline: none; }
  /* Batch-tagging progress bar (wd14-tagger.ts's setStatus()) — local
     on-device inference is real per-image compute time, unlike the async
     ComfyUI round-trip, so a plain "Tagging 3/8" text counter wasn't a
     strong enough "this is actually moving" signal on its own. */
  .wd14-progress-track{
    height: 4px; border-radius: 2px; background: var(--bg-base); margin-top: 4px; overflow: hidden;
  }
  .wd14-progress-fill{
    height: 100%; width: 0; background: var(--accent-manual); transition: width 0.2s ease;
  }
  .wd14-local-model-list{ display: flex; flex-direction: column; gap: 4px; margin-top: 4px; }
  .wd14-local-model-row{
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
    font-size: 11.5px; color: var(--text-muted); background: var(--bg-elevated); border-radius: var(--radius);
    padding: 5px 8px;
  }
  .wd14-local-model-row button{ padding: 2px 7px; font-size: 11px; }
  /* Trainflow checkpoint rows: name grows, size + Send stay right. */
  #trainflowCheckpoints .wd14-local-model-row > span:first-child{ flex: 1; min-width: 0; overflow-wrap: anywhere; }
  .tf-send-box{ display: flex; flex-direction: column; gap: 6px; min-width: min(460px, 90vw); }
  .tf-send-label{ font-size: 11.5px; color: var(--text-muted); margin-top: 4px; }
  .tf-send-name{ display: flex; align-items: center; gap: 6px; }
  .tf-send-name input{ flex: 1; min-width: 0; margin: 0; }
  .tf-send-name span{ color: var(--text-muted); font-size: 12px; }
  .tf-send-path{ font-size: 12px; overflow-wrap: anywhere; background: var(--bg-elevated); border-radius: var(--radius); padding: 6px 8px; }
  .tf-send-dest{ display: flex; gap: 6px; flex-wrap: wrap; }
  .tf-send-error{ font-size: 12px; color: var(--danger, #e5484d); min-height: 1em; }
  .wd14-download-status{ font-size: 11.5px; color: var(--text-muted); margin-top: 4px; }
  /* WD14 review before apply (wd14-tagger.ts showWd14ReviewModal): one card per image, stacked, each
     with its own picture and chip area, like Single mode's cards but independent of the left panel. */
  .wd14-review-box{ width: min(1100px, 94vw); max-width: 94vw; display: flex; flex-direction: column; max-height: 92vh; }
  .wd14-rv-head{ display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 8px; }
  .wd14-rv-msg{ flex: 1 1 320px; margin: 0; }
  .wd14-rv-tools{ display: flex; gap: 6px; flex-wrap: wrap; }
  .wd14-rv-list{ display: flex; flex-direction: column; gap: 10px; overflow-y: auto; min-height: 0; flex: 1 1 auto; padding: 2px 4px 2px 0; margin-bottom: 12px; }
  .wd14-rv-card{
    display: grid; grid-template-columns: 200px minmax(0, 1fr); gap: 14px; padding: 10px;
    border: 1px solid var(--border-soft); border-radius: 8px; background: var(--bg-panel);
  }
  .wd14-rv-card.excluded{ opacity: 0.45; }
  .wd14-rv-pic{ display: flex; flex-direction: column; gap: 4px; min-width: 0; }
  .wd14-rv-pic img{ width: 100%; max-height: 260px; object-fit: contain; border-radius: 5px; background: var(--bg-base); }
  .wd14-rv-name{ font-size: 11px; color: var(--text-faint); font-family: var(--mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .wd14-rv-body{ display: flex; flex-direction: column; gap: 8px; min-width: 0; }
  .wd14-rv-cardbar{ display: flex; align-items: center; justify-content: space-between; gap: 8px; }
  .wd14-rv-use{ display: flex; align-items: center; gap: 4px; font-size: 12px; cursor: pointer; }
  .wd14-rv-count{ font-size: 11px; color: var(--text-faint); }
  .wd14-rv-chipfield{ display: flex; flex-direction: column; gap: 8px; }
  .wd14-rv-chips{ display: flex; flex-wrap: wrap; gap: 5px; }
  .wd14-rv-cat-head{ font-size: 10.5px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-faint); margin: 0 0 3px; }
  .wd14-rv-chip{
    display: inline-flex; align-items: center; gap: 4px; padding: 3px 4px 3px 8px; border-radius: 12px;
    background: var(--bg-elevated); border: 1px solid var(--border-strong); color: var(--text-primary);
    font-family: var(--mono); font-size: 11.5px;
  }
  .wd14-rv-chip.is-new{
    background: color-mix(in srgb, var(--accent-auto) 18%, var(--bg-elevated));
    border-color: color-mix(in srgb, var(--accent-auto) 55%, var(--border-strong));
  }
  .wd14-rv-chip-x{ all: unset; cursor: pointer; color: var(--text-faint); font-size: 14px; line-height: 1; padding: 0 3px; }
  .wd14-rv-chip-x:hover{ color: var(--accent-danger); }
  .wd14-rv-removed{ display: flex; flex-wrap: wrap; align-items: center; gap: 5px; }
  .wd14-rv-removed:empty{ display: none; }
  .wd14-rv-removed-label{ font-size: 11px; color: var(--text-faint); }
  .wd14-rv-restore{
    all: unset; cursor: pointer; font-family: var(--mono); font-size: 11px; padding: 2px 7px; border-radius: 10px;
    border: 1px dashed var(--border-strong); color: var(--text-muted); text-decoration: line-through;
  }
  .wd14-rv-restore:hover{ color: var(--text-primary); text-decoration: none; }
  .wd14-rv-add input{
    width: 100%; box-sizing: border-box; background: var(--bg-elevated); border: 1px solid var(--border-strong);
    border-radius: var(--radius); color: var(--text-primary); padding: 6px 8px; font-family: var(--sans); font-size: 12px;
  }
  @media (max-width: 700px){
    .wd14-rv-card{ grid-template-columns: 1fr; }
    .wd14-rv-pic img{ max-height: 200px; }
  }

  #toast {
    position: fixed;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    background: var(--bg-elevated-2);
    border: 1px solid var(--border-strong);
    color: var(--text-primary);
    padding: 10px 18px;
    border-radius: 8px;
    font-size: 13px;
    box-shadow: 0 8px 24px rgba(0,0,0,0.4);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease, transform 0.2s ease;
    z-index: 100;
  }
  #toast.show { opacity: 1; transform: translateX(-50%) translateY(-6px); }

  /* ---------- Single image view ---------- */

  #singleView{ display: none; }
  .single-wrap{ display: flex; gap: 24px; align-items: flex-start; }
  .single-img-side{
    flex: 1; min-width: 0; min-height: 60vh;
    display: flex; align-items: center; justify-content: center;
    background: var(--mat), var(--bg-base); border-radius: 8px; border: 1px solid var(--border-soft);
  }
  .single-img-side img{ max-width: 100%; max-height: 80vh; object-fit: contain; border-radius: 4px; }
  /* The image modal zooms/pans this img with a CSS transform over the
     patterned --mat. Painted in the same layer, Chromium's GPU raster left
     stale seams of the image's old edges on the mat after zooming out (they
     vanished only when the image was dragged back over them). Its own
     compositor layer means moving it never repaints the mat at all, and
     paint containment keeps any invalidation inside the frame. Promoted via
     backface-visibility rather than will-change:transform: will-change
     freezes the layer's raster scale, so a zoomed-in image would go soft. */
  .single-img-side{ contain: paint; }
  .single-img-side img{ backface-visibility: hidden; }
  /* Normal Single view's image: a small static preview (click opens the
     fullscreen lightbox). Same dark frame + centered fit as the old big side,
     just constrained so the tag panel gets the space instead. */
  .single-preview{
    flex: 0 0 38%; max-width: 38%; max-height: 42vh; position: relative;
    display: flex; align-items: center; justify-content: center;
    background: var(--mat), var(--bg-base); border-radius: 8px; border: 1px solid var(--border-soft);
    overflow: hidden; cursor: zoom-in;
  }
  .single-preview img{ max-width: 100%; max-height: 42vh; object-fit: contain; border-radius: 4px; pointer-events: none; }
  /* Single view's left column: the preview with the add-tag field under it.
     The column takes the preview's old 38% slot; the preview fills it. */
  .single-preview-col{
    flex: 0 0 38%; max-width: 38%; min-width: 0;
    display: flex; flex-direction: column; gap: 10px;
  }
  .single-preview-col > .single-preview{ flex: 0 0 auto; max-width: 100%; }
  .single-preview-hint{
    position: absolute; bottom: 8px; left: 50%; transform: translateX(-50%);
    font-size: 10.5px; padding: 2px 8px; border-radius: 999px; white-space: nowrap;
    background: rgba(0,0,0,0.55); color: #fff; pointer-events: none; opacity: 0.9;
  }
  /* The preview frees horizontal space; widen the panel so chips wrap less and
     the rest of the Single UI needs far less scrolling. */
  .single-panel-main{ width: auto; flex: 1; max-height: 82vh; }
  .single-panel{
    width: 360px; flex-shrink: 0; max-height: 85vh; overflow-y: scroll;
    background: var(--bg-panel); border: 1px solid var(--border-soft); border-radius: 8px;
    padding: 16px; display: flex; flex-direction: column; gap: 12px;
  }
  .single-name{ font-family: var(--mono); font-size: 12px; color: var(--text-muted); word-break: break-all; }
  /* Scoped to the expanded-card modal only (not Single view, which reuses
     this same class but has no close button to clear) — .modal-close-btn is
     position:absolute top-right over .single-panel, and a long filename's
     first line had nowhere reserved to avoid it, so it ran directly under
     "✕ Close" instead of wrapping clear of it. */
  .modal-card .single-name{ padding-right: 76px; }
  .single-disabled-badge{
    background: var(--accent-auto-dim); color: var(--accent-auto); border-radius: 4px;
    padding: 4px 8px; font-size: 11px; font-family: var(--mono); align-self: flex-start;
  }
  .single-btn-row{ display: flex; gap: 8px; }
  .single-empty{ color: var(--text-faint); padding: 80px 40px; text-align: center; }

  ::-webkit-scrollbar { width: 10px; height: 10px; }
  ::-webkit-scrollbar-track { background: var(--bg-base); }
  ::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: var(--r-scroll); border: 2px solid var(--bg-base); }
  ::-webkit-scrollbar-thumb:hover { background: var(--text-faint); }

  /* Browser-drawn controls that no theme block reaches otherwise: the Settings
     font-size slider rendered stock Chromium blue (the one control Chromium
     paints from its own UI palette, not from our vars), and text selection
     shipped default. accent-color follows the existing checkbox pattern. */
  input[type="range"]{ accent-color: var(--accent-manual); }
  ::selection{ background: var(--accent-manual); color: var(--bg-base); }

  @media (max-width: 1400px) {
    /* Narrower default left-column width at this breakpoint, but the right
       column still has to defer to --right-w (see #shell's own base rule) —
       a hardcoded 330px here was silently overriding the user's dragged
       width (and the drag handle itself) any time the window was under
       1400px wide, which is most normal window sizes. */
    #shell { grid-template-columns: 230px 1fr var(--right-w, 330px); }
  }

  /* ---------- Touch-device fallbacks (capability-based, not width-based) ----------
     `.touch-device` is set once at boot in index.ts via
     matchMedia('(hover:none) and (pointer:coarse)') — unlike everything
     below this point, these rules apply on ANY touch device regardless of
     window size, since they fix things that are wrong on touch specifically
     (no real hover state), not things that are wrong because of narrow
     width. */

  /* .compact-hover-tags is hover-only by default (see its base rule) — on
     touch there's no hover to reveal it at all, and a tap on the card is
     already spoken for (opens the image card modal, view.ts) so there's no
     free gesture to toggle it with either. Simplest touch-usable
     equivalent: always show it, rather than inventing a second gesture
     that would compete with tap-to-open. */
  html.touch-device .compact-hover-tags{ opacity: 1; pointer-events: none; }

  /* attachLongPress() (shared-ui.ts) uses passive touch listeners on
     purpose (so it never blocks scroll performance) — but a passive
     listener can't call preventDefault(), so it can't stop the BROWSER's
     own native long-press behavior (text selection, and on iOS the
     copy/look-up callout) from firing at the same time as our custom
     long-press menu. Confirmed on-device: long-pressing a tag chip opened
     the context menu AND selected its text underneath. CSS is what
     actually suppresses that native behavior, not the JS. Excludes real
     text inputs — those should still support normal text
     selection/editing. */
  html.touch-device *:not(input):not(textarea){
    -webkit-user-select: none; user-select: none; -webkit-touch-callout: none;
  }

  /* Grid cards' own inline tag chips/add-tag input are impractical to hit
     precisely on a card that's a fraction of the screen wide — tapping
     anywhere in the tag area redirects to the full edit modal instead
     (view.ts's buildCard(), touch-device-gated) rather than trying to
     support real inline editing there. Children get pointer-events:none so
     the tap always lands on .tagbox itself (the thing with the redirect
     listener) rather than being swallowed by a chip's own delete button or
     focusing the input — chips/input stay visually present as a preview,
     just inert to touch. */
  html.touch-device #galleryGrid .card .tagbox{ cursor: pointer; }
  html.touch-device #galleryGrid .card .tagbox *{ pointer-events: none; }

  /* ---------- Mobile / narrow-viewport layout ---------- */

  .drawer-toggle{ display: none; }
  .drawer-backdrop{
    display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.55); z-index: 199;
  }

  @media (max-width: 900px) {
    /* Safe-area insets (notch / gesture bar) — no-op on desktop, and a
       no-op on any device that doesn't report safe-area insets at all
       (the @supports guard just avoids a broken `max()` calc on engines
       that don't understand env() rather than actually needing it here,
       since an unsupported env() argument falls back to 0 anyway — belt
       and suspenders). */
    @supports (padding: max(0px)) {
      #topbar{ padding-top: max(8px, env(safe-area-inset-top)); }
      #tabBar{ padding-bottom: max(0px, env(safe-area-inset-bottom)); }
    }
    /* Desktop's 20px side padding reads as a bigger dead margin on a
       narrow phone screen, particularly next to Help now that it sits
       flush against the actions row (fix above) — tightened for mobile. */
    #topbar{ padding-left: 10px; padding-right: 10px; }

    /* Topbar: horizontal scroll instead of updateTopbarScale()'s shrink
       (that function early-returns at this width — see index.ts). Plain
       overflow, no JS positioning math, and avoids the `transform`
       containing-block trap that broke the File/Personalization dropdown
       flyouts earlier in the mobile port. */
    /* `#topbar .actions` (not just `#topbarActions`) to match the base
       rule's own specificity (id+class, 1-1-0) — a bare `#topbarActions`
       selector (1-0-0) LOST to that base rule's `overflow:hidden` despite
       coming later in the file, since specificity beats source order;
       confirmed on-device (the row never actually scrolled). */
    /* flex-grow:0 (NOT 1) is the actual fix here, reverting an earlier
       attempt that made things worse: giving this row flex-grow made it
       fill the ENTIRE remaining topbar width, but its own buttons still
       pack to its left edge (no justify-content), so the leftover space
       just moved INSIDE .actions — an invisible dead zone sitting right
       before Help, indistinguishable from real space after it. Content-
       sized (shrink-to-fit via width:auto + overflow-x:auto, capped by
       min-width:0 so it can still shrink below content) packs File/Log/
       drawer-toggles flush against Help with no gap in between; any
       genuinely leftover width now shows up only after Help, as normal
       trailing topbar padding. */
    #topbar .actions{ overflow-x: auto; touch-action: pan-x; flex: 0 1 auto; min-width: 0; }
    /* Scrollbar exists only to hint the row scrolls, not to be dragged —
       thin and non-interactive so it can't be mistaken for a real control. */
    /* pointer-events:none on the scrollbar THUMB alone (tried previously)
       still left the TRACK clickable (jump-scroll on tap) — reported still
       tappable. none on the whole ::-webkit-scrollbar element disables all
       interaction with it while leaving normal touch-drag scrolling of the
       row's own content untouched (that's independent of the scrollbar UI
       on touch). Also brightened — the previous border-strong-based color
       read as barely visible against this dark background. */
    /* 2026-09-27: neither of those worked — pointer-events has no effect on
       scrollbar pseudo-elements, and with scrollbar-width/-color set current
       Chromium ignores ::-webkit-scrollbar styling altogether, so the native
       bar stayed a real control (tap its track, the row jump-scrolls). On
       touch, attachScrollHint() (shared-ui.ts, wired in index.ts) hides the
       native bar (.scroll-hinted) and draws .scroll-hint instead: a thin
       thumb with pointer-events:none that only shows position. */
    /* File stays first (expected/familiar), then the three Dock Panel
       switcher buttons right after it — without this they're real but
       effectively undiscoverable, since .brand-block + #btnHelp alone
       leave .actions barely any visible width before it has to scroll, and
       a bare overflow-x:auto region with no visible scrollbar in this
       WebView gives no hint there's more to swipe to. Everything else
       (Settings/Log/Personalization/etc.) keeps its natural DOM order,
       reachable by scrolling. */
    #fileCatWrap{ order: -2; }
    /* Same order value as File — flex resolves the tie by DOM order (File
       comes first in the markup), so this lands right after File and
       before the drawer toggles below, without needing its own order
       tier. */
    #btnLog{ order: -2; }
    .drawer-toggle{ order: -1; }
    /* .actions itself only gets whatever width .brand-block/#btnHelp don't
       claim first — shrinking both here is what actually gives it enough
       room to show File + the switcher buttons without immediately
       scrolling, not the `order` rules above on their own. Dropped
       entirely (not just shrunk) per direct feedback — on a phone the app
       identity/version isn't worth the width it costs .actions. */
    .brand-block{ display: none; }
    /* Matches the plain `button` base padding exactly (7px 13px) instead of
       its own slightly-taller 9px 10px — that mismatch was part of why
       Help read as visually inconsistent with its row neighbors. */
    .topbar-help-btn{ padding: 7px 13px; }
    .topbar-help-btn .help-btn-label{ display: none; }

    /* Tab bar had no overflow handling at all before this pass. */
    #tabBar{ overflow-x: auto; flex-wrap: nowrap; touch-action: pan-x; }

    #shell{
      display: block;
      grid-template-columns: none;
    }
    #shell.layout-gallery-left, #shell.layout-gallery-right{
      grid-template-columns: none;
    }

    /* #left/#right become one-at-a-time BOTTOM sheets (not side drawers) —
       "Dock Panels": Left / Tools / Overseer, switched via
       btnLeftDrawerToggle/btnRightDrawerToggle/btnOverseerDrawerToggle in
       the topbar (index.ts). "Overseer" isn't a third container of its own
       — it's #right with switchTab('master') active instead of 'gallery',
       reusing that existing logic rather than new state (see index.ts's
       drawer-toggle section). */
    /* height (not just max-height) + overflow-y:hidden: the base `aside,
       main{ overflow-y: scroll }` rule elsewhere in this file otherwise
       still applies here, and with no fixed height an aside just grows to
       fit its content — so instead of the intended horizontal dock scroll,
       the WHOLE sheet became one tall vertically-scrolling column (its
       content was never actually clipped to 48vh to begin with, confirmed
       on-device). A real fixed height plus hidden overflow here is what
       makes #normalRightTools/#left/#masterTagPanel's own horizontal
       scroll (below) the only scrolling that happens at this level. */
    #left, #right{
      position: fixed; left: 0; right: 0; bottom: 0; top: auto;
      width: 100%; height: 48vh; overflow-y: hidden;
      z-index: 200; transition: transform 0.22s ease; box-shadow: 0 -8px 30px rgba(0,0,0,0.5);
      order: initial;
    }
    #rightPanelContent{ height: 100%; }
    /* The desktop collapse state (dts-right-panel-collapsed, also forced on
       by Sequential Tagging) has no meaning for a sheet, and its header
       arrow is hidden here — without this the sheet opened empty with no
       way to un-collapse it. */
    #right.right-panel-collapsed #rightPanelContent{ display: block; }
    #right.right-panel-collapsed{ padding-left: 16px; padding-right: 16px; }
    #left{ transform: translateY(105%); }
    #right{ transform: translateY(105%); }
    #left.drawer-open{ transform: translateY(0); }
    #right.drawer-open{ transform: translateY(0); }
    /* #shell stays `display:block` here (see #left/#right above, which are
       taken out of flow entirely once `position:fixed`) but it's still a
       flex item of #app, so it DOES get a real resolved height from that.
       #gallery itself switches to flex-column so #galleryToolbar keeps its
       own natural height and the active .gallery-content gets the rest via
       `flex:1` — needed so #galleryGrid's `height:100%` (single-row layout,
       below) has something definite to resolve against; without it the
       single-row card rendered at its intrinsic image height instead,
       taller than the viewport, since a plain block child doesn't stretch
       to fill its parent's height on its own the way a flex child does. */
    #shell{ height: 100%; }
    #gallery{
      order: initial; width: 100%; height: 100%; overflow-y: hidden;
      display: flex; flex-direction: column;
    }
    #gallery > .gallery-content{ flex: 1; min-height: 0; }

    .drawer-backdrop.drawer-visible{ display: block; }
    .drawer-toggle{ display: inline-flex; }
    #rightPanelHeaderRow{ display: none; }
    /* #left/#right become fixed-position sheets here, each with its own
       `transform` — a `transform` on an ancestor creates a new containing
       block for `position: fixed` descendants, so the handle's
       viewport-relative top/left math (repositionRightResizeHandle(), in
       index.ts) no longer means what it thinks it means once #right has
       one. Resizing a fixed-size sheet isn't a meaningful action anyway —
       hide it here, same as the collapse arrow just above. */
    #rightPanelResizeHandle{ display: none; }

    /* No vertical scrolling inside the sheet, per the user's explicit
       direction — #left/#normalRightTools/#masterTagPanel each become a
       horizontally-scrolling row of side-by-side sections instead of a
       vertical stack, so switching between sections is a horizontal swipe,
       not a vertical scroll. Each section keeps its OWN internal vertical
       scroll if its content is tall (a long tag list, say) — that's normal
       for "a list beside its controls," not what this avoids. */
    /* No !important on display here (deliberately) — switchTab() (index.ts)
       now toggles a `.rt-hidden` CLASS rather than an inline style
       specifically so this rule can win through normal cascade (same
       #-specificity as the base `#normalRightTools,#masterTagPanel{
       display:block}` rule near the top of this file, later in source
       order → wins when neither is hidden). `.rt-hidden{display:none
       !important}` still always wins over this regardless — !important
       beats a non-important rule outright, specificity never enters into
       it. An earlier version used `!important` on this line too, which
       "worked" for showing the active panel but also made the HIDDEN one
       `display:flex` instead of `none` (an !important ID-selector rule
       beats an !important class-selector rule on specificity) — its
       content leaked in underneath the visible panel, confirmed on-device. */
    /* height:100% deliberately left OFF this shared rule and applied only
       to #normalRightTools/#masterTagPanel below — #left is `position:
       fixed` with its own explicit `height:48vh` from the #left,#right
       rule above; adding `height:100%` here too would (later in the
       cascade) resolve against the viewport instead for a fixed element,
       silently overriding that 48vh back to full-screen. #normalRightTools/
       #masterTagPanel aren't fixed themselves (only their #right ancestor
       is), so `height:100%` on them correctly means "100% of #right's
       content box," via #rightPanelContent{height:100%} above. */
    #normalRightTools, #masterTagPanel{
      display: flex; flex-direction: row; flex-wrap: nowrap;
      overflow-x: auto; overflow-y: hidden;
      gap: 10px; align-items: stretch;
      /* Without an explicit hint, a touch drag starting inside one of these
         rows' own vertically-scrolling children (immediately below) got
         captured as a VERTICAL scroll even for an intentionally horizontal
         swipe — confirmed on-device, not just a synthetic-input quirk.
         `pan-x` tells the browser this row only ever handles the
         horizontal axis itself, so a horizontal swipe is unambiguously
         its gesture to claim rather than something to hand to whichever
         nested vertical-scroll child happens to be under the finger. */
      touch-action: pan-x;
    }
    /* #left reverted back to a plain vertical stack — direct feedback:
       unlike Tools'/Overseer's docks (independent jobs that make sense to
       swipe between one at a time), Filter/Dataset-stats/Tags here are
       always relevant together, and horizontal swiping between them read
       as a bug ("still has a horizontal scrollable"), not a feature. */
    #left{
      display: flex; flex-direction: column; flex-wrap: nowrap;
      overflow-x: hidden; overflow-y: auto;
      gap: 10px;
    }
    /* The height:100% actually promised by the comment above — without it,
       #normalRightTools/#masterTagPanel had no definite height, so
       `align-items:stretch` just matched every dock to whichever was
       tallest AT THAT MOMENT — meaning collapsing Tag Pruner's content
       (usually the tallest, long description text) shrunk the row's own
       computed height, and every OTHER dock visibly shrank down along
       with it instead of staying full-height while only Tag Pruner
       narrowed. Confirmed on-device. */
    #normalRightTools, #masterTagPanel{ height: 100%; }
    /* No explicit touch-action here (deliberately) — this is the exact
       same "vertical-scroll child nested inside a horizontal-scroll row"
       shape that caused genuinely janky scrolling for #normalRightTools's
       docks earlier (see that section's own explanation), just not fixed
       here yet at the time: `touch-action:pan-y` on these while their
       parent (#left/#masterTagPanel) is `pan-x` gave conflicting signals
       to the gesture recognizer — confirmed on-device, Left/Overseer's
       horizontal dock-switching was noticeably less fluid than Tools'
       (which had already lost this same conflict when its lists moved to
       modals). `overflow-y:auto` stays as a safety net for genuinely tall
       content; not declaring touch-action lets the browser fall back to
       its own default per-gesture disambiguation instead of a hardcoded
       axis that fights the parent. */
    #masterTagPanel .tool-section{
      flex: 0 0 auto; width: 78vw; max-width: 320px;
      overflow-y: auto; overflow-x: hidden;
    }
    /* #left's sections stack full-width now (see #left above) — no fixed
       swipe-panel width needed. */
    #left .field-block{ flex: 0 0 auto; width: 100%; overflow-x: hidden; }
    /* index.ts moves #tagFamilyGroup's actual content (sort row, Browse
       tags button, the list) into the Tag Pruner dock on mobile — this
       wrapper is left behind empty, would otherwise just be a dead gap. */
    #tagFamilyGroup{ display: none; }

    /* The three REAL docks in #normalRightTools (Tag Pruner/Unify-Void/
       Retroactive Merge-Void — the ones with drag/collapse/resize controls,
       via docks.ts's createDockManager) get the fuller "horizontal dock"
       treatment: header/controls run down one edge instead of across the
       top, with the list beside them instead of below. createDockManager
       is instantiated with `horizontal:true` for this container (index.ts),
       which makes its collapse animation shrink width instead of height —
       this CSS is the resting-state layout that animates between. */
    /* Widened from an earlier 280px — that was sized back when the whole
       body was one column, then briefly widened to 340px/92vw to fit a
       controls+list split side by side — that split is GONE now (see
       .dock-list-area below: lists open in a full modal instead of
       competing for space in the dock itself), so this only has to fit a
       column of buttons again. Narrower than the original 280px too,
       since a pure button column needs less than a column that also had
       to leave room for a list peeking in from the side. */
    #normalRightTools .tool-section[data-dock-id]{
      flex: 0 0 auto; flex-direction: row; width: 230px; max-width: 72vw;
      overflow: hidden;
    }
    #normalRightTools .tool-section[data-dock-id] > .sec-head{
      flex-direction: column; flex-wrap: nowrap; flex-shrink: 0; width: 80px;
      align-items: flex-start; gap: 6px;
      border-right: 1px solid var(--border-soft); padding-right: 8px; margin-right: 2px;
    }
    /* .dock-controls (header's drag-handle + collapse-button span, set up
       in docks.ts — NOT the same thing as .dock-controls-area below,
       naming collision avoided on purpose) just needs to lay out
       horizontally in the narrow vertical header strip instead of
       vertically. */
    .tool-section[data-dock-id] > .sec-head .dock-controls{
      margin-left: 0; flex-direction: row;
    }
    /* Dock body is a plain vertical stack of controls again — the
       controls/list SIDE-BY-SIDE split (and its `touch-action:pan-x` on
       this element) is what was causing genuinely janky, inconsistent
       scrolling on-device: a horizontal-pan row directly containing a
       vertical-pan list, nested inside an ALREADY horizontal-pan dock
       row one level up, gave the browser's touch gesture recognizer
       conflicting signals for a lot of real touch angles. Removed
       entirely rather than tuned further — see .dock-list-area below for
       where lists went instead (a modal has none of this nesting). */
    .dock-scroll-body{
      display: flex; flex-direction: column; gap: 8px;
      max-width: none !important; max-height: none !important;
    }
    .dock-controls-area{
      display: flex; flex-direction: column; gap: 8px; width: 100%;
      /* Safety net only, not the primary design — moving each dock's list
         into a modal (above) should mean the remaining pure-button content
         fits within the sheet's height on its own. But #right itself is
         `overflow-y:hidden` (deliberately, so it isn't ALSO independently
         vertically scrollable on top of the horizontal dock-switching
         scroll), so if a dock's buttons ever did exceed that height with
         no scroll of their own, the excess would be silently clipped and
         unreachable — strictly worse than a same-dock scroll would be. */
      max-height: 100%; overflow-y: auto;
    }
    /* Buttons don't wrap text by default — a long label (e.g. "+ Add
       another Tag Pruner") otherwise forces its own box wider than the
       dock. */
    .dock-controls-area button{ white-space: normal; width: 100%; }
    /* A dock's list/grid (Tag Pruner's matches, Master Tag Control's
       thumbnail grid, etc.) no longer renders inline in the dock at all —
       direct feedback: squeezed beside a controls column, it either had no
       usable width or forced the whole dock too wide, AND it was the
       source of the touch-action conflict above. It's still the exact
       same real element (openDockListModal(), shared-ui.ts, re-parents it
       rather than cloning — every other module's own reference to it by
       id keeps working), just hidden here and shown full-size in a modal
       instead, opened via a button in .dock-controls-area
       ("Browse tags"/"Select images"/etc.). */
    .dock-list-area{ display: none; }
    .dock-list-modal-content{ display: block !important; }
    /* .master-mini-grid is itself a `.dock-list-area` (masterMiniGrid) with
       its own display:grid — the blanket !important above was winning over
       it too (same class, !important beats source order regardless), which
       silently flattened the image-select grid into a single-column
       vertical list of full-width images inside the modal. Beat it back
       with a MORE specific selector at the same !important weight — the
       CSS-specificity-trap pattern this file keeps hitting, see
       notes/Mobile-Port.md. */
    .master-mini-grid.dock-list-modal-content{ display: grid !important; }
    .dock-list-open-btn{ display: block; }
    /* Drag-to-reorder isn't implemented for touch (still HTML5 DnD,
       mouse-only — see notes/Mobile-Port.md's deferred-items list), and
       reordering itself is largely redundant on mobile anyway (docks are
       swiped through one at a time, not compared side by side the way
       desktop's stacked sidebar invites). Hiding the handle rather than
       leaving a dead/non-functional control visible. */
    .dock-drag-handle{ display: none; }
    /* Every OTHER info button inside a dock — a specific checkbox's own
       ⓘ, say — is redundant with the dock's single consolidated header ⓘ
       now (which folds every setting's explanation into one place) and
       was also rendering visibly stretched/misshapen squeezed into the
       narrow controls column. Only `.dock-info-btn` (the header one)
       stays. */
    .tool-section .info-btn:not(.dock-info-btn){ display: none; }
    /* `.dock-controls` (drag-handle+collapse span, docks.ts) is always
       APPENDED last within `.sec-head`, so a `.dock-info-btn` placed
       earlier in the static HTML would render above it by DOM order alone
       — `order` overrides that so the info button reliably lands below
       the collapse arrow regardless of where it sits in markup. */
    .tool-section[data-dock-id] > .sec-head .dock-controls{ order: 1; }
    .dock-info-btn{ display: inline-flex; margin-left: 4px; order: 2; }
    /* Resize handle: width instead of height, right edge instead of bottom
       — matches the `horizontal:true` resize-by-clientX behavior in
       docks.ts (also rewritten to pointer events there, so this is
       touch-draggable, unlike the desktop vertical handle). */
    /* Hidden rather than reoriented for horizontal dragging: this handle is
       a direct child of `.tool-section` sitting alongside `.sec-head` and
       `.dock-scroll-body` in the row layout above, and its desktop-sized
       negative margins (meant to pull a BOTTOM edge bar flush against a
       vertical dock's corners) rendered as a stray blip near the dock's
       top-right corner once the layout went horizontal, confirmed
       on-device. Width is already capped (max-width:72vw) and collapse
       handles sizing, so this isn't worth fixing up for touch dragging —
       simpler to just drop it here. */
    .dock-resize-handle{ display: none; }

    /* Mobile only ever has one Tag Pruner instance — "Save as task" in the
       Browse-tags modal (tag-pruner.ts) replaces the desktop pattern of
       several independent dock instances for several prune jobs at once,
       and folds Unify/Void into that same modal, so the separate Unify/
       Void dock is redundant here. */
    #btnAddTagPruner{ display: none; }
    .tool-section[data-dock-id="unifyVoid"]{ display: none; }
    .pruner-mobile-tasks{ display: flex; flex-direction: column; gap: 10px; margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border-soft); }

    /* Masonry mode is disabled outright on touch devices (index.ts) — hide
       its now-inert Settings toggle (and its description) rather than
       leave a dead control. */
    #dynamicCardsRow, #dynamicCardsNote{ display: none; }

    /* Tag suggestion popup ("+ Add tag") — 320px/60vh was still large
       enough to swallow most of the screen above the keyboard on a phone,
       reported as "intrusive". Capped tighter here; still scrolls for
       longer result sets. */
    .ac-panel{ max-height: 34vh; }

    button{ padding: 9px 14px; font-size: 14px; }
    .chip{ padding: 5px 6px 5px 11px; font-size: 12.5px; }
    .chip button{ width: 18px; height: 18px; }
    .img-menu-btn{ width: 28px; height: 28px; }
    .master-select-cb{ width: 22px; height: 22px; }

    /* #galleryToolbar itself already wraps (base CSS), but .viewToggle (the
       Grid/Compact/Single/Unlock all/Disabled button row inside it) didn't
       wrap ITS OWN children — on a narrow phone those 5 buttons together
       are wider than the screen, and with nothing to contain that overflow
       it was bleeding out into making the WHOLE gallery area horizontally
       pannable to reach "Disabled", fighting with #galleryGrid's own
       intentional horizontal scroll for image cards. Wrapping viewToggle's
       buttons onto a second line (plus overflow-x:hidden safety nets so
       nothing here can ever cause page-level horizontal panning again)
       means the only thing left to scroll horizontally is the image row. */
    #galleryToolbar{ overflow-x: hidden; padding: 10px 12px; gap: 8px; margin-bottom: 8px; }
    /* The manual collapse is desktop-only (the phone toolbar is already a
       one-row strip, and the ▲ would sit on it). */
    .toolbar-collapse-btn, .toolbar-expand-row{ display: none !important; }
    /* 2026-09-27: wrapping took three rows (~25% of a phone screen) for
       seven buttons. The row is now its OWN contained horizontal strip:
       max-width 100% + overflow-x:auto keeps the overflow inside it (the
       page-pan the wrap was guarding against can't happen), one row tall,
       swipe to reach the rest. `pan-x` so a vertical swipe on it still
       scrolls the page instead of being eaten by the strip. */
    .viewToggle{
      flex-wrap: nowrap; overflow-x: auto; max-width: 100%; min-width: 0;
      scrollbar-width: none; touch-action: pan-x; overscroll-behavior-x: contain;
    }
    .viewToggle::-webkit-scrollbar{ display: none; }
    .viewToggle > button{ flex-shrink: 0; }
    #gallery{ overflow-x: hidden; }
    /* Single view is now practically identical to the image card modal
       (tag editing moved there — see the touch-device .tagbox redirect
       above), which also gained the Prev/Next this view existed for
       (.modal-card-nav) — direct feedback: keeping both was redundant.
       Grid view + tapping into the modal replaces it entirely on mobile;
       untouched on desktop. */
    #viewSingleBtn{ display: none; }

    /* Gallery: a single horizontally-scrolling row instead of a multi-column
       grid, per the user's explicit direction — fixed regardless of the
       desktop column-count preference (applyGalleryColumnOverride()'s
       --gallery-cols var has no effect once this isn't `display:grid`). */
    /* !important throughout this rule and the two below it: Settings'
       "Dynamic card heights" toggle (`html.dynamic-cards`) switches
       #galleryGrid to CSS-columns masonry with variable-height, non-
       uniform-width cards — its selectors (`html.dynamic-cards
       #galleryGrid`/`.card`) have higher specificity than a bare
       `#galleryGrid`/`#galleryGrid .card` here (an id+class beats a plain
       id), so without !important that mode silently won even inside this
       breakpoint, stretching cards to irregular widths — confirmed
       on-device. Masonry doesn't make sense for a fixed-height single row
       at all, so this forces uniform-width cards regardless of that
       setting on mobile, rather than trying to support both. */
    #galleryGrid{
      display: flex !important; flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden;
      gap: 16px; height: 100%; touch-action: pan-x;
    }
    /* .thumbwrap was `height:100%` — taking the card's ENTIRE height and
       leaving nothing for .tagbox below it, which is why tags/the add-tag
       input rendered fully cut off (not scrollable, not visible at all).
       Image gets a share of the card's height now, tagbox gets the rest.
       .tagbox is non-interactive here (below) — see the touch-device rule
       there for why — but stays visible as a preview, and its own
       `overflow-y:auto` is a safety net for a genuinely tall tag list, not
       the primary interaction (tapping it opens the full edit modal). */
    /* Fixed pixel width (not width:auto driven by the thumbwrap's own
       height+aspect-ratio) — deriving a flex item's width FROM a
       percentage-height child's aspect-ratio, inside a column flex
       container, inside a row flex container, is exactly the kind of
       nested-auto-sizing case where Android's WebView (older Chromium
       builds especially) doesn't reliably match desktop Chrome — seen
       on-device as cards not landing on a uniform width and a portrait
       image's box coming out wider/shorter than the intended 3:4 (reading
       as "letterboxed like a landscape image"). Width-drives-height via
       aspect-ratio (below) is the well-supported direction. */
    #galleryGrid .card{
      display: flex !important; flex-direction: column !important;
      flex: 0 0 150px !important; width: 150px !important; height: 100%; margin-bottom: 0 !important;
    }
    #galleryGrid .card .thumbwrap{ flex: 0 0 auto; width: 100%; height: auto; aspect-ratio: 3/4; }
    #galleryGrid .card .tagbox{ flex: 1 !important; min-height: 0; width: 100%; overflow-y: auto; }
    /* Censored/Has-text/Perspective badges: buildCard() (view.ts) appends
       these as a direct child of `.card` here instead of inside
       `.thumbwrap` — desktop's absolute-positioned overlay covered too
       much of a card that's only a few hundred px wide. A plain row above
       the image instead, using space this row layout already left as
       blank top padding above each card; safe to do per-card (rather than
       only once above the whole row) specifically because every card is
       the same uniform width/height here — nothing to misalign against. */
    #galleryGrid .card > .card-status-icons{
      position: static; flex: 0 0 auto; flex-direction: row; flex-wrap: wrap;
      gap: 3px; padding-bottom: 4px; width: 100%;
    }
    #galleryGrid .card > .card-status-icons .status-icon-badge{ font-size: 12px; padding: 3px 6px; }
    #compactGrid{ grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); }

    .single-wrap{ flex-direction: column; }
    .single-panel{ width: 100%; }
    .single-preview{ flex: 0 0 auto; max-width: 100%; max-height: 32vh; }
    .single-preview-col{ flex: 0 0 auto; max-width: 100%; width: 100%; }
    .single-preview img{ max-height: 32vh; }
    .single-panel-main{ max-height: none; }

    /* Help modal was a fixed 230px TOC + content ROW regardless of
       viewport — on a ~92vw-wide phone modal that left barely a sliver for
       the actual content. A horizontally-scrolling TOC strip was tried
       first, but a swipeable row with no visible scroll hint at this width
       just buried the section list — a ☰ dropdown (help.ts, same
       .ctx-menu/positionMenu() every other popup here already uses) reads
       better than either. */
    .help-modal-card{ flex-direction: column; width: 94vw; height: 88vh; }
    .help-toc{ display: none; }
    .help-toc-toggle{ display: inline-flex; position: absolute; top: 12px; left: 12px; z-index: 10; }
    .help-content{ padding: 16px 18px 24px; }
    /* The ☰ toggle floats over this same top-left corner (its own
       position:absolute takes it out of flow, so it doesn't push the
       heading text over on its own) — same fix as modal-card-nav's own
       clearance for the ⓘ button. */
    .help-content h2{ padding-left: 46px; }

    .theme-panel{ left: 4vw; right: 4vw; width: auto; }
    .log-panel{ left: 50%; transform: translate(-50%, -8px) scale(0.98); width: 92vw; }
    .log-panel.panel-visible{ transform: translate(-50%, 0) scale(1); }

    /* Image card modal (view.ts's openImageCardModal/renderImageCardModal)
       — was giving the image side `flex:1` (grow to fill whatever the
       panel's own max-height:40vh left over, up to ~52vh of the modal's
       92vh) and the tag panel a fixed 40vh ceiling; reported cut off, with
       the image taking far too much of the modal. Flipped: image gets a
       genuinely small, fixed share (~30%) and the tag panel gets the rest
       via flex:1, with its own scroll — this is now the PRIMARY tag-editing
       surface on mobile (grid cards' own inline tags redirect here, see
       the touch-device .tagbox rule above), so it needs to be the one
       that's roomy, not the image. */
    .modal-card{ width: 96vw; height: 92vh; flex-direction: column; min-height: 0; }
    .modal-card .single-img-side{ flex: 0 0 30%; max-height: 30%; min-height: 0; }
    .modal-card .single-img-side img{ max-width: 100%; max-height: 100%; }
    .modal-card .single-panel{ width: 100%; flex: 1; min-height: 0; max-height: none; overflow-y: auto; }

    /* Filename/resolution text moves behind an ⓘ button (opposite side of
       the panel from Close) instead of the inline text, which had nowhere
       to wrap and bled under modal-close-btn — see renderImageCardModal()
       in view.ts. */
    .single-name{ display: none; }
    .modal-info-btn{ display: inline-flex; position: absolute; top: 12px; left: 12px; z-index: 10; }
    /* .modal-card-nav (Prev/Next) is the first flow content in the panel,
       landing at the same top-left corner the ⓘ button floats over —
       confirmed on-device, "Prev" was clipped under it. Reserve space
       for the button's own width instead of overlapping it. */
    .modal-card-nav{ padding-left: 46px; }
    /* .single-name being hidden above (mobile) makes .modal-status-row the
       new first flow content in the panel — same clipping-under-the-ⓘ-
       button issue .modal-card-nav already had and already solves with
       padding-left, applied here too now that this row sits above it. */
    .modal-status-row{ padding-left: 46px; }

    /* Pan/zoom by pinch-gesture already works in this modal — the slider
       duplicates it and just eats vertical space on a small screen.
       !important: view.ts sets `display:flex` via inline style.cssText on
       this element, and an inline style attribute always wins over a
       plain (non-!important) stylesheet rule regardless of specificity —
       confirmed on-device (rule alone had no effect). */
    .modal-zoom-row{ display: none !important; }

    /* Mobile quits via the OS task switcher, not an in-app button. */
    #btnQuit{ display: none; }

    /* ---------- SynthDat Overseer ---------- */
    /* Desktop's 2-column layout (.synthdat-cols/.synthdat-col) already
       falls back to stacked via flex-wrap on a narrow viewport, but the
       Generation section's own deliberately-uneven split — settings column
       capped at 360px, preview column given a 480px flex-basis, sized that
       way specifically so the preview reads as dominant in a WIDE desktop
       layout — has no reason to keep either number once there's only one
       column. #synthDatCol1's own min-width:320px is dropped too so its
       docks can't be forced wider than the screen.
       `width:100%` alone doesn't actually do it, though — desktop's own
       `.synthdat-col{flex:1 ...}` sets flex-basis to its default (0%), and
       flex-basis wins over a separately-declared `width` for a flex item's
       main-axis size. Every column (top-level ComfyUI-connection/Reference-
       image/Model-LoRA one AND the nested Generation-section one) needs its
       OWN flex-basis pushed to 100% — not just width — or two columns still
       split the row 50/50 regardless of width, which is exactly what was
       happening (reported as "docks look horizontally shortened"). */
    .synthdat-col{ flex: 1 1 100%; max-width: 100%; min-width: 0; }
    /* A number/select/button row that fit fine at desktop widths (e.g.
       Width × Height, or a LoRA stack row's model+strength+remove) has no
       slack left on a phone — wrapping instead of forcing every row onto
       one line that can't fully fit is the same fallback already relied on
       for .synthdat-cols itself, just extended to this file's other
       multi-control rows. */
    .synthdat-lora-row{ flex-wrap: wrap; }
    /* The LoRA stack row's name field wraps to earlier rules just fine, but
       browsers compute its wrap-decision size from the plain text input's
       own intrinsic width (~170px), not from `flex:1;min-width:0`'s much
       lower minimum — so on a narrow phone it was landing squeezed down to
       almost nothing alongside the strength/remove controls rather than
       actually wrapping onto its own line. flex-basis:100% forces it onto a
       full-width line of its own regardless of that intrinsic-size
       computation (whatever's before/after it wraps around it), leaving
       strength+remove to share the next line, which does fit them fine. */
    .synthdat-lora-row input[type="text"]{ flex: 1 1 100%; }
    #synthDatTab input[type="number"],
    #synthDatTab input[type="text"],
    #synthDatTab select,
    #synthDatTab textarea{
      max-width: 100%; box-sizing: border-box;
    }
  }

/* ---------- Bucket Images dock (Gallery right panel) ---------- */

/* The number fields are the ONE input type the global rule
   (`input[type="text"], textarea`) doesn't cover — without this they render as
   the browser's default white boxes instead of the theme. Mirrors
   .wd14-settings' own number-input styling. */
.bucket-params-row input[type="number"]{
  font-family: var(--sans); font-size: 12.5px; color: var(--text-primary);
  background: var(--bg-elevated); border: 1px solid var(--border-strong);
  border-radius: var(--radius); padding: 6px 9px; box-sizing: border-box;
  width: 100%; cursor: text;
}
.bucket-params-row input[type="number"]:focus{ border-color: var(--accent-manual); outline: none; }

.bucket-model-row{ display: flex; flex-direction: column; gap: 8px; margin-bottom: 8px; }
.bucket-model-row .d{ font-size: 11.5px; line-height: 1.4; color: var(--text-muted); }
.bucket-model-row button{ width: 100%; white-space: normal; }

.bucket-params-row{
  /* auto-fit so the fields wrap to their own rows instead of squeezing into
     slivers in the narrow/horizontal dock layout */
  display: grid; grid-template-columns: repeat(auto-fit, minmax(62px, 1fr));
  gap: 8px; margin-bottom: 12px;
}
.bucket-params-row label{
  display: flex; flex-direction: column; gap: 4px;
  font-size: 11px; color: var(--text-faint);
}
/* Training-size preset picker above the Min/Max/Step row, and the line under
   it saying what a 2:3 / square image becomes (bucket-images.ts). */
.bucket-preset-row{
  display: flex; flex-direction: column; gap: 4px; margin-bottom: 8px;
  font-size: 11px; color: var(--text-faint);
}
.bucket-preset-row select{
  font-family: var(--sans); font-size: 12.5px; color: var(--text-primary);
  background: var(--bg-elevated); border: 1px solid var(--border-strong);
  border-radius: var(--radius); padding: 6px 9px; width: 100%; cursor: pointer;
}
.bucket-preset-row select:focus{ border-color: var(--accent-manual); outline: none; }
.bucket-size-hint{ margin: -4px 0 12px; font-size: 11.5px; line-height: 1.45; color: var(--text-muted); }

/* The Prefer GPU toggle reuses the app's checkbox row look; just needs its own
   gap above the Bucket images button. */
.bucket-gpu-row{ margin-bottom: 10px; }

/* Both actions fill the dock's width, so the column reads as a deliberate
   control stack rather than two stray pills. */
#btnBucketRun, #btnBucketRevert{ width: 100%; white-space: normal; }
#btnBucketRevert{ margin-top: 2px; font-size: 12px; }

.bucket-log{
  font-size: 11px; line-height: 1.55; max-height: 190px; overflow-y: auto;
  white-space: pre-wrap; word-break: break-word;
  background: var(--bg-base); border: 1px solid var(--border-soft);
  border-radius: var(--radius); padding: 8px 10px;
}
.bucket-log:empty{ display: none; }
.bucket-log .bucket-log-line{ color: var(--text-muted); }
.bucket-log .bucket-log-line.err{ color: var(--accent-danger); }

/* =================== Phones & the Android app ===================
   The Android app (mobile/) is this renderer, unforked: mobile/sync-web.js
   copies this stylesheet and generates index.html with <html class=
   "mobile-app">. Three layers, narrowest first:
   - html.mobile-app: Android-only (Electron/desktop-only settings, and the
     tools that don't ship on the phone — Bucket Images and its Originals view).
   - html.touch-device: pointer-driven affordances with no touch equivalent.
   - @media (max-width: 700px): phone-width layout (Theme Studio). */
html.mobile-app #fontSizeRow,
html.mobile-app .settings-section[data-section="performance"],
html.mobile-app .settings-section[data-section="updates"],
html.mobile-app .tool-section[data-dock-id="bucketImages"],
html.mobile-app #viewOriginalsBtn,
html.mobile-app #btnResetZoom,
html.mobile-app #btnQuit{ display: none !important; } /* Android exits via back (confirmed) or the task switcher */
html.touch-device #tooltipsToggleRow,
html.touch-device #tooltipDelayRow,
html.touch-device #dockDragInstructionsRow{ display: none !important; }
@media (max-width: 900px){
  html.touch-device #viewCompactBtn,
  html.touch-device #layoutNote,
  html.touch-device #layoutRow,
  html.touch-device #rightSidebarPanelsNote{ display: none !important; }
  /* The 🔭 drawer button reaches Tag Overseer on phones; the tab is redundant. */
  html.touch-device #tabMasterTags{ display: none; }
  html.touch-device .tool-section .sec-head.dock-desc-hide-mobile{ display: none; }
  .tab-btn{ flex-shrink: 0; white-space: nowrap; }
  .modal-status-row{ padding-right: 46px; }
  #statsTab{ padding: 14px; overflow-x: hidden; }
  /* SynthDat's settings column: two narrow fields per row instead of one
     tall stack, headings and full-width controls spanning both. */
  .synthdat-gen-top .synthdat-cols > .synthdat-col:first-child{
    display: grid; grid-template-columns: 1fr 1fr; gap: 4px 10px; align-items: end;
  }
  .synthdat-gen-top .synthdat-cols > .synthdat-col:first-child > .synthdat-lora-row,
  .synthdat-gen-top .synthdat-cols > .synthdat-col:first-child > .sec-head,
  .synthdat-gen-top .synthdat-cols > .synthdat-col:first-child > button{ grid-column: 1 / -1; }
  #synthDatPromptFieldsDock{ width: 100%; }
}

/* Theme Studio on a phone: a full-screen sheet that rises from the bottom
   edge and leaves the same way (theme-studio.ts passes a matching exitMs to
   the modal shell). Preview on top (it renders the PHONE layout — the
   iframe is sized to this viewport), editor below, one row of actions. */
@media (max-width: 700px){
  .ts-backdrop{ align-items: flex-end; }
  .confirm-box.ts-box{
    width: 100vw; max-width: 100vw; height: 100dvh; max-height: 100dvh;
    border-radius: var(--r-panel) var(--r-panel) 0 0; border-bottom: 0;
    grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 44%) minmax(0, 1fr) auto;
    grid-template-areas: "head" "stage" "edit" "foot";
    opacity: 1; transform: translateY(100%);
    transition: transform calc(var(--panel-dur) * 1.75) var(--map-ease);
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }
  .ts-backdrop.modal-visible .ts-box{ transform: none; }
  .ts-head{ flex-wrap: wrap; gap: 8px 10px; padding: 10px 10px 10px 16px; }
  .ts-title-block{ order: 3; flex: 1 0 100%; }
  .ts-title{ display: none; }
  .ts-name{ width: 100%; max-width: none; flex: 1; border-left-color: transparent; padding-left: 0; font-size: 15px; }
  .ts-head-actions{ margin-left: auto; }
  .ts-head::before{
    content: 'Theme Studio'; font-family: var(--head-font); font-weight: 700; font-size: 14px;
    color: var(--text-primary); align-self: center;
  }
  .ts-import, .ts-export{ font-size: 0; padding: 8px; gap: 0; min-width: 36px; min-height: 36px; }
  .ts-import .ic, .ts-export .ic{ width: 17px; height: 17px; margin: 0; }
  .ts-close{ min-width: 36px; min-height: 36px; }
  .ts-edit{ border-right: 0; border-top: 1px solid var(--border-soft); }
  .ts-jump{ flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; padding: 8px 12px; }
  .ts-jump::-webkit-scrollbar{ display: none; }
  .ts-jump-btn{ flex-shrink: 0; min-height: 32px; }
  .ts-section{ padding: 16px 14px 20px; }
  .ts-stage{ padding: 12px 12px 10px; gap: 8px; }
  .ts-stage-note{ display: none; }
  .ts-stage-bar{ flex-wrap: nowrap; justify-content: flex-start; gap: 6px; }
  .ts-preview-tabs{
    flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; justify-content: flex-start;
    min-width: 0; flex: 1; align-self: stretch;
  }
  .ts-preview-tabs::-webkit-scrollbar{ display: none; }
  .ts-ptab{ flex-shrink: 0; min-height: 34px; }
  .ts-compare{ flex-shrink: 0; font-size: 0; padding: 8px; min-width: 38px; min-height: 38px; }
  .ts-compare .ic{ width: 17px; height: 17px; margin: 0; }
  .ts-foot{ flex-wrap: wrap; gap: 8px; padding: 10px 12px; }
  .ts-reset{ font-size: 0; padding: 8px; min-width: 36px; }
  .ts-reset .ic{ width: 17px; height: 17px; margin: 0; }
  .ts-dirty{ font-size: 11px; }
  .ts-foot-actions{ flex: 1 0 100%; }
  .ts-foot-actions > button{ flex: 1; min-height: 40px; padding: 8px 6px; font-size: 12.5px; white-space: nowrap; }
  .ts-color{ grid-template-columns: 30px minmax(0, 1fr) auto 84px; gap: 8px; }
  .ts-hex{ width: 84px; font-size: 12px; }
  .ts-font-grid{ grid-template-columns: 1fr; }
  .ts-opts{ grid-template-columns: repeat(auto-fill, minmax(78px, 1fr)); }
  .ts-lib-acts{ opacity: 1; }
}
@media (max-width: 700px) and (prefers-reduced-motion: reduce){
  .confirm-box.ts-box{ transform: none; opacity: 0; transition: opacity var(--panel-dur) ease; }
  .ts-backdrop.modal-visible .ts-box{ opacity: 1; }
}

/* Non-interactive scroll hint (shared-ui.ts attachScrollHint): replaces the
   native horizontal scrollbar on touch scrollers, which is a tappable control. */
.scroll-hinted{ position: relative; scrollbar-width: none; }
.scroll-hinted::-webkit-scrollbar{ display: none; }
.scroll-hint{
  position: absolute; left: 0; bottom: 1px; height: 3px; pointer-events: none; z-index: 1;
}
.scroll-hint[hidden]{ display: none; }
.scroll-hint-thumb{
  height: 100%; border-radius: 2px; pointer-events: none;
  background: color-mix(in srgb, var(--text-muted) 70%, transparent);
}

/* Image card modal: Crop → Isolate (Split) (view.ts startCropMode). The
   lines box sits exactly over the image; each line is a wide transparent
   hit strip with a thin visible core. */
.split-lines{ position: absolute; pointer-events: none; }
.split-line{ position: absolute; pointer-events: auto; touch-action: none; z-index: 1; }
.split-line-v{ top: 0; bottom: 0; width: 14px; margin-left: -7px; cursor: ew-resize; }
.split-line-h{ left: 0; right: 0; height: 14px; margin-top: -7px; cursor: ns-resize; }
.split-line::before{
  content: ""; position: absolute; background: #7c6bff;
  box-shadow: 0 0 0 1px rgba(0,0,0,0.55);
}
.split-line-v::before{ top: 0; bottom: 0; left: 6px; width: 2px; }
.split-line-h::before{ left: 0; right: 0; top: 6px; height: 2px; }
.split-line:hover::before, .split-line.dragging::before{ background: #a99cff; }
.split-line-v:hover::before, .split-line-v.dragging::before{ left: 5px; width: 4px; }
.split-line-h:hover::before, .split-line-h.dragging::before{ top: 5px; height: 4px; }
.split-bar{
  position: absolute; left: 8px; right: 8px; bottom: 8px; z-index: 2;
  display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
}
.split-bar label{
  display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--text-primary);
  background: color-mix(in srgb, var(--bg-panel) 88%, transparent); border: 1px solid var(--border-strong);
  border-radius: var(--radius); padding: 3px 4px 3px 9px;
}
.split-bar input[type="number"]{
  font-family: var(--sans); font-size: 12.5px; color: var(--text-primary);
  background: var(--bg-elevated); border: 1px solid var(--border-strong);
  border-radius: var(--radius); padding: 4px 6px; box-sizing: border-box; width: 52px;
}
.split-bar input[type="number"]:focus{ border-color: var(--accent-manual); outline: none; }
