:root {
  /* --- Magnific charcoal theme -------------------------------
   * Page surfaces and text. Charcoal greys replace the previous
   * forest-green palette. The Phase-6 control gradient (blue →
   * purple) lives on its own tokens below so it carries forward
   * unchanged but can be tuned without touching ten call sites.
   */
  --bg: #1b1b1d;
  --surface: #202022;
  --surface-2: #2a2a2d;
  --border: rgba(255, 255, 255, 0.08);
  --border-strong: #3a3a3d;
  --text: #f2f2f5;
  /* --muted is kept as the legacy alias for the ~50 existing call
   * sites; its value is now driven by --text-label (defined under the
   * "Text colors" block below) so the Phase A contrast lift lands
   * everywhere at once without touching the call sites. */
  --muted: var(--text-label);
  --accent: #3b6ef5;
  --accent-hover: #5582f7;
  /* Charcoal theme drops the warm-gold brand-accent; --highlight
   * collapses to primary text so existing `color: var(--highlight)`
   * call sites read as bright neutral instead of golden. */
  --highlight: #f2f2f5;

  /* --- Charcoal-specific surface tokens ----------------------
   * New tokens introduced with the theme. Class-D rewrites that
   * currently bake the old green palette into rgba()/hex literals
   * fold into these so the theme is single-source-of-truth.
   */
  --rail-bg: #161618;
  --rail-active: #2e2e31;
  --card: #262628;
  --placeholder: #8a8a90;
  --canvas-pattern-bg: #1b1b1d;

  /* --- Phase-6 control chrome tokens ------------------------
   * Same blue → purple gradient that shipped in Phase 6. Folded
   * from the ~10 raw `#3b82f6` / `#8b5cf6` / `#fff` literals in
   * styles.css:2631-2946 into named tokens so a future contrast
   * tune is a single-line edit. Values intentionally unchanged
   * — verified in this round that the gradient + thumb + on-text
   * sit at ~5:1 contrast or better on the new charcoal surfaces.
   */
  --ctl-fill-start: #3b82f6;
  --ctl-fill-end: #8b5cf6;
  --ctl-thumb: #fff;
  --ctl-on-text: #fff;

  /* --- Typography scale (Phase A) ----------------------------
   * Semantic 3-level system + micro tier. Replaces the bespoke
   * font-size / font-weight / letter-spacing values scattered
   * across panel headers, field labels, hint text, badges and
   * meta dt elements. Body stays at the inherited 14px / 1.5
   * set on html/body — these tokens describe the *non-body*
   * type hierarchy.
   *
   *   heading → panel & modal titles (.settings-title,
   *             .canvas-brand, .gallery-modal-title)
   *   label   → section headers + uppercase field labels
   *             (.expand-title, .prompt-row label, .advanced
   *              summary, .reference-label, .editor-title, ...)
   *   helper  → hint/explanatory text under controls
   *             (.reference-hint, .lora-prompt-hint, .scale-hint)
   *   micro   → badges, chips, gallery-modal meta dt, legends
   *             (.tab-badge, .display-name-label, ...)
   */
  --font-heading-size: 15px;
  --font-heading-weight: 600;
  --font-heading-tracking: 0.2px;
  --font-heading-line: 1.3;

  --font-label-size: 12px;
  --font-label-weight: 600;
  --font-label-tracking: 0.5px;
  --font-label-transform: uppercase;
  --font-label-line: 1.4;

  --font-helper-size: 12px;
  --font-helper-weight: 400;
  --font-helper-line: 1.5;

  --font-micro-size: 10px;
  --font-micro-weight: 700;
  --font-micro-tracking: 0.4px;
  --font-micro-transform: uppercase;
  --font-micro-line: 1.2;

  /* --- Text colors (Phase A) ---------------------------------
   * Semantic text tokens that split the previously overloaded
   * --muted (#9a9aa0) into two contrast-tuned tiers. --text-label
   * is the brighter tier intended for uppercase section headers
   * and field labels (~7.4:1 against --bg → AAA). --text-helper
   * is one notch dimmer for body-weight hint text (~6.0:1 → solid
   * AA). --text-placeholder keeps the dim convention for input
   * placeholders. --muted aliases --text-label so the lift flows
   * to existing call sites with zero churn.
   */
  --text-primary: var(--text);
  --text-heading: var(--text);
  --text-label: #c5c5cb;
  --text-helper: #b0b0b6;
  --text-placeholder: var(--placeholder);

  /* --- Spacing scale (Phase D) -------------------------------
   * Nine-step scale derived from the existing px usage across
   * styles.css (frequency: 8 px is the most common at 75 hits,
   * 6 px at 70, 12 px at 66, 4 px at 50, 10 px at 45, 14 px at
   * 40, 16 px at 23, 2 px at 35, 24 px at 13). The names are
   * numeric so a future tightening / loosening rounds the whole
   * studio in one edit. Phase D migrates containers + buttons +
   * inputs to these tokens byte-identically; raw px stays in
   * places that don't fit the scale (Phase-6 control hardware,
   * 1 px borders, 999 px pills, 11 / 13 px input outliers).
   */
  --space-1: 2px;
  --space-2: 4px;
  --space-3: 6px;
  --space-4: 8px;
  --space-5: 10px;
  --space-6: 12px;
  --space-7: 14px;
  --space-8: 16px;
  --space-9: 24px;

  /* --- Radius scale (Phase D) --------------------------------
   * Four tiers covering the four semantic shapes the studio
   * actually uses: tiny chips and code spans (sm), inputs and
   * small buttons (md), default buttons and card edges (lg),
   * and the display-name pill (pill). Outlier values that live
   * in the file (3 / 10 / 12 / 14 px) stay as raw literals
   * intentionally — they're either one-off accents or sit on
   * Phase-6 hardware that Phase D does not touch.
   */
  --radius-sm: 4px;
  --radius-md: 6px;
  --radius-lg: 8px;
  --radius-pill: 999px;

  /* --- Status palette + canvas-result rims -------------------
   * Theme-neutral cool/warm tints used for error banners and the
   * per-service canvas-result accent rims. Look fine on any dark
   * base, kept verbatim from the prior palette.
   */
  --error: #f5a3a3;
  --error-bg: rgba(245, 163, 163, 0.08);
  --error-border: rgba(245, 163, 163, 0.25);
  --info: #93b8e1;
  --violet: #c8a7e8;
  --peach: #f0b89f;
  --mint: #a8e1c4;
  --amber: #e8c47a;
  --rose: #e0a8c8;
  --shadow: 0 20px 60px rgba(0, 0, 0, 0.35);

  /*
   * Tells the browser to render native widgets (dropdowns, checkboxes,
   * radios, range tracks, scrollbars, color pickers, focus rings) in
   * dark mode. This single line fixes most of the "system widget shows
   * white background on dark page" cases in Chrome/Edge/Firefox/Safari.
   * Per-control overrides below cover the rest.
   */
  color-scheme: dark;
}

/* Self-hosted Inter (SIL OFL, see public/fonts/LICENSE.txt) — served
 * from /fonts instead of a Google Fonts request so the UI doesn't
 * depend on a third-party font CDN being reachable. Weights match the
 * font-weight values actually used elsewhere in this file. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url("../fonts/inter-300.woff2") format("woff2");
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/inter-400.woff2") format("woff2");
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/inter-500.woff2") format("woff2");
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/inter-600.woff2") format("woff2");
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/inter-700.woff2") format("woff2");
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    Helvetica, Arial, sans-serif;
  font-size: 14px;
  line-height: 1.5;
  min-height: 100vh;
}

button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
}

a {
  color: var(--highlight);
}

/* ------------ Native form controls — dark theme integration ------------ */

/*
 * <select> popup options are controlled by the OS, not the page. Setting
 * `color-scheme: dark` on :root + explicit background/color on the option
 * elements gets Chrome/Edge/Firefox to render the dropdown menu in dark
 * mode. Safari's option styling is more constrained; with color-scheme
 * the menu still renders in dark gray (acceptable).
 *
 * We deliberately do NOT replace native <select> with a custom dropdown
 * component — the small remaining Safari-only inconsistency is not worth
 * the JS, focus-management, and a11y maintenance cost of a custom widget.
 */
select option {
  background: var(--surface);
  color: var(--text);
}

select option:checked {
  background: var(--surface-2);
}

/* Range thumb + filled track + checkbox check + radio dot all pick up
 * the studio's accent color. accent-color is the standards-track way to
 * theme native controls without per-browser hacks. */
input[type="checkbox"],
input[type="radio"],
input[type="range"] {
  accent-color: var(--accent);
}

/* Color picker swatch (used in brand-colors). Without an explicit dark
 * background it draws a light system frame around the swatch. */
input[type="color"] {
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid var(--border);
}

/* Scrollbars — Webkit/Blink first, then Firefox via scrollbar-color.
 * color-scheme: dark already gives both a dark default; these tighten
 * the colors to match the studio palette. */
::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

::-webkit-scrollbar-track {
  background: rgba(0, 0, 0, 0.18);
}

::-webkit-scrollbar-thumb {
  background: var(--border-strong);
  border-radius: 5px;
  border: 2px solid transparent;
  background-clip: padding-box;
}

::-webkit-scrollbar-thumb:hover {
  background: var(--muted);
  background-clip: padding-box;
}

html {
  scrollbar-color: var(--border-strong) rgba(0, 0, 0, 0.18);
  scrollbar-width: thin;
}

