/* ════════════════════════════════════════════════════════════════════════════
   soup.css — styling for the simplified "soup" frontend (soup.html / soup.js).

   Extracted from editor.css: only the pieces soup.js actually uses — the design
   tokens, the reusable MenuBar chrome (burger menu), the Soup/Recipe dialogs
   (openRecipeDialog reuses the .soup-* chrome), and the form-control look shared
   by those dialogs. The shell layout that used to live inline in the page
   (viewport / burger host / optimize dock / prompt box) is folded in at the end.
   ════════════════════════════════════════════════════════════════════════════ */

:root {
  /* ── Fonts ─────────────────────────────────────────────────────────────── */
  --font-ui: 'Courier New', Courier, monospace;
  --font-mono: 'Courier New', Courier, monospace;
  /* The prompt / negative-prompt textareas — the one place the user's own words
     appear, so they get their own face and size rather than the UI's. */
  --font-prompt: var(--font-ui);
  --font-size-prompt: 1.0rem;

  /* ── Metrics ───────────────────────────────────────────────────────────── */
  --menu-bar-h: 30px;   /* top menu bar height (border-box) */
  /* Every button sprite in the dock — the action buttons and the target rack
     alike, in both orientations. The masks are fitted with `contain`, so this is
     the box they fit inside rather than the drawing's own size: a tall sprite
     ends up this high, a wide one this wide. */
  --icon-size: 2.0rem;

  /* ── Text colours ──────────────────────────────────────────────────────── */
  --c-label-text: #ffffff;         /* widget + field labels */
  --c-header-text: #fffefe;        /* headers */
  --c-text: #000000;               /* default body text */
  --c-text-muted: #fcfcfc91;         /* values, readouts, counts */
  --c-editable-text: #ffffff;      /* text / number input content */

  /* ── Surfaces & borders ────────────────────────────────────────────────── */
  --c-panel-bg: #709188ab;           /* panels, toolbars, dialogs */
  --c-editable-bg: #0000003d;      /* text / number input background */
  --c-border: #fdfdfd;             /* hairline borders, dividers */

  /* ── General concepts ──────────────────────────────────────────────────── */
  --c-highlight: #cfe7e5;          /* hover */
  --c-selected: #ff5715;           /* selection accent */
  --c-selected-entity: var(--c-selected); /* selected entity (read by soup.js) */
  --c-emphasis: #ff5100;           /* transient op-group emphasis (read by soup.js) */

  /* ── Scrollbars ────────────────────────────────────────────────────────── */
  --c-scrollbar: rgba(255, 255, 255, 0.3);
  --c-scrollbar-hover: var(--c-highlight);

  /* ── Controls ──────────────────────────────────────────────────────────── */
  --c-checkbox-bg: #ffffff;
  --c-checkbox-checked: var(--c-selected);

  --c-button-bg: #00000000;
  --c-button-bg-hover: var(--c-highlight);
  --c-button-bg-active: var(--c-highlight);

  --c-dropdown-bg: var(--c-editable-bg);

  --c-slider-track: #00000048;
  --c-slider-thumb: #ffffff;
  --c-slider-thumb-hover: var(--c-highlight);

  /* ── Chrome over the drawing ───────────────────────────────────────────── */
  /* The dock and the floating panels sit on top of the canvas, so their
     surfaces are translucent rather than opaque. */
  --c-chrome-bg: rgba(145, 133, 133, 0.384);       /* dock controls: status, knobs, facets */
  --c-chrome-panel-bg: rgba(139, 139, 139, 0.85);  /* optimizer / profiler overlays */
  --c-chrome-inset-bg: rgba(255, 255, 255, 0.6);   /* wells inside chrome (image thumb) */

  /* Dark washes laid over a control's own surface to mark its state. */
  --c-wash: rgba(0, 0, 0, 0.06);
  --c-wash-hover: rgba(0, 0, 0, 0.12);
  --c-wash-active: rgba(0, 0, 0, 0.0);

  /* ── Shadows (colour only — the offsets stay at the use site) ──────────── */
  --c-shadow-control: rgba(0, 0, 0, 0.25);
  --c-shadow-menu: rgba(0, 0, 0, 0.35);
  --c-shadow-dialog: rgba(0, 0, 0, 0.4);

  /* ── Hairlines and dividers drawn over chrome ─────────────────────────── */
  --c-rule: rgba(0, 0, 0, 0.15);
  --c-rule-soft: rgba(0, 0, 0, 0.12);
  --c-rule-strong: rgba(0, 0, 0, 0.25);

  /* ── Target facet tints ───────────────────────────────────────────────── */
  /* One per facet in the dock's rack. The same colour paints the facet itself
     in landscape and its full-width panel in portrait. */
  --c-facet-prompt: rgba(139, 243, 247, 0.473);
  --c-facet-neg: rgba(255, 134, 184, 0.404);
  --c-facet-neg-text: var(--c-text);
  --c-facet-coherence: rgba(255, 255, 255, 0.507);
  --c-facet-image: rgba(195, 255, 83, 0.582);
  --c-facet-symmetry: rgba(137, 255, 196, 0.507);

  /* ── Button icons ─────────────────────────────────────────────────────── */
  /* Sprites are painted as a flat fill through the traced mask's alpha
     (see `.icon-mask`), so their colour is a plain token. */
  --c-icon: #000000;

  /* ── One-offs ─────────────────────────────────────────────────────────── */
  --c-app-bg: #d9e6dd;                          /* window behind the viewport */
  --c-slider-accent: #2f6f8f;                   /* filled part of the dock's sliders */
  --c-dialog-scrim: rgba(73, 255, 170, 0.562);  /* wash behind a modal dialog */
  --c-profile-bar-other: rgba(0, 0, 0, 0.14);   /* "other" rows in the profiler */

  font-family: var(--font-ui);
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0px;
  padding: 0;
  width: 100%;
  height: 100%;

  background-color: var(--c-app-bg);

  /* Firefox: always-visible thin scrollbar when content overflows. */
  scrollbar-width: thin;
  scrollbar-color: var(--c-scrollbar) transparent;

  overflow: hidden;
}

