/* Hanken Grotesk — the sans body/UI font (--font-sans below), loaded from
   Bunny Fonts (a Google-Fonts-API-compatible, privacy-focused CDN)
   instead of Google Fonts. @import must come before any other rule.
   Weights 400/500/600/700 match exactly what the Inter Google Fonts
   request this replaced used to load (this file's own font-weight
   declarations on --font-sans span that same 400-700 range) — without
   all four, 500/600/700 render as the browser's own synthesized bold
   instead of Hanken Grotesk's real medium/semibold/bold faces. */
@import url(https://fonts.bunny.net/css?family=hanken-grotesk:400,500,600,700);

/* Kitchen Configurator — the shared base layer: a reset, the FULL token
   contract every theme must supply a value for, and every component rule
   shared across brands. Two brands sit on top of this file, each its own
   stylesheet loaded straight after this one: webapp/static/
   theme-kora-kitchens.css (the consumer-facing brand — marketing site,
   client role) and webapp/static/theme-datum-grain.css (the parent/
   internal-business brand — admin/maker/trade roles, pricing admin). See
   webapp/app.py's brand_for_path()/page_brand() for exactly which pages
   resolve to which, and webapp/templates/base.html for the `data-brand`
   attribute both theme files key off.

   This file's own :root below is a DEFENSIVE FALLBACK ONLY — genuinely
   brand-neutral placeholder values, not either brand's real identity (it
   used to secretly BE Kora Kitchens' palette; that coupling is exactly
   what this split removes). Every real page always resolves to one of
   the two theme files above, so these values should never actually be
   seen in practice — if they ever are, that's a bug (a page whose route
   isn't covered by brand_for_path()'s prefix table), and "obviously
   generic grey, needs a theme" is a far easier bug to spot than a
   plausible-looking wrong brand colour.

   Component rules below (buttons, panels, forms, pricing UI, ...) are
   all written against these token names via var() — never a literal
   colour — so they render correctly under whichever theme is active
   with no changes of their own. A handful of components genuinely differ
   in BOX MODEL (not just colour) between the two brands today — .field
   input, .btn, .app-shell, .wizard-option-checkbox, .preview3d-overlay
   and .preview3d-panel, and the elevation SVG's outline colour — those
   keep brand-scoped overrides in theme-datum-grain.css instead (see that
   file's own header comment).
   Everything else that theme file also carries (icon rail, flyout
   panels, the view switcher, the pricing total indicator, the segmented
   view-switcher, .btn-ghost/.btn-icon/.btn-secondary, the collapsed-
   pricing-panel toggle) has no such divergence — it's genuinely
   brand-agnostic UI that happened to only be built once, so it lives
   here instead, unscoped, available to both brands for free. */

:root {
  /* Palette — deliberately plain/generic, see this file's own header
     comment above. Real values live in theme-kora-kitchens.css /
     theme-datum-grain.css. */
  --color-bg: #f5f5f5;
  --color-bg-panel: #ececec;
  --color-bg-raised: #ffffff;
  --color-text: #1a1a1a;
  --color-text-muted: #5f5f5f;
  --color-text-faint: #9a9a9a;
  --color-border: #d9d9d9;
  --color-border-strong: #bdbdbd;

  --color-accent: #4a4a4a;
  --color-accent-soft: #e0e0e0;
  --color-accent-ink: #1a1a1a;

  --color-positive: #4caf50;
  --color-negative: #d32f2f;
  --color-negative-bg: #fdecea;

  /* A generic mid-grey 9-step ramp for each — every consuming component
     (.pricing-tag/.pricing-dialog and friends) only ever needs SOME
     value here, real or not; see this file's own header comment for why
     these are placeholders, not either brand's real ramp. */
  --color-neutral-100: #f7f7f7;
  --color-neutral-200: #ececec;
  --color-neutral-300: #d9d9d9;
  --color-neutral-400: #bdbdbd;
  --color-neutral-500: #9a9a9a;
  --color-neutral-600: #757575;
  --color-neutral-700: #5f5f5f;
  --color-neutral-800: #3d3d3d;
  --color-neutral-900: #212121;
  --color-accent-100: #f2f2f2;
  --color-accent-200: #e0e0e0;
  --color-accent-300: #c2c2c2;
  --color-accent-400: #a3a3a3;
  --color-accent-500: #858585;
  --color-accent-600: #666666;
  --color-accent-700: #4a4a4a;
  --color-accent-800: #333333;
  --color-accent-900: #1a1a1a;

  /* Type — generic system stack; each theme supplies its own real
     font(s) and @import/<link>. */
  --font-sans: system-ui, -apple-system, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
  --font-strapline: var(--font-sans);
  --font-display: var(--font-sans);
  /* Dangling until this split: referenced by pricing_admin_quotes.html/
     pricing_quote_pricing_region.html but never declared anywhere before
     now — silently falling back to the browser default font. Both real
     themes give it a real value (theme-datum-grain.css: the actual
     "Inter" value from the design handoff this app's dark theme was
     ported from; theme-kora-kitchens.css: an alias to its own
     --font-sans, matching this file's own existing h1-h4 rule below). */
  --font-heading: var(--font-sans);

  /* Spacing scale — shared structurally by both themes today; a theme
     may still override it (theme-datum-grain.css does, for its own
     denser UI density). */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;
  --space-8: 64px;

  --radius: 8px;
  --radius-lg: 14px;
  /* Shared by both themes today (no brand-specific value needed yet) —
     unlike --radius/--radius-lg above, this one has no history of ever
     being overridden per-theme either. */
  --radius-sm: 4px;

  /* Generic neutral shadows — each theme supplies its own real tuning
     (a light ground and a dark ground want a different technique, not
     just a different colour — see theme-datum-grain.css's own comment). */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.12);
  --shadow-md: 0 6px 18px rgba(0, 0, 0, 0.16);
  --shadow-lg: 0 16px 40px rgba(0, 0, 0, 0.22);

  --panel-left-width: 304px;
  --panel-right-width: 328px;
}

* {
  box-sizing: border-box;
}

html, body {
  height: 100%;
}

body {
  margin: 0;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-sans);
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 {
  font-family: var(--font-sans);
  font-weight: 500;
  margin: 0;
  color: var(--color-text);
}

a {
  color: inherit;
}

button, input, select {
  font-family: inherit;
  font-size: inherit;
  color: inherit;
}

/* --- App shell: three-panel layout ---------------------------------- */

.app-shell {
  display: grid;
  grid-template-columns: var(--panel-left-width) 1fr var(--panel-right-width);
  height: calc(100vh - 56px);
}

/* Function view (function-view-implementation-prompt.md) takes over the
   whole workspace: partials/workspace.html simply doesn't render
   .panel-left/.panel-right while session.center_view_mode == "function",
   and .panel-center-fullscreen (set on .panel-center in that same
   template) collapses this grid to one full-width column instead of
   panel-center just sitting alone in the grid's first (narrow) track.
   :has() rather than a class on #workspace itself, because every htmx
   swap targeting #workspace uses hx-swap="innerHTML" — the div's own
   class attribute is set once at page load in index.html and never
   touched again by any later swap. */
.app-shell:has(.panel-center-fullscreen) {
  grid-template-columns: 1fr;
}

/* --- Header bar --------------------------------------------------------- */

.app-header {
  height: 56px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 20px;
  background: var(--color-bg-panel);
  border-bottom: 1px solid var(--color-border);
}

.header-group {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.header-brand {
  font-size: 14px;
  font-weight: 500;
  color: var(--color-text);
}

.header-brand-lg {
  font-size: 21px;
}

.header-brand-block {
  display: flex;
  flex-direction: column;
  line-height: 1.15;
}

.header-brand-sub {
  font-size: 12px;
  color: var(--color-text-muted);
}

.header-project-name {
  font-size: 12px;
  color: var(--color-text-muted);
}

.status-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  background: var(--color-bg-raised);
  border: 1px solid var(--color-border);
  border-radius: 14px;
  padding: 5px 12px;
  font-size: 12.5px;
  white-space: nowrap;
}

.status-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  font-size: 9px;
  color: #fff;
  background: var(--color-positive);
}

.status-badge-negative {
  background: var(--color-negative);
}

.status-text-idle {
  color: var(--color-text-faint);
}

.status-detail {
  color: var(--color-text-muted);
}

.status-pill .status-saving-label {
  display: none;
  color: var(--color-accent);
}

.status-pill.htmx-request .status-saving-label {
  display: inline;
}

.status-pill.htmx-request .status-badge,
.status-pill.htmx-request .status-text,
.status-pill.htmx-request .status-detail {
  display: none;
}

.preview-banner {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  font-size: 12.5px;
  color: var(--color-accent);
}

.preview-banner .btn {
  padding: 5px 10px;
  font-size: 12.5px;
}

#undo-redo-controls {
  display: contents;
}

.checkpoint-picker {
  position: relative;
}

.checkpoint-picker summary {
  list-style: none;
  cursor: pointer;
}

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

.checkpoint-dropdown {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 10;
  width: 260px;
  background: var(--color-bg-raised);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
}

.checkpoint-save-form {
  display: flex;
  gap: var(--space-2);
}

.checkpoint-save-form input {
  flex: 1;
  min-width: 0;
  border: none;
  border-bottom: 1px solid var(--color-border-strong);
  background: transparent;
  padding: 4px 2px;
  font-size: 13px;
  color: var(--color-text);
  outline: none;
}

.checkpoint-list {
  list-style: none;
  margin: var(--space-3) 0 0;
  padding: 0;
}

.checkpoint-list li {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  padding: 6px 0;
  border-top: 1px solid var(--color-border);
  font-size: 12.5px;
}

.btn[disabled] {
  color: var(--color-text-faint);
  border-color: var(--color-border);
  background: transparent;
  cursor: not-allowed;
}