/* ------------ Layout ------------ */
/* .layout, .rail, .rail-item (+ its state selectors), .rail-icon,
 * .rail-icon svg, .rail-label, and .rail-item .tab-badge (dead code —
 * verified zero index.html elements matched it) moved to
 * src/tailwind.css in T2 (plans/TBD). .layout and .rail-item stay bare
 * (unlayered) component classes there — see the long comment above them
 * for why. Verified via exact-token search: none of these class names
 * are used anywhere in editor.html, so nothing here needed to be kept
 * for T11. The @media (max-width: 780px) responsive overrides for
 * .layout/.rail/.rail-item further down this file are DELIBERATELY left
 * in place, unmoved — being unlayered, they continue to win over the new
 * layered/bare Tailwind rules at that breakpoint regardless of which
 * mechanism defines the desktop base styles (same reasoning as T1's
 * font:inherit fix), so mobile behavior needs no separate porting. */

/* ------------ Settings stack (slide-out panel) ------------ */

.settings-stack {
  background: var(--surface);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
  transition: width 0.2s ease, opacity 0.15s ease;
}

.layout[data-panel-open="false"] .settings-stack {
  border-right-width: 0;
  opacity: 0;
  pointer-events: none;
}

.settings-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  /* Asymmetric shell: vertical = --space-8 (16 px), horizontal stays at
   * the raw 18 px outlier — the half-step between --space-8 and
   * --space-9 the panel shell prefers visually. Held as raw so the
   * 18 px stands out as a deliberate exception to the scale. */
  padding: var(--space-8) 18px;
  border-bottom: 1px solid var(--border);
  flex: 0 0 auto;
}

.settings-title {
  margin: 0;
  font-size: var(--font-heading-size);
  font-weight: var(--font-heading-weight);
  letter-spacing: var(--font-heading-tracking);
  line-height: var(--font-heading-line);
  color: var(--text-heading);
}

.settings-close-btn {
  width: 28px;
  height: 28px;
  font-size: 18px;
}

.settings-form {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

.settings-form[hidden] {
  display: none;
}

.settings-body {
  flex: 1;
  overflow-y: auto;
  /* Same 16 / 18 asymmetry as .settings-header — see note there. */
  padding: var(--space-8) 18px;
  display: flex;
  flex-direction: column;
  gap: 18px;
}

/* Restore HTML `hidden` semantics for the model-fields sibling blocks
 * (mystic-fields / gemini-fields / seedream-fields / seedream-edit-fields
 * / nano-banana-fields / flux-2-pro-fields). Without this rule, hidden
 * field blocks compute `display: block` (cause unclear — possibly a
 * UA quirk in the dark-charcoal context) and the empty slots push
 * later siblings down the panel; the Flux 2 Pro upload widget
 * drifted to the bottom of the panel.
 *
 * All model field blocks now live inside `.settings-body`; the former
 * `.settings-form > [hidden]` sibling selector became vestigial after
 * the unified Generate template moved the remaining blocks inward. */
.settings-body > [hidden] {
  display: none !important;
}

.settings-footer {
  flex: 0 0 auto;
  /* Vertical = --space-6 (12 px), horizontal = same 18 px outlier the
   * panel shell carries throughout. */
  padding: var(--space-6) 18px;
  border-top: 1px solid var(--border);
  background: var(--surface);
  display: flex;
  gap: 10px;
  justify-content: flex-end;
}

.settings-footer .btn {
  min-width: 100px;
}

/* ------------ Canvas (right column: toolbar + result) ------------ */
/* .canvas, .canvas-toolbar, .canvas-brand, .canvas-toolbar-actions, and
 * .logout-form moved to plain Tailwind utility classes directly on their
 * index.html elements in T2 (plans/TBD) — none of them are referenced by
 * app.js or shared with editor.html (verified via exact-token search), so
 * no bare component class was needed for any of them, unlike .layout/
 * .rail-item above. .canvas-area (below, holding the 13 tool result
 * panels + gallery grid) is a separate hook (gallery virtualization reads
 * els.canvasArea.scrollTop) and stays out of scope for this phase. */

.canvas-area {
  flex: 1;
  min-height: 0;
  padding: var(--space-9);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}

.canvas-result {
  flex: 1;
  display: flex;
  flex-direction: column;
}

.canvas-result[hidden] {
  display: none;
}

/* Camera diagram: tiny CSS-only orbit visualization. The subject is a
 * dot at center; the orbit ring shows the horizontal sweep, and the dot
 * position translates horizontal_angle + vertical_angle to top/left so
 * the designer sees roughly where the camera sits. No JS animation —
 * just CSS variables driven by inline style updates. */
.cam-layout {
  display: flex;
  gap: 14px;
  align-items: flex-start;
}

.cam-diagram {
  flex: 0 0 auto;
  position: relative;
  width: 84px;
  height: 84px;
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-radius: 50%;
}

.cam-subject {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 12px;
  height: 12px;
  margin: -6px 0 0 -6px;
  background: var(--text);
  border-radius: 50%;
}

.cam-orbit {
  position: absolute;
  inset: 14px;
  border: 1px dashed var(--border);
  border-radius: 50%;
}

.cam-dot {
  position: absolute;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--rose);
  box-shadow: 0 0 0 2px rgba(224, 168, 200, 0.25);
  /* JS updates left/top via inline style. Default = front (top:50%, left:88%). */
  top: 50%;
  left: 88%;
  margin: -4px 0 0 -4px;
  transition: left 0.15s ease, top 0.15s ease;
}

.cam-controls {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
}

#cam-seed {
  width: 100%;
  padding: 6px 8px;
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid var(--border);
  border-radius: 4px;
  font-size: 12px;
  -moz-appearance: textfield;
}

#cam-seed::-webkit-outer-spin-button,
#cam-seed::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

#cam-download-btn {
  display: inline-flex;
  align-self: flex-end;
  text-decoration: none;
  width: auto;
}

#cam-download-btn[hidden] {
  display: none;
}

#rim-download-btn {
  display: inline-flex;
  align-self: flex-end;
  text-decoration: none;
  width: auto;
}

#rim-download-btn[hidden] {
  display: none;
}

/* ------------ Inpainting mask canvas ------------ */

.mask-wrap {
  position: relative;
  display: inline-block;
  max-width: 100%;
  border-radius: 8px;
  overflow: hidden;
  background: var(--bg);
  border: 1px solid var(--border);
}

/* `.mask-wrap`'s `display: inline-block` has the same specificity as the
 * UA `[hidden] { display: none }` rule, and author styles win at equal
 * specificity — so setting the `hidden` attribute on `#inp-source-wrap`
 * had no visual effect. This raises the specificity so `hidden` actually
 * hides it. */
.mask-wrap[hidden] {
  display: none;
}

.mask-source {
  display: block;
  max-width: 100%;
  height: auto;
  user-select: none;
  -webkit-user-drag: none;
}

.mask-display {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  cursor: crosshair;
  touch-action: none;
}

.mask-wrap .reference-remove {
  position: absolute;
  top: 6px;
  right: 6px;
  z-index: 2;
}

.brush-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin: 8px 0;
}

.brush-size-label {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 11px;
  color: var(--muted);
  white-space: nowrap;
}

.brush-controls input[type="range"] {
  flex: 1;
  min-width: 120px;
  accent-color: var(--accent);
}

.brush-controls .btn-sm {
  width: auto;
}

#inp-mode-btn[aria-pressed="true"] {
  background: var(--surface-2);
  color: var(--highlight);
  border-color: var(--border-strong);
}

.upload-mask-section {
  margin-top: 4px;
  padding: 8px 10px;
  background: rgba(0, 0, 0, 0.15);
  border: 1px solid var(--border);
  border-radius: 6px;
}

.upload-mask-section summary {
  cursor: pointer;
  font-size: var(--font-label-size);
  font-weight: var(--font-label-weight);
  letter-spacing: var(--font-label-tracking);
  line-height: var(--font-label-line);
  text-transform: var(--font-label-transform);
  color: var(--text-label);
}

.upload-mask-section[open] summary {
  margin-bottom: 8px;
}

#inp-download-btn {
  display: inline-flex;
  align-self: flex-end;
  text-decoration: none;
  width: auto;
}

#inp-download-btn[hidden] {
  display: none;
}

#skn-download-btn {
  display: inline-flex;
  align-self: flex-end;
  text-decoration: none;
  width: auto;
}

#skn-download-btn[hidden] {
  display: none;
}

/* ------------ Image Expand: 4-direction sliders + diagram ------------ */

.expand-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
}

.expand-title {
  font-size: var(--font-label-size);
  font-weight: var(--font-label-weight);
  letter-spacing: var(--font-label-tracking);
  line-height: var(--font-label-line);
  text-transform: var(--font-label-transform);
  color: var(--text-label);
}

#exp-symmetric-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  width: auto;
}

#exp-symmetric-btn .link-icon {
  width: 14px;
  height: 14px;
}

#exp-symmetric-btn[aria-pressed="true"] {
  background: var(--accent);
  color: var(--text);
  border-color: var(--accent);
}

.expand-layout {
  display: flex;
  gap: 14px;
  align-items: flex-start;
}

/* Tiny visual map of the source image with its four expansion borders.
 * Each region brightens to the violet badge color when its direction
 * value is > 0, giving a quick at-a-glance read of which sides will
 * grow. CSS-only — values toggle data-active from app.js. */
.expand-diagram {
  flex: 0 0 auto;
  display: grid;
  grid-template-columns: 14px 56px 14px;
  grid-template-rows: 14px 42px 14px;
  gap: 2px;
}

.expand-center {
  grid-column: 2;
  grid-row: 2;
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-radius: 3px;
}

.expand-region {
  background: rgba(0, 0, 0, 0.25);
  border: 1px dashed var(--border);
  border-radius: 2px;
  transition: background 0.15s ease, border-color 0.15s ease,
    opacity 0.15s ease;
}

.expand-region[data-active="true"] {
  background: var(--violet);
  border: 1px solid var(--violet);
  opacity: 0.85;
}

.region-top {
  grid-column: 2;
  grid-row: 1;
}

.region-right {
  grid-column: 3;
  grid-row: 2;
}

.region-bottom {
  grid-column: 2;
  grid-row: 3;
}