/* ── Top menu bar (reused by the burger menu) ─────────────────────────────── */
.menu-bar {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  height: var(--menu-bar-h);
  background: transparent;
  /* border-bottom: 1px solid var(--c-border); */
  user-select: none;
  font-size: 1.0rem;
}

.menu-bar__item {
  position: relative;
  display: flex;
}

.menu-bar__top {
  border: none;
  background: transparent;
  color: inherit;
  padding: 12px;
  cursor: pointer;
  font: inherit;
}

.menu-bar__top:hover,
.menu-bar__top--open {
  background: var(--c-highlight);
}

.menu-dropdown {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 2000;
  min-width: 200px;
  padding: 4px 0;
  background: var(--c-panel-bg);
  border: 1px solid var(--c-border);
  border-radius: 4px;
  /* box-shadow: 0 6px 18px var(--c-shadow-menu); */
}

.menu-dropdown--sub {
  top: -5px;              /* align with the parent row, past the panel padding */
  left: 100%;
  max-height: 70vh;
  overflow-y: auto;
}

.menu-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 12px 4px 8px;
  cursor: pointer;
  white-space: nowrap;
}

.menu-row:hover {
  background: var(--c-highlight);
}

.menu-row--disabled {
  opacity: 0.4;
  cursor: default;
}
.menu-row--disabled:hover {
  background: transparent;
}

.menu-row__check {
  flex: 0 0 auto;
  width: 1em;
  text-align: center;
}

.menu-row__label {
  flex: 1 1 auto;
}

.menu-row__hint {
  flex: 0 0 auto;
  margin-left: 16px;
  opacity: 0.6;
}

.menu-sep {
  height: 1px;
  margin: 4px 0;
  background: var(--c-border);
}

/* ── Soup dialog chrome (reused by the Recipe dialog) ─────────────────────── */
.soup-overlay {
  position: fixed;
  inset: 0;
  z-index: 3000;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  background: var(--c-dialog-scrim);
}

.soup-dialog {
  min-width: 320px;
  max-height: 90vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 16px;
  background: var(--c-panel-bg);
  border: 1px solid var(--c-border);
  border-radius: 6px;
  color: var(--c-text);
  /* box-shadow: 0 8px 32px var(--c-shadow-dialog); */
}

.soup-title {
  font-weight: 700;
  font-size: 15px;
  color: var(--c-header-text);
}

.soup-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  color: var(--c-label-text);
}

.soup-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 4px;
}

.soup-btn {
  padding: 5px 16px;
  background: var(--c-button-bg);
  border: none; /* 1px solid var(--c-border); */
  border-radius: 3px;
  cursor: pointer;
  color: var(--c-text);
}

.soup-btn:hover {
  background: var(--c-button-bg-hover);
}