.panel {
  overflow-y: auto;
  min-width: 0;
}

.panel-left {
  border-right: 1px solid var(--color-border);
  background: var(--color-bg-panel);
  padding: var(--space-5) var(--space-4);
}

.panel-center {
  padding: var(--space-6) var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

.panel-right {
  border-left: 1px solid var(--color-border);
  background: var(--color-bg-panel);
  padding: var(--space-5) var(--space-4);
  display: flex;
  flex-direction: column;
  /* Positioning context for .pricing-panel-collapse-toggle below, which
     both brands share — see this file's own header comment. */
  position: relative;
}

/* --- Section labels --------------------------------------------------- */

.section-label {
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin-bottom: var(--space-3);
}

.hairline {
  border: none;
  border-top: 1px solid var(--color-border);
  margin: var(--space-5) 0;
}

/* --- Run / cabinet tree (left panel) ---------------------------------- */

.run-block {
  margin-bottom: var(--space-6);
}

.run-header {
  all: unset;
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  box-sizing: border-box;
  padding: var(--space-2) 0;
  cursor: pointer;
  border-bottom: 1px solid var(--color-border);
  margin-bottom: var(--space-2);
}

.run-header.selected {
  border-bottom-color: var(--color-accent);
}

.run-name {
  font-weight: 500;
  font-size: 13px;
}

.run-meta {
  font-size: 11px;
  color: var(--color-text-faint);
}

.cabinet-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.cabinet-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding: 6px var(--space-2);
  border-radius: var(--radius);
  font-size: 13px;
  color: var(--color-text-muted);
}

.cabinet-row:hover {
  background: var(--color-bg-raised);
}

.cabinet-row.selected {
  background: var(--color-bg-raised);
  color: var(--color-text);
  font-weight: 500;
}

.cabinet-row.selected::before {
  content: "";
  display: inline-block;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--color-accent);
  margin-right: var(--space-2);
}

.cabinet-row-select {
  all: unset;
  cursor: pointer;
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.reorder-controls {
  display: flex;
  gap: 2px;
  flex-shrink: 0;
}

.reorder-btn {
  all: unset;
  cursor: pointer;
  font-size: 11px;
  line-height: 1;
  padding: 2px 4px;
  border-radius: var(--radius);
  color: var(--color-text-muted);
}

.reorder-btn:hover:not(:disabled) {
  background: var(--color-bg-panel);
  color: var(--color-accent);
}

.reorder-btn:disabled {
  color: var(--color-text-faint);
  cursor: default;
}

.cabinet-row .cab-price {
  font-variant-numeric: tabular-nums;
  color: var(--color-text-faint);
  font-size: 12px;
  flex-shrink: 0;
}

.gap-row {
  padding: 0;
  margin: 2px 0;
  border: 1px dashed var(--color-border);
  border-radius: var(--radius);
}

.gap-row.selected {
  border-color: var(--color-accent);
}

.gap-row-select {
  all: unset;
  display: block;
  width: 100%;
  box-sizing: border-box;
  padding: 4px var(--space-2);
  cursor: pointer;
  font-size: 12px;
  color: var(--color-text-faint);
  font-style: italic;
}

.gap-row.selected .gap-row-select {
  color: var(--color-text);
}

.kitchen-toolbar {
  margin-bottom: var(--space-4);
}

.link-button {
  background: none;
  border: none;
  padding: 0;
  color: var(--color-text-muted);
  font-size: 12px;
  cursor: pointer;
  text-decoration: none;
  border-bottom: 1px dotted var(--color-text-faint);
}

.link-button:hover {
  color: var(--color-accent);
  border-bottom-color: var(--color-accent);
}

/* --- Center panel: visuals + editor ------------------------------------ */

.visual-block {
  background: var(--color-bg-raised);
  border-radius: var(--radius-lg);
  border: 1px solid var(--color-border);
  padding: var(--space-5);
}

.visual-block-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: var(--space-4);
}

.elevation-svg, .plan-svg {
  width: 100%;
  height: auto;
  display: block;
}

.cabinet-selection-outline {
  stroke: var(--color-accent);
  stroke-width: 12;
  pointer-events: none;
}

.cabinet-selection-caption {
  font-size: 34px;
  text-transform: uppercase;
  letter-spacing: 2px;
  fill: var(--color-accent);
  text-anchor: start;
}

.gap-label {
  font-size: 24px;
  text-transform: uppercase;
  letter-spacing: 1px;
  fill: var(--color-text-faint);
}

@keyframes cabinet-changed {
  0%   { fill-opacity: 1; }
  15%  { fill-opacity: 0.55; }
  100% { fill-opacity: 1; }
}

.cabinet.changed rect {
  animation: cabinet-changed 900ms ease-out;
}

/* height, not max-height: a max-height on a box whose own `height` is
   otherwise `auto` never becomes a *definite* height for percentage
   resolution purposes (CSS2.1 10.5) — only the box's own painted size
   gets capped. That mismatch was the actual bug here: the SVG child's
   old `height:100%` fell back to resolving against the parent's
   *content-driven* auto height instead, sizing itself purely from the
   available WIDTH and the run's own aspect ratio, with nothing capping
   the result against 46vh at all. For a wide/short run the aspect-ratio
   height always landed comfortably under 46vh regardless, so the bug
   was invisible — but a run whose width-driven height genuinely wanted
   to exceed 46vh (a wide panel and/or a tall/narrow run) rendered taller
   than this box's own painted (max-height-clamped) size, and the excess
   silently vanished under `overflow: hidden` — cutting off the bottom
   of the cabinet, worse the more the two disagreed. A real `height`
   here removes the ambiguity: the box is always exactly 46vh, full
   stop, whatever the content wants. */
.elevation-wrap {
  height: 46vh;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

/* Now that the wrap has a real height, size the SVG as a plain "contain"
   fit within it — max-width/max-height (not width/height) on both axes,
   auto/auto so the browser resolves whichever axis is more constraining
   and scales the other down from it via the intrinsic aspect ratio (the
   standard replaced-element sizing algorithm, CSS2.1 10.4). This can
   only ever shrink the run to fit inside the box — never overflow it —
   regardless of how wide/short or narrow/tall its own aspect ratio is,
   so `overflow: hidden` above is now just a defensive backstop, not
   something that should ever actually trigger. */
.elevation-wrap .elevation-svg {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
}

.plan-thumb {
  max-width: 220px;
}

.plan-view-toggle {
  margin-bottom: var(--space-5);
}

.plan-view-toggle .plan-thumb-block {
  margin-top: var(--space-4);
}

.plan-run {
  fill: var(--color-accent-soft);
  fill-opacity: 0.5;
  stroke: var(--color-border-strong);
  stroke-width: 12;
}

.plan-run.selected {
  stroke: var(--color-accent);
  stroke-width: 18;
}

.plan-front-indicator {
  stroke: var(--color-text-faint);
  stroke-width: 10;
}

/* --- Left rail: "Style & finish" disclosure --------------------------- */

.rail-style {
  margin-bottom: var(--space-4);
}

.rail-style > summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.rail-style > summary::-webkit-details-marker {
  display: none;
}

.rail-style-heading {
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.rail-style-heading::after {
  content: " \25B8"; /* ▸ */
  color: var(--color-text-faint);
}

.rail-style[open] .rail-style-heading::after {
  content: " \25BE"; /* ▾ */
}

.rail-style-values {
  font-size: 11px;
  line-height: 1.4;
  color: var(--color-text-faint);
}

/* Once expanded the fields themselves are the readout — drop the summary
   line so the header doesn't say the same thing twice. */
.rail-style[open] .rail-style-values {
  display: none;
}

.rail-style-fields {
  display: grid;
  gap: var(--space-3);
  margin-top: var(--space-3);
}

.rail-style-subhead {
  margin: var(--space-2) 0 0;
  font-size: 10px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-faint);
}

/* --- Cabinet editor form ------------------------------------------------ */

.editor-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 18px 24px;
}

@media (max-width: 900px) {
  .editor-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* Cabinet editor: one left-aligned column (Label, Cabinet type, Width,
   Depth, Front layout, then any front-layout-dependent fields, then
   Service void), rather than the old 4-up grid. */
.editor-grid-stacked,
.editor-grid-stacked.editor-grid {
  grid-template-columns: minmax(0, 340px);
  gap: 16px;
}

.overridable-field .override-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  color: var(--color-text-muted);
  text-transform: none;
  letter-spacing: normal;
  cursor: pointer;
}

.overridable-field .override-toggle input {
  width: auto;
  margin: 0;
}

.overridable-field input[type="number"]:disabled {
  color: var(--color-text-faint);
  cursor: not-allowed;
}

.field-note {
  font-size: 11px;
  color: var(--color-text-faint);
  margin-top: 2px;
}

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