.region-left {
  grid-column: 1;
  grid-row: 2;
}

.expand-controls {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
}

.expand-control {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.expand-control label {
  font-size: 11px;
  color: var(--muted);
  display: flex;
  align-items: center;
  gap: 4px;
}

.expand-input-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.expand-input-row input[type="range"] {
  flex: 1;
  accent-color: var(--accent);
  min-width: 0;
}

.expand-input-row input[type="number"] {
  width: 64px;
  padding: var(--space-2) var(--space-3);
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: 12px;
  text-align: right;
  font-variant-numeric: tabular-nums;
  -moz-appearance: textfield;
}

.expand-input-row input[type="number"]::-webkit-outer-spin-button,
.expand-input-row input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

#exp-download-btn {
  display: inline-flex;
  align-self: flex-end;
  text-decoration: none;
  width: auto;
}

#exp-download-btn[hidden] {
  display: none;
}

.dual-upload-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 12px;
}

.dual-upload-tile {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px;
  background: rgba(0, 0, 0, 0.18);
  border: 1px solid var(--border);
  border-radius: 8px;
}

.dual-upload-label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--font-label-size);
  font-weight: var(--font-label-weight);
  letter-spacing: var(--font-label-tracking);
  line-height: var(--font-label-line);
  text-transform: var(--font-label-transform);
  color: var(--text-label);
}

.multi-upload-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
  gap: 10px;
}

.multi-upload-slot {
  position: relative;
  aspect-ratio: 1 / 1;
  border: 1px dashed var(--border-strong);
  border-radius: 8px;
  background: rgba(0, 0, 0, 0.18);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}

.multi-upload-slot:hover {
  border-color: var(--accent);
  background: rgba(0, 0, 0, 0.28);
}

.multi-upload-slot:hover .multi-upload-slot-plus {
  color: var(--accent);
}

.multi-upload-slot:focus-within {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.multi-upload-slot.is-drag-over {
  border-color: var(--accent);
  background: rgba(0, 0, 0, 0.28);
}

.multi-upload-slot.is-drag-over .multi-upload-slot-plus {
  color: var(--accent);
}

.multi-upload-slot.is-filled {
  border-style: solid;
  cursor: default;
}

.multi-upload-slot.is-filled:hover {
  background: rgba(0, 0, 0, 0.18);
  border-color: var(--border);
}

.multi-upload-slot-plus {
  font-size: 22px;
  font-weight: 300;
  color: var(--muted);
  line-height: 1;
  pointer-events: none;
  transition: color 0.15s ease;
}

@media (prefers-reduced-motion: reduce) {
  .multi-upload-slot,
  .multi-upload-slot-plus {
    transition: none;
  }
}

.multi-upload-slot img {
  width: 100%;
  height: 100%;
  /* `contain` to show the full reference image inside the square slot —
   * matches the input-preview policy elsewhere. The parent stays square
   * (`aspect-ratio: 1/1` above) so the grid layout aligns; the image
   * letterboxes within. */
  object-fit: contain;
  display: block;
}

.multi-upload-slot-remove {
  position: absolute;
  top: 4px;
  right: 4px;
  background: rgba(27, 27, 29, 0.85);
  backdrop-filter: blur(4px);
}

#str-download-btn {
  display: inline-flex;
  align-self: flex-end;
  text-decoration: none;
  width: auto;
}

#str-download-btn[hidden] {
  display: none;
}

.radio-group {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  padding: 4px 0;
}

.radio-option {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  cursor: pointer;
}

.radio-option input[type="radio"] {
  width: 14px;
  height: 14px;
  accent-color: var(--accent);
  margin: 0;
  cursor: pointer;
}

#rlt-download-btn {
  display: inline-flex;
  align-self: flex-end;
  text-decoration: none;
  width: auto;
}

#rlt-download-btn[hidden] {
  display: none;
}

.checkered-bg {
  background-color: var(--canvas-pattern-bg);
  background-image:
    linear-gradient(45deg, rgba(255, 255, 255, 0.06) 25%, transparent 25%),
    linear-gradient(-45deg, rgba(255, 255, 255, 0.06) 25%, transparent 25%),
    linear-gradient(45deg, transparent 75%, rgba(255, 255, 255, 0.06) 75%),
    linear-gradient(-45deg, transparent 75%, rgba(255, 255, 255, 0.06) 75%);
  background-size: 18px 18px;
  background-position: 0 0, 0 9px, 9px -9px, -9px 0;
  border-radius: 8px;
  padding: 12px;
  width: 100%;
}

.rbg-result-image[hidden] {
  display: none;
}

.rbg-result-image img {
  max-width: 100%;
  height: auto;
  max-height: 600px;
  display: block;
  margin: 0 auto;
  object-fit: contain;
}

#rbg-download-btn {
  display: inline-flex;
  align-self: flex-end;
  text-decoration: none;
  width: auto;
}

#rbg-download-btn[hidden] {
  display: none;
}

.result-text {
  margin: 0;
  /* Matches the .settings-header / -body / -footer 16 / 18 shell rhythm
   * so the image-to-prompt result reads as a continuation of the panel
   * surface. */
  padding: var(--space-8) 18px;
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 13px;
  line-height: 1.6;
  color: var(--text);
  white-space: pre-wrap;
  word-wrap: break-word;
  width: 100%;
  text-align: left;
  max-height: 360px;
  overflow-y: auto;
}

.result-text[hidden] {
  display: none;
}

.result-link {
  font-size: 13px;
  color: var(--highlight);
  word-break: break-all;
  text-align: center;
}

.result-link[hidden] {
  display: none;
}

.result-actions {
  display: flex;
  gap: 8px;
  align-self: flex-end;
}

.result-actions[hidden] {
  display: none;
}

.result-actions .btn-sm {
  width: auto;
}

.scale-hint {
  margin: 6px 0 0;
  font-size: var(--font-helper-size);
  font-weight: var(--font-helper-weight);
  line-height: var(--font-helper-line);
  color: var(--text-helper);
}

.required-hint {
  font-size: var(--font-helper-size);
  font-weight: var(--font-helper-weight);
  line-height: var(--font-helper-line);
  color: var(--text-helper);
  margin-left: 4px;
}

#upscale-download-btn {
  display: inline-flex;
  align-self: flex-end;
  text-decoration: none;
  width: auto;
}

#upscale-download-btn[hidden] {
  display: none;
}

.tab-badge {
  font-size: var(--font-micro-size);
  font-weight: var(--font-micro-weight);
  letter-spacing: var(--font-micro-tracking);
  line-height: var(--font-micro-line);
  text-transform: var(--font-micro-transform);
  padding: 2px 6px;
  border-radius: 4px;
  background: var(--surface-2);
  color: var(--muted);
  font-weight: 600;
}

.toast {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 0 0 18px;
  padding: 10px 14px;
  background: var(--error-bg);
  border: 1px solid var(--error-border);
  color: var(--error);
  border-radius: 8px;
  font-size: 13px;
}

.toast[hidden] {
  display: none;
}

.toast-close {
  background: transparent;
  border: none;
  color: var(--error);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  padding: 0 4px;
}

.prompt-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.prompt-row label {
  font-size: var(--font-label-size);
  font-weight: var(--font-label-weight);
  letter-spacing: var(--font-label-tracking);
  line-height: var(--font-label-line);
  text-transform: var(--font-label-transform);
  color: var(--text-label);
}

.prompt-row textarea {
  width: 100%;
  resize: vertical;
  min-height: 96px;
  /* Both axes off-scale: the prompt textarea sits at 11 / 13 px so
   * the cursor caret lands flush with the textbox cap height at the
   * studio's 14 px body. The scale's nearest pair (10 / 12 or
   * 12 / 14) shifts the caret by 1 px in either axis, which reads
   * as a visible drift on the long prompts the form is built for.
   * Kept raw as a deliberate per-control rhythm. */
  padding: 11px 13px;
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  outline: none;
  font-size: 14px;
}

.prompt-row textarea:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(59, 110, 245, 0.22);
}

.prompt-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  font-size: 11px;
  color: var(--muted);
}

.prompt-actions {
  display: flex;
  gap: 6px;
  align-items: center;
}

.prompt-actions .btn-sm {
  width: auto;
  padding: 4px 10px;
  font-size: 12px;
}

.prompt-actions .btn-sm[hidden] {
  display: none;
}

.prompt-actions .btn-sm .tooltip {
  margin-left: 4px;
}

.controls-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 14px;
}