.soup-btn--primary {
  font-weight: 700;
}

/* ── Recipe dialog (tabbed: Soup / Grid / Pixels / Shape / Curve) ─────────────
   Its .recipe-* rules live with the component, in
   modules/graph/ui/recipe_dialog.css, which recipe_dialog.js injects on open.
   Only the .soup-* chrome it borrows (above) and the design tokens are ours. */

/* ════════════════════════════════════════════════════════════════════════════
   INPUT CONTROLS — styled by element type (shared by the dialogs above)
   ════════════════════════════════════════════════════════════════════════════ */

button {
  margin : 4px;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 8px;
  border: 1px solid var(--c-border);
  border-radius: 0px;
  background: var(--c-button-bg);
  cursor: pointer;
  font-size: 0.8rem;
  font-family: var(--font-ui);
  user-select: none;
  transition: background 0.1s;
}
button:hover { background: var(--c-button-bg-hover); }
button:active { background: var(--c-button-bg-active); }

/* select elements / dropdowns */
select {
  margin : 4px;
  width: 100%;
  padding: 2px 4px;
  border: none;
  border-radius: 3px;
  background: var(--c-dropdown-bg);
  cursor: pointer;
  font-size: 0.8rem;
  font-family: var(--font-ui);
}

input {
  margin : 4px;
  font-family: var(--font-ui);
  font-size: 0.8rem;
}

input[type="range"] {
  width: 100%;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  background: transparent;  /* hide the default track */
  height: 20px;           /* control height — track will fill this */
}

/* ── Chromium / Safari ── */
input[type="range"]::-webkit-slider-runnable-track {
  height: 100%;
  border: none;
  border-radius: 6px;
  background: var(--c-slider-track);
  padding: 4px;
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 12px;
  height: 12px;
  margin-top: 0px;        /* centre thumb: (track 20px - thumb 12px) / 2 */
  border: none;
  border-radius: 6px;
  background: var(--c-slider-thumb);
  cursor: pointer;
}
input[type="range"]::-webkit-slider-thumb:hover { background: var(--c-slider-thumb-hover); }

/* ── Firefox ── */
input[type="range"]::-moz-range-track {
  height: 100%;
  border: none;
  border-radius: 6px;
  background: var(--c-slider-track);
}
input[type="range"]::-moz-range-thumb {
  width: 10px;
  height: 10px;
  border: none;
  border-radius: 6px;
  background: var(--c-slider-thumb);
  cursor: pointer;
}

input[type="text"],
input[type="number"],
textarea {
  width: 100%;
  padding: 2px 5px;
  border: none;
  border-radius: 3px;
  font-size: 0.8rem;
  background: var(--c-editable-bg);
  color: var(--c-editable-text);
}
input[type="text"]:focus,
input[type="number"]:focus,
textarea:focus { outline: none; }

/* Fully custom checkbox: native rendering ignores background on the unchecked
   box (accent-color only tints the checked fill), so we opt out with
   appearance:none and draw the box + checkmark ourselves. */
input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  margin: 0;
  width: 14px;
  height: 14px;
  cursor: pointer;
  border: 1px solid var(--c-border);
  border-radius: 3px;
  background: var(--c-checkbox-bg);
  display: inline-grid;
  place-content: center;
}
input[type="checkbox"]:checked {
  background: var(--c-checkbox-checked);
  border-color: var(--c-checkbox-checked);
}
/* Checkmark (shown only while checked). */
input[type="checkbox"]::after {
  content: "";
  width: 4px;
  height: 8px;
  border: solid var(--c-editable-text);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg) translateY(-1px);
  opacity: 0;
}
input[type="checkbox"]:checked::after { opacity: 1; }

/* Chromium / Safari: visible thumb whenever a panel is scrollable. */
::-webkit-scrollbar {
  width: 8px;
  height: 8px;
  background: transparent;
}
::-webkit-scrollbar-button { display: none; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--c-scrollbar);
  border-radius: 8px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover {
  background: var(--c-scrollbar-hover);
  background-clip: padding-box;
}

/* ════════════════════════════════════════════════════════════════════════════
   SOUP SHELL — full-window viewport + floating chrome (formerly inline in the
   page): a burger menu (top-left) and the optimize dock (bottom-center).
   ════════════════════════════════════════════════════════════════════════════ */