.field label {
  font-size: 11px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

/* A visible "hover for more info" affordance next to a field's label
   (fm.field_tooltip_icon() in _macros.html) — the actual explanation
   still shows via the browser's own native title="" tooltip on this
   icon; this just gives something to notice and hover in the first
   place, which a bare title="" on invisible label text never did. */
.field-tooltip-icon {
  display: inline-block;
  margin-left: 4px;
  font-size: 12px;
  color: var(--color-text-faint);
  cursor: help;
  vertical-align: middle;
}

.field-tooltip-icon:hover {
  color: var(--color-accent);
}

.field input[type="text"],
.field input[type="password"],
.field input[type="number"],
.field select {
  border: none;
  border-bottom: 1px solid var(--color-border-strong);
  background: transparent;
  padding: 6px 2px;
  font-size: 14px;
  color: var(--color-text);
  outline: none;
  transition: border-color 0.15s ease;
}

.field input:focus,
.field select:focus {
  border-bottom-color: var(--color-accent);
}

.field-hint {
  font-size: 11px;
  color: var(--color-text-faint);
}

.field-error {
  font-size: 11px;
  color: var(--color-negative);
  margin-top: 2px;
}

.field-error:empty {
  margin-top: 0;
  display: none;
}

.field-input-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.field-input-row input,
.field-input-row select {
  flex: 1;
  min-width: 0;
}

.field-input-prefix {
  flex: none;
  color: var(--color-text-muted);
  font-size: 14px;
}

.field-marker {
  font-size: 10px;
  white-space: nowrap;
  flex-shrink: 0;
}

.field-marker-negative {
  color: var(--color-negative);
}

.field-marker-positive {
  color: var(--color-positive);
  animation: field-saved-fade 2.5s ease forwards;
}

@keyframes field-saved-fade {
  0% { opacity: 1; }
  70% { opacity: 1; }
  100% { opacity: 0; }
}

/* Driven by the marker span's own class rather than a class on .field
   itself: the marker (and .field-error below) are the only parts of a
   field that ever get hx-swap-oob'd on a debounced commit now — the
   input/select and its .field wrapper are never touched, so a focused
   input's caret position survives its own commit landing. See
   partials/_macros.html's own module docstring. */
.field:has(.field-marker-positive) input,
.field:has(.field-marker-positive) select {
  border-bottom-color: var(--color-accent);
}

.field:has(.field-marker-negative) label {
  color: var(--color-negative);
}

.field:has(.field-marker-negative) input,
.field:has(.field-marker-negative) select {
  color: var(--color-negative);
  border-bottom-color: var(--color-negative);
  background: var(--color-negative-bg);
}

.field-correction {
  font-size: 11px;
  color: var(--color-negative);
  text-decoration: underline;
  border-bottom: none;
}

.field-correction:hover {
  color: var(--color-accent-ink);
}

/* Groups conditionally-visible fields (partials/_macros.html's
   dependent_fields()) without becoming a grid item itself, so its children
   keep participating directly in .editor-grid's own column layout. */
.dependent-fields {
  display: contents;
}

.field.overridden label::after {
  content: " · overridden";
  color: var(--color-accent);
  text-transform: none;
  letter-spacing: 0;
}

.error-banner {
  background: #f3e6de;
  color: var(--color-negative);
  border-left: 2px solid var(--color-negative);
  padding: var(--space-3) var(--space-4);
  font-size: 13px;
  border-radius: var(--radius);
  margin-bottom: var(--space-4);
}

/* --- Buttons ------------------------------------------------------------ */

/* Fully pill-shaped (999px), not --radius — landing.html's own .btn is
   the same shape regardless of everything else on that page being much
   more rounded than this one; a button reads as "the same button" across
   both surfaces this way even though the surrounding density doesn't
   match. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  border: 1px solid var(--color-border-strong);
  background: var(--color-bg-raised);
  color: var(--color-text);
  padding: 8px 16px;
  font-size: 13px;
  border-radius: 999px;
  cursor: pointer;
  text-decoration: none;
  transition: border-color 0.15s ease, background 0.15s ease;
}

.btn:hover {
  border-color: var(--color-accent);
}

.btn-primary {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: #fff;
  font-weight: 500;
}

.btn-primary:hover {
  background: var(--color-accent-ink);
  border-color: var(--color-accent-ink);
}

.btn-quiet {
  border: none;
  background: none;
  padding: 4px 8px;
  color: var(--color-text-muted);
}

.btn-quiet:hover {
  color: var(--color-negative);
}

.btn-row {
  display: flex;
  gap: var(--space-3);
  margin-top: var(--space-5);
}

/* --- Right panel: pricing ------------------------------------------------ */

.grand-total {
  margin-top: var(--space-2);
}

.grand-total .amount {
  font-size: 30px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--color-text);
  letter-spacing: -0.01em;
}

.pricing-recalc {
  font-size: 11px;
  color: var(--color-accent);
  opacity: 0;
  transition: opacity 0.15s ease;
  margin-left: var(--space-2);
}

.htmx-request .pricing-recalc {
  opacity: 1;
}

.pricing-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.rail-kitchen-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.toggle-switch {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  cursor: pointer;
  text-transform: none;
  letter-spacing: normal;
}

.toggle-switch input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
}

.toggle-switch-track {
  position: relative;
  display: inline-block;
  width: 34px;
  height: 18px;
  border-radius: 999px;
  background: var(--color-border-strong);
  transition: background 0.15s ease;
  flex-shrink: 0;
}

.toggle-switch-thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--color-bg-raised);
  transition: transform 0.15s ease;
}

.toggle-switch input:checked + .toggle-switch-track {
  background: var(--color-accent);
}

.toggle-switch input:checked + .toggle-switch-track .toggle-switch-thumb {
  transform: translateX(16px);
}

.toggle-switch input:focus-visible + .toggle-switch-track {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.toggle-switch-label {
  font-size: 11px;
  color: var(--color-text-muted);
  min-width: 52px;
}

/* Two static flanking labels (mm/cm — see the header's own units_toggle()
   macro) instead of this control's usual single dynamic label — that
   label's own min-width (sized for one label filling the space) would
   make two of them look oddly wide side by side. */
.toggle-switch-flanked .toggle-switch-label {
  min-width: 0;
}

.cabinet-price-list {
  margin-top: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.price-run-header {
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-faint);
  margin-top: var(--space-3);
}

.price-run-header:first-child {
  margin-top: 0;
}

.cabinet-price-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: 5px 0;
  font-size: 12.5px;
  border-bottom: 1px solid var(--color-border);
}

.cabinet-price-row .name {
  color: var(--color-text-muted);
}

.cabinet-price-row .price {
  font-variant-numeric: tabular-nums;
  color: var(--color-text);
}

.price-delta {
  font-size: 11px;
  font-weight: 500;
  padding: 1px 6px;
  border-radius: 10px;
  margin-left: var(--space-2);
  /* Fades in only — holds indefinitely once shown, until that cabinet's
     (or the session's) next commit replaces or clears it. No fade-out:
     this used to expire on a fixed 2.6s timer regardless of whether the
     price it described was still current, which meant a delta could
     vanish while its cabinet was still sitting at that changed price, or
     equally could still be fading as a completely unrelated edit landed. */
  animation: delta-in 250ms ease;
}

.price-delta.positive {
  color: var(--color-negative);
  background: color-mix(in srgb, var(--color-negative) 12%, transparent);
}

.price-delta.negative {
  color: var(--color-positive);
  background: color-mix(in srgb, var(--color-positive) 14%, transparent);
}

@keyframes delta-in {
  0% { opacity: 0; transform: translateY(-2px); }
  100% { opacity: 1; transform: translateY(0); }
}

.session-baseline {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-4);
}

.cost-breakdown {
  margin-top: var(--space-5);
  font-size: 12px;
  color: var(--color-text-muted);
}

.cost-breakdown-row {
  display: flex;
  justify-content: space-between;
  padding: 3px 0;
}

.transport-tier {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-text-faint);
}

/* --- Warnings (collapsible, unobtrusive) --------------------------------- */

.warnings {
  margin-top: auto;
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-border);
}

.warnings summary {
  font-size: 11px;
  color: var(--color-text-faint);
  cursor: pointer;
  list-style: none;
}

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

.warnings summary::before {
  content: "⚠ ";
  opacity: 0.6;
}

.warnings ul {
  margin: var(--space-2) 0 0;
  padding-left: var(--space-4);
  font-size: 11px;
  color: var(--color-text-faint);
}

.warnings li {
  margin-bottom: 4px;
}

/* --- Project settings drawer -------------------------------------------- */

.settings-toggle {
  margin-top: var(--space-5);
}

.settings-drawer {
  margin-top: var(--space-4);
  padding: var(--space-4);
  background: var(--color-bg-raised);
  border: 1px dashed var(--color-border-strong);
  border-radius: var(--radius-lg);
}

.settings-drawer .field {
  margin-bottom: var(--space-3);
}

/* --- Generate panel ------------------------------------------------------ */

.generate-block {
  margin-top: var(--space-6);
  padding-top: var(--space-5);
  border-top: 1px solid var(--color-border);
}

.generate-result {
  font-size: 12.5px;
  color: var(--color-text-muted);
  margin-top: var(--space-3);
}

.generate-result .path {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 11.5px;
  color: var(--color-text);
}

.pipeline-log {
  margin-top: var(--space-3);
  max-height: 160px;
  overflow-y: auto;
  background: var(--color-text);
  color: var(--color-bg);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 11px;
  padding: var(--space-3);
  border-radius: var(--radius);
  white-space: pre-wrap;
}

.pipeline-status {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-text-muted);
}

.pipeline-status.done { color: var(--color-positive); }
.pipeline-status.error { color: var(--color-negative); }

@media (max-width: 1100px) {
  .app-shell {
    grid-template-columns: 220px 1fr 300px;
  }
}

/* --- Client view (task 25) ----------------------------------------------- */

.client-shell {
  max-width: 1480px;
  margin: 0 auto;
  padding: 44px 64px 52px;
  display: flex;
  flex-direction: column;
  gap: 44px;
}

.client-headline {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-6);
}

.client-headline h1 {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 30px;
  margin-bottom: var(--space-2);
}

.client-headline-description {
  font-size: 15px;
  color: var(--color-text-muted);
  margin: 0;
}

.client-total {
  text-align: right;
  flex-shrink: 0;
}