.control {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.control label {
  font-size: 12px;
  color: var(--muted);
  display: flex;
  align-items: center;
  gap: 6px;
}

.control select,
.control input[type="number"] {
  /* Vertical = 9 px raw matches .btn (see note there). Horizontal
   * = 11 px raw, the half-step between --space-5 and --space-6 the
   * narrow numeric inputs need so the spinner arrows don't crowd
   * the digits. Both off-scale, kept raw with intent. */
  padding: 9px 11px;
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  outline: none;
}

.control input[type="range"] {
  accent-color: var(--accent);
  width: 100%;
}

.button-grid {
  display: grid;
  gap: 8px;
}

.ar-grid {
  grid-template-columns: repeat(auto-fill, minmax(76px, 1fr));
}

.res-grid {
  grid-template-columns: repeat(3, 1fr);
}

.count-grid {
  grid-template-columns: repeat(4, 1fr);
}

.button-grid button {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 8px 6px;
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid var(--border);
  border-radius: 6px;
  cursor: pointer;
  color: var(--muted);
  font-size: 11px;
  transition: background-color 0.15s ease, border-color 0.15s ease,
    color 0.15s ease;
}

.button-grid button:hover {
  background: var(--surface-2);
  border-color: var(--border-strong);
  color: var(--text);
}

.button-grid button[aria-checked="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--text);
}

.button-grid button:focus-visible {
  outline: 2px solid var(--highlight);
  outline-offset: 2px;
}

.ar-icon-box {
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.ar-icon {
  background: currentColor;
  opacity: 0.65;
  border-radius: 2px;
  display: block;
}

.button-grid button[aria-checked="true"] .ar-icon {
  opacity: 1;
}

.ar-text {
  font-weight: 600;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

.ar-hint {
  font-size: var(--font-micro-size);
  font-weight: var(--font-helper-weight);
  color: var(--text-helper);
}

.res-grid button {
  padding: 10px 0;
  font-weight: 600;
  font-size: 13px;
}

.checkbox-label input[type="checkbox"]:disabled + span {
  color: var(--muted);
}

.checkbox-label input[type="checkbox"]:disabled {
  cursor: not-allowed;
  opacity: 0.7;
}

/* ------------ Generic collapsible section (Playground style) ------------ */

.collapse-section {
  border: 1px solid var(--border);
  border-radius: 8px;
  background: rgba(0, 0, 0, 0.15);
  overflow: hidden;
}

.collapse-summary {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  cursor: pointer;
  color: var(--muted);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  user-select: none;
  list-style: none;
}

.collapse-summary::-webkit-details-marker {
  display: none;
}

.collapse-summary::after {
  content: '';
  width: 7px;
  height: 7px;
  margin-left: auto;
  margin-right: 2px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg) translateY(-1px);
  transition: transform 0.15s ease;
  flex: 0 0 auto;
}

.collapse-section[open] > .collapse-summary::after {
  transform: rotate(-135deg) translateY(-1px);
}

.collapse-summary:hover {
  color: var(--text);
}

.collapse-summary:focus-visible {
  outline: 2px solid var(--highlight);
  outline-offset: -2px;
  border-radius: 6px;
}

.collapse-title {
  font-weight: 600;
  flex: 0 0 auto;
}

.collapse-value {
  font-size: 11px;
  text-transform: none;
  letter-spacing: 0;
  color: var(--accent);
  font-weight: 500;
  flex: 0 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.collapse-value:empty {
  display: none;
}

.collapse-summary .tooltip {
  flex: 0 0 auto;
}

.collapse-body {
  padding: 12px 14px 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  border-top: 1px solid var(--border);
}

.reference-label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--font-label-size);
  font-weight: var(--font-label-weight);
  letter-spacing: var(--font-label-tracking);
  line-height: var(--font-label-line);
  text-transform: var(--font-label-transform);
  color: var(--text-label);
}

.reference-empty {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
}

.reference-hint {
  /* Compact format/size hint sitting under every reference-upload
   * button (17 instances in index.html — Mystic structure/style,
   * Upscaler Creative + Precision, Inpainting, Image to Prompt,
   * Relight, Style Transfer, Image Expand, Skin Enhancer, Change
   * Camera, Flux 2 Pro). Shrunk from helper-tier 12 px to micro-tier
   * 10 px and the colour dropped one notch from --text-helper (~6:1)
   * to --text-placeholder (~4.5:1) so the same accept-format
   * information stays readable but no longer competes with the
   * primary upload action above it. Weight + line stay on the helper
   * tier — micro tier's 700 / uppercase semantics belong to badges
   * and legends, not prose hints. The hybrid is intentional. */
  font-size: var(--font-micro-size);
  font-weight: var(--font-helper-weight);
  line-height: var(--font-helper-line);
  color: var(--text-placeholder);
}

.reference-preview {
  position: relative;
  width: 100%;
  /* No forced aspect-ratio: portraits and landscapes both letterbox naturally
   * via the inner `object-fit: contain` rule. Designers reported the input
   * preview was clipping tall portraits because the old `aspect-ratio: 1/1`
   * + `object-fit: cover` cropped to a square. max-height caps the panel so
   * very tall sources don't push the form down indefinitely. */
  max-height: 320px;
  border-radius: 8px;
  overflow: hidden;
  background: var(--bg);
  border: 1px solid var(--border);
}

.reference-preview[hidden] {
  display: none;
}

.reference-preview img {
  width: 100%;
  height: 100%;
  /* `contain` so designers see the full uploaded image before processing —
   * portraits letterbox vertically instead of being cropped to a square. */
  object-fit: contain;
  display: block;
}

.reference-remove {
  position: absolute;
  top: 6px;
  right: 6px;
  background: rgba(27, 27, 29, 0.85);
  backdrop-filter: blur(4px);
}

.reference-sliders {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.reference-sliders[hidden] {
  display: none;
}

.reference-sliders label {
  font-size: 12px;
  color: var(--muted);
  display: flex;
  align-items: center;
  gap: 6px;
}

.reference-sliders input[type="range"] {
  accent-color: var(--accent);
  width: 100%;
}

.lora-section {
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 14px;
  background: rgba(0, 0, 0, 0.15);
}

.lora-section[open] summary {
  margin-bottom: 10px;
}

.lora-section summary {
  cursor: pointer;
  color: var(--muted);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  padding: 4px 0;
}

.lora-banner {
  padding: 8px 12px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  color: var(--text);
  font-size: 12px;
  margin-bottom: 8px;
}

.lora-banner[hidden] {
  display: none;
}

.lora-prompt-hint {
  margin: 0 0 8px;
  font-size: var(--font-helper-size);
  font-weight: var(--font-helper-weight);
  line-height: var(--font-helper-line);
  color: var(--text-helper);
}

.lora-prompt-hint code {
  background: rgba(0, 0, 0, 0.35);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 1px 5px;
  font-size: 11px;
}

.lora-content.disabled {
  opacity: 0.45;
  pointer-events: none;
}

.lora-list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 8px;
}

.lora-list:empty::before {
  content: attr(data-empty);
  font-size: 12px;
  color: var(--muted);
}

.lora-list button {
  padding: 5px 10px;
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text);
  font-size: 12px;
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease;
}

.lora-list button:hover {
  background: var(--surface-2);
  border-color: var(--border-strong);
}

.lora-list button[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
}

.lora-selected {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  background: var(--surface-2);
  border: 1px solid var(--accent);
  border-radius: 6px;
  font-size: 12px;
  margin-bottom: 8px;
}

.lora-selected[hidden] {
  display: none;
}

.lora-selected-name {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.lora-strength {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.lora-strength[hidden] {
  display: none;
}

.lora-strength label {
  font-size: 12px;
  color: var(--muted);
  display: flex;
  align-items: center;
  gap: 6px;
}

.lora-strength input[type="range"] {
  accent-color: var(--accent);
  width: 100%;
}

.advanced {
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 14px;
  background: rgba(0, 0, 0, 0.15);
}

.advanced summary {
  cursor: pointer;
  font-size: var(--font-label-size);
  font-weight: var(--font-label-weight);
  letter-spacing: var(--font-label-tracking);
  line-height: var(--font-label-line);
  text-transform: var(--font-label-transform);
  color: var(--text-label);
  padding: 4px 0;
}

.advanced[open] summary {
  margin-bottom: 10px;
}

/* --- Phase B — Advanced sub-groups ------------------------------
 * Visual sub-section structure inside an open <details class="advanced">.
 * Wraps related controls (e.g. Engine + Creative Detailing) into a
 * group introduced by a micro-tier title and separated from the
 * preceding group by a hairline border. The group is purely a CSS
 * wrapper — every control inside keeps its existing id/name/value/
 * checked/disabled and the JS readers see byte-identical state.
 */
.advanced-group {
  padding-top: 12px;
  border-top: 1px solid var(--border);
}

.advanced-group:first-of-type {
  padding-top: 0;
  border-top: none;
}

.advanced-group + .advanced-group {
  margin-top: 12px;
}

.advanced-group-title {
  margin: 0 0 10px;
  font-size: var(--font-micro-size);
  font-weight: var(--font-micro-weight);
  letter-spacing: var(--font-micro-tracking);
  line-height: var(--font-micro-line);
  text-transform: var(--font-micro-transform);
  color: var(--text-label);
}

.control-full {
  margin-top: 14px;
}

.checkbox-label {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
}

.checkbox-label input[type="checkbox"] {
  width: 16px;
  height: 16px;
  accent-color: var(--accent);
  margin: 0;
  cursor: pointer;
}

.brand-colors-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 8px;
}

.brand-color-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 8px;
  background: rgba(0, 0, 0, 0.2);
  border: 1px solid var(--border);
  border-radius: 6px;
}

.brand-color-input {
  width: 36px;
  height: 28px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: transparent;
  cursor: pointer;
}

.brand-color-weight {
  flex: 1;
  accent-color: var(--accent);
}

.brand-color-weight-val {
  min-width: 28px;
  font-variant-numeric: tabular-nums;
  font-size: 12px;
  color: var(--muted);
  text-align: right;
}

.btn-icon {
  background: transparent;
  border: 1px solid var(--border-strong);
  color: var(--muted);
  width: 26px;
  height: 26px;
  border-radius: var(--radius-md);
  cursor: pointer;
  font-size: 16px;
  line-height: 1;
  padding: 0;
}

.btn-icon:hover {
  background: var(--surface-2);
  color: var(--error);
  border-color: var(--error-border);
}

/* .btn-sm, .btn, .btn-primary (+ its 3 states), .btn-ghost (+ hover)
 * moved to bare component classes in src/tailwind.css in T2b.2
 * (plans/TBD) — see the long comment there for why (button/link
 * elements need to be bare to beat styles.css's own unlayered
 * button/a normalization rules) and for the deliberate .btn-sm-before-
 * .btn ordering that reproduces this codebase's existing behavior
 * exactly (verified live). .btn-icon and .btn-lg below are untouched —
 * out of the approved T2b.2 scope. Every more-specific compound
 * override elsewhere in this file (.result-actions .btn-sm,
 * .prompt-actions .btn-sm, .brush-controls .btn-sm,
 * .settings-footer .btn, ...) is also untouched — those stay correct
 * regardless of which file defines the base rule they override, since
 * they're higher-specificity (also confirmed live). Used on both
 * index.html and editor.html; both already load tailwind.css (T0), so
 * this needed zero markup changes on either page. */