/* Square rendering viewport: JS ({@link layoutViewport} in soup.js) sizes and
   places it — a square whose side fits the window width and the space left
   above the optimize dock, horizontally centred with its bottom edge sitting
   just above that bottom row of controls. The values here are only the
   pre-layout fallback. */
#viewport {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  border: 1px solid var(--c-border);
}

/* Floating burger-menu host, overlaid on the top-left of the viewport.

   Stacked above every other overlay — the dock and the profiler at 20, the
   optimizer-config panel at 25 — so an open dropdown is never buried under one.
   A tall profiler reaches this corner (it is centred on the window height), and
   without this it would win on document order alone, both being 20.

   The z-index has to be said *here* rather than on `.menu-dropdown`: this
   element is positioned and stacked, so it opens a stacking context, and the
   dropdown's own `z-index: 2000` only orders it against its siblings inside
   that context — it can never lift it past a panel outside. Kept below
   `.soup-overlay` (3000), which is modal and should cover the menu. */
#burger-host {
  position: absolute;
  top: 16px;
  left: 16px;
  z-index: 40;
}
/* Collapse the reused MenuBar from a full-width bar to a compact,
   rounded floating control. */
#burger-host .menu-bar {
  height: auto;
  width: auto;
  border: none; /*1px solid var(--c-border); */
  border-radius: 1px;
  /* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25); */
  overflow: visible;
}
#burger-host .menu-bar__top {
  font-size: 1.5rem;
  line-height: 1;
}

/* ── Optimize dock (bottom bar) ─────────────────────────────────────────
   A full-window-width bar along the bottom edge, 10% tall (min 100px), laid out
   as
       [opt][..... target rack .....][recipe][rnd][reset][align]
       [................ status ..............................]
   The bar itself stays transparent — only its controls carry chrome — so the
   drawing above still reads through the gaps.
*/
.optimize-dock {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 0 8px 8px;
  height: 10%;
  min-height: 100px;
}

/* Top row: button + prompt, filling the remaining block height. */
.optimize-dock__row {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  gap: 6px;
  flex: 1 1 auto;
  min-height: 0;
}

.optimize-dock__status {
  flex: 0 0 auto;
  padding: 3px 10px;
  font-size: 0.78rem;
  color: var(--c-text);
  background: var(--c-chrome-bg);
  border: none; /* 1px solid var(--c-border); */
  border-radius: 6px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.optimize-dock__btn {
  flex: 0 0 auto;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.2rem;
  font-weight: 600;
  color: var(--c-icon);
  padding: 6px 16px;
  border: 1px solid var(--c-border);
  border-radius: 2px;
  cursor: pointer;
}

/* Sprite icons. The sheets in the icon set soup.js points at (`ICON_SET_DIR`)
   are ink on opaque paper, so it traces their darkness into an alpha mask first
   (see `traceIcon`); rather than paint that image and re-tint it, each icon is an
   empty span filled with a flat colour and clipped to the mask — which is what
   makes the colour a token (`--c-icon`) instead of a filter. soup.js hands the
   traced mask's address in through `--icon-url` (see `buildIcon`). */
.optimize-dock__btn-icon,
.target-facet__icon {
  display: block;
  width: var(--icon-size);
  height: var(--icon-size);
  background-color: var(--c-icon);
  -webkit-mask-image: var(--icon-url);
          mask-image: var(--icon-url);
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: center;
          mask-position: center;
  -webkit-mask-size: contain;
          mask-size: contain;
}

/* Small labelled control cluster at the right end of the dock row (symmetry),
   stacked vertically and matching the dock's translucent chrome. */
.optimize-dock__knob {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 1px;
  padding: 4px 8px;
  border-radius: 2px;
  background: var(--c-chrome-bg);
  /* box-shadow: 0 10px 8px var(--c-shadow-control); */
  font-size: 0.72rem;
  color: var(--c-text);
  user-select: none;
}

.optimize-dock__knob-title {
  font-weight: 600;
  opacity: 0.7;
}

.optimize-dock__knob-row {
  display: flex;
  align-items: center;
  gap: 3px;
  cursor: pointer;
}

.optimize-dock__knob-row input[type="checkbox"] {
  margin: 0;
  cursor: pointer;
}

/* Qualified: the global `input[type="number"]` rule sets width:100%, and a bare
   class selector would lose to it. */
input.optimize-dock__knob-num {
  flex: 0 0 auto;
  width: 3.4rem;
  padding: 0 2px;
  border: none;
  border-radius: 2px;
  background: var(--c-dropdown-bg);
  font-size: 0.72rem;
  font-variant-numeric: tabular-nums;
  color: var(--c-editable-text);
  cursor: text;
}

input.optimize-dock__knob-num:disabled {
  opacity: 0.4;
  cursor: default;
}

/* ── Target rack ─────────────────────────────────────────────────────────
   The five target facets — prompt, negative prompt, coherence, image
   similarity, symmetry — as one exclusive row of icon buttons. The active
   facet expands to the right, taking every spare pixel in the rack, and
   reveals its own editor; the rest stay collapsed to their button.

   The expansion animates on `flex-grow` (a plain number, hence interpolable)
   against a fixed `flex-basis` of one button width: a collapsed facet is
   exactly its button, the open one is that plus all the free space. */
.target-rack {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: row;
  align-items: stretch;
  gap: 4px;
  overflow: hidden;
}

.target-facet {
  --facet-btn-w: 46px;
  display: flex;
  flex-direction: row;
  align-items: stretch;
  flex: 0 1 var(--facet-btn-w);
  min-width: 0;
  color: var(--c-text);
  background: var(--c-chrome-bg);
  /* box-shadow: 0 10px 8px var(--c-shadow-control); */
  opacity: 1.0;
  transition: flex-grow 260ms cubic-bezier(0.22, 0.61, 0.36, 1),
              opacity 200ms ease;
}
.target-facet:hover { opacity: 0.85; }
.target-facet--open { flex-grow: 1; opacity: 1; }

/* Facet tints, carried over from the old target box: bluish positive prompt,
   reddish negative, sandy image. */
.target-facet--prompt    { background: var(--c-facet-prompt); }
.target-facet--neg       { background: var(--c-facet-neg); color: var(--c-facet-neg-text); }
.target-facet--coherence { background: var(--c-facet-coherence); }
.target-facet--image     { background: var(--c-facet-image); }
.target-facet--symmetry  { background: var(--c-facet-symmetry); }

.target-facet__btn {
  flex: 0 0 var(--facet-btn-w);
  width: var(--facet-btn-w);
  margin: 0;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--c-border);  
  border-radius: 0;
  background: var(--c-wash);
  color: inherit;
  cursor: pointer;
}
.target-facet__btn:hover { background: var(--c-wash-hover); }
.target-facet--open .target-facet__btn { 
  background: var(--c-wash-active); 
  border-right: none;
}