.client-total .amount {
  font-size: 52px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

.client-total-hint {
  font-size: 12px;
  color: var(--color-text-muted);
}

.client-elevation-block {
  padding: var(--space-4) var(--space-5);
}

.client-elevation-wrap {
  height: 320px;
  display: flex;
  justify-content: center;
  overflow: hidden;
}

/* Same "contain" idiom as .elevation-wrap's own override above — see its
   comment. This wrap's own height was already a real fixed 320px (not
   auto+max-height), so the old width:auto/height:100%/max-width:100%
   never actually cropped anything here — the replaced-element algorithm
   correctly re-derives both dimensions together when max-width binds.
   Aligned to max-width/max-height + auto/auto anyway, for the same
   "can only ever shrink to fit, never overflow" guarantee regardless of
   how this rule's own height ever changes in the future. */
.client-elevation-wrap .elevation-svg {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
}

.client-columns {
  display: grid;
  grid-template-columns: 1.35fr 1fr;
  gap: 56px;
}

.client-getting-list {
  margin-top: var(--space-3);
}

.client-getting-row {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: var(--space-4);
  padding: var(--space-3) 0;
  font-size: 13.5px;
  border-bottom: 1px solid var(--color-border);
}

.client-getting-width {
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}

.client-getting-price {
  font-variant-numeric: tabular-nums;
  min-width: 70px;
  text-align: right;
}

.client-materials {
  margin-top: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.client-material-row {
  display: grid;
  grid-template-columns: 34px 1fr;
  grid-template-rows: auto auto;
  column-gap: var(--space-3);
  align-items: center;
}

.client-swatch {
  width: 34px;
  height: 34px;
  border-radius: var(--radius);
  grid-row: 1 / 3;
  border: 1px solid var(--color-border-strong);
}

.client-material-name {
  font-size: 13.5px;
  grid-column: 2;
}

.client-material-role {
  font-size: 11.5px;
  color: var(--color-text-muted);
  grid-column: 2;
}

.client-options {
  margin-top: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.client-option-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  font-size: 13.5px;
}

.client-option-row.active {
  border-color: var(--color-accent);
}

.client-option-name {
  flex: 1;
}

.client-option-price {
  font-variant-numeric: tabular-nums;
}

.client-footer {
  border-top: 1px solid var(--color-border);
  padding-top: var(--space-4);
  font-size: 11.5px;
  color: var(--color-text-muted);
}

.client-footer p {
  margin: 0 0 var(--space-1);
}

@media print {
  .no-print {
    display: none;
  }

  body {
    background: #fff;
  }

  .client-shell {
    padding: 0;
    gap: var(--space-6);
  }

  .visual-block {
    border: none;
    background: none;
    padding: 0;
  }

  .client-getting-row {
    break-inside: avoid;
  }
}

/* --- Login gate ----------------------------------------------------------- */

.login-shell {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-bg);
}

.login-card {
  width: 320px;
  padding: var(--space-6);
  background: var(--color-bg-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
}

.login-title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 22px;
  margin-bottom: var(--space-2);
}

/* --- Design-handoff Phase 5: tree run controls, insert affordance ------- */

.run-control-row {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  padding: 4px var(--space-3) var(--space-2);
  font-size: 11px;
}

.run-position-readout {
  color: var(--color-text-faint);
  font-variant-numeric: tabular-nums;
}

.run-control-disclosure summary {
  cursor: pointer;
  list-style: none;
}

.run-control-disclosure summary::-webkit-details-marker {
  display: none;
}

.run-position-form,
.run-bulk-form {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

.run-bulk-row {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  flex-wrap: wrap;
}

.tree-insert-slot {
  height: 0;
  overflow: visible;
  position: relative;
  list-style: none;
}

.tree-insert-affordance {
  display: none;
  width: 100%;
  border: none;
  background: none;
  color: var(--color-accent);
  font-size: 11px;
  cursor: pointer;
  padding: 2px 0;
  position: relative;
  top: -6px;
}

.tree-insert-slot:hover .tree-insert-affordance {
  display: block;
}

.rail-footer {
  margin-top: auto;
  padding-top: var(--space-3);
}

/* --- Design-handoff Phase 5: elevation header / dimension line --------- */

.dimension-line {
  text-align: center;
  font-size: 11px;
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
  padding-top: var(--space-2);
  border-top: 1px solid var(--color-border);
  margin-top: var(--space-2);
}

/* --- Design-handoff Phase 5: estimate rail cost breakdown / sheet yield - */

.cabinet-price-row.selected .name {
  font-weight: 500;
  color: var(--color-text);
}

.cost-breakdown-detail {
  background: var(--color-bg-raised);
  border-radius: var(--radius-lg);
  padding: var(--space-2) var(--space-3);
  margin: 2px 0 var(--space-2);
}

.cost-breakdown-detail .cost-breakdown-row {
  font-size: 11.5px;
  color: var(--color-text-muted);
}

.sheet-yield-list .cost-breakdown-row {
  font-size: 11.5px;
  color: var(--color-text-muted);
}

.sheet-yield-disclosure > summary.sheet-yield-heading {
  list-style: none;
  cursor: pointer;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  margin-bottom: var(--space-3);
}

.sheet-yield-disclosure > summary.sheet-yield-heading::-webkit-details-marker {
  display: none;
}

.sheet-yield-disclosure > summary.sheet-yield-heading::after {
  content: " \25B8"; /* ▸ */
  color: var(--color-text-faint);
}

.sheet-yield-disclosure[open] > summary.sheet-yield-heading::after {
  content: " \25BE"; /* ▾ */
}

.before-generating {
  margin-top: auto;
  padding-top: var(--space-4);
}

.blocking-list {
  list-style: none;
  margin: var(--space-2) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.blocking-list li {
  font-size: 11.5px;
  color: var(--color-text-muted);
  padding-left: var(--space-3);
  position: relative;
}

.blocking-list li::before {
  content: "•";
  color: var(--color-accent);
  position: absolute;
  left: 0;
}

/* --- Design-handoff Phase 5: project settings page ----------------------- */

.settings-page {
  max-width: 720px;
  margin: 0 auto;
  padding: var(--space-6) var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.settings-page-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
}

.settings-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--color-border);
}

.settings-group:last-child {
  border-bottom: none;
}

/* --- Setup wizard (section 2a) ------------------------------------------- */

.wizard-page {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-6);
}

.wizard-card {
  width: 440px;
  flex: none;
  background: var(--color-bg-panel);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-lg);
  padding: var(--space-6) var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.wizard-eyebrow {
  font-size: 10px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.wizard-heading {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.wizard-title {
  font-family: var(--font-display);
  font-size: 19px;
  font-weight: 400;
}

.wizard-card-entry .wizard-title {
  font-size: 21px;
}

/* --- Start page (post-login landing fork) ------------------------------ */

.wizard-card-start {
  width: 720px;
}

.wizard-start-grid {
  gap: var(--space-5);
}

.wizard-start-tile {
  text-decoration: none;
}

.wizard-start-tile-image {
  height: 160px;
}

.wizard-start-tile-label {
  font-size: 15px;
  line-height: 1.4;
}

.wizard-start-tile:hover .wizard-start-tile-label {
  color: var(--color-accent);
}

.wizard-start-estimate-link {
  display: block;
  margin-top: var(--space-4);
  text-align: center;
}

/* --- Assistant page (Botpress webchat) ---------------------------------- */

.assistant-shell {
  max-width: 720px;
  margin: var(--space-8) auto;
  padding: 0 var(--space-5);
  text-align: center;
}

.assistant-intro .wizard-title {
  margin-bottom: var(--space-2);
}

/* Botpress mounts its webchat inside this element (embeddedChatId); it
   fills the box, so the box is what positions the chat — centred in the
   page, not pinned to a corner. */
.assistant-webchat {
  width: 100%;
  max-width: 460px;
  height: min(70vh, 640px);
  margin: var(--space-6) auto 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--color-bg-raised);
}

.wizard-subtitle {
  margin: 0;
  font-size: 13px;
  color: var(--color-text-muted);
  line-height: 1.6;
}

.wizard-entry-hero {
  height: 160px;
}

.wizard-entry-actions {
  margin-top: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  align-items: stretch;
}

.wizard-entry-actions form {
  display: flex;
  justify-content: center;
}

.wizard-skip-link {
  font-size: 12.5px;
}

/* Placeholder for a photo this prototype has no asset pipeline for (see
   README's "Assets" section) — a plain labelled tile, same convention as
   the app's text-glyph icons elsewhere. */
.wizard-tile-image {
  position: relative;
  height: var(--tile-height, 96px);
  border-radius: var(--radius);
  background: var(--color-bg-raised);
  border: 1px solid var(--color-border);
  color: var(--color-text-faint);
  font-size: 11px;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: var(--space-2);
}

.wizard-step-nav {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: 11px;
}

.wizard-step-nav-item {
  color: var(--color-text-faint);
}

.wizard-step-nav-item.done {
  color: var(--color-text-muted);
  text-decoration: none;
  border-bottom: 1px dotted var(--color-text-faint);
}

.wizard-step-nav-item.done:hover {
  color: var(--color-accent);
  border-bottom-color: var(--color-accent);
}

.wizard-step-nav-item.current {
  color: var(--color-text);
  font-weight: 500;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.wizard-step-nav-sep {
  color: var(--color-text-faint);
}

.wizard-tile-grid {
  display: grid;
  gap: var(--space-4);
  margin: 0;
  padding: 0;
  border: none;
}

.wizard-tile-grid-grid2 {
  grid-template-columns: repeat(2, 1fr);
}

.wizard-tile-grid-grid3 {
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-3);
}

.wizard-tile {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  text-align: left;
  font-family: inherit;
}

.wizard-tile .wizard-tile-image {
  width: 100%;
}

.wizard-tile.selected .wizard-tile-image {
  border: 2px solid var(--color-accent);
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.wizard-tile-check {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--color-accent);
  color: #faf7f1;
  font-size: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.wizard-tile-label {
  font-size: 12.5px;
  color: var(--color-text-muted);
}

.wizard-tile.selected .wizard-tile-label {
  color: var(--color-text);
  font-weight: 500;
}

.wizard-tile-sublabel {
  font-size: 11.5px;
  color: var(--color-text-muted);
}

.wizard-tile-description {
  font-size: 11px;
  color: var(--color-text-muted);
  line-height: 1.4;
}

.wizard-tile-delta {
  font-size: 11px;
  color: var(--color-text-faint);
  font-variant-numeric: tabular-nums;
}

.wizard-tile.selected .wizard-tile-delta {
  color: var(--color-accent);
}

/* Mobile-only (<400px) collapsed caption, see the media query below —
   hidden by default; the query both hides the per-tile description and
   reveals this instead. */
.wizard-tile-caption {
  display: none;
  margin: 0;
  font-size: 12px;
  color: var(--color-text-muted);
}

.wizard-measurement-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.wizard-measurement-row {
  display: flex;
  gap: var(--space-2);
}

.wizard-measurement-row input {
  flex: 1;
  min-width: 0;
}

.wizard-price-footer {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border);
  font-size: 13px;
}

.wizard-price-label {
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text-faint);
}