.btn-lg {
  /* Both axes off-scale: 11 vertical lands the button at 42 px tall
   * (the primary-action height the Generate / Upscale forms expect),
   * 22 horizontal balances the visual weight. Kept raw — the
   * .btn-lg footprint is its own visual rhythm, not a token tier. */
  padding: 11px 22px;
  font-size: 15px;
}

/* ------------ Result card ------------ */

.result {
  margin-top: 22px;
}

.result-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: var(--space-9);
  min-height: 240px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
}

.result-placeholder {
  margin: 0;
  color: var(--muted);
  font-size: 13px;
  text-align: center;
}

.result-error-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  width: 100%;
}

.result-error-wrap[hidden] {
  display: none;
}

.result-error {
  margin: 0;
  padding: 12px 14px;
  background: var(--error-bg);
  border: 1px solid var(--error-border);
  color: var(--error);
  border-radius: 8px;
  font-size: 13px;
  width: 100%;
  text-align: center;
}

.result-images {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
  width: 100%;
}

.result-images[hidden] {
  display: none;
}

.result-images.multi {
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}

.result-image {
  max-width: 100%;
  height: auto;
  max-height: 640px;
  border-radius: 8px;
  display: block;
  object-fit: contain;
  margin: 0 auto;
}

/* --- Phase C — Before/After slider (Upscale result canvas) ------
 * Magnific-style draggable comparison widget. The AFTER image
 * defines the box (position: static, max-height 640 px, object-fit
 * contain), the BEFORE image overlays it absolutely with a clip-path
 * that reveals the left X% of the BEFORE on top of the AFTER. A
 * draggable handle sits at X% with the standard line + center
 * circle + arrows visual.
 *
 * X is held in `--slider-pos` on .before-after (set by JS on the
 * pointer events handler) so the clip-path + handle position update
 * in a single re-style. Default 50 % so the contrast lands at first
 * paint without needing a JS init call.
 *
 * Convention: BEFORE on the left, AFTER on the right — natural
 * reading order (the "improvement" reads left → right).
 */
.before-after {
  position: relative;
  width: 100%;
  max-width: 100%;
  border-radius: 8px;
  overflow: hidden;
  user-select: none;
  display: block;
  --slider-pos: 50%;
}

.before-after[hidden] {
  display: none;
}

.before-after-img {
  display: block;
  width: 100%;
  max-height: 640px;
  object-fit: contain;
  margin: 0 auto;
  /* Drag interactions on the widget should not start a native image
   * drag (which would cancel the slider grab). */
  -webkit-user-drag: none;
}

.before-after-img-after {
  /* sizer — defines the widget box; AFTER renders fully and the
   * BEFORE layer clips on top of it */
  position: relative;
  z-index: 0;
}

.before-after-img-before {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 1;
  clip-path: inset(0 calc(100% - var(--slider-pos)) 0 0);
}

.before-after-handle {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--slider-pos);
  transform: translateX(-50%);
  width: 2px;
  background: var(--ctl-thumb);
  cursor: ew-resize;
  z-index: 3;
  /* Disable native panning/zoom on the handle so a touch drag updates
   * the slider rather than scrolling the page. */
  touch-action: none;
}

.before-after-handle::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--bg);
  border: 2px solid var(--ctl-thumb);
  transform: translate(-50%, -50%);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
  transition: border-color 0.12s ease;
}

@media (prefers-reduced-motion: reduce) {
  .before-after-handle::before {
    transition: none;
  }
}

.before-after-handle:hover::before {
  border-color: var(--accent);
}

.before-after-handle:focus-visible {
  outline: none;
}

.before-after-handle:focus-visible::before {
  border-color: var(--accent);
  box-shadow: 0 0 0 4px rgba(59, 110, 245, 0.25), 0 2px 8px rgba(0, 0, 0, 0.4);
}

.before-after-handle-arrows {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: var(--ctl-thumb);
  font-size: 13px;
  font-weight: 700;
  line-height: 1;
  pointer-events: none;
  z-index: 1;
}

.before-after-label {
  position: absolute;
  top: 12px;
  font-size: var(--font-micro-size);
  font-weight: var(--font-micro-weight);
  letter-spacing: var(--font-micro-tracking);
  line-height: var(--font-micro-line);
  text-transform: var(--font-micro-transform);
  color: var(--ctl-thumb);
  background: rgba(0, 0, 0, 0.55);
  padding: 4px 8px;
  border-radius: 4px;
  pointer-events: none;
  z-index: 2;
}

.before-after-label-before {
  left: 12px;
}

.before-after-label-after {
  right: 12px;
}

/* ------------ Tooltips (floating bubble, JS-positioned) ------------ */

.tooltip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  color: var(--muted);
  font-size: 10px;
  font-weight: 600;
  cursor: help;
  position: relative;
}

.tooltip-bubble {
  position: fixed;
  top: 0;
  left: 0;
  pointer-events: none;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border-strong);
  padding: 6px 8px;
  border-radius: 5px;
  font-size: 11px;
  line-height: 1.4;
  font-weight: 400;
  width: max-content;
  max-width: 200px;
  z-index: 2000;
  box-shadow: var(--shadow);
  white-space: normal;
  text-align: left;
}

.tooltip-bubble[hidden] {
  display: none;
}

/* ------------ Responsive ------------ */

@media (max-width: 780px) {
  .layout,
  .layout[data-panel-open="false"] {
    grid-template-columns: 1fr;
    grid-template-rows: 1fr;
    padding-bottom: 72px;
  }
  .canvas {
    grid-column: 1;
    grid-row: 1;
  }
  .canvas-area {
    padding: 16px;
  }
  /*
   * On narrow screens the settings stack becomes a full-screen drawer
   * floating above the canvas. The rail (bottom bar) stays visible.
   * Closing slides it left off-screen; opening slides back in.
   */
  .settings-stack {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 72px;
    width: auto;
    min-height: 0;
    border-right: none;
    z-index: 60;
    transform: translateX(0);
    transition: transform 0.2s ease;
    opacity: 1;
  }
  .layout[data-panel-open="false"] .settings-stack {
    transform: translateX(-100%);
    opacity: 0;
    pointer-events: none;
    border-right-width: 0;
  }
  .canvas-toolbar {
    padding: 8px 16px;
  }
  .canvas-brand {
    font-size: 14px;
  }
  /*
   * Rail moves to a fixed bottom bar — thumb-reachable on tall phones.
   * Horizontal scroll handles overflow when the 8 service tabs don't
   * fit; the rail-items keep their stacked icon+label layout but get
   * a min-width so they don't squeeze too narrow.
   */
  .rail {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    flex-direction: row;
    overflow-x: auto;
    overflow-y: hidden;
    border-right: none;
    border-top: 1px solid var(--border);
    padding: 6px 8px;
    z-index: 50;
    background: var(--surface);
  }
  .rail-item {
    flex: 0 0 auto;
    min-width: 68px;
  }
}

/* ------------ Gallery (Phase 2 — read-only) ------------ */

.gallery-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 14px;
  padding: 4px;
}

/* The skeleton placeholder also carries `.gallery-grid` so it shares
 * the auto-fill layout. That class wins on specificity over the UA
 * `[hidden] { display: none }` rule, so setGalleryView's
 * `gallerySkeleton.hidden = true` was being ignored — the skeleton
 * stayed `display: grid` above the real list and painted 8 empty
 * cards on top of the loaded 18 (Dispatch closeout-pass observation).
 * The attribute-targeted form below has specificity 0,1,1 and beats
 * the bare class rule above, restoring the intended hide-on-load
 * behaviour for every `.gallery-grid` (real list AND skeleton). */
.gallery-grid[hidden] {
  display: none;
}

.gallery-card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
  cursor: pointer;
  transition: transform 0.12s ease, border-color 0.12s ease,
    box-shadow 0.12s ease;
  text-align: left;
  font: inherit;
  color: inherit;
  padding: 0;
}

.gallery-card:hover {
  transform: translateY(-2px);
  border-color: var(--border-strong);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
}

.gallery-card:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.gallery-card-image-wrap {
  width: 100%;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  background: var(--bg);
}

.gallery-card-image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.gallery-card-meta {
  padding: 10px 12px 12px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.gallery-card-prompt {
  font-size: 12px;
  color: var(--text);
  line-height: 1.4;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin: 0;
}

.gallery-card-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.gallery-card-time {
  font-size: 11px;
  color: var(--muted);
}

.gallery-skeleton-card {
  background: linear-gradient(
    90deg,
    var(--surface) 0%,
    var(--surface-2) 50%,
    var(--surface) 100%
  );
  background-size: 200% 100%;
  animation: gallery-shimmer 1.4s linear infinite;
  border-radius: 10px;
  aspect-ratio: 1 / 1.25;
}

@keyframes gallery-shimmer {
  0% {
    background-position: 200% 0;
  }
  100% {
    background-position: -200% 0;
  }
}

.gallery-state {
  text-align: center;
  padding: 60px 24px;
  color: var(--muted);
}

.gallery-state code {
  background: var(--surface-2);
  padding: 2px 6px;
  border-radius: 4px;
  color: var(--text);
}

.gallery-state .btn {
  margin-top: 14px;
}

#gallery-load-more-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-8) var(--space-9) var(--space-9);
}

#gallery-load-more-wrap[hidden] {
  display: none;
}

.gallery-shown-count {
  font-size: var(--font-micro-size);
  color: var(--text-placeholder);
}

.gallery-shown-count[hidden] {
  display: none;
}

#gallery-load-more-btn[aria-busy='true'] {
  opacity: 0.7;
  cursor: default;
}

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