/* Every facet's sprite looks the same, the anti prompt's included: it is
   inverted in the mask itself — a filled tile with the prompt's drawing knocked
   out of it (see `ICONS_INVERTED`) — so unlike the old sprite it needs no colour
   flip of its own here. */
.target-facet__icon {
  opacity: 0.8;
}

/* The revealed editor. Clipped while collapsed (the facet is then only as wide
   as its button, so this box is 0px) and faded in a beat after the width starts
   moving, which hides the content reflowing as the panel widens. */
.target-facet__panel {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  opacity: 0;
  pointer-events: none;
  transition: opacity 160ms ease;
  border: 1px solid var(--c-border);
  border-left: none;
}
.target-facet__panel--open {
  opacity: 1;
  pointer-events: auto;
  transition: opacity 200ms ease 110ms;
}

/* Portrait's second row. Empty in landscape (the panels are inside their
   facets there), and hidden so the dock's column gap does not reserve space for
   it. */
.target-panel-row {
  display: none;
}

/* Names the open editor in its top-right corner — the facet's icon alone does
   not say which target is being edited. Inert to the pointer so it never steals
   a click from the control underneath. */
.target-facet__label {
  position: absolute;
  top: 3px;
  right: 8px;
  font-size: 0.6rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  color: inherit;
  opacity: 0.5;
  pointer-events: none;
}

/* ── Facet editors ───────────────────────────────────────────────────────── */

/* Prompt / negative prompt: a bare textarea filling the panel. */
.facet-prompt { height: 100%; }

.facet-prompt__input {
  display: block;
  width: 100%;
  height: 100%;
  /* Extra headroom so the first line clears the corner label. */
  padding: 18px 10px 6px;
  margin: 0;
  border: none;
  background: transparent;
  color: inherit;
  font-family: var(--font-prompt);
  font-size: var(--font-size-prompt);
  font-weight: bold;
  line-height: 1.3;
  resize: none;
  outline: none;
}
.facet-prompt__input::placeholder { color: currentColor; opacity: 0.5; }

/* One-line slider editor: [label][range][value]. Used on its own by the
   coherence facet and inline (--inline) inside the image facet's controls. */