.wizard-price-value {
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}

/* The "quick ballpark estimate" quiz's own result box
   (design_handoff_kora_estimate_2a) — the one genuinely new box treatment
   this feature needs; reuses --color-accent/--color-accent-soft, the same
   tokens every other accent-tinted element in this file already uses. */
.wizard-estimate-result {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  text-align: center;
  padding: var(--space-5);
  border: 1px solid var(--color-accent);
  border-radius: 12px;
  background: var(--color-accent-soft);
}

.wizard-estimate-price {
  font-size: 28px;
  font-weight: 600;
  color: var(--color-accent-ink);
  font-variant-numeric: tabular-nums;
}

.wizard-estimate-email-form {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-4);
}

.wizard-estimate-email-form input[type="email"] {
  flex: 1;
}

.wizard-estimate-confirmation {
  margin-top: var(--space-4);
  color: var(--color-text-muted);
}

.wizard-breakdown summary {
  font-size: 12px;
}

.wizard-breakdown-rows {
  margin-top: var(--space-2);
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.wizard-breakdown-row {
  display: flex;
  justify-content: space-between;
  font-size: 11.5px;
  color: var(--color-text-muted);
}

.wizard-breakdown-row span:last-child {
  font-variant-numeric: tabular-nums;
}

.wizard-footer {
  margin-top: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border);
}

.wizard-footer form {
  display: contents;
}

.wizard-link-disabled {
  color: var(--color-text-faint);
  border-bottom-color: transparent;
  cursor: default;
}

/* --- Shape & size step (design_handoff_kora_journey/, screen 1d) -------- */

.wizard-card-shape {
  width: 720px;
}

.shape-diagram {
  display: block;
}

.shape-diagram-sm {
  width: 40px;
  height: 30px;
  flex: none;
}

.shape-diagram-lg {
  width: 100%;
  max-width: 280px;
  height: auto;
}

.wizard-shape-picker {
  display: grid;
  grid-template-columns: 250px 1fr;
  gap: var(--space-5);
  align-items: start;
}

.wizard-shape-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.wizard-shape-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  background: none;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: var(--space-2) var(--space-3);
  cursor: pointer;
  text-align: left;
  font-family: inherit;
}

.wizard-shape-row.selected {
  border-color: var(--color-accent);
  background: var(--color-accent-soft);
}

.wizard-shape-preview {
  border: 1px dashed var(--color-border-strong);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
}

.wizard-run-fields {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  width: 100%;
}

.wizard-run-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.wizard-run-field label {
  font-size: 12.5px;
  color: var(--color-text-muted);
}

.wizard-run-field-row {
  display: flex;
  gap: var(--space-2);
}

.wizard-run-field-row input {
  flex: 1;
  min-width: 0;
}

.wizard-run-field.is-typical .wizard-run-field-row input {
  border-style: dashed;
  color: var(--color-text-muted);
}

.wizard-run-field-chip {
  align-self: flex-start;
  background: none;
  border: none;
  padding: 0;
  font-size: 11px;
  color: var(--color-text-faint);
  cursor: pointer;
  text-decoration: underline;
  text-align: left;
  font-family: inherit;
}

/* --- Handoff summary step (screen 1g) ----------------------------------- */

.wizard-summary-rows {
  display: flex;
  flex-direction: column;
}

.wizard-summary-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  border-bottom: 1px dashed var(--color-border);
}

.wizard-summary-row:last-child {
  border-bottom: none;
}

.wizard-summary-label {
  width: 110px;
  flex: none;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-faint);
}

.wizard-summary-value {
  flex: 1;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: 13px;
}

.wizard-summary-assumed {
  color: var(--color-text-muted);
  font-size: 12px;
}

/* --- Options beyond the standard step (design_handoff_options_beyond_
   standard/, screen 1) — also reused by settings_panel.html's own group
   via the same option_row() macro, single-column there instead of
   two-column. -------------------------------------------------------- */

.wizard-card-options {
  width: 760px;
}

.wizard-options-layout {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 260px);
  gap: var(--space-6);
  align-items: start;
}

.wizard-options-group-label {
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-faint);
  margin: var(--space-4) 0 var(--space-2);
}

.wizard-options-group-label:first-child {
  margin-top: 0;
}

.wizard-option-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-lg);
  background: var(--color-bg-panel);
  cursor: pointer;
  margin-bottom: var(--space-2);
}

/* The real checkbox stays in the tab order and reachable by screen
   readers — visually hidden (not display:none, which would drop it from
   the tab order in some browsers), with .wizard-option-checkbox (a plain
   sibling span) as its custom visual. */
.wizard-option-row input[type="checkbox"] {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.wizard-option-checkbox {
  flex: none;
  width: 18px;
  height: 18px;
  margin-top: 2px;
  border-radius: 4px;
  border: 1.5px solid var(--color-border-strong);
  background: var(--color-bg-raised);
  display: flex;
  align-items: center;
  justify-content: center;
  color: transparent;
}

.wizard-option-row input[type="checkbox"]:checked + .wizard-option-checkbox {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: #fff;
}

.wizard-option-row-body {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.wizard-option-row-title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: 14px;
  font-weight: 500;
  color: var(--color-text);
}

.wizard-option-tag {
  display: inline-block;
  padding: 1px 8px;
  border-radius: 999px;
  font-size: 10.5px;
  font-weight: 500;
  background: var(--color-accent-soft);
  color: var(--color-accent-ink);
}

.wizard-option-row-description {
  font-size: 12.5px;
  color: var(--color-text-muted);
  line-height: 1.45;
}

.wizard-options-summary {
  position: sticky;
  top: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.wizard-options-summary-card {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  background: var(--color-bg-raised);
}

.wizard-options-price-note {
  border: 1px solid var(--color-accent-soft);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  background: var(--color-bg-raised);
}

.wizard-options-unlocked-list {
  list-style: none;
  margin: var(--space-2) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  font-size: 12.5px;
  line-height: 1.45;
}

/* Back + Continue grouped on the left, Skip pushed to the far right —
   deliberately not .wizard-footer's own justify-content:space-between
   (that's built for exactly two items; a 3rd item here would just land
   evenly spaced in the middle rather than grouped with the other two). */
.wizard-options-actions {
  margin-top: auto;
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border);
}

.wizard-options-skip {
  margin-left: auto;
}

@media (max-width: 640px) {
  .wizard-page {
    padding: 0;
    align-items: stretch;
  }

  .wizard-card {
    width: 100%;
    min-height: 100vh;
    border: none;
    border-radius: 0;
    padding: var(--space-5) var(--space-4);
  }

  /* Both tile grids stay three-across at every breakpoint down to 375px
     (repeat(3, minmax(0,1fr)) — plain 1fr would let the image tiles force
     the tracks open and overflow the card, per README's mobile spec). */
  .wizard-tile-grid-grid3 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .wizard-shape-picker {
    grid-template-columns: 1fr;
  }

  .wizard-options-layout {
    grid-template-columns: 1fr;
  }

  .wizard-options-summary {
    position: static;
  }

  /* The step-nav moves into a slim header sticky to the viewport top
     rather than floating inside the card — needed once a tall tile grid
     pushes the rest of the screen below the fold. The negative margins
     pull it flush to the card's own edges (undoing the card's own padding
     just above) so it reads as a full-bleed bar, not an inset one. No
     scrolling ancestor sits between this and the viewport on mobile (the
     card itself no longer scrolls independently), so a plain
     `position: sticky` is enough — no JS. */
  .wizard-step-nav {
    position: sticky;
    top: 0;
    z-index: 2;
    margin: calc(-1 * var(--space-5)) calc(-1 * var(--space-4)) 0;
    padding: var(--space-3) var(--space-4);
    background: var(--color-bg-panel);
    border-bottom: 1px solid var(--color-border);
  }

  /* The price footer + back/skip footer share one sticky region at the
     viewport bottom (README: "share one sticky footer... the workspace
     CTA stays inside the scroll so the footer never carries two
     actions") instead of sitting at `margin-top: auto` inside a now-
     unbounded-height card — same full-bleed trick as the step-nav above,
     plus safe-area padding so it clears a device's home indicator. The
     price line keeps its own top border (unchanged from desktop); only
     the group as a whole gets the sticky/full-bleed treatment. */
  .wizard-sticky-footer {
    position: sticky;
    bottom: 0;
    margin: var(--space-5) calc(-1 * var(--space-4)) calc(-1 * var(--space-5));
    padding: var(--space-3) var(--space-4) calc(var(--space-3) + env(safe-area-inset-bottom));
    background: var(--color-bg-panel);
    border-top: 1px solid var(--color-border);
  }

  .wizard-price-footer {
    border-top: none;
    padding-top: 0;
  }

  /* Type steps up slightly from desktop, and controls take more vertical
     padding, per README's mobile spec. */
  .wizard-title {
    font-size: 21px;
  }

  .wizard-subtitle,
  .wizard-tile-label,
  .wizard-measurement-row input {
    font-size: 13.5px;
  }

  .btn,
  .wizard-measurement-row input {
    padding-top: 15px;
    padding-bottom: 15px;
  }
}

/* The drawers question's three per-tile descriptions collapse into one
   live caption below the grid, showing only the selected option — the
   tiles themselves keep their name and £ delta regardless. README:
   "Per-tile descriptions collapse below 400px" — deliberately its own,
   narrower breakpoint than the general 640px mobile layout above (the
   size question's short "around 3.6m" captions stay in place at every
   width, so this only ever touches .wizard-tile-description/
   .wizard-tile-caption, both drawers-screen-only). */
@media (max-width: 400px) {
  .wizard-tile-description {
    display: none;
  }

  .wizard-tile-caption {
    display: block;
  }
}

/* Minimum 44x44 tap target on every wizard tile, at every breakpoint — the
   label text is already inside the same <button> as the image, so the tap
   target already includes it; this just guarantees the button itself is
   never shorter than that regardless of a tile's own image height. */
.wizard-tile {
  min-height: 44px;
}

/* --- 3D preview overlay ----------------------------------------------- */

.preview3d-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-6);
  background: rgba(52, 48, 42, 0.28);
}