.gallery-modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-9);
}

.gallery-modal[hidden] {
  display: none;
}

.gallery-modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  backdrop-filter: blur(2px);
}

.gallery-modal-content {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  max-width: 1100px;
  width: 100%;
  max-height: 90vh;
  /* Flex column so the parent strip takes its natural height and the
   * grid below fills the remaining space; outer overflow stays hidden
   * so the modal itself never grows past 90vh — each inner region
   * (info pane, prompt box, settings pre, children grid) handles its
   * own overflow. Without this split, long prompts or open Settings
   * scrolled the whole modal and pushed the image off-screen. */
  display: flex;
  flex-direction: column;
  overflow: hidden;
  box-shadow: var(--shadow);
}

.gallery-modal-close-btn {
  position: absolute;
  top: 12px;
  right: 12px;
  z-index: 2;
}

.gallery-modal-grid {
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  gap: 0;
  /* Fills the remaining height of the modal shell. min-height:0 is
   * essential — without it the grid items refuse to shrink and the
   * column scrolling never engages. */
  flex: 1 1 auto;
  min-height: 0;
}

@media (max-width: 760px) {
  .gallery-modal-grid {
    grid-template-columns: 1fr;
    grid-template-rows: minmax(280px, 1fr) auto;
  }
}

.gallery-modal-image-wrap {
  background: var(--bg);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-8);
  min-height: 320px;
  /* Allow the image column to shrink within the grid so the image
   * never overflows the modal shell. */
  min-height: 0;
  overflow: hidden;
}

.gallery-modal-image-wrap img {
  max-width: 100%;
  /* Image fits the column it lives in rather than a viewport-wide
   * 70vh; this is what keeps the image visible while the info pane
   * scrolls independently next to it. */
  max-height: 100%;
  object-fit: contain;
  border-radius: 6px;
}

.gallery-modal-info {
  /* Vertical = --space-9 (24 px); horizontal 28 px is a deliberate
   * half-step above the scale so the info pane sits visually
   * generous against the image column it shares the modal with. */
  padding: var(--space-9) 28px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  border-left: 1px solid var(--border);
  /* The info pane is the long-content column. min-height: 0 lets it
   * shrink within the grid; overflow-y: auto turns the rest into an
   * internal scroller so long prompts + open Settings + chain strips
   * + edit form never push the modal off-screen. */
  min-height: 0;
  overflow-y: auto;
}

@media (max-width: 760px) {
  .gallery-modal-info {
    border-left: none;
    border-top: 1px solid var(--border);
  }
}

.gallery-modal-title {
  margin: 0;
  font-size: var(--font-heading-size);
  font-weight: var(--font-heading-weight);
  letter-spacing: var(--font-heading-tracking);
  line-height: var(--font-heading-line);
  color: var(--text-heading);
}

.gallery-modal-meta {
  margin: 0;
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 4px 12px;
  font-size: 12px;
}

.gallery-modal-meta dt {
  font-size: var(--font-micro-size);
  font-weight: var(--font-micro-weight);
  letter-spacing: var(--font-micro-tracking);
  line-height: var(--font-micro-line);
  text-transform: var(--font-micro-transform);
  color: var(--text-label);
  align-self: center;
}

.gallery-modal-meta dd {
  margin: 0;
  color: var(--text);
}

.gallery-modal-prompt-wrap h4 {
  margin: 0 0 6px;
  font-size: var(--font-label-size);
  font-weight: var(--font-label-weight);
  letter-spacing: var(--font-label-tracking);
  line-height: var(--font-label-line);
  text-transform: var(--font-label-transform);
  color: var(--text-label);
}

.gallery-modal-prompt-wrap p {
  margin: 0;
  font-size: 13px;
  line-height: 1.5;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 10px 12px;
  max-height: 160px;
  overflow: auto;
  white-space: pre-wrap;
  word-break: break-word;
}

/* Cap the whole <details> as a safety net. Closed it collapses to the
 * summary; open it can grow up to ~260px and the inner pre then handles
 * its own scroll. Without a cap here, a full Mystic settings dump still
 * grew the info pane noticeably even with the pre's max-height in
 * place. */
.gallery-modal-settings {
  max-height: 260px;
  overflow: hidden;
}

.gallery-modal-settings[open] {
  display: flex;
  flex-direction: column;
}

.gallery-modal-settings summary {
  cursor: pointer;
  font-size: var(--font-label-size);
  font-weight: var(--font-label-weight);
  letter-spacing: var(--font-label-tracking);
  line-height: var(--font-label-line);
  text-transform: var(--font-label-transform);
  color: var(--text-label);
  flex: 0 0 auto;
}

.gallery-modal-settings pre {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 10px 12px;
  font-size: 11px;
  line-height: 1.4;
  color: var(--text);
  max-height: 210px;
  overflow-y: auto;
  overflow-x: auto;
  margin: 8px 0 0;
  min-height: 0;
  flex: 1 1 auto;
}

.gallery-modal-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: auto;
}

.gallery-modal-actions .btn[disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}

/* ------------ Session identity (Phase 3) ------------ */
/* .display-name-badge/-label/-value removed in T2b.1 (plans/TBD) —
 * replaced by the Account Menu (avatar + dropdown identity card) in
 * src/tailwind.css. Only index.html ever used these classes. */

/* Delete button in the gallery modal — push to the right edge so it
 * doesn't sit visually next to the primary Reuse action. Subtle red
 * accent matches the error palette already used elsewhere. */
.gallery-modal-delete {
  margin-left: auto;
  color: var(--error);
}

.gallery-modal-delete:hover {
  background: var(--error-bg);
  border-color: var(--error-border);
}

/* ------------ Gallery modal — chain navigation (Phase 5.B) ------------ */

.gallery-modal-parent-strip {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: var(--space-5) var(--space-9) 0;
}

.gallery-modal-parent-strip[hidden] {
  display: none;
}

.gallery-modal-parent-link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 4px 10px;
}

.gallery-modal-parent-thumb {
  width: 28px;
  height: 28px;
  object-fit: cover;
  border-radius: 4px;
  border: 1px solid var(--border);
}

.gallery-modal-parent-deleted {
  font-size: 12px;
  color: var(--muted);
  font-style: italic;
}

.gallery-modal-children-wrap {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.gallery-modal-children-wrap[hidden] {
  display: none;
}

.gallery-modal-children-wrap h4 {
  margin: 0;
  font-size: var(--font-label-size);
  font-weight: var(--font-label-weight);
  letter-spacing: var(--font-label-tracking);
  line-height: var(--font-label-line);
  text-transform: var(--font-label-transform);
  color: var(--text-label);
}

.gallery-modal-children {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(64px, 1fr));
  gap: 6px;
}

.gallery-modal-child {
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: 6px;
  overflow: hidden;
  background: var(--bg);
  border: 1px solid var(--border);
  padding: 0;
  cursor: pointer;
  transition: border-color 0.12s ease, transform 0.12s ease;
}

.gallery-modal-child:hover {
  border-color: var(--accent);
  transform: scale(1.03);
}

.gallery-modal-child img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* ------------ Gallery modal — edit form (Phase 5.A) ------------ */

.gallery-modal-edit-form {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px;
  background: var(--bg);
  border: 1px solid var(--border-strong);
  border-radius: 8px;
}

.gallery-modal-edit-form[hidden] {
  display: none;
}

.gallery-modal-edit-form h4 {
  margin: 0;
  font-size: 13px;
  color: var(--highlight);
}

.gallery-edit-engine {
  margin: 0;
  padding: 0;
  border: none;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.gallery-edit-engine legend {
  font-size: var(--font-micro-size);
  font-weight: var(--font-micro-weight);
  letter-spacing: var(--font-micro-tracking);
  line-height: var(--font-micro-line);
  text-transform: var(--font-micro-transform);
  color: var(--text-label);
  padding: 0 0 4px;
}

.gallery-edit-engine label {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 8px;
  align-items: center;
  cursor: pointer;
  font-size: 13px;
}

.gallery-edit-engine-hint {
  font-size: var(--font-helper-size);
  font-weight: var(--font-helper-weight);
  line-height: var(--font-helper-line);
  color: var(--text-helper);
}

#gallery-modal-edit-prompt {
  width: 100%;
  padding: var(--space-4) var(--space-5);
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text);
  font-size: 13px;
  font-family: inherit;
  resize: vertical;
}

#gallery-modal-edit-prompt:focus {
  border-color: var(--accent);
  outline: none;
}

.gallery-modal-edit-actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}

/* ------------ Gallery modal — busy spinner (during edit) ------------ */

.gallery-modal-image-wrap {
  position: relative;
}

.gallery-modal-busy {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  background: rgba(27, 27, 29, 0.7);
  backdrop-filter: blur(2px);
  border-radius: 6px;
  font-size: 13px;
  color: var(--text);
}

.gallery-modal-busy[hidden] {
  display: none;
}

/* "Open ready now (X)" — pulses gently to draw attention while N-1
 * variations are still finishing. Sits beneath the progress text inside
 * the busy overlay; hidden by default until at least one save lands. */
.gallery-modal-open-ready {
  margin-top: 4px;
}

/* Variations count selector — tight row above the prompt textarea,
 * styled to match the engine fieldset (legend-on-left, control-grid-
 * on-right) without introducing a new fieldset wrapper. */
.gallery-edit-count {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 8px;
}
.gallery-edit-count-label {
  font-size: var(--font-label-size);
  font-weight: var(--font-label-weight);
  letter-spacing: var(--font-label-tracking);
  line-height: var(--font-label-line);
  text-transform: var(--font-label-transform);
  color: var(--text-label);
}
.gallery-edit-count-grid {
  display: inline-flex;
  gap: 0;
}