.facet-slider {
  height: 100%;
  padding: 6px 12px;
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 8px;
  min-width: 0;
  font-size: 0.78rem;
}
.facet-slider--inline {
  height: auto;
  padding: 0;
  font-size: 0.72rem;
}

.facet-slider__label {
  flex: 0 0 auto;
  opacity: 0.75;
  white-space: nowrap;
}

.facet-slider__range {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  accent-color: var(--c-slider-accent);
  cursor: pointer;
}
.facet-slider__range:disabled { opacity: 0.4; cursor: default; }

.facet-slider__value {
  flex: 0 0 auto;
  min-width: 3.4rem;
  text-align: right;
  font-variant-numeric: tabular-nums;
  opacity: 0.85;
}

/* Image target: thumbnail + [load button / balance slider]. */
.facet-image {
  height: 100%;
  padding: 6px 10px;
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

.facet-image__thumb {
  flex: 0 0 auto;
  height: 100%;
  max-height: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border: 1px solid var(--c-rule-strong);
  background: var(--c-chrome-inset-bg);
  cursor: pointer;
}
.facet-image__thumb--empty {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.72rem;
  color: var(--c-text);
  opacity: 0.6;
  border-style: dashed;
}

.facet-image__controls {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 6px;
}

.facet-image__load {
  align-self: flex-start;
  margin: 0;
  padding: 3px 12px;
  font-size: 0.85rem;
  font-weight: 600;
  border: 1px solid var(--c-border);
  border-radius: 4px;
  background: var(--c-panel-bg);
  color: var(--c-text);
  cursor: pointer;
}

/* Symmetry: the knob cluster laid out along the panel instead of stacked, and
   stripped of the chrome the facet already provides. */
.target-facet__panel .optimize-dock__knob {
  flex-direction: row;
  align-items: center;
  justify-content: flex-start;
  height: 100%;
  gap: 14px;
  padding: 6px 12px;
  background: transparent;
  box-shadow: none;
  font-size: 0.85rem;
}

/* ── Portrait: three stacked rows ────────────────────────────────────────
   A narrow window has no horizontal room for a facet to expand into, so the
   dock splits into
       [ ▶  recipe  randomize  reset  align ]   ← actions
       [ prompt  anti  coherence  image  sym ]  ← the expandable facets
       [ ............ open editor .......... ]
   soup.js moves the panels into `.target-panel-row`, so the facet buttons sit
   directly above the panel they open and the active one fades up there instead
   of growing sideways.

   The split is a wrap, not a second container: the rack takes `order: 1` and a
   full-width basis, which pushes it past the action buttons and onto its own
   line. Every button then stretches to an equal share of its row.
*/
@media (max-aspect-ratio: 1 / 1) {
  /* Sized by its contents rather than a share of the window: the button row is
     as tall as its buttons and the panel row has a fixed band, so the bar grows
     only by what it actually holds. */
  .optimize-dock--portrait {
    height: auto;
    min-height: 0;
  }

  /* The button rows are only as tall as their buttons; the panel row takes the
     slack. */
  .optimize-dock--portrait .optimize-dock__row {
    flex: 0 0 auto;
    flex-wrap: wrap;
  }

  /* Each action button takes an equal share of the first line. */
  .optimize-dock--portrait .optimize-dock__btn {
    flex: 1 1 0;
    justify-content: center;
  }

  /* A full-width basis breaks the line, and `order` puts that break after the
     action buttons rather than in the middle of them. */
  .optimize-dock--portrait .target-rack {
    order: 1;
    flex: 1 1 100%;
    overflow: visible;
  }

  /* No expansion sideways: the open facet is one equal share of the rack row
     like every other, and only the dimming distinguishes it. */
  .optimize-dock--portrait .target-facet,
  .optimize-dock--portrait .target-facet--open {
    flex: 1 1 0;
  }

  /* On its own line the rack no longer stretches to the action buttons' height,
     so match their padding deliberately — the sprites are already the same
     `--icon-size` — which leaves the two rows level. */
  .optimize-dock--portrait .target-facet__btn {
    flex: 1 1 auto;
    width: auto;
    padding: 6px 0;
  }

  .optimize-dock--portrait .target-panel-row {
    position: relative;
    display: block;
    flex: 0 0 88px;
    /* box-shadow: 0 10px 8px var(--c-shadow-control); */
  }

  /* Every panel is stacked in the same full-width box; only the open one is
     painted, so switching facets crossfades in place. */
  .optimize-dock--portrait .target-panel-row > .target-facet__panel {
    position: absolute;
    inset: 0;
    background: var(--c-chrome-bg);
  }

  /* Panel tints, matching the facet buttons they belong to. Written with the
     same `> ` shape as the fallback above so they outrank it. */
  .optimize-dock--portrait .target-panel-row > .target-facet__panel--prompt    { background: var(--c-facet-prompt); }
  .optimize-dock--portrait .target-panel-row > .target-facet__panel--neg       { background: var(--c-facet-neg); color: var(--c-facet-neg-text); }
  .optimize-dock--portrait .target-panel-row > .target-facet__panel--coherence { background: var(--c-facet-coherence); }
  .optimize-dock--portrait .target-panel-row > .target-facet__panel--image     { background: var(--c-facet-image); }
  .optimize-dock--portrait .target-panel-row > .target-facet__panel--symmetry  { background: var(--c-facet-symmetry); }
}

/* ── Optimizer-config panel (right edge) ─────────────────────────────────
   Toggled from the ☰ menu; its rows are generated from the backend's option
   schema by soup.js. Sits between the top-right corner and the dock, and
   scrolls its body, so a long schema stays reachable without covering
   anything. Hidden until .optim-panel--open.
*/
.optim-panel {
  position: absolute;
  top: 16px;
  right: 16px;
  /* Clear the dock, which is `bottom: 8px` and `height: 10%` (min 100px). */
  bottom: calc(max(10%, 100px) + 16px);
  min-height: 180px;
  z-index: 25;
  width: 340px;
  max-width: calc(100vw - 32px);
  display: none;
  flex-direction: column;
  padding: 8px 10px 8px 12px;
  border-radius: 2px;
  border: 1px solid var(--c-border);
  background: var(--c-chrome-panel-bg);
  /* box-shadow: 0 10px 8px var(--c-shadow-control); */
  color: var(--c-text);
  font-size: 0.72rem;
  user-select: none;
}

.optim-panel--open { display: flex; }

.optim-panel__head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.optim-panel__title {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--c-header-text);
}