.preview3d-overlay[hidden] {
  display: none;
}

.preview3d-panel {
  display: flex;
  flex-direction: column;
  width: min(1400px, 94vw);
  height: min(900px, 90vh);
  background: var(--color-bg-raised);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-lg);
  box-shadow: 0 12px 48px rgba(0, 0, 0, 0.22);
  overflow: hidden;
}

.preview3d-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--color-border);
  flex: none;
}

.preview3d-bar .section-label {
  margin: 0;
}

.preview3d-stage {
  position: relative;
  flex: 1;
  min-height: 0;
}

.preview3d-canvas {
  position: absolute;
  inset: 0;
}

.preview3d-canvas canvas {
  display: block;
}

.preview3d-loading,
.preview3d-error {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  padding: var(--space-3) var(--space-5);
  background: var(--color-bg-raised);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius);
  font-size: 13px;
  color: var(--color-text-muted);
  max-width: 80%;
  text-align: center;
}

.preview3d-error {
  color: var(--color-negative);
  border-color: var(--color-negative);
}

/* --- Function view (function-view-implementation-prompt.md) -----------
   Additive only — every value below is one of this file's own existing
   tokens (--color-*, --space-*, --radius*); no new colour/font/radius
   constants. Structural classes (.visual-block, .section-label, .field,
   .cabinet-list/.cabinet-row, .btn, .link-button, .hairline) are reused
   verbatim from the rest of the app, not redefined here. */

.function-view-mode-toggle {
  display: flex;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}

/* Function view takes over the whole workspace grid (panel-center-
   fullscreen, see .app-shell in this file) for room to work with anchors/
   items and the plan — capped and centred rather than left to stretch
   edge-to-edge on a wide monitor, where the 1fr/1.6fr/1fr columns below
   would otherwise grow uncomfortably wide for their actual content. */
.function-view-block {
  max-width: 1600px;
  margin: 0 auto;
}

.function-view-columns {
  display: grid;
  grid-template-columns: 1fr 1.6fr 1fr;
  gap: var(--space-6);
  align-items: start;
}

/* Anchor row (function_view.html): badge + name + right-aligned status,
   all on one line — mirrors the design handoff's own mockup (function-
   view-plan.dc.html's anchor rows: a small circular initial badge, the
   label, then the source pushed to the row's far edge). */
.function-view-anchor-button {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
}

.function-view-anchor-badge {
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 1px solid var(--color-border-strong);
  font-size: 10px;
  font-weight: 600;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-text-muted);
}

.cabinet-row.selected .function-view-anchor-badge {
  border-color: var(--color-accent);
  color: var(--color-accent-ink);
}

.function-view-anchor-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.function-view-row-status {
  margin-left: auto;
  flex-shrink: 0;
  font-size: 11px;
  color: var(--color-text-faint);
  font-weight: 400;
}

/* Item row (function_view.html): icon + label on one line, "where" it's
   placed indented underneath on a second line — mirrors the design
   handoff's own mockup (function-view-plan.dc.html's item rows: a
   Phosphor icon + label, then a smaller "where" line indented past the
   icon's own width). */
.function-view-item-button {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
}

.function-view-item-main {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.function-view-item-main i {
  font-size: 15px;
  opacity: 0.85;
  width: 15px;
  text-align: center;
}

.function-view-item-where {
  font-size: 11px;
  color: var(--color-text-faint);
  padding-left: calc(15px + var(--space-2));
}

.function-view-plan-header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-3);
}

.function-view-plan-hint {
  font-size: 12px;
  color: var(--color-text-faint);
}

.function-view-plan-header-unit {
  flex-shrink: 0;
  font-size: 12px;
  color: var(--color-text-faint);
}

.function-view-plan-wrap {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-3);
  background: var(--color-bg-raised);
}

/* Set by cabinet_workspace.html/function_view.html whenever an anchor is
   armed — a hook for app.js's own click-to-place listener and a visual
   cue that clicking the plan background will now drop a marker there. */
.function-view-plan-wrap[data-armed-anchor]:not([data-armed-anchor=""]) {
  cursor: crosshair;
}

.function-view-cabinet-fronts {
  margin-top: var(--space-5);
}

.function-view-fronts-header {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.function-view-slot-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-top: var(--space-3);
}

.function-view-slot-row label {
  text-transform: none;
  letter-spacing: normal;
  font-size: 12.5px;
  color: var(--color-text-muted);
}

/* One bordered card holding the overall score + zone bars — mirrors the
   design handoff's own mockup (function-view-plan.dc.html's score card):
   a plain "{{score}} / 100 · {{rating}}" line, not a colour-coded badge
   pill (the zone bars below it already carry the good/workable/poor
   colour coding). */
.function-view-score-card {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
  background: var(--color-bg-raised);
}

.function-view-overall {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}

.function-view-overall-score {
  font-size: 32px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
  color: var(--color-text);
}

.function-view-overall-rating {
  font-size: 13px;
  color: var(--color-text-muted);
}

.function-view-zone-field {
  margin-bottom: var(--space-2);
}

.function-view-zone-bar-track {
  background: var(--color-bg-panel);
  border-radius: var(--radius);
  height: 8px;
  overflow: hidden;
}

.function-view-zone-bar-fill {
  height: 100%;
  transition: width 0.15s ease;
}

.function-view-zone-bar-good {
  background: var(--color-positive);
}

.function-view-zone-bar-workable {
  background: var(--color-accent);
}

.function-view-zone-bar-poor {
  background: var(--color-negative);
}

.function-view-finding-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  padding: var(--space-2);
  border-radius: var(--radius);
  cursor: pointer;
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--color-text-muted);
}

.function-view-finding-row i {
  flex-shrink: 0;
  font-size: 14px;
  margin-top: 1px;
}

.function-view-finding-row:hover {
  background: var(--color-bg-panel);
  color: var(--color-text);
}

.function-view-finding-fail {
  color: var(--color-negative);
}

.function-view-finding-warn {
  color: var(--color-accent-ink);
}

/* --- Function view v2 (Kitchen function and workflow analysis (3)) -----
   First-run explainer, guided setup, the on-demand report launcher and
   its full-width report section. Additive only, existing tokens only —
   same rule as the rest of this file's own Function view section. */

.function-view-intro-card {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-bg-raised);
  padding: var(--space-4);
  margin-bottom: var(--space-5);
}

.function-view-intro-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-3);
}

.function-view-setup-card {
  border: 1px solid var(--color-accent);
  border-radius: var(--radius);
  background: var(--color-accent-soft);
  padding: var(--space-3);
  margin-bottom: var(--space-5);
}

.function-view-setup-eyebrow {
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-accent-ink);
}

.function-view-setup-title {
  font-size: 13.5px;
  font-weight: 500;
  color: var(--color-text);
  margin-top: 2px;
}

.function-view-setup-actions {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

.function-view-row-dimmed {
  opacity: 0.45;
}

.function-view-row-dimmed .cabinet-row-select {
  cursor: default;
}

.function-view-reopen-link {
  margin-left: var(--space-2);
  font-size: 11px;
  text-transform: none;
  letter-spacing: normal;
}

.function-view-report-launcher {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-bg-raised);
  padding: var(--space-4);
}

.function-view-report-launcher-notrun .btn {
  margin-top: var(--space-3);
  width: 100%;
}

.function-view-report-stale-note {
  font-size: 11px;
  color: var(--color-accent-ink);
  margin-top: var(--space-2);
}

.function-view-report-launcher-actions {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

.function-view-report-section {
  margin-top: var(--space-6);
  padding-top: var(--space-5);
  border-top: 1px solid var(--color-border);
}

.function-view-report-header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-5);
  flex-wrap: wrap;
  margin-bottom: var(--space-5);
}

.function-view-report-zones {
  display: flex;
  gap: var(--space-5);
  flex-wrap: wrap;
}

.function-view-report-zones .function-view-zone-field {
  min-width: 100px;
  margin-bottom: 0;
}

.function-view-report-body {
  display: grid;
  grid-template-columns: minmax(240px, 320px) 1fr;
  gap: var(--space-6);
  align-items: start;
}

.function-view-scenarios-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(240px, 1fr));
  gap: var(--space-4);
  margin-top: var(--space-3);
}

.function-view-scenario-card {
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-bg-raised);
  padding: var(--space-3);
}