/* Clickable toast message (partial-failure prompt that jumps to the
 * first ready variation). Hover reveals the affordance without a heavy
 * underline; keyboard focus matches the rest of the studio. */
.toast-clickable {
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: var(--accent);
}
.toast-clickable:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

.gallery-modal-busy .spinner {
  width: 36px;
  height: 36px;
  border: 3px solid var(--border-strong);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: gallery-spin 0.9s linear infinite;
}

@keyframes gallery-spin {
  to {
    transform: rotate(360deg);
  }
}

/* ============================================================
 * Unified control system — gradient sliders + sliding-pill toggles.
 * Class-opt-in (`.ctl-slider`, `.ctl-toggle`) so native chrome
 * stays the default; controls that are NOT meant to be re-skinned
 * (gallery filters, confirmation toggles, etc.) simply omit the
 * class. Visual chrome only — zero functional change: native
 * <input> elements keep their id / name / checked / value /
 * disabled semantics, so readSettings() and every other consumer
 * see byte-identical state.
 * ============================================================ */

/* --- Slider track ---------------------------------------------
 * Gradient fill driven by the --ctl-fill CSS variable, set by JS
 * in bindCtlSliders() on every `input` event as a percentage. We
 * cover both Webkit/Blink (single track gradient with hard color
 * stops at --ctl-fill) and Firefox (separate ::-moz-range-track
 * + ::-moz-range-progress where the progress pseudo handles the
 * filled portion natively).
 */
.ctl-slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 22px;
  background: transparent;
  cursor: pointer;
  --ctl-fill: 0%;
}

.ctl-slider::-webkit-slider-runnable-track {
  height: 6px;
  border-radius: 3px;
  background: linear-gradient(
    to right,
    var(--ctl-fill-start) 0%,
    var(--ctl-fill-end) var(--ctl-fill),
    var(--surface-2) var(--ctl-fill),
    var(--surface-2) 100%
  );
}

.ctl-slider::-moz-range-track {
  height: 6px;
  border-radius: 3px;
  background: var(--surface-2);
}

.ctl-slider::-moz-range-progress {
  height: 6px;
  border-radius: 3px;
  background: linear-gradient(to right, var(--ctl-fill-start), var(--ctl-fill-end));
}

.ctl-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 16px;
  height: 16px;
  margin-top: -5px;
  border-radius: 50%;
  background: var(--ctl-thumb);
  border: 1px solid rgba(0, 0, 0, 0.18);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
  cursor: pointer;
  transition: transform 0.12s ease;
}

.ctl-slider::-moz-range-thumb {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--ctl-thumb);
  border: 1px solid rgba(0, 0, 0, 0.18);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
  cursor: pointer;
}

.ctl-slider:hover::-webkit-slider-thumb {
  transform: scale(1.08);
}

.ctl-slider:focus {
  outline: none;
}

.ctl-slider:focus-visible::-webkit-slider-thumb {
  box-shadow: 0 0 0 3px var(--border-strong), 0 1px 3px rgba(0, 0, 0, 0.3);
}

.ctl-slider:focus-visible::-moz-range-thumb {
  box-shadow: 0 0 0 3px var(--border-strong), 0 1px 3px rgba(0, 0, 0, 0.3);
}

/* --- Slider value chip ---------------------------------------
 * Small bordered pill displayed inside the slider's label row.
 * `margin-left: auto` inside the label's flex (`.control label`
 * is already display:flex) pushes the chip to the right edge so
 * the row reads as [title  …  value] like a playground panel.
 */
.ctl-slider-val {
  margin-left: auto;
  min-width: 32px;
  padding: 1px 7px;
  background: rgba(0, 0, 0, 0.28);
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  text-align: center;
}

/* --- Sliding-pill toggle -------------------------------------
 * The native <input type="checkbox"> stays in the DOM at its
 * original size + position but visually hidden (opacity 0),
 * directly overlaying the pill so the hit-target is preserved
 * for keyboard and pointer. The pill + thumb are pure CSS, the
 * thumb sliding right when the checkbox is :checked.
 */
.ctl-toggle {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  position: relative;
}

.ctl-toggle input[type="checkbox"] {
  position: absolute;
  top: 0;
  left: 0;
  width: 36px;
  height: 20px;
  margin: 0;
  opacity: 0;
  cursor: pointer;
  z-index: 1;
}

.ctl-toggle-track {
  position: relative;
  display: inline-block;
  width: 36px;
  height: 20px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  transition: background 0.18s ease, border-color 0.18s ease;
  flex: 0 0 36px;
}

.ctl-toggle-thumb {
  position: absolute;
  top: 1px;
  left: 1px;
  width: 16px;
  height: 16px;
  background: var(--ctl-thumb);
  border-radius: 50%;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
  transition: left 0.18s ease;
}

.ctl-toggle input[type="checkbox"]:checked + .ctl-toggle-track {
  background: linear-gradient(135deg, var(--ctl-fill-start), var(--ctl-fill-end));
  border-color: transparent;
}

.ctl-toggle input[type="checkbox"]:checked + .ctl-toggle-track .ctl-toggle-thumb {
  left: 18px;
}

.ctl-toggle input[type="checkbox"]:disabled + .ctl-toggle-track {
  opacity: 0.55;
}

.ctl-toggle input[type="checkbox"]:disabled {
  cursor: not-allowed;
}

.ctl-toggle input[type="checkbox"]:focus-visible + .ctl-toggle-track {
  outline: 2px solid var(--highlight);
  outline-offset: 2px;
}

/* --- Adherence/HDR disabled visual ----------------------------
 * Replaces the previous `hidden` toggle on #style-sliders so the
 * adherence + hdr rows stay in the Advanced Settings panel at all
 * times. Greys them out and blocks pointer interaction when no
 * style_reference is uploaded. Submission logic is unchanged —
 * readSettings() still gates those keys on referenceImages.style.
 */
.ctl-needs-ref {
  opacity: 0.5;
}

.ctl-needs-ref .ctl-slider {
  pointer-events: none;
}

.ctl-needs-ref label {
  cursor: not-allowed;
}

/* --- Mystic Advanced Settings panel ---------------------------
 * Light-touch container override for the Engine <select> living
 * as the first row of the panel (kept as a native select per the
 * order — does NOT carry .ctl-* classes; this rule just nudges
 * the visual spacing to align with the slider chrome below it).
 */
.mystic-advanced-engine {
  margin-bottom: 4px;
}

/* ============================================================
 * Unified control system — round 2: radio groups + button-grid
 * segments. Same opt-in pattern as round 1 (.ctl-slider /
 * .ctl-toggle): native <input type="radio"> elements and
 * <button>[aria-checked] toggles keep their name / value /
 * checked / aria semantics, and JS readers
 *   document.querySelector('input[name="…"]:checked').value
 * see byte-identical state. Visual chrome only.
 * ============================================================ */

/* --- Pill radio group ----------------------------------------
 * Horizontal-wrap variant used by Imagination (Reimagine),
 * Lighting source (Relight), and Skin enhancer mode. Each
 * <label> is the pill; the native radio overlays the pill at
 * opacity:0 so click + keyboard hit-targets survive. Selection
 * is detected via `:has(input:checked)` (supported in every
 * major browser since 2023).
 *
 * Specificity strategy: scope to `.radio-group.ctl-radio-group`
 * so it slots cleanly atop the existing `.radio-group` baseline
 * rules without `!important`.
 */
.radio-group.ctl-radio-group {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 4px 0;
}

.radio-group.ctl-radio-group > label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 14px;
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--muted);
  font-size: 13px;
  cursor: pointer;
  position: relative;
  transition: background 0.15s ease, border-color 0.15s ease,
    color 0.15s ease;
}

.radio-group.ctl-radio-group > label:hover {
  background: var(--surface-2);
  border-color: var(--border-strong);
  color: var(--text);
}

.radio-group.ctl-radio-group > label:has(input[type="radio"]:checked) {
  background: linear-gradient(135deg, var(--ctl-fill-start), var(--ctl-fill-end));
  border-color: transparent;
  color: var(--ctl-on-text);
}

.radio-group.ctl-radio-group > label:has(input[type="radio"]:focus-visible) {
  outline: 2px solid var(--highlight);
  outline-offset: 2px;
}