.optim-panel__close {
  margin: 0;
  padding: 2px 6px;
  background: transparent;
  font-size: 0.8rem;
  line-height: 1;
}

.optim-panel__master {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 0;
  font-weight: 600;
  cursor: pointer;
  border-bottom: 1px solid var(--c-rule);
}

.optim-panel__master input[type="checkbox"] { margin: 0; }

.optim-panel__body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 6px 4px 6px 0;
}

.optim-panel__section {
  margin-top: 6px;
  padding-top: 5px;
  border-top: 1px solid var(--c-rule-soft);
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--c-label-text);
}
.optim-panel__section:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}

/* One option: [☑ label ................ widget], with a bounded numeric's
   slider dropping onto a second line under its readout. */
.optim-panel__row {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.optim-panel__row-head {
  display: flex;
  align-items: center;
  gap: 6px;
}

.optim-panel__row-label {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
}

.optim-panel__row-label input[type="checkbox"] { flex: 0 0 auto; margin: 0; }

.optim-panel__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Dimmed wholesale while the master switch is off; the name alone greys out
   for a row whose own override is simply not ticked. */
.optim-panel__row--off { opacity: 0.4; }
.optim-panel__row--inactive .optim-panel__name { color: var(--c-text-muted); }

.optim-panel__value-check { flex: 0 0 auto; }

/* Qualified: the global `input[type="number"]`/`select` rules set width:100%,
   and a bare class selector would lose to them. */
.optim-panel__row-head input.optim-panel__num {
  flex: 0 0 auto;
  width: 5.2rem;
  margin: 0;
  padding: 1px 4px;
  border: none;
  border-radius: 2px;
  background: var(--c-dropdown-bg);
  font-size: 0.72rem;
  font-variant-numeric: tabular-nums;
  color: var(--c-editable-text);
}

.optim-panel__row-head select.optim-panel__select {
  flex: 0 0 auto;
  width: 10rem;
  margin: 0;
  padding: 1px 2px;
  font-size: 0.66rem;
}

.optim-panel__readout {
  flex: 0 0 auto;
  min-width: 3rem;
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--c-text-muted);
}

.optim-panel__slider { width: 100%; margin: 0; }

.optim-panel__row-head input.optim-panel__num:disabled,
.optim-panel__row-head select.optim-panel__select:disabled,
.optim-panel__slider:disabled { opacity: 0.45; cursor: default; }