.function-view-scenario-header {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: 13px;
  color: var(--color-text);
}

.function-view-scenario-plan {
  margin-top: var(--space-3);
}

.function-view-scenario-stat {
  font-size: 12px;
  color: var(--color-text-muted);
  margin-top: var(--space-2);
}

.function-view-scenario-stat-incomplete {
  color: var(--color-accent-ink);
}

@media (max-width: 900px) {
  .function-view-columns {
    grid-template-columns: 1fr;
  }

  .function-view-report-body {
    grid-template-columns: 1fr;
  }

  .function-view-scenarios-grid {
    grid-template-columns: 1fr;
  }
}

/* --- Plan SVG (webapp/svg_render.py's render_plan()) -------------------
   Previously dead code with zero real callers, so these elements had no
   styling at all before the Function view started using it — default
   SVG fill (solid black) would be unusable. */

.plan-cabinet {
  fill: var(--color-bg-raised);
  stroke: var(--color-border-strong);
  stroke-width: 12;
  cursor: pointer;
}

.plan-cabinet:hover {
  stroke: var(--color-accent);
}

.plan-cabinet.selected {
  stroke: var(--color-accent);
  stroke-width: 18;
}

.plan-gap {
  stroke: var(--color-border-strong);
  stroke-width: 12;
  stroke-dasharray: 30 20;
  cursor: pointer;
}

.plan-gap.selected {
  stroke: var(--color-accent);
  stroke-width: 18;
}

.plan-front-indicator {
  stroke: var(--color-text-faint);
  stroke-width: 8;
}

/* The row-spanning "rail" line along each run's front edge — see
   render_plan()'s own rails/rail_defs (its stroke is a per-run
   <linearGradient>, already fading at both ends, so only stroke-width is
   set here). This is what "lines appear between the cabinets in plan
   view" refers to (function-view-plan.dc.html's own two row-divider
   lines) — a single line completing the run, not a border added to every
   individual cabinet (each already has its own polygon stroke). */
.plan-row-rail {
  stroke-width: 6;
}

.plan-entry-label {
  font-size: 28px;
  fill: var(--color-text-muted);
}

.plan-anchor-marker {
  stroke: var(--color-bg-raised);
  stroke-width: 8;
}

.plan-anchor-label {
  font-size: 26px;
  font-weight: 600;
  fill: var(--color-accent-ink);
}

.plan-measure-label {
  font-size: 24px;
  font-weight: 600;
}

/* --- Scenario mini-plan SVG (webapp/svg_render.py's render_scenario_
   plan()) — a small, non-interactive illustration inside each scenario
   card, styled the same additive/token-only way as .plan-* above. */

.scenario-plan-entry {
  fill: var(--color-bg-panel);
  stroke: var(--color-border);
  stroke-width: 10;
}

.scenario-plan-line {
  stroke: var(--color-accent);
  stroke-width: 10;
  stroke-dasharray: 20 14;
}

.scenario-plan-stop {
  fill: var(--color-accent);
  stroke: var(--color-bg-raised);
  stroke-width: 8;
}

.scenario-plan-stop-number {
  font-size: 60px;
  font-weight: 600;
  fill: #fff;
}

/* --- Pricing admin (webapp/templates/partials/pricing_admin_panel.html) -
   The admin Pricing view's per-cabinet-type cards — additive only,
   existing tokens only, same discipline as every other section in this
   file. No dedicated amber/warn token exists in this codebase (see
   svg_render.py's own WARN_LINE_COLOR comment) — --color-accent is the
   established stand-in, used here for the amber RAG pill too. */

.pricing-type-card {
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: var(--space-3);
  margin-bottom: var(--space-4);
}

.pricing-type-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-2);
}

.pricing-type-name {
  font-size: 13.5px;
  font-weight: 500;
  color: var(--color-text);
}

.pricing-comparators {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-3) 0;
}

.rag-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: 12px;
  padding: 3px 10px;
  border-radius: 999px;
  color: #fff;
}

.rag-pill-green {
  background: var(--color-positive);
}

.rag-pill-amber {
  background: var(--color-accent);
}

.rag-pill-red {
  background: var(--color-negative);
}

.pricing-match-button {
  color: inherit;
  text-decoration: underline;
  font-size: 11px;
}

.pricing-margin-field {
  margin-top: var(--space-3);
}

/* --- Pricing admin: Global factors page ---------------------------------
   Additive, existing tokens only. Named .pricing-* (not a generic .card/.tag)
   so nothing here can collide with another page's components; the dark
   Nocturne look comes for free from the token overrides under .nocturne-theme. */

.settings-page-wide {
  max-width: 1180px;
}

.pricing-subnav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  border-bottom: 1px solid var(--color-border);
  padding-bottom: var(--space-3);
}

.pricing-subnav-tabs {
  display: flex;
  gap: var(--space-2);
}

.pricing-tab {
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius);
  color: var(--color-text-muted);
  text-decoration: none;
  font-size: 14px;
}

.pricing-tab:hover {
  color: var(--color-text);
}

.pricing-tab[aria-current="page"] {
  color: var(--color-text);
  background: var(--color-accent-soft);
}

.pricing-export-cta {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  text-decoration: none;
}

.pricing-global {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

.pricing-page-title {
  font-size: 22px;
  font-weight: 500;
  color: var(--color-text);
  margin-bottom: var(--space-2);
}

.pricing-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--space-5);
}

.pricing-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  background: var(--color-bg-panel);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: var(--space-5);
}

.pricing-card-title {
  font-size: 15px;
  font-weight: 500;
  color: var(--color-text);
  margin-bottom: var(--space-2);
}

.pricing-factor-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-2) 0;
}

.pricing-factor-form {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
}

.pricing-factor-label {
  font-size: 14px;
  color: var(--color-text);
}

.pricing-factor-control {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--color-text-muted);
}

.pricing-factor-input {
  width: 84px;
  text-align: right;
  padding: var(--space-2) var(--space-3);
  background: var(--color-bg-raised);
  color: var(--color-text);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius);
  font: inherit;
}

.pricing-factor-input:focus {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}

.pricing-slider {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  flex: 1;
  justify-content: flex-end;
}

.pricing-slider input[type="range"] {
  flex: 1;
  min-width: 80px;
  accent-color: var(--color-accent);
}

.pricing-slider-readout {
  min-width: 48px;
  text-align: right;
  color: var(--color-text);
}

.pricing-factor-meta {
  text-align: right;
}

.pricing-card-wide {
  grid-column: 1 / -1;
}

.pricing-competitor-tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-2) 0;
}

.pricing-competitor-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  border-top: 1px solid var(--color-border);
  padding: var(--space-3) 0;
}

.pricing-pills {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.pricing-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: 12.5px;
  padding: 3px 10px;
  border-radius: 999px;
  background: var(--color-bg-raised);
  color: var(--color-text);
  border: 1px solid var(--color-border);
}

.pricing-pill-remove {
  display: inline-flex;
  border: 0;
  background: transparent;
  color: var(--color-text-muted);
  cursor: pointer;
  padding: 0;
}

.pricing-pill-remove:hover {
  color: var(--color-negative);
}

.pricing-competitor-add {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-4);
}

.pricing-ballpark {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: 13px;
  margin-bottom: var(--space-2);
}

/* --- Pricing admin: Quotes list + builder --------------------------------
   Additive, .pricing-*-prefixed (same isolation reasoning as the Global
   factors section above), porting the design handoff's own .tag/.table/
   .dialog components onto this app's existing tokens (--color-surface ->
   --color-bg-raised, --color-divider -> --color-border, matching the
   preview3d dialog's own precedent). */

.pricing-tag {
  display: inline-flex;
  align-items: center;
  font-size: 11px;
  letter-spacing: 0.02em;
  padding: 3px 10px;
  border-radius: calc(var(--radius) * 0.75);
}

.pricing-tag-accent {
  background: var(--color-accent-800);
  color: var(--color-accent-100);
}

.pricing-tag-neutral {
  background: var(--color-neutral-800);
  color: var(--color-neutral-100);
}

.pricing-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
}

.pricing-table th {
  text-align: left;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-faint);
  padding: var(--space-2);
  border-bottom: 1px solid var(--color-border);
}

.pricing-table td {
  padding: var(--space-2);
  border-bottom: 1px solid var(--color-border);
}

.pricing-table tbody tr:hover {
  background: color-mix(in srgb, var(--color-text) 4%, transparent);
}

.pricing-table tbody tr[data-zero-count] {
  opacity: 0.55;
}

.pricing-table-card {
  padding: 0;
  overflow: auto;
}

.pricing-quotes-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: var(--space-4);
  flex-wrap: wrap;
}

.pricing-dialog-backdrop {
  position: fixed;
  inset: 0;
  display: grid;
  place-items: center;
  padding: var(--space-4);
  background: color-mix(in srgb, var(--color-neutral-900) 50%, transparent);
  z-index: 40;
}

.pricing-dialog {
  width: min(440px, 100%);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-5);
  border-radius: var(--radius-lg);
  background: var(--color-bg-raised);
  box-shadow: var(--shadow-lg);
}

.pricing-dialog-title {
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: 20px;
}

.pricing-dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

.pricing-quote-header {
  display: flex;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  align-items: flex-start;
}

.pricing-quote-title-input {
  font-size: 22px;
  font-family: var(--font-heading);
  font-weight: 500;
  padding: 8px 10px;
  width: 100%;
  background: var(--color-bg-raised);
  color: var(--color-text);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius);
}

.pricing-quote-meta {
  display: flex;
  gap: var(--space-3);
  align-items: center;
  flex-wrap: wrap;
  font-size: 13px;
  color: var(--color-text-muted);
}

.pricing-quote-totals-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--space-5);
}

.pricing-quote-totals-row {
  display: flex;
  justify-content: space-between;
  padding: 4px 0;
}