.ctl-radio-group input[type="radio"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

/* --- Pill radio — stacked variant (Gallery edit-engine) ------
 * The gallery edit-engine fieldset stacks its two options
 * vertically and each option carries a name + a muted hint
 * (".gallery-edit-engine-hint") on the right. Preserves the
 * pre-existing 3-column grid layout inside each label so the
 * hint stays right-aligned; only the pill chrome (border, fill,
 * gradient-on-checked) is overlaid.
 */
.gallery-edit-engine.ctl-radio-group > label {
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 12px;
  position: relative;
  transition: background 0.15s ease, border-color 0.15s ease,
    color 0.15s ease;
}

.gallery-edit-engine.ctl-radio-group > label:hover {
  background: var(--surface-2);
  border-color: var(--border-strong);
}

.gallery-edit-engine.ctl-radio-group
  > label:has(input[type="radio"]:checked) {
  background: linear-gradient(135deg, var(--ctl-fill-start), var(--ctl-fill-end));
  border-color: transparent;
  color: var(--ctl-on-text);
}

.gallery-edit-engine.ctl-radio-group
  > label:has(input[type="radio"]:checked)
  .gallery-edit-engine-hint {
  color: rgba(255, 255, 255, 0.85);
}

/* --- Pill button-grid segments -------------------------------
 * Applied to grid containers whose children are <button>
 * elements driven by aria-checked. Only resolution-grid + the
 * scale-factor-grid carry the class. Aspect-ratio grid is
 * intentionally NOT classed — its proportion-icon affordance
 * is a feature.
 *
 * Specificity matches the existing `.button-grid button[…]`
 * rule (both 0,2,1); placed later in the file so source order
 * wins where they overlap.
 */
.ctl-segment > button {
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid var(--border);
  color: var(--muted);
  transition: background 0.15s ease, border-color 0.15s ease,
    color 0.15s ease;
}

.ctl-segment > button:hover {
  background: var(--surface-2);
  border-color: var(--border-strong);
  color: var(--text);
}

.ctl-segment > button[aria-checked="true"] {
  background: linear-gradient(135deg, var(--ctl-fill-start), var(--ctl-fill-end));
  border-color: transparent;
  color: var(--ctl-on-text);
}

/* --- Editor Page (Phase 1 skeleton) -----------------------------------
 * Standalone full-screen view served from /editor/:id. Reuses the
 * charcoal palette tokens at the top of this file via the body element
 * inheriting --bg / --text / etc. Phase 2 will add the bottom command
 * bar; Phase 1 is image-center + back link only.
 *
 * Layout: 100vh column. Topbar fixed-height (52px), stage fills the
 * rest, image scales to fit with breathing room on both axes.
 */

.editor-body {
  margin: 0;
  min-height: 100vh;
  background: var(--bg);
  color: var(--text);
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    Helvetica, Arial, sans-serif;
  display: flex;
  flex-direction: column;
}

.editor-topbar {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 16px;
  border-bottom: 1px solid var(--border);
  background: var(--rail-bg);
  flex: 0 0 auto;
}

.editor-back {
  text-decoration: none;
}

.editor-title {
  font-size: var(--font-label-size);
  font-weight: var(--font-label-weight);
  letter-spacing: var(--font-label-tracking);
  line-height: var(--font-label-line);
  text-transform: var(--font-label-transform);
  color: var(--text-label);
}

.editor-topbar-spacer {
  flex: 1;
}

.editor-stage {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-9);
  overflow: hidden;
}

.editor-image-wrap {
  position: relative;
  max-width: min(1200px, 92vw);
  max-height: 80vh;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.editor-image {
  max-width: 100%;
  max-height: 80vh;
  object-fit: contain;
  border-radius: 8px;
  background: var(--canvas-pattern-bg);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}

.editor-loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  color: var(--muted);
  font-size: 14px;
}

.editor-loading .spinner {
  width: 36px;
  height: 36px;
  border: 3px solid var(--border-strong);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: gallery-spin 0.9s linear infinite;
}

.editor-error {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  color: var(--muted);
  text-align: center;
}

.editor-error p {
  margin: 0;
  font-size: 14px;
  color: var(--text);
}

/* Specificity override: `display: flex` on .editor-loading and
 * .editor-error wins against the browser default `[hidden]
 * { display: none }`, so the elements stay visible even when JS
 * toggles `.hidden = true`. Same Phase-7 pattern as
 * `.gallery-grid[hidden]`. Explicit override restores the toggle. */
.editor-loading[hidden],
.editor-error[hidden] {
  display: none;
}

/* --- Editor Dock (Phase 2A) ----------------------------------------
 * Bottom-floating command bar, Magnific-style. Sits above the image
 * stage on a charcoal surface with subtle border + shadow so the
 * image area below stays the visual anchor. Inner row is flex with
 * the prompt taking remaining width; engine row, count grid, and
 * submit button hug the right.
 */

.editor-dock {
  position: fixed;
  left: 50%;
  bottom: 18px;
  transform: translateX(-50%);
  width: min(1100px, calc(100vw - 32px));
  z-index: 30;
  pointer-events: none;
}

.editor-dock-inner {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: 14px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);
  backdrop-filter: blur(6px);
}

.editor-prompt {
  flex: 1 1 auto;
  min-width: 0;
  padding: var(--space-5) var(--space-7);
  background: var(--surface-2);
  border: 1px solid var(--border);
  /* 10 px sits one notch above --radius-lg (8 px) — chosen to give
   * the editor dock's primary prompt a slightly softer corner than
   * the inline form prompts. Outlier on purpose, kept raw. */
  border-radius: 10px;
  color: var(--text);
  font-size: 14px;
  outline: none;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.editor-prompt::placeholder {
  color: var(--placeholder);
}
.editor-prompt:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(59, 110, 245, 0.18);
}

.editor-engine-row {
  display: flex;
  gap: 4px;
  flex: 0 0 auto;
}
.editor-engine-row label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px 6px 6px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  cursor: pointer;
  font-size: 12px;
  color: var(--muted);
  transition: border-color 0.15s ease, color 0.15s ease,
    background 0.15s ease;
}
.editor-engine-row label:hover {
  border-color: var(--border-strong);
  color: var(--text);
}
.editor-engine-row label:has(input[type="radio"]:checked) {
  background: linear-gradient(
    135deg,
    var(--ctl-fill-start),
    var(--ctl-fill-end)
  );
  border-color: transparent;
  color: var(--ctl-on-text);
}
.editor-engine-row input[type="radio"] {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}
.editor-engine-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 30px;
  height: 22px;
  padding: 0 6px;
  background: rgba(0, 0, 0, 0.4);
  border-radius: 999px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--text);
}
.editor-engine-label {
  font-weight: 500;
}

.editor-count-wrap {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding-left: 4px;
  border-left: 1px solid var(--border);
  flex: 0 0 auto;
}
.editor-count-label {
  font-size: 12px;
  color: var(--muted);
}
.editor-count-grid {
  display: inline-flex;
}

.editor-submit {
  flex: 0 0 auto;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: none;
  cursor: pointer;
  background: linear-gradient(
    135deg,
    var(--ctl-fill-start),
    var(--ctl-fill-end)
  );
  color: var(--ctl-on-text);
  font-size: 18px;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: filter 0.15s ease, transform 0.05s ease;
}
.editor-submit:hover:not(:disabled) {
  filter: brightness(1.08);
}
.editor-submit:active:not(:disabled) {
  transform: scale(0.97);
}
.editor-submit:disabled {
  background: var(--surface-2);
  color: var(--placeholder);
  cursor: not-allowed;
}

/* Pad the stage bottom so the dock never overlaps the image. */
.editor-stage {
  padding-bottom: 100px;
}
.editor-image {
  max-height: calc(100vh - 200px);
}

/* Phase 2A — canvas busy overlay. Lives inside .editor-image-wrap so it
 * floats over the image during a generation run. Shows the
 * "Generating… (X/N done)" progress text. */
.editor-busy-overlay {
  position: absolute;
  left: 50%;
  bottom: 18px;
  transform: translateX(-50%);
  padding: 8px 14px;
  background: rgba(27, 27, 29, 0.85);
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  color: var(--text);
  font-size: 13px;
  letter-spacing: 0.01em;
  pointer-events: none;
  backdrop-filter: blur(6px);
}
.editor-busy-overlay[hidden] {
  display: none;
}

/* --- Phase 2B — reference upload UI -------------------------------- */

.editor-ref-cluster {
  position: relative;
  flex: 0 0 auto;
}
.editor-ref-btn {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text);
  font-size: 18px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.editor-ref-btn:hover:not(:disabled) {
  border-color: var(--border-strong);
  background: var(--surface);
}
.editor-ref-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.editor-ref-menu {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 0;
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  padding: 4px;
  min-width: 180px;
  z-index: 35;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.55);
}
.editor-ref-menu[hidden] { display: none; }
.editor-ref-menu button {
  display: block;
  width: 100%;
  padding: 8px 12px;
  background: transparent;
  border: none;
  border-radius: 6px;
  color: var(--text);
  font-size: 13px;
  text-align: left;
  cursor: pointer;
}
.editor-ref-menu button:hover {
  background: var(--rail-active);
}

/* Reference chips strip — sits above the dock-inner */
.editor-refs-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: var(--space-4) var(--space-6) 0;
}
.editor-refs-strip[hidden] { display: none; }

.editor-ref-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 8px 4px 4px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 11px;
}
.editor-ref-chip img {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  object-fit: cover;
}
.editor-ref-chip-label {
  color: var(--muted);
  font-weight: 500;
}
.editor-ref-chip-remove {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: none;
  background: rgba(255, 255, 255, 0.08);
  color: var(--text);
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.editor-ref-chip-remove:hover {
  background: rgba(255, 255, 255, 0.18);
}

/* Gallery picker overlay */
.editor-gallery-picker {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 40;
}
.editor-gallery-picker[hidden] { display: none; }
.editor-gallery-picker-card {
  width: min(720px, calc(100vw - 32px));
  max-height: 80vh;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: 14px;
  display: flex;
  flex-direction: column;
}
.editor-gallery-picker-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-6) var(--space-8);
  border-bottom: 1px solid var(--border);
  font-size: 14px;
  color: var(--text);
  font-weight: 500;
}
.editor-gallery-picker-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: 8px;
  padding: var(--space-8);
  overflow-y: auto;
}
.editor-gallery-picker-cell {
  aspect-ratio: 1;
  padding: 0;
  border: 1px solid var(--border);
  background: var(--surface-2);
  border-radius: 8px;
  cursor: pointer;
  overflow: hidden;
  transition: border-color 0.15s ease;
}
.editor-gallery-picker-cell:hover {
  border-color: var(--accent);
}
.editor-gallery-picker-cell img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.editor-gallery-picker-empty,
.editor-gallery-picker-loading {
  padding: var(--space-9);
  text-align: center;
  color: var(--muted);
  font-size: 13px;
}

/* --- Phase 2C — result actions (Save & Continue + Download) ----- */

.editor-result-actions {
  position: absolute;
  top: 12px;
  right: 12px;
  display: flex;
  gap: 8px;
  z-index: 5;
  pointer-events: auto;
}
.editor-result-actions[hidden] {
  display: none;
}
.editor-result-actions .btn {
  backdrop-filter: blur(6px);
  background: rgba(27, 27, 29, 0.72);
}
.editor-result-actions a[aria-disabled="true"] {
  opacity: 0.45;
  pointer-events: none;
}