.optim-panel__empty {
  color: var(--c-text-muted);
  font-style: italic;
}

.optim-panel__foot {
  flex: 0 0 auto;
  display: flex;
  justify-content: flex-end;
  padding-top: 6px;
  border-top: 1px solid var(--c-rule);
}

.optim-panel__clear {
  margin: 0;
  padding: 3px 8px;
  background: transparent;
  font-size: 0.7rem;
}

/* ── Profiler panel (middle-left) ────────────────────────────────────────
   Backend timing breakdown, streamed in the optimizer's update messages when
   the burger menu's "Profiler" toggle is on (see soup.js renderProfilePanel
   and backend/diff_draw/profiling.py). Each row's background is a two-stop
   gradient: solid to the forward share of the step, hatched on to the total,
   so the fwd/bwd split reads without looking at the numbers.
*/
.profile-panel {
  position: absolute;
  left: 16px;
  top: 50%;
  transform: translateY(-50%);
  z-index: 20;
  width: 360px;
  max-width: calc(100vw - 32px);
  max-height: 70vh;
  overflow-y: auto;
  padding: 8px 10px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: var(--c-text);
  background: var(--c-chrome-panel-bg);
  border-radius: 6px;
  /* soup's viewport has no pointer interaction of its own (view-only, zoom is
     fit-on-resize), so the panel can take events — which is what makes each
     row's fwd/bwd tooltip reachable. */
  user-select: text;
}

.profile-panel__stats {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* Per-step transport stats, above the timings. Its own section so the snapshot
   redraw (every Nth step) leaves a line that updates every step alone. */
.profile-panel__msg {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-bottom: 4px;
}

/* Debug composites from the worker, stacked under the timings. Its own section
   so a snapshot redraw (every Nth step) leaves the images alone. */
.profile-panel__debug {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.profile-panel__frame {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.profile-panel__frame img {
  display: block;
  width: 100%;
  border-radius: 3px;
  /* Composites are small rasters blown up to panel width — keep them crisp
     rather than letting the browser smooth them into mush. */
  image-rendering: pixelated;
  background: var(--c-wash);
}

.profile-panel__frame figcaption {
  font-size: 0.65rem;
  opacity: 0.65;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.profile-panel__head {
  font-weight: 700;
  color: var(--c-header-text);
  padding-bottom: 2px;
}

.profile-panel__hint {
  opacity: 0.7;
  line-height: 1.35;
}

.profile-panel__swatch {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 1px;
}
.profile-panel__swatch--fwd { background: var(--c-highlight); }
.profile-panel__swatch--bwd { background: color-mix(in srgb, var(--c-highlight) 45%, transparent); }

.profile-panel__legend,
.profile-panel__sep {
  margin-top: 4px;
  font-size: 0.65rem;
  opacity: 0.6;
}

.profile-panel__mem {
  margin-top: 6px;
  font-size: 0.65rem;
  opacity: 0.75;
  line-height: 1.3;
}

.profile-panel__row {
  --indent: 0;
  --fwd-pct: 0%;
  --total-pct: 0%;
  --bar: var(--c-highlight);
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding: 1px 3px;
  padding-left: calc(3px + var(--indent) * 9px);
  border-radius: 2px;
  background:
    linear-gradient(to right,
      var(--bar) 0 var(--fwd-pct),
      color-mix(in srgb, var(--bar) 45%, transparent) var(--fwd-pct) var(--total-pct),
      transparent var(--total-pct) 100%);
}

/* Compositing is the row kind the whole exercise exists to isolate, so it gets
   the selection accent rather than the generic highlight. */
.profile-panel__row--compositing { --bar: color-mix(in srgb, var(--c-selected) 55%, white); }
.profile-panel__row--losses      { --bar: var(--c-highlight); }
.profile-panel__row--other       { --bar: var(--c-profile-bar-other); }
/* Payload shares are a different axis from the timings — colour them apart so a
   fat section doesn't read as a slow one. */
.profile-panel__row--msg         { --bar: color-mix(in srgb, var(--c-selected) 30%, white); }

.profile-panel__name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.profile-panel__ms {
  flex: 0 0 auto;
  min-width: 52px;
  text-align: right;
  font-weight: 700;
}

.profile-panel__pct {
  flex: 0 0 auto;
  min-width: 30px;
  text-align: right;
  opacity: 0.65;
}