.pricing-quote-grand-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding-top: var(--space-2);
}

.pricing-quote-grand-row .pricing-quote-grand-label {
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: 16px;
}

.pricing-quote-grand-row .pricing-quote-grand-value {
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: 22px;
  color: var(--color-accent-300, var(--color-accent));
}

.pricing-text-muted {
  color: var(--color-text-muted);
}


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

/* --- Shared configurator-workspace components ---------------------------
   Icon rail, flyout panels, the header view switcher, the collapsed-
   pricing-panel toggle/indicator, and a few small button variants — all
   promoted here from the old webapp/static/nocturne.css during the base/
   theme split (see this file's own header comment). None of these had a
   competing rule anywhere else in this file (verified directly, selector
   by selector) — they're brand-agnostic UI that happened to only be
   built once, under what was then the only dark theme, not something
   inherently tied to either brand. Every rule below is written purely
   against token names (var(--...)), so it renders correctly under
   whichever brand's theme file is active with no changes of its own. */

.btn-ghost {
  border: none;
  background: transparent;
  color: var(--color-accent);
  padding-inline: var(--space-1);
}

.btn-ghost:hover {
  background: color-mix(in srgb, var(--color-accent) 10%, transparent);
}

.btn-icon {
  width: 36px;
  height: 36px;
  padding: 0;
  border-radius: var(--radius);
}

/* Only ever combined with .btn (`class="btn btn-secondary"`) — .btn
   itself supplies everything else (padding, pill/rect shape per brand,
   border, text colour); this variant just clears the solid fill .btn
   gets by default. */
.btn-secondary {
  background: transparent;
}

/* --- Icon rail (64px) -----------------------------------------------
   Icon rail + canvas (flexible) + pricing column. Flyout panels are
   position:absolute (see below), so they never occupy a grid track of
   their own. The pricing column collapses to a slim strip (holding just
   its own toggle button) rather than vanishing entirely, so it's always
   one click to bring back. */

.icon-rail {
  width: 64px;
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: var(--space-4) 0;
  gap: var(--space-2);
  background: var(--color-bg-panel);
  border-right: 1px solid var(--color-border);
  /* Scrolls inside its own column when the window is too short for every
     tab, rather than overflowing the page. */
  min-height: 0;
  overflow-y: auto;
  scrollbar-width: thin;
}

.icon-rail-btn {
  flex: none; /* never squashed when the rail scrolls */
  width: 44px;
  height: 44px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 8px 2px;
  border: none;
  border-radius: var(--radius);
  cursor: pointer;
  background: transparent;
  color: var(--color-neutral-300);
  text-decoration: none;
  transition: background 0.15s ease, color 0.15s ease;
}

.icon-rail-btn:hover {
  color: var(--color-text);
}

.icon-rail-btn.active {
  color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 22%, transparent);
  box-shadow: inset 3px 0 0 var(--color-accent);
}

.icon-rail-btn i {
  font-size: 20px;
  line-height: 1;
}

.icon-rail-btn-label {
  font-size: 9px;
  letter-spacing: 0.02em;
}

/* Splits the rail into "available to everyone" and admin-only groups —
   the divider only renders when at least one admin-only button is (see
   workspace.html), so a trade/client rail never shows a label with
   nothing under it. */
.icon-rail-divider {
  width: 32px;
  height: 1px;
  margin: var(--space-2) 0 0;
  border: none;
  background: var(--color-border);
}

.icon-rail-group-label {
  width: 100%;
  padding: 0 4px;
  font-size: 8px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-align: center;
  color: var(--color-neutral-500);
}

/* --- Flyout panels ------------------------------------------------------
   position: absolute over the canvas, only one visible at a time (driven
   by session.active_flyout_panel — see webapp/state.py); the closed
   state stays in the DOM (translateX/opacity/pointer-events). Offset by
   the rail's own 64px width (left: 64px, not 0) so the rail always
   stays visible/clickable to its left instead of being covered — lets
   the maker see which panel is active and jump straight to another one
   without needing to close the current panel first. */

.flyout-panel {
  position: absolute;
  top: 0;
  left: 64px;
  height: 100%;
  width: 340px;
  z-index: 5;
  background: var(--color-bg-panel);
  border-right: 1px solid var(--color-border);
  box-shadow: var(--shadow-lg);
  padding: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  overflow-y: auto;
  transform: translateX(-16px);
  opacity: 0;
  pointer-events: none;
  transition: transform 0.18s ease, opacity 0.18s ease;
}

.flyout-panel.flyout-open {
  transform: translateX(0);
  opacity: 1;
  pointer-events: auto;
}

/* Shared wrapper for a flyout's own field/section list (see
   webapp/templates/partials/flyouts/_layout.html, _customer.html,
   _project_edits.html, _wastage.html) — denser than the panel's own
   default --space-4 gap, since every field already carries its own
   internal spacing (label, input, hint) and a --space-4 gap between each
   one made the whole panel feel padded out (per the user's own
   "too much padding/spacing at item config level" note). */
.flyout-content {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* Denser field/group spacing throughout every flyout — same complaint,
   applied to the settings-style panels (Commercial/CNC settings/Global
   settings), which use .settings-group directly rather than
   .flyout-content. Scoped to .flyout-panel only, not the standalone
   /settings page, which keeps its current (roomier) spacing. */
.flyout-panel .settings-group {
  gap: var(--space-2);
  padding-bottom: var(--space-3);
}

.flyout-panel .field {
  gap: 4px;
}

/* Sentence case throughout every flyout, not shouty all-caps (per the
   user's own request) — .section-label/.field label/
   .wizard-options-group-label are all uppercase-transformed globally
   (above), scoped back to normal case + tracking here rather than
   changing that shared rule, which the standalone /settings page and
   every other page still use as-is. */
.flyout-panel .section-label,
.flyout-panel .field label,
.flyout-panel .wizard-options-group-label {
  text-transform: none;
  letter-spacing: normal;
}

.flyout-panel .hairline {
  margin: var(--space-3) 0;
}

/* Cabinet unit detail's own field grid (shared with the light theme's
   inline editor it used to be — see webapp/templates/partials/
   cabinet_editor.html) — same density complaint, scoped here rather than
   changing that shared rule everywhere it's used. */
.flyout-panel .editor-grid-stacked {
  gap: 10px;
}

/* Shifts the canvas right by the flyout's own width while one is open —
   the flyout itself is position:absolute (doesn't take up grid space),
   so without this the elevation would sit underneath it instead of
   moving into view. */
.panel-center.flyout-open {
  margin-left: 340px;
  transition: margin-left 0.18s ease;
}

.flyout-panel-header {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.flyout-panel-header h4 {
  margin: 0;
}

/* --- View switcher (header, top right) ---------------------------------
   Elevation (home) | Plan view | 3D view | Function view — see
   fm.view_switcher() in webapp/templates/partials/_macros.html. Segments
   are real <button>s; .active marks the current canvas view (3D view is a
   modal, never "active"). No brand-specific styling ever existed for
   this component (verified: zero occurrence anywhere else in this file
   before this promotion) — it's simply been exclusive to the always-
   Datum+Grain-branded pages so far, not inherently tied to that brand. */

.seg {
  display: inline-flex;
  overflow: hidden;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-bg-panel);
}

.seg-opt {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 12px;
  border: none;
  border-radius: 0;
  background: transparent;
  color: var(--color-text);
  font-size: 13px;
  white-space: nowrap;
  cursor: pointer;
}

.seg-opt + .seg-opt {
  border-left: 1px solid var(--color-border);
}

.seg-opt.active {
  color: var(--color-accent);
  box-shadow: inset 0 0 0 1px var(--color-accent);
}

.seg-opt:not(.active):not(:disabled):hover {
  background: color-mix(in srgb, var(--color-text) 7%, transparent);
}

.seg-opt:disabled {
  color: var(--color-text-faint);
  cursor: not-allowed;
}

/* Plan view isn't built yet: muted, but still clickable so it can say so. */
.seg-opt-soon {
  color: var(--color-text-faint);
}

/* The "coming soon" note sits under the switcher, outside .seg (whose
   overflow:hidden would clip it). Empty until static/app.js fills it. */
.view-switcher {
  position: relative;
}

.view-switcher-note {
  position: absolute;
  top: calc(100% + var(--space-2));
  right: 0;
  z-index: 5;
  padding: 4px 10px;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius);
  background: var(--color-bg-raised);
  font-size: 12px;
  white-space: nowrap;
  box-shadow: var(--shadow-md);
}

.view-switcher-note:empty {
  display: none;
}

/* --- Collapsed pricing panel --------------------------------------------
   .panel-right itself (above) already has the position:relative this
   needs. No competing rule ever existed for either class (verified) —
   promoted here alongside the rest of this section. */

.panel-right-collapsed {
  padding: var(--space-4) 0;
  overflow: visible;
}

.pricing-panel-collapse-toggle {
  position: absolute;
  top: var(--space-2);
  left: var(--space-2);
}

.panel-right-collapsed .pricing-panel-collapse-toggle {
  position: static;
  margin: 0 auto;
}

/* --- Floating pricing total indicator ------------------------------------
   Shown bottom-right whenever the pricing panel itself is collapsed
   (defaults collapsed now — see webapp/state.py's own note) — just the
   running total, one click to bring the full panel back. */
.pricing-total-indicator {
  position: fixed;
  bottom: var(--space-6);
  right: var(--space-6);
  z-index: 4;
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-bg-panel);
  box-shadow: var(--shadow-lg);
  cursor: pointer;
  color: var(--color-text);
}

.pricing-total-indicator:hover {
  border-color: var(--color-accent);
}

.pricing-total-indicator-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
}

.pricing-total-indicator-amount {
  font-size: 18px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}
