/* ==================================================================
   TUDS PRO ESTIMATOR — DIRECTION D APPLICATION SHELL
   ------------------------------------------------------------------
   "Studio as the primary, with Guided elements."

   Loaded AFTER deckcalculatorstyles.css. This file owns:
     1. the shared token set (Inter, one type scale, one accent)
     2. the component set  (.row .card .chip .swatch .field .btn
                            .badge .callout .sheet .seg .tbtn)
     3. the shell layout   (56px header + stepper · canvas rail ·
                            380px right panel · 64px money footer)
     4. the legacy neutralisers that fold the 91 ad-hoc button classes
        onto the one .btn spec (the class names stay on the elements
        because app.js selects by several of them — only the visual
        overrides are replaced).

   Reference: mockups/review-design-critique.md §2-4,
              mockups/direction-b-studio.html (grammar),
              mockups/direction-c-guided.html (teaching surfaces).
   ================================================================== */

/* ==================================================================
   1 · TOKENS
   ================================================================== */
:root {
  /* --- one type system ------------------------------------------ */
  --font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-display: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;

  /* five rungs, nothing between them, nothing below 12px */
  --t-xs: 12px;
  --t-sm: 14px;
  --t-md: 16px;
  --t-lg: 20px;
  --t-xl: 28px;

  /* two weights */
  --w-reg: 400;
  --w-semi: 600;

  --lh-tight: 1.25;   /* titles */
  --lh-body:  1.5;    /* prose, helper text */

  /* legacy scale re-pointed at the five rungs so the 299 existing
     `font-size: var(--text-*)` rules land on the same ladder.
     No two aliases may resolve to the same rung: --text-sm and --text-lg
     used to collapse onto --t-xs / --t-md, which made 20px and 28px
     unreachable from the legacy sheet (01-visual-design F5). */
  --text-xs:   var(--t-xs);
  --text-sm:   var(--t-sm);
  --text-base: var(--t-sm);
  --text-md:   var(--t-md);
  --text-lg:   var(--t-lg);
  --text-xl:   var(--t-lg);
  --text-2xl:  var(--t-xl);

  /* --- SPACE · one 4-pt scale, seven rungs, nothing between ------
     Declared HERE so the shell is self-sufficient: it consumed
     --space-* 84 times while declaring none of them, which left every
     one of those rules resolving through deckcalculatorstyles.css
     (01-visual-design F3). Banned raw values: 5, 6, 7, 9, 10, 11, 13px. */
  --space-1:  4px;
  --space-2:  8px;
  --space-3: 12px;
  --space-4: 16px;   /* THE GUTTER: panel inset, card padding */
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;

  /* --- PANEL METRICS -------------------------------------------- */
  --panel-pad:     var(--space-4, 16px);
  --panel-sec-gap: var(--space-6, 32px);
  --panel-head-h:  28px;             /* was 40px */
  --card-pad:      var(--space-4, 16px);
  --card-gap:      var(--space-3, 12px);
  --row-pad-y:     var(--space-3, 12px);   /* was the off-scale 10px */
  --row-pad-x:     var(--space-4, 16px);
  --row-min-h:     44px;             /* 12 + 20 + 12 */

  /* --- fallbacks for the four tokens the shell still borrows from
     deckcalculatorstyles.css, so the shell degrades instead of
     collapsing to zero if that sheet is ever unlinked (06 F3). ---- */
  --tuds-navy: #133A52;
  --tuds-teal: #2D6A6A;
  --transition-fast: 150ms ease;
  --shadow-inner: inset 0 2px 4px rgba(0, 0, 0, 0.06);
  --pill-active-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);

  /* --- ink & surface -------------------------------------------- */
  --ink:   #16222B;
  --ink-2: #50606B;   /* AA 5.9:1 on white; kept a step darker than --ink-3 */
  --ink-3: #5F6C76;   /* WCAG AA: 5.4:1 white, 4.74:1 on the --accent-50 tint (was 2.9:1; QA U2-01) */
  --line:   #E3E8EB;
  --line-2: #EDF1F3;
  --bg:      #F6F8F9;
  --surface: #FFFFFF;
  --surface-2: #FAFBFC;

  /* --- the single accent (the existing teal token) --------------- */
  --accent:     #2D6A6A;
  --accent-700: #1F4F4F;
  --accent-50:  #EAF2F1;
  --accent-ring: rgba(45, 106, 106, 0.22);
  /* --accent on --chrome is a low-contrast pairing, and the footer CTA is
     the most important control in the product (01 F13). This is the same
     hue, lifted enough to read as the brightest thing on the dark strip. */
  --accent-on-chrome:     #3E9A8F;
  --accent-on-chrome-700: #348379;

  /* --- chrome (header + footer) --------------------------------- */
  --chrome:   #0E2C3F;
  --chrome-2: #16394E;
  --chrome-ink:    #DCE9F0;
  --chrome-ink-2:  #A9C4D3; /* lifted for the kiosk (QA 2026-10-01): ≈7.9:1 on --chrome (was 6.3) */
  --chrome-ink-3:  #98B3C3; /* lifted (QA 2026-10-01): ≈6.6:1 on --chrome (was 4.9) */
  --rail:      #EEF2F4;
  --rail-line: #DCE3E7;
  --canvas-bg: #EDF1F3;

  /* --- status (badges and callouts only) ------------------------ */
  --ok:   #2A7348;  --ok-bg:   #EAF4EE;   /* 5.1:1 on --ok-bg (was #2F7D4F, 4.48) */
  --warn: #9A6212;  --warn-bg: #FBF2E4;
  --err:  #B3261E;  --err-bg:  #FBEBEA;

  /* --- frame metrics -------------------------------------------- */
  --h-hdr: 56px;
  --h-rail: 44px;
  --h-foot: 64px;
  /* Fluid, so the panel is 340px at 1280 and 440px at 1920 instead of a
     hard 380px with a 1360px step (L6). */
  --w-panel: clamp(340px, 24vw, 440px);

  /* --- radii: six, not ten -------------------------------------- */
  --r-sm: 6px;
  --r: 8px;
  --r-lg: 12px;
  --r-xl: 16px;
  --r-full: 999px;

  --shadow-float: 0 2px 6px rgba(19, 58, 82, .06), 0 10px 28px rgba(19, 58, 82, .10);
  --shadow-pop:   0 6px 24px rgba(19, 58, 82, .12), 0 1px 2px rgba(19, 58, 82, .08);
  --shadow-modal: 0 24px 60px rgba(19, 58, 82, .22);
}

/* Every number in this product is tabular so columns of money,
   dimensions and quantities stack instead of jittering. */
body,
.num,
input[type="number"],
.shell-panel,
.shell-foot,
.bom-table,
.cost-summary-card,
.dashboard-table {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* ==================================================================
   2 · COMPONENTS
   ================================================================== */

/* --- 2.1 button: one base, four variants, three sizes ------------ */
.btn,
.dashboard-new-btn,
.dashboard-scratchpad-btn,
.dashboard-bulk-btn,
.dashboard-load-more-btn,
.welcome-btn-primary,
.welcome-btn-secondary,
.login-submit,
.login-submit-secondary,
.login-google-btn,
.wizard-btn,
.footer-back-btn,
.footer-next-btn,
.substep-next-btn,
.substep-prev-btn,
.review-edit-btn,
.confirm-dialog__btn {
  height: 36px;
  min-height: 36px;
  padding: 0 var(--space-4, 16px);
  border-radius: var(--r);
  border: 1px solid transparent;
  font-family: var(--font-family);
  font-size: var(--t-sm);
  font-weight: var(--w-semi);
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2, 8px);
  cursor: pointer;
  white-space: nowrap;
  text-decoration: none;
  /* kill every gradient, bevel and inset highlight in one place */
  background-image: none;
  box-shadow: none;
  text-shadow: none;
  transition: background var(--transition-fast, 150ms ease), border-color var(--transition-fast, 150ms ease),
              color var(--transition-fast, 150ms ease);
}

.btn svg,
.btn i { width: 16px; height: 16px; flex: none; }

.btn-sm, .btn.sm { height: 28px; padding: 0 var(--space-3, 12px); font-size: var(--t-xs); }
.btn-lg, .btn.lg { height: 44px; padding: 0 var(--space-6, 32px); }

/* primary — flat accent, one step darker on hover, never a transform */
.btn-primary,
.btn.primary,
.welcome-btn-primary,
.login-submit,
.dashboard-new-btn,
.footer-next-btn,
.confirm-dialog__btn--primary {
  background: var(--accent) !important;
  background-image: none !important;
  border-color: var(--accent) !important;
  color: #fff !important;
  box-shadow: none !important;
}

.btn-primary:hover:not(:disabled),
.btn.primary:hover:not(:disabled),
.welcome-btn-primary:hover:not(:disabled),
.login-submit:hover:not(:disabled),
.dashboard-new-btn:hover:not(:disabled),
.footer-next-btn:hover:not(:disabled) {
  background: var(--accent-700) !important;
  border-color: var(--accent-700) !important;
  transform: none !important;
}

.btn-primary:active:not(:disabled),
.btn.primary:active:not(:disabled) {
  background: var(--accent-700) !important;
  box-shadow: var(--shadow-inner, inset 0 2px 4px rgba(0,0,0,.06)) !important;
  transform: none !important;
}

/* secondary — white with a hairline */
.btn-secondary,
.btn.secondary,
.welcome-btn-secondary,
.login-submit-secondary,
.login-google-btn,
.dashboard-scratchpad-btn,
.dashboard-bulk-btn,
.dashboard-load-more-btn,
.footer-back-btn,
.review-edit-btn,
.confirm-dialog__btn--secondary {
  background: var(--surface) !important;
  background-image: none !important;
  border: 1px solid var(--line) !important;
  color: var(--ink) !important;
  box-shadow: none !important;
}

.btn-secondary:hover:not(:disabled),
.btn.secondary:hover:not(:disabled),
.welcome-btn-secondary:hover:not(:disabled),
.login-google-btn:hover:not(:disabled),
.dashboard-scratchpad-btn:hover:not(:disabled),
.dashboard-bulk-btn:hover:not(:disabled),
.footer-back-btn:hover:not(:disabled),
.review-edit-btn:hover:not(:disabled) {
  background: var(--surface-2) !important;
  border-color: #B9C6CD !important;
  transform: none !important;
}

/* quiet — text only, for escape hatches */
.btn-quiet,
.btn.quiet,
.btn-ghost,
.dashboard-bulk-btn-ghost,
.dashboard-link-btn {
  background: transparent !important;
  background-image: none !important;
  border-color: transparent !important;
  color: var(--ink-2) !important;
  box-shadow: none !important;
}
.btn-quiet:hover, .btn.quiet:hover, .btn-ghost:hover,
.dashboard-bulk-btn-ghost:hover, .dashboard-link-btn:hover {
  background: var(--line-2) !important;
  color: var(--ink) !important;
}

/* destructive */
.btn-danger,
.btn.destructive,
.confirm-dialog__btn--danger {
  background: var(--err) !important;
  background-image: none !important;
  border-color: var(--err) !important;
  color: #fff !important;
}
.btn-danger:hover { background: #8E1E17 !important; }

.btn:disabled,
.btn.disabled,
.btn-disabled {
  opacity: .4;
  cursor: not-allowed;
  pointer-events: none;
}

.btn-full-width, .btn.full { width: 100%; }

/* icon button — the only control allowed in the canvas rail */
.iconbtn {
  width: 32px; height: 32px;
  min-width: 32px;
  padding: 0;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--ink-2);
  display: inline-grid;
  place-items: center;
  cursor: pointer;
  transition: background var(--transition-fast, 150ms ease), color var(--transition-fast, 150ms ease);
}
.iconbtn:hover { background: rgba(19, 58, 82, .07); color: var(--ink); }
.iconbtn[aria-pressed="true"], .iconbtn.active { background: var(--tuds-navy, #133A52); color: #fff; }
.iconbtn svg, .iconbtn i { width: 18px; height: 18px; }

/* --- 2.1b the mockup's panel primitives (L4) ----------------------
   Ported from mockups/direction-d-blend.html. `.stack` is the panel's
   vertical rhythm primitive — it replaces the 34 Tailwind `mt-*`
   utilities that used to set the gap between panel blocks one element
   at a time. `.eyebrow / .title / .hint` is the step header, and
   `.explain` is the one-line clarifier that sits under a control. */
.stack       { display: grid; gap: var(--space-4, 16px); }
.stack.tight { gap: var(--space-3, 12px); }

/* A row list that pulls out to the panel edges but keeps a top rule. */
.rows { border-top: 1px solid var(--line-2); }

.eyebrow {
  font-size: var(--t-xs);
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--accent-700);
  font-weight: var(--w-semi);
}
/* 20px — the rung that used to be unreachable because --text-lg was
   aliased onto --t-md (L5, "step title → 20px"). */
.title {
  font-size: var(--t-lg);
  font-weight: var(--w-semi);
  color: var(--tuds-navy, #133A52);
  letter-spacing: -.01em;
  line-height: var(--lh-tight, 1.25);
  margin: 0;
}
.hint {
  font-size: var(--t-xs);
  color: var(--ink-2);
  line-height: var(--lh-body, 1.5);
  margin: var(--space-1, 4px) 0 0;
}
.explain {
  font-size: var(--t-xs);
  color: var(--ink-2);
  line-height: var(--lh-body, 1.5);
  margin: var(--space-1, 4px) 0 0;
}
.explain b { color: var(--ink); font-weight: 500; }

/* Every step opens with the same three lines. */
.app-shell .step-head { margin: 0 0 var(--space-4, 16px); }

/* --- 2.1c the panel's own spacing utilities (L11) ------------------
   The panel region of index.html used to carry ~70 Tailwind spacing
   utilities, which is a second spacing system sitting on top of the
   shell's. These are the same rungs, expressed in the shell's tokens,
   so the panel has one scale and the markup no longer depends on the
   Tailwind CDN for its rhythm. Prefer `.stack` over a run of these. */
.stack.hair  { gap: var(--space-1, 4px); }
.u-m-0   { margin: 0; }
.u-mt-1  { margin-top: var(--space-1, 4px); }
.u-mt-2  { margin-top: var(--space-2, 8px); }
.u-mt-3  { margin-top: var(--space-3, 12px); }
.u-mb-1  { margin-bottom: var(--space-1, 4px); }
.u-mb-2  { margin-bottom: var(--space-2, 8px); }
.u-mb-3  { margin-bottom: var(--space-3, 12px); }
.u-mb-4  { margin-bottom: var(--space-4, 16px); }
.u-ml-1  { margin-left: var(--space-1, 4px); }
.u-gap-1 { gap: var(--space-1, 4px); }
.u-gap-2 { gap: var(--space-2, 8px); }
.u-px-1  { padding-left: var(--space-1, 4px); padding-right: var(--space-1, 4px); }
.u-px-3  { padding-left: var(--space-3, 12px); padding-right: var(--space-3, 12px); }
.u-py-2  { padding-top: var(--space-2, 8px); padding-bottom: var(--space-2, 8px); }
.u-p-3   { padding: var(--space-3, 12px); }

/* --- 2.2 row: label · value · chevron, 48px, expands in place ---- */
.row {
  width: 100%;
  min-height: var(--row-min-h, 44px);
  display: flex;
  align-items: center;
  gap: var(--space-3, 12px);
  padding: var(--row-pad-y, 12px) var(--row-pad-x, 16px);
  border: 0;
  border-bottom: 1px solid var(--line-2);
  background: transparent;
  text-align: left;
  cursor: pointer;
  transition: background var(--transition-fast, 150ms ease);
}
.row:hover { background: var(--surface-2); }
.row .row-label {
  flex: 1; min-width: 0;
  font-size: var(--t-sm);
  font-weight: 500;
  color: var(--ink);
}
.row .row-label small {
  display: block;
  font-size: var(--t-xs);
  font-weight: var(--w-reg);
  color: var(--ink-3);
  line-height: 1.45;
  margin-top: 2px;
}
.row .row-value {
  font-size: var(--t-sm);
  color: var(--ink-2);
  text-align: right;
  white-space: nowrap;
}
.row .row-chevron { color: var(--ink-3); flex: none; width: 16px; height: 16px; }
.row-body { padding: var(--space-4, 16px); border-bottom: 1px solid var(--line-2); }

/* --- 2.3 card ---------------------------------------------------- */
/* --surface-2, not --surface: a white card on a white panel was invisible
   as a grouping (01 F11). The half-step fill is what says "these things
   belong together" without adding a shadow. */
.card {
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: var(--space-4, 16px);
  box-shadow: none;              /* shadow means floating, not grouped */
}
.card + .card { margin-top: var(--space-3, 12px); }
.card-header {
  font-size: var(--t-xs);
  font-weight: var(--w-semi);
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ink-2);
  margin-bottom: var(--space-3, 12px);
}

/* completion gate card (borrowed from Direction C) */
.gate {
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: var(--space-3, 12px) var(--space-4, 16px);
  display: flex;
  align-items: center;
  gap: var(--space-3, 12px);
  background: var(--surface-2);
}
.gate.is-done { border-color: rgba(47, 125, 79, .35); background: var(--ok-bg); }
.gate.is-todo { border-color: rgba(154, 98, 18, .35); background: var(--warn-bg); }
.gate-icon {
  width: 28px; height: 28px; border-radius: var(--r-full);
  display: grid; place-items: center; flex: none; color: #fff;
  background: var(--ink-3);
}
.gate.is-done .gate-icon { background: var(--ok); }
.gate.is-todo .gate-icon { background: var(--warn); }
.gate-text { flex: 1; min-width: 0; }
.gate-text b { display: block; font-size: var(--t-sm); font-weight: var(--w-semi); color: var(--ink); }
.gate-text small { display: block; font-size: var(--t-xs); color: var(--ink-2); margin-top: 1px; line-height: 1.4; }
.gate-value { font-size: var(--t-md); font-weight: var(--w-semi); color: var(--tuds-navy, #133A52); }

/* --- 2.4 chip (single-select) ------------------------------------ */
.chip {
  height: 32px;
  padding: 0 var(--space-3, 12px);
  border: 0;
  border-radius: var(--r-full);
  font-size: var(--t-sm);
  font-weight: 500;
  background: var(--line-2);
  color: var(--ink-2);
  display: inline-flex;
  align-items: center;
  gap: var(--space-2, 8px);
  cursor: pointer;
  transition: background var(--transition-fast, 150ms ease), color var(--transition-fast, 150ms ease);
}
.chip:hover { background: #E3E9EC; color: var(--ink); }
.chip[aria-pressed="true"], .chip.selected { background: var(--tuds-navy, #133A52); color: #fff; }
.chip:disabled, .chip.disabled { opacity: .4; cursor: not-allowed; }
.chips { display: flex; flex-wrap: wrap; gap: var(--space-2, 8px); }

/* --- 2.5 swatch --------------------------------------------------- */
.swatch {
  width: 56px; height: 56px;
  border-radius: var(--r-lg);
  border: 1px solid rgba(0, 0, 0, .10);
  overflow: hidden;
  position: relative;
  flex: none;
  background: var(--line-2);
}
.swatch[aria-pressed="true"], .swatch.selected {
  box-shadow: 0 0 0 2px var(--accent);
  border-color: var(--accent);
}
.swatch-name { font-size: var(--t-xs); color: var(--ink-2); margin-top: var(--space-1, 4px); }

/* --- 2.6 field: label above, full width, help below --------------- */
.field { display: flex; flex-direction: column; gap: var(--space-1, 4px); }
.field > label,
.field-label {
  font-size: var(--t-xs);
  font-weight: 500;
  color: var(--ink-2);
  display: flex;
  align-items: center;
  gap: var(--space-1, 4px);
}
.field input,
.field select,
.field textarea,
.form-select,
.form-input,
.permit-info-input {
  height: 36px;
  width: 100%;
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 0 var(--space-3, 12px);
  font-family: var(--font-family);
  font-size: var(--t-sm);
  color: var(--ink);
  background: var(--surface);
}
.field textarea, textarea.form-input { height: auto; padding: var(--space-2, 8px) var(--space-3, 12px); line-height: var(--lh-body, 1.5); }
.field input:focus,
.field select:focus,
.field textarea:focus,
.form-select:focus,
.permit-info-input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-ring);
}
.field-help,
.field .help {
  font-size: var(--t-xs);
  color: var(--ink-3);
  line-height: 1.4;
}
.field input::placeholder,
.field textarea::placeholder,
.permit-info-input::placeholder { color: var(--ink-3); font-size: var(--t-xs); }

/* --- 2.7 badge: status lives here, never in a button colour ------- */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1, 4px);
  height: 22px;
  padding: 0 var(--space-2, 8px);
  border-radius: var(--r-full);
  font-size: var(--t-xs);
  font-weight: var(--w-semi);
  white-space: nowrap;
}
.badge-neutral { background: var(--line-2); color: var(--ink-2); }
.badge-info    { background: var(--accent-50); color: var(--accent-700); }
.badge-success { background: var(--ok-bg);   color: var(--ok); }
.badge-warning { background: var(--warn-bg); color: var(--warn); }
.badge-error   { background: var(--err-bg);  color: var(--err); }

/* --- 2.8 callout -------------------------------------------------- */
.callout {
  display: flex;
  gap: var(--space-3, 12px);
  padding: var(--space-3, 12px) var(--space-4, 16px);
  border-radius: var(--r);
  border: 1px solid var(--line);
  background: var(--surface-2);
  font-size: var(--t-xs);
  line-height: 1.5;
  color: var(--ink-2);
}
.callout-info    { border-color: rgba(45,106,106,.3); background: var(--accent-50); color: var(--accent-700); }
.callout-warning { border-color: rgba(154,98,18,.3);  background: var(--warn-bg);   color: var(--warn); }
.callout-error   { border-color: rgba(179,38,30,.3);  background: var(--err-bg);    color: var(--err); }
.callout-success { border-color: rgba(47,125,79,.3);  background: var(--ok-bg);     color: var(--ok); }

/* --- 2.9 segmented control ---------------------------------------- */
.seg {
  display: flex;
  gap: 2px;
  background: #E2E8EB;
  border-radius: var(--r-sm);
  padding: 2px;
  flex: none;
}
.seg button {
  height: 26px;
  padding: 0 12px;
  border: 0;
  border-radius: 5px;
  background: transparent;
  font-size: var(--t-xs);
  font-weight: 500;
  color: var(--ink-2);
  cursor: pointer;
  transition: background var(--transition-fast, 150ms ease), color var(--transition-fast, 150ms ease);
}
/* The proposal send sheet has four Materials choices (round 2 R10): let them wrap. */
#proposalSendSheet .seg { flex-wrap: wrap; }
.seg button.active,
.seg button[aria-pressed="true"] {
  background: #fff;
  color: var(--tuds-navy, #133A52);
  font-weight: var(--w-semi);
  box-shadow: var(--pill-active-shadow, 0 1px 3px rgba(0,0,0,.08));
}

/* --- 2.10 sheet: one overlay surface for every long form ---------- */
.sheet-scrim {
  position: fixed;
  inset: 0;
  background: rgba(9, 29, 41, .5);
  z-index: 420;
  display: flex;
  justify-content: flex-end;
}
.sheet-scrim.hidden { display: none !important; }
.sheet {
  background: var(--surface);
  width: 720px;
  max-width: 96vw;
  height: 100%;
  display: flex;
  flex-direction: column;
  box-shadow: var(--shadow-modal);
  animation: sheetIn .18s ease-out;
}
.sheet.sheet-lg { width: 1040px; }
.sheet.sheet-sm { width: 480px; }
@keyframes sheetIn { from { transform: translateX(16px); opacity: .4; } to { transform: none; opacity: 1; } }

.sheet-head {
  height: 56px;
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--space-3, 12px);
  padding: 0 var(--space-6, 32px);
  border-bottom: 1px solid var(--line);
}
.sheet-head h2 {
  margin: 0;
  font-size: var(--t-md);
  font-weight: var(--w-semi);
  color: var(--tuds-navy, #133A52);
  letter-spacing: -.01em;
}
.sheet-head .sheet-close { margin-left: auto; }
.sheet-tabs { display: flex; gap: 4px; margin-left: var(--space-4, 16px); }
.sheet-tabs button {
  height: 28px; padding: 0 12px; border: 0; border-radius: var(--r-full);
  background: transparent; font-size: var(--t-xs); color: var(--ink-2); cursor: pointer;
}
.sheet-tabs button[aria-pressed="true"] { background: var(--tuds-navy, #133A52); color: #fff; font-weight: var(--w-semi); }
.sheet-body { padding: var(--space-6, 32px); overflow: auto; flex: 1; min-height: 0; }
.sheet-foot {
  height: 64px; flex: none; display: flex; align-items: center; gap: var(--space-3, 12px);
  padding: 0 var(--space-6, 32px); border-top: 1px solid var(--line); background: var(--surface-2);
}
.sheet-foot .sheet-foot-spacer { flex: 1; }

/* Two-column field grid — what a 30-field permit form needs and a
   380px rail physically cannot give it. */
.sheet-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4, 16px) var(--space-6, 32px); }
.sheet-grid .wide { grid-column: 1 / -1; }
.sheet-subhead {
  grid-column: 1 / -1;
  font-size: var(--t-xs); font-weight: var(--w-semi); letter-spacing: .07em;
  text-transform: uppercase; color: var(--ink-3);
  padding-top: var(--space-2, 8px); border-top: 1px solid var(--line-2); margin-top: var(--space-2, 8px);
}
.sheet-subhead:first-child { border-top: 0; margin-top: 0; padding-top: 0; }

/* --- 2.11 popover / dropdown: ONE surface ------------------------- */
.snap-popover,
#layersDropdown,
.mode-badge-popover {
  background: var(--surface) !important;
  border: 1px solid var(--line) !important;
  border-radius: var(--r-lg) !important;
  box-shadow: var(--shadow-pop) !important;
  padding: 4px !important;
  color: var(--ink) !important;
}
.snap-option,
#layersDropdown label {
  display: flex !important;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-height: 32px;
  padding: 0 var(--space-3, 12px) !important;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  font-size: var(--t-xs) !important;
  color: var(--ink) !important;
  cursor: pointer;
  text-align: left;
}
.snap-option:hover,
#layersDropdown label:hover { background: var(--line-2); }
#layersDropdown > div:first-child {
  font-size: var(--t-xs) !important;
  color: var(--ink-3) !important;
  letter-spacing: .06em;
  padding: var(--space-1, 4px) var(--space-3, 12px);
}
#layersDropdown input[type="checkbox"] { accent-color: var(--accent); }

/* ==================================================================
   3 · SHELL LAYOUT
   ================================================================== */

.app-shell {
  height: 100vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* --- 3.1 header: 56px, logo · project · stepper · account -------- */
.app-shell > .deck-calculator-header {
  height: var(--h-hdr);
  flex: none;
  background: var(--chrome);
  background-image: none;
  border-bottom: 0;
  padding: 0 var(--space-4, 16px);
  gap: var(--space-4, 16px);
}
.app-shell > .deck-calculator-header::before { display: none; }
.app-shell > .deck-calculator-header .header-content { gap: var(--space-3, 12px); flex: none; }
.app-shell > .deck-calculator-header .header-logo { height: 26px; }
.app-shell > .deck-calculator-header h1 {
  font-size: var(--t-sm) !important;
  font-weight: var(--w-semi) !important;
  letter-spacing: -.01em;
  color: #fff;
}

.shell-projname {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 0;
  font-size: var(--t-sm);
  color: var(--chrome-ink);
  min-width: 0;
}
.shell-projname-input {
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  color: #fff;
  font-family: var(--font-family);
  font-size: var(--t-sm);
  font-weight: var(--w-semi);
  padding: 4px 8px;
  max-width: 220px;
  min-width: 80px;
  text-overflow: ellipsis;
}
.shell-projname-input:hover { background: rgba(255, 255, 255, .08); }
.shell-projname-input:focus {
  outline: none;
  background: rgba(255, 255, 255, .14);
  border-color: rgba(255, 255, 255, .3);
}
.hdr-divider { width: 1px; height: 24px; background: rgba(255, 255, 255, .14); flex: none; }

/* the ONE stepper, on a track, centred */
.shell-stepper {
  margin: 0 auto;
  display: flex;
  align-items: center;
  flex: none;
  min-width: 0;
}
.shell-stepper .stepper-track {
  display: flex;
  gap: 2px;
  background: var(--chrome-2);
  border-radius: var(--r-full);
  padding: 3px;
}
.shell-step {
  display: flex;
  align-items: center;
  gap: var(--space-2, 8px);
  padding: var(--space-1, 4px) var(--space-3, 12px);
  border: 0;
  border-radius: var(--r-full);
  background: transparent;
  font-family: var(--font-family);
  font-size: var(--t-xs);
  color: #BCD0DC; /* upcoming steps: lifted for the kiosk (QA 2026-10-01) */
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--transition-fast, 150ms ease), color var(--transition-fast, 150ms ease);
}
.shell-step:hover { background: rgba(255, 255, 255, .07); color: #fff; }
.shell-step-num {
  width: 17px; height: 17px;
  border-radius: var(--r-full);
  display: grid; place-items: center; flex: none;
  font-size: 11px; font-weight: var(--w-semi);
  border: 1px solid rgba(255, 255, 255, .3);
  color: inherit;
}
.shell-step-num svg { width: 11px; height: 11px; }
.shell-step.complete { color: #CFE3D9; }
.shell-step.complete .shell-step-num { background: var(--ok); border-color: var(--ok); color: #fff; }
.shell-step.active { background: #fff; color: var(--chrome); font-weight: var(--w-semi); }
.shell-step.active .shell-step-num { background: var(--tuds-navy, #133A52); border-color: var(--tuds-navy, #133A52); color: #fff; }
.shell-step.unavailable { opacity: .45; cursor: not-allowed; }

.app-shell > .deck-calculator-header .header-actions { gap: var(--space-2, 8px); flex: none; }
.app-shell > .deck-calculator-header .header-action-btn {
  height: 30px;
  padding: 0 var(--space-3, 12px);
  border-radius: var(--r-full);
  font-size: var(--t-xs);
  font-weight: var(--w-semi);
  background: rgba(255, 255, 255, .10);
}
.app-shell > .deck-calculator-header .header-action-btn:hover { background: rgba(255, 255, 255, .2); }
.app-shell > .deck-calculator-header .header-action-btn svg { width: 14px; height: 14px; }
/* The one grey line under the project name: status + save state (owner 2026-10-03). */
.app-shell .shell-status-line {
  display: block;
  font-size: var(--t-xs);
  line-height: 1.2;
  color: var(--chrome-ink-2);
  padding: 0 8px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.app-shell .shell-status-line[hidden] { display: none; }
.app-shell .shell-status-line.save-error { color: #fca5a5; }

/* --- 3.2 work area: canvas + one right panel --------------------- */
.app-shell .main-layout {
  flex: 1;
  min-height: 0;
  display: flex;
  overflow: hidden;
  background: var(--bg);
}
/* the left rail is gone — #input-panel is now the right context panel */
.app-shell .main-layout #main-content-panel {
  order: 1;
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--canvas-bg);
}
.app-shell .main-layout #input-panel {
  order: 2;
  width: var(--w-panel);
  flex: 0 0 var(--w-panel);
  background: var(--surface);
  border-right: 0;
  border-left: 1px solid var(--line);
  box-shadow: none;
  overflow: hidden;
}
.app-shell .main-layout #input-panel.inspector-hidden {
  width: 0 !important;
  flex: 0 0 0 !important;
  border-left-width: 0;
}
/* the collapse tab straddles the LEFT edge of the right panel now */
.app-shell .inspector-edge-toggle {
  right: auto;
  left: -16px;
  border: 1px solid var(--line);
  border-right: none;
  border-radius: 6px 0 0 6px;
  box-shadow: -2px 0 4px rgba(0, 0, 0, .05);
}
.app-shell .inspector-edge-toggle svg { transform: rotate(180deg); }
.app-shell .main-layout #input-panel.inspector-hidden .inspector-edge-toggle { left: -16px; }
.app-shell .main-layout #input-panel.inspector-hidden .inspector-edge-toggle svg { transform: none; }

/* --- 3.3 canvas rail: the toolbar on the canvas's top edge -------- */
.canvas-rail {
  height: var(--h-rail);
  flex: none;
  background: var(--rail);
  border-bottom: 1px solid var(--rail-line);
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 0 var(--space-2, 8px);
  position: relative;
  z-index: 30;
}
.canvas-rail-tools { display: flex; align-items: center; gap: 2px; }
.canvas-rail-tools.hidden { display: none !important; }
.rail-divider { width: 1px; height: 20px; background: var(--rail-line); margin: 0 var(--space-2, 8px); flex: none; }
.rail-spacer { flex: 1; }

/* every rail control is an icon button, whatever classes JS needs on it */
.canvas-rail .btn,
.canvas-rail .tbtn {
  height: 32px;
  min-width: 32px;
  padding: 0 8px;
  border: 0 !important;
  border-radius: var(--r-sm) !important;
  background: transparent !important;
  color: var(--ink-2) !important;
  font-size: var(--t-xs) !important;
  font-weight: 500;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2, 8px);
  box-shadow: none !important;
  cursor: pointer;
}
.canvas-rail .btn:hover:not(:disabled),
.canvas-rail .tbtn:hover:not(:disabled) { background: rgba(19, 58, 82, .07) !important; color: var(--ink) !important; }
.canvas-rail .btn.active,
.canvas-rail .btn[aria-pressed="true"],
.canvas-rail .tbtn[aria-pressed="true"] { background: var(--tuds-navy, #133A52) !important; color: #fff !important; }
.canvas-rail .btn svg,
.canvas-rail .btn i,
.canvas-rail .tbtn svg { width: 17px; height: 17px; }
.canvas-rail .btn:disabled, .canvas-rail .btn.btn-disabled { opacity: .35; pointer-events: none; }
.canvas-rail .seg { margin-left: auto; }

/* the rail's popovers drop DOWN, never into the drawing */
.canvas-rail .snap-popover,
.canvas-rail #layersDropdown {
  position: absolute;
  top: calc(100% + var(--space-2, 8px));
  left: 0;
  right: auto;
  min-width: 150px;
  z-index: 60;
}

/* The 3D view's controls in the rail (owner 2026-10-01): the plan tools'
   components — segmented groups, icon buttons, the Layers popover. */
.canvas-rail-3d .rail-3d { display: flex; align-items: center; gap: 4px; flex-wrap: nowrap; }
.canvas-rail .canvas-rail-3d .seg { margin-left: 0; }
.canvas-rail-3d .seg button { padding: 0 10px; display: inline-flex; align-items: center; justify-content: center; }
.canvas-rail-3d .seg button svg,
.canvas-rail-3d .seg button i { width: 16px; height: 16px; }
.canvas-rail-3d .seg[hidden],
.canvas-rail-3d .rail-divider[hidden] { display: none; }
.canvas-rail-3d .rail-popover {
  position: absolute;
  top: calc(100% + var(--space-2, 8px));
  left: 0;
  min-width: 210px;
  z-index: 60;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-pop);
  padding: 4px;
  color: var(--ink);
}
.canvas-rail-3d .rail-popover[hidden] { display: none; }
.canvas-rail-3d .rail-popover-title {
  font-size: var(--t-xs);
  color: var(--ink-3);
  letter-spacing: .06em;
  padding: var(--space-1, 4px) var(--space-3, 12px);
}
.canvas-rail-3d .rail-popover label {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 32px;
  padding: 0 var(--space-3, 12px);
  border-radius: var(--r-sm);
  font-size: var(--t-xs);
  cursor: pointer;
}
.canvas-rail-3d .rail-popover label[hidden] { display: none; }
.canvas-rail-3d .rail-popover label:hover { background: var(--line-2); }
.canvas-rail-3d .rail-popover input[type="checkbox"] { accent-color: var(--accent); }
/* A narrow bar (a phone): the wide items fold into the ⋯ menu. */
.canvas-rail-3d .rail-3d-more { display: none; }
.canvas-rail-3d.is-compact .rail-3d-wide { display: none !important; }
.canvas-rail-3d.is-compact .rail-3d-more { display: block; }
.canvas-rail-3d .rail-popover-row {
  display: flex;
  align-items: center;
  width: 100%;
  min-height: 32px;
  padding: 0 var(--space-3, 12px);
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  font-size: var(--t-xs);
  color: var(--ink);
  text-align: left;
  cursor: pointer;
}
.canvas-rail-3d .rail-popover-row:hover { background: var(--line-2); }
.canvas-rail-3d .rail-popover-row.active { color: var(--tuds-navy, #133A52); font-weight: var(--w-semi, 600); background: var(--accent-50, #EAF4F4); }
.canvas-rail-3d .rail-popover [data-light-group][hidden] { display: none; }
.touch .canvas-rail-3d .rail-popover-row { min-height: var(--touch-min); }
.touch .canvas-rail-3d .seg button { min-width: var(--touch-min); }
.touch .canvas-rail-3d .rail-popover label { min-height: var(--touch-min); }

/* --- 3.4 canvas stage -------------------------------------------- */
.app-shell #main-content-panel > .info-card {
  flex: 1;
  min-height: 0;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none !important;
}
/* The stage is grey and the plan is a white sheet sitting on it (L10).
   The drawing used to bleed to all four edges of a full-width off-white
   box, so there was nothing to tell a customer they were looking at a
   drawing rather than at the application's background. */
.app-shell #canvasContainerWrapper {
  background: var(--canvas-bg);
  padding: var(--space-3, 12px);
}
/* One grid (look feedback 13): the canvas draws it, zooming with the plan.
   The container's dot pattern was a second, fixed 24px grid under it. */
.app-shell #canvasContainer {
  background: var(--surface);
  border: 1px solid var(--rail-line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-float);
  overflow: hidden;
}


/* the old right-edge icon rail is gone; if any legacy markup survives,
   it must never float over the drawing again. */
.app-shell .floating-controls { display: none !important; }

/* --- 3.5 right panel: three stacked sections, ONE scroll region --- */
.shell-panel {
  height: 100%;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: thin;
  background: var(--surface);
}
.shell-panel::-webkit-scrollbar { width: 9px; }
.shell-panel::-webkit-scrollbar-thumb { background: #D5DDE2; border-radius: 9px; border: 3px solid #fff; }

/* Sections are separated by SPACE and a hairline, not by an 8px grey bar
   (L5). The bar cost ~8px of panel height at every seam and read as a gap
   in the surface rather than a change of subject.

   It is still drawn as a border-TOP on a visible section that follows
   another visible one: the Estimate and Selected-material sections are
   permanently `hidden` and still count as children, so a border-bottom
   with `:last-child` exempt left a trailing rule under the last section
   on Draw and Decking. */
.ctx-section { border-bottom: 0; }
.ctx-section:not([hidden]) ~ .ctx-section:not([hidden]) {
  border-top: 1px solid var(--line);
  margin-top: var(--panel-sec-gap, 32px);
}
/* 28px, not 40px: this is a label strip, not a toolbar. Twelve pixels
   back on every section, and the step content starts above the fold. */
.ctx-section-head {
  height: var(--panel-head-h, 28px);
  display: flex;
  align-items: center;
  gap: var(--space-2, 8px);
  padding: 0 var(--space-4, 16px);
  border-bottom: 1px solid var(--line-2);
  background: var(--surface);
  z-index: 3;
}
/* Only the step's own header pins — a sticky header on a section the rep
   has scrolled past is just a second header. */
#panelSelectionSection > .ctx-section-head { position: sticky; top: 0; }
.ctx-section-head h3 {
  margin: 0;
  font-size: var(--t-xs);
  font-weight: var(--w-semi);
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--accent-700);
}
.ctx-section-head .ctx-section-meta { margin-left: auto; font-size: var(--t-xs); color: var(--ink-3); }
.ctx-section-body { padding: var(--space-4, 16px); }
.ctx-section-body.flush { padding: 0; }

/* the wizard panel now lives inside Selection and must not scroll itself */
.app-shell .wizard-panel { overflow: visible; flex: none; }
.app-shell .wizard-content { overflow: visible; }
/* ONE containment rule for the panel: `.ctx-section-body` owns the gutter
   (var(--space-4, 16px), above). The step content inside it contributes none of its
   own — the legacy 16px/68px on .wizard-step-content would otherwise double
   the inset and leave a 68px hole above the footer — so these two zero out
   and the section body is the only place the number lives.

   `flush` is for self-padded row lists only (the Estimate section). The
   Selection body used to carry it too, which left Draw's entry cards, the
   template gallery and the Quick Rectangle inputs with NO horizontal padding
   at any width: they ran into both panel edges, and the Depth field's "ft"
   suffix was clipped — worst at 1280, where the panel drops to 340px. */
.app-shell .wizard-step-content { padding: 0; }
/* The atoms that are meant to be full-bleed pull back out of the gutter, the
   way the mockup's row lists do, so they keep their edge-to-edge hover band. */
.app-shell .config-section,
.app-shell .config-accordion-divider,
.app-shell .config-pinned-zone,
.app-shell .wizard-step-content > .row,
.app-shell .panel-content > .row {
  margin-left: calc(-1 * var(--space-4, 16px));
  margin-right: calc(-1 * var(--space-4, 16px));
}
/* The step title is visible again, as the mockup's `.title` (L4). It is no
   longer the same string as the sticky header: the header is the eyebrow
   (the step's short name, "DECKING"), the title says what to do on the step
   ("Pick the boards"), and one `.hint` line says why. */
.app-shell .wizard-step-title {
  font-size: var(--t-lg);
  font-weight: var(--w-semi);
  color: var(--tuds-navy, #133A52);
  letter-spacing: -.01em;
  line-height: var(--lh-tight, 1.25);
  margin: 0;
  padding: 0;
  border: 0;
  text-transform: none;
}
.app-shell .panel-content { padding: 0; }

/* Framing's accordion is already the row atom — normalise it so
   Decking, Railing, Review and Permit all read as the same thing. */
.app-shell .config-section {
  border: 0;
  border-bottom: 1px solid var(--line-2);
  border-radius: 0;
  /* Vertical margin 0; the horizontal pull-out of the section body's gutter
     is set above and must not be reset here. */
  margin-top: 0;
  margin-bottom: 0;
  background: transparent;
  box-shadow: none;
}
.app-shell .config-section-header {
  min-height: var(--row-min-h, 44px);
  padding: var(--row-pad-y, 12px) var(--row-pad-x, 16px);
  gap: var(--space-3, 12px);
  background: transparent;
  border: 0;
  cursor: pointer;
}
.app-shell .config-section-header:hover { background: var(--surface-2); }
.app-shell .config-section-label { font-size: var(--t-sm); font-weight: 500; color: var(--ink); }
.app-shell .config-section-value { font-size: var(--t-sm); color: var(--ink-2); }
.app-shell .config-section-body { padding: 0 var(--space-4, 16px) var(--space-4, 16px); }
.app-shell .config-accordion-divider {
  font-size: var(--t-xs);
  font-weight: var(--w-semi);
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ink-3);
  padding: var(--space-3, 12px) var(--space-4, 16px) var(--space-2, 8px);
  background: transparent;
  border: 0;
}
.app-shell .config-pinned-zone { padding: var(--space-4, 16px); border-bottom: 1px solid var(--line-2); }
/* Framing's two group heads (polish trio A1): Structure, then Code & site. */
.app-shell .config-group-head { border-bottom: 1px solid var(--line-2); }
.app-shell .config-group-head + .config-group-head,
.app-shell .config-group-head[data-group="codeSite"] { margin-top: var(--space-4, 16px); }

/* estimate-by-category rows */
.cat-row {
  display: flex;
  align-items: center;
  gap: var(--space-3, 12px);
  padding: var(--row-pad-y, 12px) var(--row-pad-x, 16px);
  border-bottom: 1px solid var(--line-2);
  width: 100%;
  border-left: 0; border-right: 0; border-top: 0;
  background: transparent;
  text-align: left;
}
.cat-row-icon {
  width: 26px; height: 26px; border-radius: var(--r-sm);
  background: var(--accent-50); color: var(--accent-700);
  display: grid; place-items: center; flex: none;
  font-size: 11px; font-weight: var(--w-semi);
}
.cat-row-name { flex: 1; min-width: 0; font-size: var(--t-sm); color: var(--ink); font-weight: 500; }
.cat-row-value { font-size: var(--t-sm); color: var(--ink); white-space: nowrap; }
.cat-row.is-empty .cat-row-icon { background: var(--line-2); color: var(--ink-3); }
.cat-row.is-empty .cat-row-name { color: var(--ink-3); }
.cat-row.is-empty .cat-row-value { color: var(--accent); font-style: italic; font-size: var(--t-xs); font-weight: var(--w-semi); }
.cat-total {
  display: flex; align-items: center; gap: var(--space-3, 12px);
  padding: 12px var(--space-4, 16px); background: var(--surface-2);
}
.cat-total-name { flex: 1; font-size: var(--t-sm); font-weight: var(--w-semi); color: var(--ink); }
.cat-total-value { font-size: var(--t-md); font-weight: var(--w-semi); color: var(--tuds-navy, #133A52); }

/* selected-material card */
.matcard { display: flex; gap: var(--space-3, 12px); align-items: flex-start; }
.matcard-swatch {
  width: 62px; height: 62px; border-radius: var(--r); overflow: hidden; flex: none;
  border: 1px solid rgba(0, 0, 0, .08); background: var(--line-2);
}
.matcard-name { font-size: var(--t-sm); font-weight: var(--w-semi); color: var(--ink); line-height: 1.25; }
.matcard-brand { font-size: var(--t-xs); color: var(--ink-2); margin-top: 1px; }
.matcard-meta { font-size: var(--t-xs); color: var(--ink-3); margin-top: 4px; }
.matcard-empty {
  display: flex; align-items: center; gap: var(--space-3, 12px);
  border: 1px dashed var(--line); border-radius: var(--r-lg);
  padding: var(--space-3, 12px) var(--space-4, 16px);
  width: 100%; background: var(--surface-2); cursor: pointer; text-align: left;
}
.matcard-empty:hover { border-color: var(--accent); background: var(--accent-50); }
.matcard-empty .matcard-empty-tile {
  width: 46px; height: 46px; border-radius: var(--r); flex: none;
  background: repeating-linear-gradient(45deg, var(--line-2), var(--line-2) 6px, #fff 6px, #fff 12px);
  border: 1px solid var(--line);
}
.matcard-empty b { display: block; font-size: var(--t-sm); font-weight: var(--w-semi); color: var(--ink); }
.matcard-empty small { display: block; font-size: var(--t-xs); color: var(--ink-3); margin-top: 2px; }

/* --- 3.6 footer: category tiles · grand total · Back · Continue --- */
.app-shell .app-footer {
  position: static;
  height: var(--h-foot);
  flex: none;
  background: var(--chrome);
  border-top: 0;
  box-shadow: none;
  padding: 0 var(--space-4, 16px);
  gap: var(--space-5, 24px);
  z-index: 30;
}
.foot-cats { display: flex; align-items: center; gap: var(--space-5, 24px); min-width: 0; overflow: hidden; }
/* The strip is a READOUT, not a to-do list (AGREED-PLAN W8): the same five
   categories on every step, an em-dash where a number hasn't arrived yet, and
   each cell is a button that lands on the step that owns it. */
.foot-cat {
  display: flex; flex-direction: column; gap: 1px; white-space: nowrap;
  align-items: flex-start; text-align: left;
  background: none; border: 0; padding: 2px 4px; margin: 0 -4px;
  border-radius: var(--r-sm, 6px);
  font-family: inherit; cursor: pointer;
  transition: background var(--transition-fast, .12s ease);
}
.foot-cat:hover { background: rgba(255, 255, 255, .08); }
.foot-cat:focus-visible { outline: 2px solid rgba(255, 255, 255, .5); outline-offset: 1px; }
.foot-cat-key {
  font-size: var(--t-xs);
  color: var(--chrome-ink-3);
  letter-spacing: .05em;
  text-transform: uppercase;
}
.foot-cat-val { font-size: 13px; color: var(--chrome-ink); }
/* Pending is a dimmed em-dash — never an italic imperative. "Choose decking"
   in the money strip told the rep to do something in the one place that is
   supposed to only report. */
/* Grok Q3: the key keeps its normal colour (--chrome-ink-3 ≈6.6:1 on
   --chrome; the old 32% white was ≈2.5:1); the em-dash value is what
   reads as pending. */
.foot-cat.is-pending .foot-cat-key { color: var(--chrome-ink-3); }
.foot-cat.is-pending .foot-cat-val { color: var(--chrome-ink-3); }
/* At laptop widths Review's three actions share the strip with all five
   categories; tighten the gaps and the label tracking so the last label
   ("Hardware") is never clipped at 1280. */
@media (max-width: 1360px) {
  .app-shell .app-footer { gap: var(--space-3, 12px); }
  .foot-cats { gap: var(--space-3, 12px); padding-right: 4px; }
  .foot-cat-key { letter-spacing: .02em; }
}
/* I-04 — the footer never clips its badge or buttons: the category cells are
   the part that gives (they shrink and ellipsize, keys first), the "N not
   priced" badge and the total never do. Seven columns since the one money
   model (Stairs and Install are constant columns). */
.foot-cat { flex: 0 1 auto; min-width: 0; }
.foot-cat-key, .foot-cat-val { max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
.foot-cats > .foot-unpriced { flex: none; }
@media (max-width: 1500px) {
  .foot-cats { gap: var(--space-3, 12px); }
  .foot-cat-key { letter-spacing: 0; }
}
.foot-divider { width: 1px; height: 34px; background: rgba(255, 255, 255, .14); flex: none; }
.foot-spacer { flex: 1; }
.app-shell .footer-cost { flex-direction: column; align-items: flex-end; gap: 1px; line-height: 1.1; }
.app-shell .footer-cost-label {
  font-size: var(--t-xs);
  color: var(--chrome-ink-3);
  letter-spacing: .05em;
  text-transform: uppercase;
  font-weight: var(--w-reg);
}
.app-shell .footer-cost-value {
  font-size: var(--t-xl);
  font-weight: var(--w-semi);
  color: #fff;
  letter-spacing: -.02em;
}
/* "· before tax" inline in the total's label while a tax rate is set (owner
   rule 2026-09-30: tax shows on the Review bottom line only). Inline so the
   footer keeps its height. */
.app-shell .footer-cost-note::before { content: ' · '; }
.app-shell .footer-nav-buttons { gap: var(--space-2, 8px); }
.app-shell .footer-back-btn {
  background: transparent !important;
  border-color: transparent !important;
  color: var(--chrome-ink) !important;
}
.app-shell .footer-back-btn:hover { background: rgba(255, 255, 255, .12) !important; }
.app-shell .footer-next-btn { min-width: 176px; height: 40px; }

/* --- 3.8 review summary ------------------------------------------ */
.review-summary-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3, 12px);
  margin-bottom: var(--space-3, 12px);
}
.review-summary-cell .k {
  font-size: var(--t-xs);
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.review-summary-cell .v { font-size: var(--t-md); font-weight: var(--w-semi); color: var(--tuds-navy, #133A52); margin-top: 2px; }
.review-actions { display: grid; gap: var(--space-2, 8px); margin-top: var(--space-3, 12px); }
.review-actions-row { display: flex; gap: var(--space-2, 8px); }
.review-actions-row > * { flex: 1; }
/* What "Share with customer" does — this replaced a whole "Customer link"
   card that carried a second Share button (QA item 10). */
.review-actions-hint {
  margin: var(--space-2, 8px) 0 0;
  font-size: var(--t-xs);
  color: var(--ink-3);
  line-height: 1.45;
}
/* Overview tiles: three equal columns, values never wrap ("4' 0"" on two
   lines at 1280/1440). A total of $10,000+ steps down one size to fit. */
.app-shell .project-overview-metrics { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; }
.app-shell .project-overview-metrics .metric-card { min-width: 0; padding: var(--space-2, 8px) var(--space-1, 4px); }
.app-shell .project-overview-metrics .metric-value {
  white-space: nowrap;
  font-size: 0.9375rem;
  letter-spacing: -0.01em;
}
.app-shell .project-overview-metrics .metric-value.is-long { font-size: 0.8125rem; }
/* The chips under the Overview metrics are specs, and say so (QA item 11). */
.project-overview-specs-label {
  font-size: var(--t-xs);
  font-weight: var(--w-semi);
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-top: var(--space-3, 12px);
}

/* ==================================================================
   4 · LEGACY NEUTRALISERS
   Nothing below adds a new look; each line removes a competing one.
   ================================================================== */

/* the 2011 bevel on the primary button, and the !important re-override
   that gave it a second appearance inside .floating-controls */
.floating-controls .btn.active,
.floating-controls .btn-primary { background-image: none !important; }

/* sub-11px labels go to 12px — reps read this across a desk */
.progress-step-number,
.visual-option-desc,
.breaker-width-select,
.tab-count,
.scratchpad-hint,
.mode-badge-custom-label { font-size: var(--t-xs) !important; }

/* The panel's inline Tailwind text utilities, folded onto the ladder.
   No !important: Tailwind's own utilities live in @layer utilities and
   `.app-shell .text-xs` already outranks a bare `.text-xs`, so the flag
   was cargo-culted and only made these two rules impossible to
   override locally (L11). */
.app-shell .text-xs { font-size: var(--t-xs); }
.app-shell .text-sm { font-size: var(--t-sm); }

/* dashboard + auth surfaces adopt the same tokens */
.dashboard-page .dashboard-table th {
  font-size: var(--t-xs);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-3);
  font-weight: var(--w-semi);
}
.dashboard-page .dashboard-table td { font-size: var(--t-sm); color: var(--ink); }
.dashboard-filter-select,
.dashboard-search-input,
.dashboard-bulk-select {
  height: 36px;
  border-radius: var(--r);
  font-family: var(--font-family);
  font-size: var(--t-sm);
}
.login-page .login-field input,
.login-page .login-field select { height: 40px; border-radius: var(--r); font-size: var(--t-sm); }
.login-page .login-title { font-size: var(--t-lg); font-weight: var(--w-semi); }

/* ==================================================================
   4b · SHEETS OVER THE CANVAS
   The permit form, the materials list and the full specifications all
   open as ONE surface on the right of the canvas instead of three
   different dialogs (a navy bar with ghost buttons, a white modal, and
   a 30-field form crammed into the rail).
   ================================================================== */

/* The panel must not become a second scroll region, and the step panels
   must not keep a transform after their entrance animation — a transform
   makes a containing block and would trap position:fixed sheets inside. */
.app-shell .wizard-step-content {
  height: auto;
  overflow: visible;
  padding-bottom: 0;
  animation: none !important;
  opacity: 1;
  transform: none;
}

.sheet .permit-info-grid,
.sheet-body .permit-info-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-4, 16px) var(--space-6, 32px);
}
.sheet-body .permit-info-label {
  display: flex;
  flex-direction: column;
  gap: var(--space-1, 4px);
  font-size: var(--t-xs);
  color: var(--ink-2);
  font-weight: 500;
}
.sheet-body .permit-info-checkbox {
  font-size: var(--t-sm);
  color: var(--ink);
  display: flex;
  align-items: center;
  gap: 8px;
}
.inf {
  width: 15px; height: 15px; border-radius: var(--r-full);
  background: var(--line-2); color: var(--ink-3);
  display: inline-grid; place-items: center;
  font-size: 11px; font-weight: var(--w-semi); flex: none;
  cursor: help;
}

/* The two legacy dialogs adopt the sheet surface. Their markup and every
   onclick stay exactly as they were. */
.specs-modal:not(.hidden),
.cut-list-modal:not(.hidden) {
  position: fixed;
  inset: 0;
  z-index: 420;
  display: flex;
  justify-content: flex-end;
  background: rgba(9, 29, 41, .5);
}
.specs-modal-content,
.cut-list-modal-content {
  width: 720px !important;
  max-width: 96vw;
  height: 100%;
  max-height: 100%;
  margin: 0;
  border-radius: 0;
  box-shadow: var(--shadow-modal);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.specs-modal-header,
.cut-list-modal-header,
#framingBomModal .specs-modal-header {
  height: 56px;
  flex: none;
  padding: 0 var(--space-6, 32px) !important;
  border-bottom: 1px solid var(--line);
  /* never a navy bar with white ghost buttons — one header, one surface */
  background: var(--surface) !important;
  color: var(--ink) !important;
}
#framingBomModal .specs-modal-header h2,
.specs-modal-header h2,
.cut-list-modal-header h2 {
  font-size: var(--t-md) !important;
  font-weight: var(--w-semi) !important;
  color: var(--tuds-navy, #133A52) !important;
}
#framingBomModal .specs-modal-header .btn,
#framingBomModal .specs-modal-header button {
  background: var(--surface) !important;
  border: 1px solid var(--line) !important;
  color: var(--ink) !important;
  font-size: var(--t-xs) !important;
}
#framingBomModal .specs-modal-header #sendToQmBtn {
  background: var(--accent) !important;
  border-color: var(--accent) !important;
  color: #fff !important;
}
.specs-modal-body,
.cut-list-modal-body,
#framingBomContent {
  flex: 1;
  min-height: 0;
  overflow: auto;
  padding: var(--space-6, 32px) !important;
  max-height: none !important;
}
.specs-modal-footer,
.cut-list-modal-footer {
  height: 64px;
  flex: none;
  border-top: 1px solid var(--line);
  background: var(--surface-2);
  padding: 0 var(--space-6, 32px);
  display: flex;
  align-items: center;
  gap: var(--space-3, 12px);
  justify-content: flex-end;
}

/* --- Review summary meta ------------------------------------------ */
.review-summary-meta { margin-top: var(--space-3, 12px); }
.review-scope-badges { display: flex; gap: var(--space-2, 8px); flex-wrap: wrap; margin-bottom: var(--space-3, 12px); }
.review-meta-row {
  display: flex;
  align-items: baseline;
  gap: var(--space-3, 12px);
  padding: var(--space-1, 4px) 0;
  border-top: 1px solid var(--line-2);
  font-size: var(--t-xs);
}
.review-meta-k { color: var(--ink-3); width: 86px; flex: none; }
.review-meta-v { color: var(--ink); flex: 1; min-width: 0; }
.review-meta-v.is-empty { color: var(--accent); font-style: italic; font-weight: var(--w-semi); }
/* "Edit" on a read-only overview row (Install → the Install step): a teal text link. */
.review-meta-edit {
  flex: none; border: 0; background: none; padding: 0; cursor: pointer;
  font: inherit; font-weight: var(--w-semi); color: var(--tuds-teal, #2D6A6A);
}
.review-meta-edit:hover { text-decoration: underline; }

.app-shell .review-edit-links { display: flex; flex-wrap: wrap; gap: var(--space-2, 8px); margin-top: var(--space-4, 16px); }
.app-shell .review-edit-btn { height: 28px; padding: 0 var(--space-3, 12px); font-size: var(--t-xs); }

/* ==================================================================
   4c · PICKER DENSITY, SWATCH CARDS, "WHY THESE SIZES?"
   ================================================================== */

.picker-density-row {
  display: flex;
  align-items: center;
  gap: var(--space-3, 12px);
  margin-bottom: var(--space-2, 8px);
}
.picker-density-row .field-label { flex: 1; }
.picker-density-toggle { flex: none; }
.picker-density-toggle button { height: 24px; padding: 0 var(--space-2, 8px); }

/* ---- the product card (L7, decision 2) ---------------------------
   Ported from mockups/direction-d-blend.html. Texture, colour name,
   brand + line, and two or three benefit bullets: the only surface in
   the app a customer is meant to look at. The compact list is still one
   click away on the density toggle. */
.pgrid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3, 12px); }
.pgrid.one { grid-template-columns: 1fr; }
.pcard {
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--surface);
  text-align: left;
  padding: 0;
  position: relative;
  display: block;
  cursor: pointer;
  transition: border-color var(--transition-fast, 150ms ease),
              box-shadow var(--transition-fast, 150ms ease),
              transform var(--transition-fast, 150ms ease);
}
.pcard:hover { border-color: #B9C6CD; box-shadow: var(--shadow-float); transform: translateY(-1px); }
.pcard[aria-pressed="true"] { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-ring); }
.pcard .pimg { display: block; height: 78px; position: relative; overflow: hidden; }
.pcard.tall .pimg { height: 112px; }
/* The real board texture swatch (3D realism phase 1): fills the picture box. */
.pcard .pimg img { display: block; width: 100%; height: 100%; object-fit: cover; }
.pcard .pbody { display: block; padding: var(--space-2, 8px) var(--space-3, 12px) var(--space-3, 12px); }
.pcard .pn { display: block; font-size: var(--t-sm); font-weight: var(--w-semi); color: var(--ink); line-height: var(--lh-tight, 1.25); }
.pcard .pb { display: block; font-size: var(--t-xs); color: var(--ink-3); margin-top: 1px; }
.pcard .ptick {
  position: absolute; right: var(--space-2, 8px); top: var(--space-2, 8px);
  width: 20px; height: 20px; border-radius: var(--r-full);
  background: var(--accent); color: #fff; display: none; place-items: center;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .25);
}
.pcard[aria-pressed="true"] .ptick { display: grid; }
.pcard .pflag { position: absolute; left: var(--space-2, 8px); top: var(--space-2, 8px); }
.bens { list-style: none; margin: var(--space-2, 8px) 0 0; padding: 0; display: grid; gap: var(--space-1, 4px); }
.bens li {
  font-size: var(--t-xs); color: var(--ink-2); line-height: 1.4;
  display: flex; gap: var(--space-2, 8px); align-items: flex-start;
}
.bens svg { flex: none; margin-top: 2px; color: var(--accent); }

.swatch-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-2, 8px); }
.swatch-card {
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  padding: 8px;
  display: flex;
  flex-direction: column;
  gap: var(--space-1, 4px);
  cursor: pointer;
  text-align: left;
  transition: border-color var(--transition-fast, 150ms ease), box-shadow var(--transition-fast, 150ms ease);
}
.swatch-card:hover { border-color: #B9C6CD; box-shadow: var(--shadow-float); }
.swatch-card.selected { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-ring); }

/* ---- ONE CARD GRAMMAR (L8) ---------------------------------------
   .template-card (8px radius, 1px grey, teal wash on hover) and
   .visual-option (12px radius, 1.5px grey, teal wash when selected)
   were two more card looks on the same panel. Both fold onto the
   .swatch-card spec above: one radius, one border, one hover, and
   selection shown by the accent ring rather than by repainting the
   card's fill. */
.app-shell .template-card,
.app-shell .visual-option {
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  transition: border-color var(--transition-fast, 150ms ease),
              box-shadow var(--transition-fast, 150ms ease);
}
.app-shell .template-card:hover,
.app-shell .visual-option:hover {
  border-color: #B9C6CD;
  background: var(--surface);
  box-shadow: var(--shadow-float);
}
.app-shell .visual-option.selected,
.app-shell .template-card.selected {
  border-color: var(--accent);
  background: var(--surface);
  box-shadow: 0 0 0 3px var(--accent-ring);
}
.app-shell .visual-option.selected .visual-option-icon { color: var(--accent); }
.app-shell .template-card:hover .template-svg { color: var(--accent); }
.swatch-card .swatch { width: 100%; height: 44px; border-radius: var(--r); }
.swatch-card .swatch-name { margin: 0; font-size: var(--t-xs); font-weight: 500; color: var(--ink); line-height: 1.25; }
.swatch-card .badge { align-self: flex-start; }

/* Board direction follows the board in the list (look feedback 11): a
   block between rows, on the rows' rhythm. */
.app-shell .decking-direction-block {
  padding: var(--space-3, 12px) 0 var(--space-4, 16px);
  border-bottom: 1px solid var(--line-2);
}

/* Composite colour is swatches only (look feedback 10). */
#compositeColorCards { display: grid; }

/* the railing picker is already a tile grid — "list" collapses it to rows */
body[data-picker-density="list"] #railingColorGrid {
  display: flex !important;
  flex-direction: column;
  gap: 0;
}
body[data-picker-density="list"] #railingColorGrid .visual-option {
  display: flex;
  align-items: center;
  gap: var(--space-3, 12px);
  min-height: 40px;
  padding: 0 var(--space-3, 12px);
  border: 0;
  border-bottom: 1px solid var(--line-2);
  border-radius: 0;
  text-align: left;
}
body[data-picker-density="list"] #railingColorGrid .visual-option-icon { margin: 0 !important; }
body[data-picker-density="list"] #railingColorGrid .visual-option-name { font-size: var(--t-sm); }

/* "Why these sizes?" */
#whySizesBtn { margin: 0 0 var(--space-3, 12px); padding-left: 0; }
.why-row { padding: var(--space-3, 12px) 0; border-bottom: 1px solid var(--line-2); }
.why-row:last-of-type { border-bottom: 0; }
.why-row b { display: block; font-size: var(--t-sm); font-weight: var(--w-semi); color: var(--ink); }
.why-row p { margin: 4px 0 0; font-size: var(--t-xs); color: var(--ink-2); line-height: 1.55; }

.review-total-link { margin-top: var(--space-2, 8px); padding-left: 0; }

/* "Why these sizes?" — a button under the height card, with room around it. */
.app-shell .why-sizes-btn {
  margin: var(--space-3, 12px) 0;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2, 8px);
}

/* The Pro grand total is an exact number — never a range, and never a
   caption under it. (Ranges belong to Homeowner mode, Phase 10.) */
.app-shell .footer-cost .foot-range,
.app-shell .footer-cost-caption { display: none !important; }

/* ==================================================================
   5 · NARROW DESKTOP
   The product is desktop-only today (Phase 8 owns the mobile pass);
   this only keeps 1280px usable.
   ================================================================== */
/* The 1360px `--w-panel: 340px` override is gone: the clamp on --w-panel
   already lands on 340px at 1280 and grows from there (L6). */
@media (max-width: 1360px) {
  /* Labels SHRINK, they do not hide. Hiding them left six numbered dots and
     one word at 1280 — the stepper stopped being a map of the job and became
     a progress bar nobody could read (AGREED-PLAN, responsive floor). */
  .shell-step { padding: var(--space-1, 4px) var(--space-2, 8px); gap: var(--space-1, 4px); font-size: 11px; }
  .shell-step-num { width: 16px; height: 16px; font-size: 11px; }
  .shell-step-num svg { width: 10px; height: 10px; }
}
@media (max-width: 1180px) {
  .shell-step { padding: var(--space-1, 4px); gap: var(--space-1, 4px); font-size: 11px; }
}

/* The "ft" suffix is a fixed label, not a flexible column: the number
   input owns the slack. Without this the span was the thing that gave
   way at 1280 and the unit was clipped to "f" (L6). */
.app-shell .dimension-input-group { min-width: 0; }
.app-shell .dimension-input-group input { min-width: 0; flex: 1 1 auto; }
.app-shell .dimension-unit { flex: 0 0 auto; white-space: nowrap; }

/* ==================================================================
   6 · THE HEIGHT PROMPT
   The first modal in the product, and the only dark one: navy card,
   slate borders, white text, while every sheet and card behind it is
   light (QA item 17). Two modal styles in one flow reads as two
   different applications. It stays non-dismissable — no scrim click,
   no close button — because the height is mandatory; only its
   surface changes.
   ================================================================== */
.height-prompt-overlay { background: rgba(9, 29, 41, .5); }

.height-prompt {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-modal);
  color: var(--ink);
}
.height-prompt-title { color: var(--tuds-navy, #133A52); }
.height-prompt-subtitle,
.height-prompt-hint { color: var(--ink-2); }
.height-prompt-toggle-label { color: var(--ink-3); }

.height-prompt-stepper-label { color: var(--ink-3); }
.height-prompt-stepper-controls { border-color: var(--line); }
.height-prompt-stepper-btn {
  background: var(--surface-2);
  border-color: var(--line);
  color: var(--ink-2);
}
.height-prompt-stepper-btn:hover { background: var(--line-2); color: var(--ink); }
.height-prompt-stepper-value {
  background: var(--surface);
  border-top-color: var(--line);
  border-bottom-color: var(--line);
  color: var(--ink);
}
.height-prompt-stepper-value input { color: var(--ink); }

.height-prompt-toggle { border-color: var(--line); }
.height-prompt-toggle-option {
  background: var(--surface);
  color: var(--ink-2);
  border-right-color: var(--line);
}
.height-prompt-toggle-option:hover { background: var(--surface-2); color: var(--ink); }
.height-prompt-toggle-option.active { background: var(--tuds-teal, #2D6A6A); color: #fff; }

.height-prompt-keep { color: var(--ink-2); }

/* ==================================================================
   7 · THE "NONE" AFFIRMATIVES  (W4 part B)
   "No stairs on this deck" / "No railing on this quote". A tick box,
   not a button: the rep is recording a decision, and the control has
   to look like it can be un-ticked. Off it is quiet; on it reads the
   same green as a finished gate, because that is what it is.
   ================================================================== */
.none-affirmative {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2, 8px);
  width: 100%;
  margin-top: var(--space-3, 12px);
  padding: var(--space-3, 12px);
  text-align: left;
  background: var(--surface-2, #FAFBFC);
  border: 1px solid var(--line, #E3E9ED);
  border-radius: var(--r-md, 8px);
  font-family: inherit;
  cursor: pointer;
  transition: background var(--transition-fast, .12s ease),
              border-color var(--transition-fast, .12s ease);
}
.none-affirmative:hover { border-color: var(--line-2, #CFD8DE); }
.none-affirmative b {
  display: block;
  font-size: var(--t-sm, 13px);
  font-weight: var(--w-semi, 600);
  color: var(--ink, #14212B);
}
.none-affirmative small {
  display: block;
  font-size: var(--t-xs, 11px);
  color: var(--ink-2, #5B6B77);
  margin-top: 1px;
  line-height: 1.4;
}
.none-affirmative-box {
  width: 16px; height: 16px; flex: none; margin-top: 1px;
  border: 1.5px solid var(--line-2, #CFD8DE);
  border-radius: 4px;
  background: var(--surface, #fff);
}
.none-affirmative.is-on { border-color: rgba(47, 125, 79, .35); background: var(--ok-bg, #EEF6F1); }
.none-affirmative.is-on .none-affirmative-box {
  background: var(--ok, #2F7D4F);
  border-color: var(--ok, #2F7D4F);
  /* the tick, drawn rather than shipped as an icon font */
  background-image: linear-gradient(45deg, transparent 45%, #fff 45%, #fff 55%, transparent 55%),
                    linear-gradient(-45deg, transparent 60%, #fff 60%, #fff 70%, transparent 70%);
  background-size: 10px 10px, 10px 10px;
  background-position: 1px 3px, 4px 2px;
  background-repeat: no-repeat;
}

/* ---- primaries mounted on the dark chrome (L9) -------------------
   Continue sits on --chrome, where the panel accent is the lowest-contrast
   thing in the strip. It takes the chrome variant so the CTA is the
   brightest element in the footer. */
.app-shell .app-footer .btn-primary,
.app-shell .app-footer .footer-next-btn {
  background: var(--accent-on-chrome) !important;
  border-color: var(--accent-on-chrome) !important;
  color: #04231F !important;
}
.app-shell .app-footer .btn-primary:hover:not(:disabled),
.app-shell .app-footer .footer-next-btn:hover:not(:disabled) {
  background: var(--accent-on-chrome-700) !important;
  border-color: var(--accent-on-chrome-700) !important;
}

/* The Review edit-jump cluster is a grid, not a wrapped row: five links
   that re-flow on every viewport read as leftovers (01 F12). */
.app-shell .review-edit-links {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-2, 8px);
  margin-top: var(--space-4, 16px);
}
.app-shell .review-edit-btn { width: 100%; justify-content: center; }

/* The unpriced count is a control, not a caption (W10). */
.foot-unpriced {
  display: inline-flex;
  align-items: center;
  height: 22px;
  padding: 0 var(--space-2, 8px);
  margin-left: var(--space-3, 12px);
  border-radius: var(--r-full, 999px);
  border: 1px solid rgba(255, 199, 128, .45);
  background: rgba(255, 199, 128, .14);
  color: #F2C68A;
  font-family: inherit;
  font-size: var(--t-xs, 12px);
  font-weight: var(--w-semi, 600);
  white-space: nowrap;
  cursor: pointer;
  flex: none;
}
.foot-unpriced:hover { background: rgba(255, 199, 128, .26); }

/* What the INSTALL money is, and where to change it (QA P1-02). */
.foot-install-note {
  display: inline-flex;
  align-items: center;
  min-height: 22px;
  padding: 0 var(--space-2, 8px);
  margin-left: var(--space-3, 12px);
  border-radius: var(--r-full, 999px);
  border: 1px solid rgba(255, 255, 255, .28);
  background: rgba(255, 255, 255, .08);
  color: var(--chrome-ink, #fff);
  font-family: inherit;
  font-size: var(--t-xs, 12px);
  font-weight: var(--w-semi, 600);
  text-decoration: underline;
  text-underline-offset: 2px;
  white-space: nowrap;
  cursor: pointer;
  /* Never wider than its room: it shortens with an ellipsis instead of pushing
     into (or over) the subtotal (2.0.1, iPad 768). */
  flex: 0 1 auto;
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}
.foot-install-note:hover { background: rgba(255, 255, 255, .16); }
.foot-install-note[hidden] { display: none; }
html.touch .foot-install-note { min-height: 44px; }

/* A badge that happens to be a button keeps the badge's look. */
.badge-button {
  border: 0;
  font-family: inherit;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* I-09 — while "Auto (recommended)" is on, the size the spans resolved to
   carries a quiet dashed mark; the selected state belongs to Auto. */
.visual-option.auto-resolved:not(.selected) { outline: 1px dashed var(--teal, #2d6a6a); outline-offset: -3px; }

/* Inline field warnings (I-08 height, P-08 stair width, I-33 install inputs):
   one line of help-sized text under the control, amber, never a popup. */
.field-warning { margin: 6px 0 0; font-size: 12px; line-height: 1.35; color: #8a4b0f; }
.field-warning.hidden { display: none; }
/* Browser check of #158: the Framing step's top callouts (low-deck flush,
   framing error, too low, beam-bend detail) — filled, above Structure. */
.framing-notices { display: flex; flex-direction: column; gap: 8px; margin: 0 0 12px; }
.framing-notices.hidden, .framing-notices[hidden] { display: none; }
/* Canonical-frame Task 5: "Joists run front–back · Turn joists 90°" (the plan's arrow, as a button). */
.joist-turn-row { margin: 0 0 12px; padding: 8px 10px; border: 1px solid #b8d6d4; border-left: 4px solid #12726f; border-radius: 6px; background: #f1f8f7; }
.joist-turn-row[hidden] { display: none; }
.joist-turn-main { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
.joist-turn-text { font-size: 13px; font-weight: 600; color: #12313a; }
.joist-turn-note { margin: 6px 0 0; font-size: 12px; line-height: 1.35; color: #4a5562; }
html.touch .joist-turn-btn { height: 44px; min-width: 44px; }
.framing-notice { margin: 0; padding: 10px 12px; border-radius: 6px; font-size: 13px; font-weight: 600; line-height: 1.35; }
.framing-notice-error { background: #fdecea; border: 1px solid #e5928a; border-left: 4px solid #b42318; color: #7a1a12; }
.framing-notice-warning { background: #fff4e0; border: 1px solid #e3a33b; border-left: 4px solid #c26a00; color: #6b3d00; }
.framing-notice-note { background: #eef4fb; border: 1px solid #a9c4e4; border-left: 4px solid #2f6db3; color: #1d3f66; font-weight: 500; }

/* Q17: Pylex 24" extensions per pile — a touch-size stepper (reps use kiosks). */
.pylex-stepper { display: inline-flex; align-items: center; gap: 10px; margin-top: 6px; }
.pylex-step-btn {
  min-width: 44px; min-height: 44px; border-radius: 8px; border: 1px solid #c9d2dc;
  background: #fff; font-size: 22px; font-weight: 700; line-height: 1; cursor: pointer;
}
.pylex-step-btn:disabled { opacity: 0.4; cursor: default; }
.pylex-step-count { min-width: 28px; text-align: center; font-size: 18px; font-weight: 700; }
.pylex-pile-length { margin: 6px 0 0; font-size: 12px; color: #4a5562; }

/* D3 (production QA 2026-10-02): the low-deck framing notes must be seen —
   a filled amber callout, not a line of help text. */
.field-warning.prominent {
  margin: 8px 0 0; padding: 8px 10px; font-size: 13px; font-weight: 600;
  background: #fff4e0; border: 1px solid #e3a33b; border-left: 4px solid #c26a00; border-radius: 6px;
}

/* I-07 — the tall-deck footing switch, as a callout on the footing row (in
   flow between the row header and its options, so it can never cover the
   canvas or another control). The "Switch back" link sits inline. */
.footing-auto-note {
  margin: 0 var(--space-4, 16px) var(--space-3, 12px);
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: var(--space-2, 8px);
  row-gap: var(--space-1, 4px);
}
.footing-auto-note.hidden, .footing-auto-note-action.hidden { display: none; }
.footing-auto-note-text { flex: 1 1 100%; min-width: 0; }
.footing-auto-note-action { flex: none; font-weight: var(--w-semi); text-decoration: underline; text-underline-offset: 2px; }
.touch .footing-auto-note-action { min-height: var(--touch-min, 44px); }

/* One inline action in a toast ("Undo", "Switch back") — P-09, I-07. */
.notification__action {
  flex: none; margin-left: 8px; padding: 4px 10px; border-radius: 6px;
  border: 1px solid currentColor; background: transparent; color: inherit;
  font: inherit; font-weight: 600; cursor: pointer; white-space: nowrap;
}

/* I-26 / P-22 — the sign-in modal says why it appeared. */
.auth-modal-reason { margin: 0 0 12px; font-size: 14px; color: var(--ink-2, #374151); }
.auth-modal-reason.hidden { display: none; }

/* I-27 — the shell never scrolls as a page: a focused Cost Breakdown row used
   to scroll <body> 69px (overflow:hidden still allows programmatic scroll),
   hiding the header. overflow:clip does not create a scroll container. */
body:has(.app-shell) { overflow: clip; }

/* ==================================================================
   8 · THE INSTALL STEP  (2026-09-25)
   One card per group: a toggle row (label left, switch right, the group
   subtotal beside the switch), then each line as label · math · amount.
   Teal = on. The cards keep the one 16px gutter (.card padding) and the
   three text levels: card-header eyebrow, 14px label, 12px grey help.
   ================================================================== */
.install-toggle-row {
  display: flex;
  align-items: center;
  gap: var(--space-3, 12px);
  cursor: pointer;
}
.install-toggle-text { flex: 1; min-width: 0; }
/* A priced line that can't take a price override: the reason is its title. */
.fbom-override-na { font-size: 0.75rem; color: var(--ink-3, #6b7280); cursor: help; text-decoration: underline dotted; }
/* The step-header master switch (stepScope.js): the step's work in or out of
   the quote. A teal rule on the left (grey when off) marks it as the master. */
.step-scope-toggle { border-left: 3px solid var(--tuds-teal, #2D6A6A); }
.step-scope-toggle.is-off { border-left-color: var(--line-2, #CFD8DE); }
.step-scope-toggle .install-toggle-label { font-size: var(--t-md, 1rem); }
.step-scope-slot:empty { display: none; }
/* A group with nothing to install (Stairs, no stairs), or every group while
   install is switched off: greyed, switch locked. */
.install-group.is-empty { opacity: 0.55; }
.install-group.is-empty .install-toggle-row { cursor: default; }
.install-toggle-label {
  display: block;
  font-size: var(--t-sm);
  font-weight: var(--w-semi);
  color: var(--ink);
}
.install-toggle-text small {
  display: block;
  font-size: var(--t-xs);
  color: var(--ink-2);
  line-height: 1.4;
  margin-top: 2px;
}
.install-amount {
  font-size: var(--t-sm);
  font-weight: var(--w-semi);
  color: var(--tuds-navy, #133A52);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.install-amount.is-muted { color: var(--ink-3); font-weight: var(--w-reg); }
/* The switch: a native checkbox (keyboard + screen reader for free) drawn
   as a 36×20 track. */
.install-switch {
  appearance: none;
  -webkit-appearance: none;
  flex: none;
  position: relative;
  width: 36px;
  height: 20px;
  margin: 0;
  border-radius: var(--r-full, 999px);
  background: var(--line-2, #CFD8DE);
  cursor: pointer;
  transition: background var(--transition-fast, 150ms ease);
}
.install-switch::after {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--surface, #fff);
  box-shadow: 0 1px 2px rgba(19, 58, 82, .25);
  transition: transform var(--transition-fast, 150ms ease);
}
.install-switch:checked { background: var(--tuds-teal, #2D6A6A); }
.install-switch:checked::after { transform: translateX(16px); }
.install-switch:focus-visible { outline: 2px solid var(--tuds-teal, #2D6A6A); outline-offset: 2px; }
/* The stack's gap spaces the cards; .card + .card would double it. */
#installGroupCards > .card + .card { margin-top: 0; }
.install-lines:empty { display: none; }
.install-lines { margin-top: var(--space-3, 12px); border-top: 1px solid var(--line-2); }
.install-line {
  display: flex;
  align-items: baseline;
  gap: var(--space-3, 12px);
  padding: var(--space-2, 8px) 0;
  border-bottom: 1px solid var(--line-2);
}
.install-line:last-child { border-bottom: 0; }
.install-line-text { flex: 1; min-width: 0; }
.install-line-label { display: block; font-size: var(--t-sm); color: var(--ink); }
.install-line-math {
  display: block;
  font-size: var(--t-xs);
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
.install-line.is-muted .install-line-label,
.install-line.is-muted .install-line-math { color: var(--ink-3); }
.install-num { max-width: 120px; }
.install-total-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3, 12px);
}
.install-lines + .install-total-row { margin-top: var(--space-3, 12px); }
.install-note.hidden { display: none; }

/* ==================================================================
   9 · ONE SYSTEM — the ten LOOK-track rules as shared components
   (audit/USABILITY-PASS-2026-09-25.md, "LOOK-track consistency rules")
   ------------------------------------------------------------------
   Everything a step panel draws is one of these. A screen that needs a
   new look adds a component here; it never styles a control in place.

     8.1  three text levels ........ .eyebrow · .field-label · .help
     8.2  one gutter ............... the panel body owns 16px; nothing insets twice
     8.3  choice grammar ........... picture card · .ui-select · .toggle-row · .seg
     8.4  two action colours ....... teal (primary / selected), navy (secondary)
     8.5  decided-value row ........ .decided-row  ("Height · 4' 0" · Ledger")
     8.6  sheets, not popovers ..... .edge-sheet (light, Escape closes)
     8.7  no nested scroll ......... the panel is the only scroller
   ================================================================== */
:root {
  --t-2xs: 11px;     /* eyebrows only — the one size below the ladder */
  --t-label: 13px;   /* field labels */
  --control-h: 36px; /* inputs, selects and segmented controls share it */
  --chevron-down: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='%235A6A75'%3E%3Cpath fill-rule='evenodd' d='M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z' clip-rule='evenodd'/%3E%3C/svg%3E");
}

/* --- 8.1 three text levels ----------------------------------------
   Eyebrow = 11px uppercase grey (a section's name).
   Field label = 13px sentence case, dark (what the control sets).
   Help = 12px grey, under the control (why / what it does). */
.app-shell .eyebrow,
.app-shell .ctx-section-head h3,
.app-shell .config-accordion-divider,
.app-shell .card-header,
.app-shell .project-overview-specs-label,
.app-shell .sheet-subhead {
  font-size: var(--t-2xs);
  font-weight: var(--w-semi);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.app-shell .field-label,
.app-shell .field > label,
.app-shell .visual-selector-label,
.app-shell .gate-width-label {
  display: flex;
  align-items: center;
  gap: var(--space-1, 4px);
  margin-bottom: var(--space-2, 8px);
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink);
}
.app-shell .help,
.app-shell .field-help,
.app-shell .field .help {
  margin: var(--space-2, 8px) 0 0;
  padding: 0;
  font-size: var(--t-xs);
  line-height: var(--lh-body, 1.5);
  color: var(--ink-2);
}
/* --- 8.1b the (i) info button (look feedback round 1, task 1) -------
   Explanations never sit under a control. The label stays clean and an
   (i) beside it opens a small popover (deckCalcjs/infoTip.js: one open
   at a time, Escape closes, keyboard reachable). The popover is fixed
   and placed from the button, so the scrolling panel never clips it. */
.info-tip { display: inline-flex; align-items: center; vertical-align: middle; flex: none; }
.info-tip-btn {
  display: inline-grid;
  place-items: center;
  width: 16px;
  height: 16px;
  /* The legacy button min-height (40px at 768–1024, 44px on phones)
     stretched the circle into a tall oval without .touch; touch screens get
     their 44px hit area from the expander in §12, not from the circle. */
  min-height: 0;
  min-width: 0;
  margin: 0 0 0 var(--space-1, 4px);
  padding: 0;
  border: 1px solid #B9C6CD;
  border-radius: 50%;
  background: var(--surface, #fff);
  color: var(--ink-3);
  font: italic 600 10px/1 Georgia, 'Times New Roman', serif;
  text-transform: none;
  letter-spacing: 0;
  cursor: pointer;
}
.info-tip-btn:hover,
.info-tip-btn[aria-expanded="true"] { border-color: var(--accent); color: var(--accent-700); }
.info-tip-btn:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 1px; }
.info-tip-pop {
  position: fixed;
  z-index: 460; /* above the edge sheet (440) */
  width: max-content;
  max-width: min(280px, calc(100vw - 16px));
  padding: var(--space-2, 8px) var(--space-3, 12px);
  background: var(--surface, #fff);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: var(--shadow-pop);
  font-family: var(--font-family);
  font-size: var(--t-xs);
  font-style: normal;
  font-weight: 400;
  line-height: var(--lh-body, 1.5);
  letter-spacing: 0;
  text-transform: none;
  text-align: left;
  white-space: normal;
  color: var(--ink-2);
}
.info-tip-pop[hidden] { display: none; }

/* A control group: label, control, help — stacked with one rhythm. */
.app-shell .field-group + .field-group,
.app-shell .visual-selector + .visual-selector,
.app-shell .help + .visual-selector,
.app-shell .help + .field-group,
.app-shell .visual-selector + .field-group,
.app-shell .field-group + .visual-selector { margin-top: var(--space-4, 16px); }
.app-shell .visual-selector { margin-bottom: 0; }
/* The pinned choices at the top of Decking / Railing stack on one rhythm. */
.app-shell .config-pinned-zone { display: grid; gap: var(--space-4, 16px); border-bottom: 0; }
.app-shell .config-pinned-zone > .hidden,
.app-shell .config-pinned-zone > [hidden] { display: none !important; }
.app-shell .ui-select + .field-label,
.app-shell .ui-select + label.field-label { margin-top: var(--space-3, 12px); }

/* --- 8.2 one gutter ------------------------------------------------
   The Framing rows used to sit in a bordered box while the rows pulled
   out of the gutter, so every label touched the box's border (P-02).
   The rows are a full-bleed list on every step now, the way Decking and
   Railing already drew them. */
.app-shell .framing-config {
  background: transparent;
  border: 0;
  border-radius: 0;
  overflow: visible;
}
.app-shell .config-section-header { min-height: var(--row-min-h, 44px); }
/* A collapsed row is its header and nothing else: the body's 16px bottom
   padding used to survive the collapse and made every row 65px (P-02). */
.app-shell .config-section:not(.expanded) .config-section-body { padding-top: 0; padding-bottom: 0; }
.app-shell .config-change-btn { padding: 2px; }
.app-shell .config-section-value { margin-right: 0; }
/* The bodies hold whatever a row needs; 800px clipped the helical kit. */
.app-shell .config-section.expanded .config-section-body {
  padding: var(--space-1, 4px) var(--space-4, 16px) var(--space-4, 16px);
  max-height: none;
}
.app-shell .config-section.expanded .config-section-header { background: transparent; border-bottom: 0; }
/* The height card, the draw action cards and the hint share one edge. */
.app-shell .draw-action-cards { padding: 0; margin: 0; }
.app-shell .edit-shape-panel { padding: 0; }
/* Draw: the gate card, the action cards and the hint share one rhythm
   (P-19 — the height card sat against the next block with no gap). */
.app-shell #wizard-step-draw .panel-content { display: grid; gap: var(--space-3, 12px); }
.app-shell #wizard-step-draw .panel-content > .hidden,
.app-shell #wizard-step-draw .panel-content > [hidden] { display: none !important; }
.app-shell #wizard-step-draw [data-height-control]:empty { display: none; }
.app-shell .edit-shape-hint {
  margin: var(--space-2, 8px) 0 0; padding: 0; background: none; border-radius: 0;
  text-align: left; font-size: var(--t-xs); color: var(--ink-2); line-height: var(--lh-body, 1.5);
}
.app-shell .draw-action-card { border-color: var(--line); border-radius: var(--r-lg); }
.app-shell .draw-action-card:hover { border-color: #B9C6CD; background: var(--surface); box-shadow: var(--shadow-float); }
.app-shell .draw-action-name { font-size: var(--t-sm); color: var(--ink); }
.app-shell .draw-action-desc { font-size: var(--t-xs); color: var(--ink-3); }
/* Templates: equal heights, one line of subtitle (P-20). */
.app-shell .template-grid { gap: var(--space-2, 8px); grid-auto-rows: 1fr; }
.app-shell .template-card { height: 100%; justify-content: flex-start; padding: var(--space-2, 8px); }
.app-shell .template-label {
  font-size: var(--t-label); color: var(--ink);
  max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.app-shell .dimension-field label {
  font-size: var(--t-label); font-weight: 500; color: var(--ink);
  text-transform: none; letter-spacing: 0; margin-bottom: var(--space-2, 8px);
}
.app-shell .dimension-input-group input { height: var(--control-h, 36px); text-align: left; padding: 0 var(--space-3, 12px); }
.app-shell .template-size {
  font-size: var(--t-xs); color: var(--ink-3);
  max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.app-shell .sidebar-section-title {
  font-size: var(--t-2xs); letter-spacing: .08em; color: var(--ink-3); margin-bottom: var(--space-2, 8px);
}
.app-shell .sidebar-divider { color: var(--ink-3); font-size: var(--t-xs); }
/* The scale mark (look feedback 13): permanent, bottom-left of the canvas,
   in the plan's dimension style — a grid square and plain dark text. */
.app-shell .grid-scale-indicator {
  left: var(--space-3, 12px);
  bottom: var(--space-3, 12px);
  gap: 6px;
  padding: 3px 8px;
  background: rgba(255, 255, 255, .9);
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  opacity: 1;
}
.app-shell .grid-scale-indicator .scale-square {
  width: 10px;
  height: 10px;
  border: 1px solid #9AA5AD;
  background: transparent;
}
.app-shell .grid-scale-indicator .scale-label {
  font-family: var(--font-family);
  font-size: var(--t-xs);
  font-weight: 500;
  letter-spacing: 0;
  color: var(--ink-2);
}

/* --- 8.3a picture card (the one card) ------------------------------
   Every 2–4 option choice that carries a picture. Equal heights in a
   row, the title never smaller than its subtitle (P-05), selection by
   the accent ring. */
.app-shell .visual-selector-grid { gap: var(--space-2, 8px); align-items: stretch; }
.app-shell .visual-option,
.app-shell .visual-selector.compact .visual-option {
  height: 100%;
  min-width: 0;
  justify-content: flex-start;
  gap: 2px;
  padding: var(--space-2, 8px) var(--space-1, 4px);
}
.app-shell .visual-option-name,
.app-shell .visual-selector.compact .visual-option-name {
  font-size: var(--t-label);
  font-weight: var(--w-semi);
  color: var(--ink);
  line-height: 1.25;
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  margin: 0;
}
.app-shell .visual-option-desc,
.app-shell .visual-selector.compact .visual-option-desc {
  font-size: var(--t-xs);
  color: var(--ink-3);
  line-height: 1.3;
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
}
.app-shell .visual-selector.compact .visual-option-icon { width: 32px; height: 32px; margin: 0 auto 2px; }
.app-shell .visual-option.selected .visual-option-name { color: var(--accent-700); }
/* A picture card outside a .visual-selector (the pile cap, a stair's tread
   style): the same card, its own small icon box. A radio inside the card
   is visually hidden; the card is the control (look feedback task 4). */
.app-shell .pic-choice .visual-option { font: inherit; text-align: center; cursor: pointer; }
.app-shell .pic-choice .visual-option-icon { display: block; width: 32px; height: 32px; margin: 0 auto 2px; }
.app-shell .pic-choice .visual-option-name { display: block; }
.app-shell .pic-radio { position: relative; }
.app-shell .pic-radio > input { position: absolute; opacity: 0; width: 1px; height: 1px; pointer-events: none; }
.app-shell .pic-radio:has(input:focus-visible) { outline: 2px solid var(--accent-ring); outline-offset: 1px; }

/* --- 8.3b segmented control (the one segmented look) ----------------
   2–4 short text options with no picture: joist/beam size, rail height,
   picket vs glass, pile cap, GapCap colour, primary vs accent, gate
   width, board count. A text-only option grid is drawn as one too, so
   every such choice in the panel reads the same without each renderer
   knowing. Selected = white, raised, teal text. */
.app-shell .seg,
.app-shell .segmented-control,
.app-shell .accent-toggle,
.app-shell .visual-selector-grid:not(:has(.visual-option-icon)) {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 2px;
  padding: 2px;
  background: #E6ECEF;
  border: 0;
  border-radius: var(--r);
}
/* A text-only grid keeps its column count (a 2×2 brand list stays 2×2). */
.app-shell .visual-selector-grid.two-col:not(:has(.visual-option-icon)),
.app-shell .visual-selector-grid.three-col:not(:has(.visual-option-icon)),
.app-shell .visual-selector-grid.four-col:not(:has(.visual-option-icon)) { grid-auto-flow: row; }
.app-shell .seg.seg-inline { display: inline-grid; }
.app-shell .seg > button,
.app-shell .seg > label,
.app-shell .segmented-control > .segmented-option,
.app-shell .accent-toggle > .accent-toggle-btn,
.app-shell .visual-selector-grid:not(:has(.visual-option-icon)) > .visual-option {
  height: calc(var(--control-h, 36px) - 4px);
  min-width: 0;
  margin: 0;
  padding: 0 var(--space-2, 8px);
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: var(--space-1, 4px);
  border: 0;
  border-radius: 6px;
  background: transparent;
  box-shadow: none;
  font-family: inherit;
  font-size: var(--t-label);
  font-weight: 500;
  color: var(--ink-2);
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--transition-fast, 150ms ease), color var(--transition-fast, 150ms ease);
}
.app-shell .visual-selector-grid:not(:has(.visual-option-icon)) > .visual-option .visual-option-name {
  font-size: var(--t-label);
  font-weight: inherit;
  color: inherit;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.app-shell .visual-selector-grid:not(:has(.visual-option-icon)) > .visual-option .visual-option-desc { display: none; }
.app-shell .seg > button:hover:not(:disabled),
.app-shell .seg > label:hover,
.app-shell .segmented-control > .segmented-option:hover,
.app-shell .accent-toggle > .accent-toggle-btn:hover,
.app-shell .visual-selector-grid:not(:has(.visual-option-icon)) > .visual-option:hover {
  background: rgba(255, 255, 255, .6);
  color: var(--ink);
  box-shadow: none;
}
.app-shell .seg > button.active,
.app-shell .seg > button[aria-pressed="true"],
.app-shell .seg > label:has(input:checked),
.app-shell .segmented-control > .segmented-option.selected,
.app-shell .accent-toggle > .accent-toggle-btn.selected,
.app-shell .visual-selector-grid:not(:has(.visual-option-icon)) > .visual-option.selected {
  background: var(--surface);
  color: var(--accent-700);
  font-weight: var(--w-semi);
  box-shadow: 0 1px 3px rgba(19, 58, 82, .14);
}
.app-shell .visual-selector-grid:not(:has(.visual-option-icon)) > .visual-option.selected .visual-option-name { color: var(--accent-700); }
/* Joist / beam size (look feedback task 3): no Auto card. The recommended
   size is preselected and wears a small "Recommended" tag; a size the rep
   picks instead wears "Changed". Every option keeps the two-line height so
   the control never jumps as the tag moves. */
.app-shell .size-picker .visual-selector-grid:not(:has(.visual-option-icon)) > .visual-option {
  height: 44px;
  flex-direction: column;
  gap: 1px;
  line-height: 1.2;
}
.app-shell .size-picker .size-tag {
  font-size: 11px;
  font-weight: var(--w-semi);
  letter-spacing: .02em;
  color: var(--ink-3);
}
.app-shell .size-picker .size-tag:empty { display: none; }
.app-shell .size-picker .size-tag[data-tag="recommended"] { color: var(--accent-700); }
.app-shell .size-picker .size-tag[data-tag="changed"] { color: var(--warn); }
/* Framing's Sizes row (polish trio A2): the decided sizes, then the same tag. */
.app-shell .config-sizes-value { display: inline-flex; align-items: baseline; gap: var(--space-2, 8px); flex-wrap: wrap; justify-content: flex-end; }
.app-shell .config-sizes-value .size-tag { font-size: 11px; font-weight: var(--w-semi); letter-spacing: .02em; color: var(--ink-3); white-space: nowrap; }
.app-shell .config-sizes-value .size-tag:empty { display: none; }
.app-shell .config-sizes-value .size-tag[data-tag="recommended"] { color: var(--accent-700); }
.app-shell .config-sizes-value .size-tag[data-tag="changed"] { color: var(--warn); }

/* Recently quoted chips (polish trio C1): history at the top of the decking
   and railing pickers. Teal outline chips (LOOK rule 3); never "selected". */
.app-shell .recent-chips { padding: var(--space-3, 12px) 0 var(--space-2, 8px); }
.app-shell .recent-chips[hidden] { display: none; }
.app-shell .recent-chips-label { margin-bottom: var(--space-2, 8px); }
.app-shell .recent-chips-row { display: flex; flex-wrap: wrap; gap: var(--space-2, 8px); }
.app-shell .recent-chip {
  border: 1px solid var(--line, #D5DEE3); background: var(--surface, #fff); color: var(--tuds-teal, #2D6A6A);
  border-radius: var(--r-full, 999px); padding: 4px 12px; font: inherit; font-size: var(--t-xs); font-weight: 500;
  cursor: pointer; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.app-shell .recent-chip:hover { border-color: var(--tuds-teal, #2D6A6A); background: var(--accent-50, #EAF2F1); }
.app-shell .recent-chip:focus-visible { outline: 2px solid var(--tuds-teal, #2D6A6A); outline-offset: 2px; }

/* A teal text action (LOOK rule 3): "Why these sizes?", "Override price". */
.text-link {
  border: 0; background: none; padding: 0; cursor: pointer;
  font: inherit; font-size: var(--t-sm); font-weight: var(--w-semi);
  color: var(--tuds-teal, #2D6A6A);
}
.text-link:hover { text-decoration: underline; }
/* Per-quote price override on the Materials List (polish trio B2). */
.fbom-override-action { margin-top: 2px; }
.fbom-override-action .text-link { font-size: var(--t-xs); font-weight: 500; }
.price-override { display: inline-flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 2px 6px; }
.price-override .price-was { color: var(--ink-3); font-size: var(--t-xs); }
.price-override .price-now { font-weight: var(--w-semi); color: var(--ink); }
.override-tag {
  font-size: 11px; font-weight: var(--w-semi); letter-spacing: .02em; text-transform: uppercase;
  color: var(--tuds-teal, #2D6A6A); background: var(--accent-50, #E8F1F1);
  border-radius: var(--r-full, 999px); padding: 1px 6px;
}
.price-override-item { margin: 0; font-weight: var(--w-semi); color: var(--ink); }
.price-override-catalog { margin: 0; font-size: var(--t-sm); color: var(--ink-2); }
.price-override-input { max-width: 160px; }
.text-link:focus-visible { outline: 2px solid var(--tuds-teal, #2D6A6A); outline-offset: 2px; border-radius: 2px; }
.app-shell .seg > label > input { position: absolute; opacity: 0; width: 1px; height: 1px; pointer-events: none; }
.app-shell .seg > label:has(input:focus-visible) { outline: 2px solid var(--accent-ring); outline-offset: 1px; }
.app-shell .seg > button:disabled,
.app-shell .seg > label:has(input:disabled) { opacity: .45; cursor: not-allowed; }
/* The canvas rail's Plan / 3D / Elevation and the picker-density switch
   are the same component, sized for their strip. */
.canvas-rail .seg { display: flex; }
.canvas-rail .seg button { height: 26px; font-size: var(--t-xs); }
.app-shell .picker-density-toggle { display: inline-grid; flex: none; }
.app-shell .picker-density-toggle > button { height: 24px; font-size: var(--t-xs); padding: 0 var(--space-2, 8px); }
/* The chip groups the helical kit and GapCap used to style with Tailwind. */
.app-shell .seg > .helical-cap-chip,
.app-shell .seg > .gapcap-color-chip { border: 0; }
/* The height prompt's attachment toggle is the same control, two lines. */
.height-prompt-toggle { background: #E6ECEF; border: 0; border-radius: var(--r); padding: 2px; gap: 2px; }
.height-prompt-toggle-option {
  background: transparent; border: 0 !important; border-radius: 6px; color: var(--ink-2);
}
.height-prompt-toggle-option:hover { background: rgba(255, 255, 255, .6); color: var(--ink); }
.height-prompt-toggle-option.active {
  background: var(--surface); color: var(--accent-700);
  box-shadow: 0 1px 3px rgba(19, 58, 82, .14);
}
.height-prompt-toggle-option.active * { color: inherit; }
/* P-13: the joist-spacing choice is the same segmented control as the
   attachment toggle above it (it was two pills). */
.height-prompt-seg { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); background: #E6ECEF; border-radius: var(--r); padding: 2px; }
.height-prompt-seg button { height: 32px; font-size: var(--t-sm); }

/* --- 8.3c select: a select that looks like a select ----------------
   Chevron, the input height, never a text-input look (P-03). Every
   visible <select> in the product carries .ui-select (source test). */
.ui-select,
.app-shell #shellPanel select:not(.hidden-select) {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: var(--control-h, 36px);
  padding: 0 36px 0 var(--space-3, 12px);
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--surface) var(--chevron-down) no-repeat right 10px center / 16px 16px;
  font-family: var(--font-family);
  font-size: var(--t-sm);
  color: var(--ink);
  cursor: pointer;
  text-overflow: ellipsis;
  box-shadow: none;
}
.ui-select:hover:not(:disabled) { border-color: #B9C6CD; }
.ui-select:focus,
.app-shell #shellPanel select:not(.hidden-select):focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-ring);
}
.ui-select:disabled { background-color: var(--surface-2); color: var(--ink-3); cursor: not-allowed; }
.ui-select.ui-select-sm { height: 32px; font-size: var(--t-xs); padding-right: 30px; background-position: right 8px center; }
/* Selects that sit in a row of filters keep their natural width. */
.ui-select.dashboard-filter-select,
.ui-select.dashboard-bulk-select,
.ui-select.admin-store-select,
[id^="welcomeFilter"].ui-select,
.breaker-add-controls .ui-select { width: auto; min-width: 120px; }
.accent-select-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-2, 8px); margin-bottom: var(--space-4, 16px); }
.app-shell .breaker-add-controls { display: flex; align-items: center; gap: var(--space-2, 8px); margin-top: var(--space-3, 12px); }
/* A segmented control mirrored from a select (deckCalcjs/segMirror.js): the
   breaker width sits above its Add button; nothing to choose = no control. */
.app-shell .breaker-add-controls:has([data-seg-for]) { display: grid; }
.app-shell .seg[hidden],
.app-shell [data-seg-label-for][hidden] { display: none !important; }
/* Number inputs in the panel share the control height. */
.ui-input {
  height: var(--control-h, 36px);
  padding: 0 var(--space-3, 12px);
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--surface);
  font-family: var(--font-family);
  font-size: var(--t-sm);
  color: var(--ink);
  min-width: 0;
}
.ui-input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-ring); }
.ui-input-row { display: flex; align-items: center; gap: var(--space-2, 8px); }
.ui-input-row .ui-input { width: 72px; }
.ui-input-row .ui-unit { font-size: var(--t-label); color: var(--ink-2); }

/* --- 8.3d toggle row: label left, switch right ----------------------
   Every on/off decision: "No stairs", "No railing", "Include fascia",
   GapCap, riser boards, railing sides, the helical options. The input
   is a real checkbox, so every existing onchange keeps working. */
.toggle-row {
  display: flex;
  align-items: center;
  gap: var(--space-3, 12px);
  width: 100%;
  min-height: var(--row-min-h, 44px);
  margin: 0;
  padding: var(--space-2, 8px) 0;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  background: transparent;
  border: 0;
}
.toggle-row + .toggle-row { border-top: 1px solid var(--line-2); }
.toggle-row-text { flex: 1; min-width: 0; }
.toggle-row-text b,
.toggle-row-text > span:first-child {
  display: block;
  font-size: var(--t-label);
  font-weight: 500;
  color: var(--ink);
  line-height: 1.35;
}
.toggle-row-text small {
  display: block;
  margin-top: 2px;
  font-size: var(--t-xs);
  line-height: 1.4;
  color: var(--ink-2);
}
.switch,
input[type="checkbox"].switch {
  -webkit-appearance: none;
  appearance: none;
  position: relative;
  flex: none;
  width: 36px;
  height: 20px;
  margin: 0;
  border-radius: var(--r-full);
  background: #C9D3D9;
  cursor: pointer;
  transition: background var(--transition-fast, 150ms ease);
}
.switch::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .25);
  transition: transform var(--transition-fast, 150ms ease);
}
input[type="checkbox"].switch:checked,
.switch[aria-checked="true"] { background: var(--accent); }
.switch:checked::after,
.switch[aria-checked="true"]::after { transform: translateX(16px); }
.switch:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 2px; }
.switch:disabled { opacity: .45; cursor: not-allowed; }
.toggle-row:has(.switch:disabled) { cursor: not-allowed; }
.toggle-row:has(.switch:disabled) .toggle-row-text { opacity: .6; }
/* A run of toggles inside a card or a row body. */
.toggle-list { display: block; }
.toggle-list > .toggle-row:first-child { padding-top: 0; }

/* The "none" affirmatives ("No stairs on this deck", "No railing on
   this quote") ARE toggle rows: the box they drew becomes the switch,
   moved to the right, and the green panel goes (a decision recorded is
   shown by the switch, like every other on/off). */
.app-shell .none-affirmative {
  align-items: center;
  gap: var(--space-3, 12px);
  margin-top: var(--space-4, 16px);
  padding: var(--space-3, 12px) 0;
  background: transparent;
  border: 0;
  border-top: 1px solid var(--line-2);
  border-bottom: 1px solid var(--line-2);
  border-radius: 0;
}
.app-shell .none-affirmative:hover { background: transparent; }
.app-shell .none-affirmative > span:not(.none-affirmative-box) { flex: 1; min-width: 0; }
.app-shell .none-affirmative b { font-size: var(--t-label); font-weight: 500; }
.app-shell .none-affirmative small { font-size: var(--t-xs); color: var(--ink-2); margin-top: 2px; }
.app-shell .none-affirmative .none-affirmative-box {
  order: 2;
  position: relative;
  width: 36px;
  height: 20px;
  margin: 0;
  border: 0;
  border-radius: var(--r-full);
  background: #C9D3D9;
  transition: background var(--transition-fast, 150ms ease);
}
.app-shell .none-affirmative .none-affirmative-box::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .25);
  transition: transform var(--transition-fast, 150ms ease);
}
.app-shell .none-affirmative.is-on { background: transparent; border-color: var(--line-2); }
.app-shell .none-affirmative.is-on .none-affirmative-box {
  background: var(--accent);
  background-image: none;
}
.app-shell .none-affirmative.is-on .none-affirmative-box::after { transform: translateX(16px); }
.app-shell .none-affirmative:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 2px; }

/* --- 8.4 two action colours ----------------------------------------
   Teal for primary and selected, navy for secondary (P-04). Links,
   Fix links and chips are teal; icon actions (trash, edit) are grey
   and turn teal (or red, for delete) on hover. No browser blue. */
/* Every tick box and radio in the product — shell, dashboard, sign-in and
   every modal — takes the teal, never the browser blue. */
input[type="checkbox"]:not(.switch),
input[type="radio"] { accent-color: var(--accent); }
.app-shell .link,
.app-shell a:not(.btn) { color: var(--accent-700); }
.link {
  display: inline;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-weight: var(--w-semi);
  color: var(--accent-700);
  text-decoration: none;
  cursor: pointer;
}
.link:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }
/* The Before-you-send "Fix" actions are links (P-04: they were
   browser-blue and underlined). */
.app-shell .quote-checklist-fix {
  background: none; border: 0; padding: 0; cursor: pointer;
  font: inherit; font-size: var(--t-xs); font-weight: var(--w-semi);
  color: var(--accent-700); text-decoration: none; opacity: 1;
}
.app-shell .quote-checklist-fix:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; opacity: 1; }
.app-shell .badge-button { text-decoration: none; }
.icon-action {
  width: 28px;
  height: 28px;
  display: inline-grid;
  place-items: center;
  flex: none;
  padding: 0;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--ink-3);
  cursor: pointer;
}
.icon-action:hover { background: var(--line-2); color: var(--ink); }
.icon-action.is-danger:hover { color: var(--err); background: var(--err-bg); }
.app-shell .chip[aria-pressed="true"],
.app-shell .chip.selected { background: var(--accent); color: #fff; }
/* Secondary is navy: navy text on white with a navy-tinted hairline. */
.app-shell .shell-panel .btn-secondary,
.sheet .btn-secondary {
  color: var(--tuds-navy, #133A52) !important;
  border-color: #C5D2DA !important;
}
.app-shell .shell-panel .btn-secondary:hover:not(:disabled),
.sheet .btn-secondary:hover:not(:disabled) { border-color: var(--tuds-navy, #133A52) !important; background: var(--surface-2) !important; }
/* The "+ Add …" actions in the panel are the ordinary secondary button:
   the step's primary is the footer's Next. */
.app-shell .btn-add {
  width: 100%;
  margin-top: var(--space-3, 12px);
}

/* --- 8.4b the stair item: title + facts, grey trash, then fields ---- */
.app-shell .stair-flight { padding: var(--space-3, 12px) 0 var(--space-4, 16px); }
.app-shell .stair-flight-head { align-items: flex-start; gap: var(--space-2, 8px); margin: 0; }
.stair-flight-title { flex: 1; min-width: 0; }
.stair-flight-title b { display: block; font-size: var(--t-sm); font-weight: var(--w-semi); color: var(--ink); }
.stair-flight-title small { display: block; margin-top: 2px; font-size: var(--t-xs); color: var(--ink-2); line-height: 1.4; }
.stair-flight-actions { display: flex; flex-wrap: wrap; gap: var(--space-2, 8px); margin: var(--space-2, 8px) 0 var(--space-3, 12px); }
.stair-flight-actions:empty { display: none; }
.stair-flight-actions .help { margin: 0; }
.app-shell .stair-flight .field-group + .toggle-list,
.app-shell .stair-flight .toggle-list + .field-group,
.app-shell .stair-flight .toggle-list + .toggle-list { margin-top: var(--space-2, 8px); }
.app-shell .stair-flight .field-group + .toggle-list { margin-top: var(--space-3, 12px); }
.app-shell #railingStairList .stair-flight + .stair-flight { border-top: 1px solid var(--line-2); }
.field-label-note { font-weight: var(--w-reg); color: var(--ink-3); font-size: var(--t-xs); }

/* --- 8.4c Review on the same field components (P-14) --------------- */
/* The Quote card (plan 2026-09-27 A1): people line, Rundoo # on one line,
   facts + total, then one-line links (priced separately, customer link). */
.app-shell .quote-card-people {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3, 12px);
  margin-top: var(--space-2, 8px); font-size: var(--t-sm); color: var(--ink);
}
.app-shell .quote-card-people-text { min-width: 0; }
.app-shell .quote-card-customer { font-weight: var(--w-semi); }
.app-shell .quote-card-missing { color: var(--ink-3); font-style: italic; }
.app-shell .quote-card-sep { color: var(--ink-3); }
.app-shell .quote-card-edit { flex: none; font-size: var(--t-xs); }
.app-shell .quote-card .rundoo-quote-field {
  display: flex; align-items: center; gap: var(--space-2, 8px);
  margin: var(--space-3, 12px) 0 0; padding: 0; border-top: 0;
}
.app-shell .quote-card .rundoo-quote-label { margin: 0; white-space: nowrap; }
.app-shell .quote-card .rundoo-quote-input { flex: 1; min-width: 0; }
.app-shell .quote-card-line { margin-top: var(--space-2, 8px); font-size: var(--t-xs); color: var(--ink-2); }
.app-shell .quote-card-line:empty,
.app-shell .quote-card-line.hidden { display: none; }
.app-shell .quote-card-priced { font-size: var(--t-xs); }
.app-shell .rundoo-quote-field { margin-top: var(--space-4, 16px); padding-top: var(--space-4, 16px); border-top: 1px solid var(--line-2); }
.app-shell .rundoo-quote-label {
  font-size: var(--t-label); font-weight: 500; color: var(--ink); margin-bottom: var(--space-2, 8px);
}
.app-shell .rundoo-quote-input {
  width: 100%; max-width: none; height: var(--control-h, 36px); padding: 0 var(--space-3, 12px);
  border: 1px solid var(--line); border-radius: var(--r); background: var(--surface);
  font-family: var(--font-family); font-size: var(--t-sm); color: var(--ink);
}
.app-shell .rundoo-quote-input::placeholder { color: var(--ink-3); font-family: var(--font-family); }
.app-shell .rundoo-quote-input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-ring); }
/* Cost breakdown: a plain row list — categories, then the sums, then the
   total — not a green card restating the footer (P-14). */
.app-shell .cost-summary-card {
  margin: 0; padding: 0; background: transparent; border: 0; border-radius: 0;
  display: flex; flex-direction: column;
}
.app-shell .cost-category-breakdown { order: -1; margin: 0 0 var(--space-2, 8px); padding: 0; border: 0; }
.app-shell .cost-category-row,
.app-shell .cost-row {
  display: flex; justify-content: space-between; align-items: baseline;
  padding: var(--space-1, 4px) 0; font-size: var(--t-sm); color: var(--ink-2);
}
.app-shell .cost-category-row span:last-child,
.app-shell .cost-row span:last-child { color: var(--ink); }
.app-shell .cost-summary-rows { border-top: 1px solid var(--line-2); padding-top: var(--space-2, 8px); gap: 0; }
.app-shell .cost-row-grand {
  margin-top: var(--space-1, 4px); padding-top: var(--space-2, 8px);
  border-top: 1px solid var(--line); font-size: var(--t-md); font-weight: var(--w-semi); color: var(--tuds-navy, #133A52);
}
.app-shell .cost-row-grand span:last-child { color: var(--tuds-navy, #133A52); }
.app-shell .cost-row-warning { color: var(--warn); }
.app-shell .cost-row.hidden, .app-shell .cost-summary-card.hidden { display: none; }
/* Look feedback 14: the overview is one facts line and the total; the
   specs, the install / customer / salesperson rows and the line-items link
   are the Specs card below it. */
.app-shell .overview-facts-block { display: block; margin: 0; padding: 0; }
.app-shell .overview-facts {
  margin: var(--space-2, 8px) 0 var(--space-3, 12px);
  font-size: var(--t-sm);
  line-height: var(--lh-body, 1.5);
  color: var(--ink-2);
}
.app-shell .overview-total {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-3, 12px);
  padding-top: var(--space-3, 12px);
  border-top: 1px solid var(--line-2);
}
.app-shell .overview-total-label { font-size: var(--t-sm); font-weight: 500; color: var(--ink); }
.app-shell .overview-total-note { font-size: var(--t-xs); font-weight: var(--w-reg); color: var(--ink-3, var(--ink-2)); }
.app-shell .overview-total-value {
  font-size: var(--t-lg, 20px);
  font-weight: var(--w-semi);
  color: var(--tuds-navy, #133A52);
  font-variant-numeric: tabular-nums;
}
.app-shell .review-specs-card .project-overview-specs-label { margin-top: 0; }
.app-shell .review-specs-card .review-summary-meta:not(:empty) {
  margin-top: var(--space-3, 12px);
  padding-top: var(--space-2, 8px);
  border-top: 1px solid var(--line-2);
}

/* The spec sheet (plan 2026-09-27 A3, specRows.js): one block per group —
   eyebrow label + Edit, then a two-column label / value list. */
.app-shell .project-overview-specs:has(.spec-sheet) { display: block; padding: 0; border: 0; background: none; }
.app-shell .spec-sheet { display: grid; gap: 0; }
.app-shell .spec-group { padding: var(--space-3, 12px) 0; border-top: 1px solid var(--line-2); }
.app-shell .spec-group:first-child { border-top: 0; padding-top: var(--space-2, 8px); }
.app-shell .spec-group-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3, 12px); margin-bottom: var(--space-1, 4px); }
.app-shell .spec-group-label {
  font-size: var(--t-2xs); font-weight: var(--w-semi); letter-spacing: .08em; text-transform: uppercase; color: var(--ink-3);
}
/* "Edit structure" etc.: a 44px touch target without moving the text
   (it was 22x18 on the kiosk — QA 2026-10-01). */
.app-shell .spec-edit {
  font-size: var(--t-xs);
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px; min-width: 44px;
  margin: -13px -8px;
  padding: 0 8px;
}
.app-shell .spec-rows {
  display: grid; grid-template-columns: 86px minmax(0, 1fr); column-gap: var(--space-3, 12px); row-gap: var(--space-1, 4px);
  margin: 0; font-size: var(--t-sm); line-height: var(--lh-body, 1.5);
}
.app-shell .spec-k { margin: 0; color: var(--ink-2); }
.app-shell .spec-v { margin: 0; color: var(--ink); overflow-wrap: anywhere; }
/* Review cards stack on one rhythm. */
.app-shell #wizard-step-review .panel-content > .card,
.app-shell #wizard-step-review .panel-content > .quote-checklist { margin-bottom: var(--space-4, 16px); }

/* --- 8.4d Review redesign (owner, 2026-10-02) -----------------------
   Who + total + what's left, then every action as a big button (Send ·
   Print & download · Present), then the details folded away. Above the
   folds fits a 1440×900 panel without scrolling; on the portrait kiosk the
   same order runs full width under the drawing. */
.app-shell #wizard-step-review #reviewHead { margin-bottom: var(--space-3, 12px); }
.app-shell .review-head-who { display: flex; align-items: baseline; gap: var(--space-3, 12px); min-width: 0; }
.app-shell .review-head-who .wizard-step-title {
  flex: 1; min-width: 0; margin: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.app-shell .review-head-rep { margin-left: var(--space-2, 8px); font-size: var(--t-xs); font-weight: var(--w-reg); color: var(--ink-3); }
.app-shell #reviewHead .quote-card-edit { font-size: var(--t-sm); }
.app-shell #wizard-step-review #reviewHead .hint { margin-top: 2px; }
.app-shell #wizard-step-review #reviewHead .overview-facts { margin: 0; font-size: var(--t-xs); }
/* uiController's pre-tax total row: Review shows its own (with tax) below. */
.app-shell #wizard-step-review #reviewHead .overview-total { display: none; }

.app-shell #wizard-step-review .panel-content > .review-hero { padding: var(--space-3, 12px) var(--space-4, 16px); margin-bottom: var(--space-3, 12px); }
.app-shell .review-total { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; column-gap: var(--space-3, 12px); }
.app-shell .review-total-label { font-size: var(--t-sm); font-weight: 500; color: var(--ink-2); }
.app-shell .review-total-value {
  font-size: 28px; line-height: 1.15; font-weight: var(--w-semi);
  color: var(--tuds-navy, #133A52); font-variant-numeric: tabular-nums;
}
.app-shell .review-total-tax { flex-basis: 100%; text-align: right; font-size: var(--t-xs); color: var(--ink-3); font-variant-numeric: tabular-nums; }
.app-shell .review-total-tax[hidden] { display: none; }

/* The checklist, compact: one line inside the Quote card. */
.app-shell .review-hero .quote-checklist.quote-checklist-compact {
  margin: var(--space-2, 8px) 0 0; padding: var(--space-2, 8px) 0 0;
  border: 0; border-top: 1px solid var(--line-2); border-radius: 0; background: none;
}
.app-shell .quote-checklist-line {
  display: flex; flex-wrap: wrap; align-items: baseline; column-gap: var(--space-2, 8px); row-gap: 2px;
  font-size: var(--t-xs); line-height: 1.45; color: var(--ink);
}
.app-shell .quote-checklist-status { font-weight: var(--w-semi); }
.app-shell .quote-checklist-blocked .quote-checklist-status { color: var(--err); }
.app-shell .quote-checklist-clear .quote-checklist-status { color: var(--ok); }
.app-shell .quote-checklist-clear .quote-checklist-status::before { content: "✓ "; }
.app-shell .quote-checklist-inline { display: inline-flex; align-items: baseline; gap: var(--space-1, 4px); }
.app-shell .quote-checklist-inline .quote-checklist-fix { margin: 0; padding: 0; }
.app-shell .quote-checklist-priced,
.app-shell .quote-checklist-more { font-size: var(--t-xs); }
.app-shell .quote-checklist-priced { color: var(--warn); }
.app-shell .quote-checklist-more { margin-left: auto; }
.app-shell .quote-checklist-full { margin-top: var(--space-2, 8px); }
.app-shell .quote-checklist-full[hidden] { display: none; }
.app-shell .review-hero .quote-card-line { margin-top: var(--space-2, 8px); }

/* Sections: a small eyebrow, then the buttons. */
.app-shell .review-section { margin: 0 0 var(--space-3, 12px); }
.app-shell .review-section-title {
  margin: 0 0 var(--space-2, 8px);
  font-size: var(--t-2xs); font-weight: var(--w-semi); letter-spacing: .08em; text-transform: uppercase; color: var(--ink-3);
}
.app-shell .review-send-actions { display: grid; gap: var(--space-2, 8px); }

/* Save-safety Task 2: a Scratch Pad is never saved or sent by accident.
   setScratchPadMode() (app.js) toggles body.scratch-pad-mode. */
body.scratch-pad-mode [data-real-project-only] { display: none !important; }
body:not(.scratch-pad-mode) [data-scratch-only] { display: none !important; }

/* One project opens at a time (save-safety Task 3): while one is opening,
   every other open action on the dashboard waits, and the tapped card says
   "Opening…". setDashboardOpening() (app.js) toggles these. */
body.dashboard-opening [data-project-id],
body.dashboard-opening .proj-open-btn { pointer-events: none; }
body.dashboard-opening [data-project-id]:not(.is-opening) { opacity: .6; }
[data-project-id].is-opening { position: relative; }
[data-project-id].is-opening::after {
  content: 'Opening…';
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  background: rgba(255, 255, 255, .72); color: var(--ink, #1f2937);
  font-weight: 600; border-radius: inherit;
}

/* A big action: icon, label, one-line note. ≥ 56 px tall. */
.app-shell .review-action {
  display: flex; align-items: center; gap: var(--space-3, 12px);
  width: 100%; min-height: 56px; padding: var(--space-2, 8px) var(--space-4, 16px);
  border: 1px solid var(--line); border-radius: var(--r-lg, 12px);
  background: var(--surface); color: var(--ink);
  text-align: left; cursor: pointer; font: inherit;
  transition: background-color .12s, border-color .12s;
}
.app-shell .review-action:hover:not(:disabled) { background: var(--surface-2); border-color: var(--ink-3); }
.app-shell .review-action:focus-visible,
.app-shell .review-tile:focus-visible,
.app-shell .review-fold-head:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--accent-ring); }
.app-shell .review-action:disabled { opacity: .6; cursor: progress; }
.app-shell .review-action > svg,
.app-shell .review-action > i { flex: none; width: 22px; height: 22px; }
.app-shell .review-action-text { display: flex; flex-direction: column; min-width: 0; }
.app-shell .review-action-label { font-size: var(--t-sm); font-weight: var(--w-semi); line-height: 1.3; }
.app-shell .review-action-note { font-size: var(--t-xs); line-height: 1.35; color: var(--ink-2); }
.app-shell .review-action-primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.app-shell .review-action-primary:hover:not(:disabled) { background: var(--accent-700); border-color: var(--accent-700); }
.app-shell .review-action-navy { background: var(--tuds-navy, #133A52); border-color: var(--tuds-navy, #133A52); color: #fff; }
.app-shell .review-action-navy:hover:not(:disabled) { background: var(--chrome); border-color: var(--chrome); }
.app-shell :is(.review-action-primary, .review-action-navy) .review-action-note { color: rgba(255, 255, 255, .82); }

/* Print & download: tiles, two columns (room for a fourth). */
.app-shell .review-tiles { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-2, 8px); }
/* Label over note, the icon on the right across both lines. */
.app-shell .review-tile {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 6px; row-gap: 1px; align-content: center;
  min-height: 56px; padding: var(--space-2, 8px) 10px var(--space-2, 8px) 12px;
  border: 1px solid var(--line); border-radius: var(--r-lg, 12px);
  background: var(--surface); color: var(--ink); text-align: left; cursor: pointer; font: inherit;
}
.app-shell .review-tile:hover { background: var(--surface-2); border-color: var(--ink-3); }
.app-shell .review-tile > svg,
.app-shell .review-tile > i { grid-column: 2; grid-row: 1 / span 2; align-self: center; width: 16px; height: 16px; color: var(--accent); }
.app-shell .review-tile .review-action-label { grid-column: 1; grid-row: 1; font-size: var(--t-xs); }
.app-shell .review-tile .review-action-note { grid-column: 1; grid-row: 2; font-size: 11px; }

.app-shell #wizard-step-review .review-present { margin-bottom: var(--space-4, 16px); }
.app-shell .review-present > svg,
.app-shell .review-present > i { color: var(--accent); }

/* Folded rows: native <details>, so Enter / Space / a tap open them. */
.app-shell .review-folds { border-top: 1px solid var(--line-2); }
.app-shell .review-fold { border-bottom: 1px solid var(--line-2); }
.app-shell .review-fold-head {
  display: flex; align-items: center; gap: var(--space-3, 12px);
  min-height: 48px; padding: var(--space-2, 8px) 0;
  list-style: none; cursor: pointer; border-radius: var(--r-sm);
}
.app-shell .review-fold-head::-webkit-details-marker { display: none; }
.app-shell .review-fold-label { flex: none; font-size: var(--t-sm); font-weight: 500; color: var(--ink); }
.app-shell .review-fold-value {
  flex: 1; min-width: 0; text-align: right;
  font-size: var(--t-xs); color: var(--ink-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.app-shell .review-fold-chevron { flex: none; color: var(--ink-3); transition: transform .15s; }
.app-shell .review-fold[open] > .review-fold-head .review-fold-chevron { transform: rotate(180deg); }
.app-shell .review-fold-body { padding: 0 0 var(--space-3, 12px); }
.app-shell .review-fold .rundoo-quote-field {
  display: flex; align-items: center; gap: var(--space-2, 8px);
  margin: 0 0 var(--space-2, 8px); padding: 0; border-top: 0;
}
.app-shell .review-fold .rundoo-quote-label { margin: 0; white-space: nowrap; }
.app-shell .review-fold .rundoo-quote-input { flex: 1; min-width: 0; }
.touch .app-shell .review-fold-head,
.touch .review-fold-head { min-height: var(--touch-min, 44px); }

/* --- 8.5 decided-value row ----------------------------------------
   One read-only row for a value set somewhere else: "Height · 4' 0" ·
   Ledger · Change". Used after Draw on every step (P-01). */
.decided-row {
  display: flex;
  align-items: center;
  gap: var(--space-3, 12px);
  min-height: var(--row-min-h, 44px);
  padding: var(--space-2, 8px) 0;
  border-bottom: 1px solid var(--line-2);
}
.decided-row-label { font-size: var(--t-sm); font-weight: 500; color: var(--ink); }
.decided-row-value {
  flex: 1;
  min-width: 0;
  text-align: right;
  font-size: var(--t-sm);
  color: var(--ink-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.decided-row .link { flex: none; font-size: var(--t-sm); }

/* --- 8.6 the railing edge sheet (I-16) ------------------------------
   The edge menu was a dark navy popover; it is a light sheet like every
   other overlay, names the side ("Right side · 12'"), closes on Escape. */
.edge-sheet {
  position: fixed;
  z-index: 440;
  width: 260px;
  max-height: calc(100vh - 32px);
  overflow: auto;
  padding: var(--space-2, 8px);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-pop);
  color: var(--ink);
  font-family: var(--font-family);
}
.edge-sheet-head {
  display: flex;
  align-items: center;
  gap: var(--space-2, 8px);
  padding: var(--space-1, 4px) var(--space-1, 4px) var(--space-2, 8px);
  border-bottom: 1px solid var(--line-2);
  margin-bottom: var(--space-2, 8px);
}
.edge-sheet-title { flex: 1; font-size: var(--t-sm); font-weight: var(--w-semi); color: var(--tuds-navy, #133A52); }
.edge-sheet-group { padding: var(--space-1, 4px); }
.edge-sheet-group + .edge-sheet-group { border-top: 1px solid var(--line-2); margin-top: var(--space-1, 4px); padding-top: var(--space-2, 8px); }
.edge-sheet .field-label { margin-bottom: var(--space-1, 4px); }
.edge-sheet .seg { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); }
.edge-sheet-actions { display: grid; gap: var(--space-2, 8px); padding: var(--space-2, 8px) var(--space-1, 4px) var(--space-1, 4px); }
.edge-sheet-sub { font-size: var(--t-xs); color: var(--ink-2); }
.edge-sheet-option {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-2, 8px);
  width: 100%; min-height: 32px; padding: 0 var(--space-2, 8px);
  border: 0; border-radius: var(--r-sm); background: transparent;
  font: inherit; font-size: var(--t-sm); color: var(--ink); text-align: left; cursor: pointer;
}
.edge-sheet-option:hover { background: var(--line-2); }
.edge-sheet-option[aria-pressed="true"] { color: var(--accent-700); font-weight: var(--w-semi); }
.edge-sheet-option[aria-pressed="true"]::after { content: "✓"; color: var(--accent); }
.edge-sheet .btn { width: 100%; }
/* --- 8.6c the one canvas hint bar (look feedback task 5) -----------
   "Click a deck edge to place the stair", "Click the wall(s)…", "Click
   the deck to place the breaker board"… one component (#canvasHintBar,
   deckCalcjs/canvasHint.js), bottom-centre of the canvas, one style, one
   entry (a short rise-and-fade) and one exit (it goes). */
.canvas-hint {
  position: absolute;
  left: 50%;
  bottom: var(--space-4, 16px);
  transform: translateX(-50%);
  z-index: 30; /* over the drawing, under every sheet */
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: calc(100% - 32px);
  padding: 10px 18px;
  border-radius: var(--r-full);
  background: var(--accent);
  color: #fff;
  font-family: var(--font-family);
  font-size: var(--t-sm);
  font-weight: var(--w-semi);
  line-height: 1.3;
  box-shadow: var(--shadow-float);
  animation: canvas-hint-in 160ms ease-out;
}
.canvas-hint[hidden] { display: none; }
.canvas-hint > svg { flex: none; }
.canvas-hint-action {
  flex: none;
  height: 28px;
  padding: 0 14px;
  border: 0;
  border-radius: var(--r-full);
  background: #fff;
  color: var(--accent-700);
  font: inherit;
  cursor: pointer;
}
.canvas-hint-action:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
@keyframes canvas-hint-in {
  from { opacity: 0; transform: translate(-50%, 6px); }
  to { opacity: 1; transform: translate(-50%, 0); }
}
@media (prefers-reduced-motion: reduce) { .canvas-hint { animation: none; } }

/* A measurement tag that follows the cursor over the drawing. */
.canvas-tag {
  position: fixed; pointer-events: none; z-index: 40; /* under every sheet (420+) */
  padding: 4px 8px; border-radius: var(--r-sm);
  background: var(--surface); border: 1px solid var(--line); box-shadow: var(--shadow-pop);
  font-family: var(--font-family); font-size: var(--t-xs); font-weight: 500; color: var(--ink);
  white-space: nowrap;
}

/* The question sheet (rule 7): a centred light card, not a side drawer,
   and a body that keeps its line breaks (bullet lists of blockers). */
/* z-index: a question is always the top layer. At the sheets' 420 it opened
   BEHIND the full-screen modals (.auth-modal / .settings-modal at 10000 —
   Feedback admin, feedback detail, settings), so "Delete selected" looked
   dead: the confirm was there, just hidden (owner report 2026-09-29). Above
   the project menu (10050) too. */
.sheet-scrim.ask-sheet { justify-content: center; align-items: center; z-index: 10060; }
.ask-sheet .sheet { height: auto; max-height: 80vh; border-radius: var(--r-lg); }
.ask-sheet .sheet-head { padding: 0 var(--space-5, 24px); }
.ask-sheet .sheet-body { padding: var(--space-4, 16px) var(--space-5, 24px); }
.ask-sheet .sheet-body p { margin: 0; white-space: pre-line; font-size: var(--t-sm); line-height: var(--lh-body, 1.5); color: var(--ink-2); }
.ask-sheet .sheet-foot { padding: 0 var(--space-5, 24px); border-radius: 0 0 var(--r-lg) var(--r-lg); }

/* --- 8.6b the footer never clips (rule 10, I-04) ---------------------
   A money value is never ellipsized ("$4…" at 1280 was the audit's
   finding). Review's actions moved into the panel (2026-10-02), so the
   footer no longer has to make room for them. */
/* The header gives way the same way at laptop widths: with seven steps in
   the stepper, Feedback / Sign In / Dashboard were pushed off the edge at
   1280. The icons stay (each button has a title), the words go. */
@media (max-width: 1360px) {
  .app-shell > .deck-calculator-header .header-action-btn > span { display: none; }
  .app-shell > .deck-calculator-header .header-action-btn { padding: 0 var(--space-2, 8px); }
}
.app-shell .foot-cat-val { overflow: visible; text-overflow: clip; max-width: none; }
.app-shell .foot-cat { flex: 0 0 auto; }
/* A pending category ("—") is narrow; its key may shorten, never a number. */
.app-shell .foot-cat.is-pending { flex: 0 1 auto; min-width: 24px; }
.app-shell .foot-cat.is-pending .foot-cat-key { max-width: 100%; overflow: hidden; text-overflow: ellipsis; }

/* --- 8.7 no nested scroll, solid sticky headers --------------------
   The panel is the only scroller (I-31, rule 9). */
.app-shell #shellPanel .pgrid,
.app-shell #shellPanel #compositeColorCards,
.app-shell #shellPanel #gateList,
.app-shell #shellPanel .breaker-boards-list {
  max-height: none !important;
  overflow: visible !important;
}
#panelSelectionSection > .ctx-section-head {
  background: var(--surface);
  box-shadow: 0 1px 0 var(--line-2);
}
.pcard .pstock { display: block; margin-top: var(--space-1, 4px); }
/* "Stocked in Saskatoon" (stocked colours, 2026-09-30) wraps on a narrow card. */
.pcard .pstock { height: auto; min-height: 22px; white-space: normal; line-height: 1.25; padding-top: 3px; padding-bottom: 3px; }

/* --- 8.8 project actions: Open → + ⋮ menu (Grok Q1, 2026-09-29) ----
   Opening is the main action; the rest sits in one menu. Touch kiosk:
   every control ≥ 44x44 CSS px, nothing hover- or title-only. */
.proj-open-btn {
  height: 44px;
  min-height: 44px;
  padding: 0 var(--space-4, 16px);
}
.proj-menu-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  width: 44px;
  height: 44px;
  padding: 0;
  border-radius: var(--r);
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink-2);
  cursor: pointer;
}
.proj-menu-btn:hover,
.proj-menu-btn[aria-expanded="true"] { background: var(--line-2); color: var(--ink); }
.proj-menu-btn:focus-visible,
.proj-menu-item:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.proj-menu {
  position: fixed;
  z-index: 10050;
  min-width: 200px;
  padding: var(--space-1, 4px);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: var(--shadow-pop);
  display: flex;
  flex-direction: column;
}
.proj-menu-item {
  min-height: 44px;
  padding: 0 var(--space-4, 16px);
  border: 0;
  border-radius: calc(var(--r) - 2px);
  background: transparent;
  color: var(--ink);
  font-family: var(--font-family);
  font-size: var(--t-sm);
  font-weight: var(--w-semi);
  text-align: left;
  cursor: pointer;
}
.proj-menu-item:hover,
.proj-menu-item:focus { background: var(--line-2); }
.proj-menu-item-danger { color: var(--err); }
.proj-menu-item-danger:hover,
.proj-menu-item-danger:focus { background: var(--err-bg); }
.dashboard-row-actions,
.dash-card-actions,
.project-card-actions { display: flex; align-items: center; gap: var(--space-2, 8px); }
.dash-card-actions .proj-open-btn { flex: 1; }
.project-card-openable { cursor: pointer; }

/* --- 8.9 dashboard readable text + empty states (Grok Q3) ----------
   Secondary cell text was #9ca3af on white (≈2.5:1); --ink-2 is ≈5.7:1.
   The empty states were white-on-light (written for the old dark page). */
.dashboard-table .dashboard-cell-secondary,
.dashboard-cell-secondary { color: var(--ink-2); }
.dashboard-empty-icon { width: 48px; height: 48px; color: var(--ink-3); margin-bottom: var(--space-3, 12px); }
.dashboard-empty-text { color: var(--ink-2); font-size: var(--t-sm); margin: 0 0 var(--space-4, 16px); }
.dashboard-clear-filters-btn { height: 44px; min-height: 44px; }

/* --- 8.10 full project names (Grok Q4) ------------------------------
   Dashboard: up to two lines, then an ellipsis (the title carries the rest).
   Header: the name field sizes to its text and only truncates when the
   header is out of room (syncShellProjectNameTitle adds the tooltip then). */
.dashboard-project-name,
.dash-card-name {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  white-space: normal;
  overflow-wrap: anywhere;
}
.dash-card-title-row { align-items: flex-start; }
.shell-projname-input {
  field-sizing: content;
  max-width: min(480px, 32vw);
}
/* The name gives way before the stepper does: the project block may shrink
   (down to the input's 80px minimum), the logo and the stepper never. */
.app-shell > .deck-calculator-header .header-content { flex: 0 1 auto; min-width: 0; }
.app-shell > .deck-calculator-header .header-content > :not(.shell-projname) { flex: none; }
.app-shell > .deck-calculator-header .shell-projname { flex: 0 1 auto; min-width: 0; }
.app-shell > .deck-calculator-header .shell-projname-input { flex: 0 1 auto; min-width: 80px; }
/* The name and its status line stay inside their own block (iPad portrait,
   768: "Unsaved changes" ran under the Save pill). The block is the one that
   shrinks; both lines are capped at its width and ellipsize, never spill. */
.app-shell > .deck-calculator-header .shell-projname { overflow: hidden; min-width: 80px; }
.app-shell > .deck-calculator-header .shell-projname > * { max-width: 100%; box-sizing: border-box; }
.app-shell > .deck-calculator-header .shell-projname-input { max-width: min(480px, 32vw, 100%); }

/* --- 8.11 New Project customer search hints (Grok Q6) ---------------
   The dropdown is the dark one (#1e293b); the hint sits above "+ Create
   new customer" with a 44px Use existing. NEW = created this session. */
.np-dup-hint {
  display: flex;
  align-items: center;
  gap: var(--space-3, 12px);
  padding: var(--space-2, 8px) var(--space-4, 16px);
  border-top: 1px solid rgba(255, 255, 255, .12);
  background: rgba(255, 255, 255, .06);
}
.np-dup-hint-text { flex: 1; min-width: 0; color: #fff; font-size: var(--t-xs); line-height: var(--lh-body); }
.np-dup-use { height: 44px; min-height: 44px; flex: none; }
.np-new-tag {
  display: inline-block;
  margin-left: var(--space-1, 4px);
  padding: 1px 6px;
  border-radius: var(--r-full);
  background: var(--accent);
  color: #fff;
  font-size: 11px;
  font-weight: var(--w-semi);
  letter-spacing: .04em;
  vertical-align: middle;
}
#npCustomerDropdown { max-height: 320px; }

/* --- 8.12 the list fits a 1024-1200px screen (Grok Q1/Q4 follow-up) ---
   Open → + ⋮ is wider than the old icon row; tighter cell padding keeps
   the project name readable and the ⋮ on screen without a sideways scroll. */
.dashboard-table .dashboard-project-name { min-width: 140px; }
@media (max-width: 1200px) {
  .dashboard-table thead th,
  .dashboard-table tbody td { padding-left: var(--space-2, 8px); padding-right: var(--space-2, 8px); }
  .dashboard-row-actions .proj-open-btn { padding: 0 var(--space-3, 12px); }
}
/* The same hint under Phone / Email, on the light dialog. */
.np-contact-dup { margin-top: var(--space-2, 8px); display: flex; flex-direction: column; gap: var(--space-2, 8px); }
.np-contact-dup .np-dup-hint { background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--r); }
.np-contact-dup .np-dup-hint-text { color: var(--ink); }
/* A result row that is a likely duplicate (review of #91), on the dark dropdown. */
.np-result-dup { box-shadow: inset 3px 0 0 var(--warn-bg); }
.np-dup-tag { font-size: 11px; font-weight: var(--w-semi); color: var(--warn-bg); margin-bottom: 2px; }

/* --- 8.13 the save state + Save (owner 2026-10-03) ---------------------
   Its own group after the project name, always on screen on a real project
   (it used to float over the name for 2.5 s and vanish in a tight header). */
.app-shell .shell-save { display: flex; align-items: center; gap: var(--space-2, 8px); flex: none; }
.app-shell .shell-save[hidden] { display: none; }
.app-shell .shell-save-btn {
  min-height: 32px;
  padding: 0 var(--space-3, 12px);
  border-radius: var(--r-full, 999px);
  border: 1px solid rgba(255, 255, 255, .35);
  background: rgba(255, 255, 255, .1);
  color: var(--chrome-ink, #fff);
  font: inherit;
  font-size: var(--t-sm, 13px);
  font-weight: var(--w-semi, 600);
  cursor: pointer;
}
.app-shell .shell-save-btn:hover { background: rgba(255, 255, 255, .18); }
.app-shell .shell-save-btn:disabled { opacity: .55; cursor: default; }
html.touch .app-shell .shell-save-btn { min-height: 44px; min-width: 64px; }
body.scratch-pad-mode .app-shell .shell-save { display: none; }

/* --- 8.14 tick boxes: the whole cell is the target (review of #91) ----
   Kiosk: a 15px box is a miss; the label around it is ≥ 44x44 and ticks
   it from anywhere in the cell. The card's corner box grows to 44px. */
.dashboard-select-hit {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  margin: 0 auto;
  cursor: pointer;
}
.dashboard-table .dashboard-select-col { width: 44px; padding-left: 0; padding-right: 0; }
.dash-card-select { width: 44px; height: 44px; top: 4px; left: 4px; }

/* ==================================================================
   10 · TOUCH  (kiosk plan 2026-09-29 K1)
   In-store portrait touchscreens, fingers only. Everything here is
   scoped to `.touch` on <html>: set before first paint from
   `(pointer: coarse)` (index.html), and by deckCalcjs/touchMode.js on
   the first touchstart for a kiosk that reports a fine pointer. A mouse
   screen never matches, so the desktop look is untouched.

   Every control gets a 44x44 CSS px hit area. Two ways:
     · the generic minimum — buttons, selects and text inputs grow to 44
       (covers the stepper .shell-step, the .canvas-rail tools, Plan / 3D /
       Elevation, header buttons, footer .foot-cat and Back, chips, the
       dashboard pills and view toggle, .height-prompt-stepper-btn and
       .canvas-hint-action);
     · the expander — icons that must stay small (the (i) tip, the row
       toggles, text links, switches) keep their look and get an invisible
       44px ::before centred on them.
   ================================================================== */
:root { --touch-min: 44px; }

.touch :is(button, [role="button"], select,
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="hidden"]):not([type="color"]),
  textarea):not(:is(.info-tip-btn, .config-change-btn, .link, .quote-checklist-fix,
    .review-meta-edit, .foot-unpriced, .inspector-edge-toggle, .switch, .install-switch)) {
  min-height: var(--touch-min);
  min-width: var(--touch-min);
}

/* The expander. `position: relative` only where the control is static; the
   legacy 768–1024 `button { min-height: 40px }` must not stretch them. */
.touch :is(.info-tip-btn, .config-change-btn, .link, .quote-checklist-fix,
  .review-meta-edit, .foot-unpriced) { position: relative; min-height: 0; }
/* The hit area sits above its neighbours (review of #92, 9): a Fix link or an
   (i) right under a row or a section head kept losing half its 44px to the
   next element in paint order. Sticky heads (z-index 3) still stay on top. */
.touch :is(.info-tip-btn, .config-change-btn, .link, .quote-checklist-fix,
  .review-meta-edit, .foot-unpriced, .switch, .install-switch) { z-index: 1; }
/* "Show archived" is an inline label: give it a 44px row of its own. */
.touch .dashboard-archive-toggle { display: inline-flex; align-items: center; gap: var(--space-2, 8px); min-height: var(--touch-min); padding: 0 var(--space-3, 12px); cursor: pointer; }
/* Two Fix links a line apart took each other's half: each checklist row is
   at least a finger tall. */
.touch .quote-checklist-item { min-height: var(--touch-min); }
/* An open accordion clips its body (overflow: hidden, for the slide); the (i)
   on its first line lost the top of its hit area. Keep the clip, give it a
   half-target margin while open (a closed body stays fully clipped). */
.touch .config-section.expanded .config-section-body { overflow: clip; overflow-clip-margin: calc(var(--touch-min) / 2); }
.touch :is(.info-tip-btn, .config-change-btn, .link, .quote-checklist-fix,
  .review-meta-edit, .foot-unpriced, .inspector-edge-toggle, .switch, .install-switch)::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: max(100%, var(--touch-min));
  height: max(100%, var(--touch-min));
  transform: translate(-50%, -50%);
}

/* Tick boxes: the row is the target, 44px tall; the box itself is bigger. */
.touch label:has(input[type="checkbox"]:not(.switch):not(.install-switch), input[type="radio"]) {
  min-height: var(--touch-min);
}
.touch input[type="checkbox"]:not(.switch):not(.install-switch):not(.pic-radio > input):not(.seg input),
.touch input[type="radio"]:not(.pic-radio > input):not(.seg input) {
  width: 22px;
  height: 22px;
}

/* The chrome grows to hold 44px controls. */
.touch .app-shell > .deck-calculator-header .header-logo { box-sizing: content-box; padding: 9px; margin: 0 -9px; }
.touch .app-shell > .deck-calculator-header .header-text {
  min-height: var(--touch-min);
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.touch .shell-step { justify-content: center; }
.touch .canvas-rail { height: calc(var(--touch-min) + var(--space-2, 8px)); }
.touch .canvas-rail .btn,
.touch .canvas-rail .tbtn { height: var(--touch-min); }
.touch .seg button { height: var(--touch-min); }
.touch .app-shell .footer-next-btn { height: var(--touch-min); }

/* Words that depend on the pointer ("Click" / "Tap", keyboard shortcuts):
   index.html carries both; the one for the pointer in use shows. */
html:not(.touch) .when-touch,
.touch .when-mouse { display: none !important; }

/* Press-and-hold name for an icon-only control (kiosk K6, touchMode.js). */
.touch-press-label {
  position: fixed;
  z-index: 470; /* above sheets (420+) and the info-tip popover (460) */
  pointer-events: none;
  padding: var(--space-1, 4px) var(--space-3, 12px);
  border-radius: var(--r);
  background: var(--chrome);
  color: #fff;
  box-shadow: var(--shadow-pop);
  font-family: var(--font-family);
  font-size: var(--t-sm);
  font-weight: var(--w-semi);
  white-space: nowrap;
}
.touch-press-label[hidden] { display: none; }

/* The page never scrolls or zooms under a drawing finger: the canvases own
   one-finger draw/drag and two-finger pinch/pan (canvasLogic.js). */
.app-shell canvas { touch-action: none; }

/* ==================================================================
   11 · PORTRAIT, TABLET AND THE HEADER THAT GIVES WAY  (kiosk K2)
   A portrait screen at least ~700px wide (the store kiosks at 100–150%
   Windows scaling, a portrait tablet) stacks the work area: the drawing
   on top, the panel full width under it, the money bar at the bottom.
   This also fixes 768 portrait, where the legacy mobile stack lost to
   `#input-panel { width: var(--w-panel) }` (§3.2) and left a 340px panel
   under the canvas with the right half blank. Landscape desktop never
   matches.
   ================================================================== */
@media (orientation: portrait) and (min-width: 700px) {
  .app-shell .main-layout { flex-direction: column; }
  .app-shell .main-layout #main-content-panel {
    order: 1;
    flex: 0 0 var(--portrait-canvas-share, 54%);
    min-height: 0;
  }
  .app-shell .main-layout #input-panel,
  .app-shell .main-layout #input-panel.inspector-hidden {
    order: 2;
    width: 100% !important;
    min-width: 0;
    max-height: none;
    flex: 1 1 0 !important;
    min-height: 0;
    border-left: 0;
    border-top: 1px solid var(--line);
  }
  /* Nothing to collapse sideways any more — the panel is under the drawing. */
  .app-shell .inspector-edge-toggle { display: none !important; }
  /* A full-width row list reads as a settings page; past ~960px the rows
     stop getting more readable, so the step content keeps a measure. */
  .app-shell #shellPanel > .ctx-section { width: 100%; max-width: 960px; margin-left: auto; margin-right: auto; }
}

/* Landscape under 1100px (a 1024x768 tablet): side by side, narrower panel. */
@media (orientation: landscape) and (max-width: 1100px) {
  :root { --w-panel: 300px; }
}

/* The legacy ≤768px header pins the account buttons absolutely over the
   right end (deckcalculatorstyles.css "Mobile responsiveness for header"),
   on top of the stepper. In the shell they stay in the row. */
.app-shell > .deck-calculator-header .header-actions { position: relative; top: auto; right: auto; }
.app-shell > .deck-calculator-header .header-content { padding-right: 0; }
/* A finger screen reads the stepper at arm's length: the laptop-width
   shrink (§5, 10–11px) does not apply; the give-way levels below do. */
.touch .shell-step { font-size: var(--t-xs); padding: 0 var(--space-3, 12px); gap: var(--space-2, 8px); }

/* The header's give-way levels (deckCalcjs/shellFit.js sets data-fit), in
   the order they go (review of #92, 7): 1 the product name, 2 the header
   buttons' words (icons stay; the name shows on a press-and-hold), 3 the
   labels of the steps you are not on, 4 a narrower project name. A hidden
   label stays in the accessibility tree. */
.app-shell > .deck-calculator-header:is([data-fit="1"], [data-fit="2"], [data-fit="3"], [data-fit="4"]) :is(.header-text, .hdr-divider) { display: none; }
.app-shell > .deck-calculator-header:is([data-fit="2"], [data-fit="3"], [data-fit="4"]) .header-action-btn > span { display: none; }
.app-shell > .deck-calculator-header:is([data-fit="2"], [data-fit="3"], [data-fit="4"]) .header-action-btn { padding: 0 var(--space-2, 8px); }
.app-shell > .deck-calculator-header:is([data-fit="3"], [data-fit="4"]) .shell-step:not(.active) .shell-step-label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.app-shell > .deck-calculator-header[data-fit="4"] .shell-projname-input { max-width: 120px; min-width: 60px; }
.app-shell > .deck-calculator-header[data-fit="4"] .shell-projname { min-width: 60px; }
/* The status line stays at every level (it is one short grey line under the name). */

/* The canvas toolbar's give-way levels (same mechanism). The snap button
   keeps its aria-label "Snap distance" when the words go. */
.canvas-rail:is([data-fit="1"], [data-fit="2"]) #snapToggleBtnLabel { display: none; }
.canvas-rail:is([data-fit="1"], [data-fit="2"]) .rail-divider { margin: 0 2px; }
.canvas-rail[data-fit="2"] .seg button { padding: 0 var(--space-2, 8px); }
.canvas-rail[data-fit="2"] .rail-divider { display: none; }

/* ==================================================================
   12 · KIOSK SCALE  (kiosk K3)
   A 27" 1440x2560 portrait kiosk at 100% Windows scaling shows the
   desktop-size UI (12px text) at arm's length. On a portrait screen
   1300px or wider the shell's own scale steps up ~1.3x: type, spacing,
   chrome heights, row heights and the finger minimum are all tokens, so
   the whole panel, header, toolbar and money bar grow together. The
   drawing is not zoomed by CSS (its hit tests map screen px 1:1); it
   simply gets the larger canvas and fit-to-view uses it. Only while a
   finger drives the page (html.touch): a big portrait desktop monitor
   used with a mouse keeps the desktop scale.
   ================================================================== */
@media (orientation: portrait) and (min-width: 1300px) {
  :root.touch {
    --t-xs: 16px;
    --t-sm: 18px;
    --t-md: 21px;
    --t-lg: 26px;
    --t-xl: 36px;
    --space-1: 5px;
    --space-2: 10px;
    --space-3: 16px;
    --space-4: 21px;
    --space-5: 31px;
    --space-6: 42px;
    --space-7: 62px;
    --h-hdr: 72px;
    --h-rail: 56px;
    --h-foot: 84px;
    --row-min-h: 56px;
    --panel-head-h: 36px;
    --touch-min: 56px;
    --portrait-canvas-share: 52%;
  }
  .touch .app-shell > .deck-calculator-header .header-logo { height: 34px; }
  .touch .shell-step-num { width: 22px; height: 22px; font-size: 13px; }
  .touch .shell-step-num svg { width: 14px; height: 14px; }
  .touch .app-shell .foot-cat-val { font-size: var(--t-sm); }
  .touch .btn, .touch .footer-back-btn, .touch .footer-next-btn, .touch .review-edit-btn { height: var(--touch-min); min-height: var(--touch-min); }
  .touch .canvas-rail .btn svg, .touch .canvas-rail .btn i, .touch .canvas-rail .tbtn svg { width: 22px; height: 22px; }
  .touch .info-tip-btn { width: 20px; height: 20px; font-size: 13px; }
}

/* ==================================================================
   13 · THE MONEY BAR NEVER CLIPS + THE LAST CHANGE  (kiosk K4, Grok M5)
   The total is always visible. When the categories don't fit, they give
   way (deckCalcjs/shellFit.js on #appFooter): 1 = the pending "—"
   categories hide; 2 = all of them fold into "Breakdown", which opens
   the same strip as a list above the bar. Portrait: two rows — the
   money on top, the actions below with the primary button full width.
   ================================================================== */
.app-shell .app-footer:is([data-fit="1"], [data-fit="2"]) .foot-cat.is-pending { display: none; }
.app-shell .app-footer[data-fit="2"]:not(.is-breakdown-open) .foot-cats { display: none; }

.foot-breakdown-btn { display: none; }
.app-shell .app-footer[data-fit="2"] .foot-breakdown-btn {
  display: inline-flex;
  align-items: center;
  flex: none;
  height: 36px;
  padding: 0 var(--space-3, 12px);
  border: 1px solid rgba(255, 255, 255, .22);
  border-radius: var(--r);
  background: transparent;
  color: var(--chrome-ink);
  font: inherit;
  font-size: var(--t-sm);
  font-weight: var(--w-semi);
  cursor: pointer;
}
.app-shell .app-footer[data-fit="2"] .foot-breakdown-btn:hover,
.app-shell .app-footer .foot-breakdown-btn[aria-expanded="true"] { background: rgba(255, 255, 255, .12); }

/* The Breakdown list: the footer's own strip, stacked, floating above it. */
.app-shell .app-footer.is-breakdown-open { position: relative; }
.app-shell .app-footer.is-breakdown-open .foot-cats {
  display: flex;
  position: absolute;
  left: var(--space-4, 16px);
  bottom: calc(100% + var(--space-2, 8px));
  z-index: 60;
  flex-direction: column;
  align-items: stretch;
  gap: 0;
  min-width: 280px;
  padding: var(--space-2, 8px);
  overflow: visible;
  background: var(--chrome);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-modal);
}
.app-shell .app-footer.is-breakdown-open :is(.foot-cat, .foot-cat.is-pending) {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-5, 24px);
  min-height: 44px;
  margin: 0;
  padding: 0 var(--space-3, 12px);
}
.app-shell .app-footer.is-breakdown-open .foot-cat-key { max-width: none; overflow: visible; }
.app-shell .app-footer.is-breakdown-open .foot-unpriced { align-self: flex-start; margin: var(--space-2, 8px) var(--space-3, 12px); }

/* The last-change line. On a landscape bar it sits to the left of the total,
   on the total's line (the bar keeps its height and the "Estimated total"
   label stays); on a portrait bar it is a line under the total. */
.app-shell .footer-cost { position: relative; flex: none; }
.app-shell .footer-change-note {
  font-size: var(--t-xs);
  line-height: 1.1;
  /* White and medium weight: it read faint on the dark bar (QA 2026-10-01). */
  color: #fff;
  font-weight: var(--w-medium, 500);
  white-space: nowrap;
  transition: opacity var(--transition-fast, 150ms ease);
}
.app-shell .footer-change-note[data-kind="hint"] { color: var(--chrome-ink-2); }
.app-shell .footer-change-note.is-fading { opacity: 0; transition-duration: .6s; }
@media (orientation: landscape) {
  .app-shell .footer-change-note { position: absolute; right: calc(100% + var(--space-3, 12px)); bottom: .45em; padding-left: var(--space-2, 8px); background: var(--chrome); }
  .app-shell .app-footer:has(.footer-change-note:not([hidden])) .foot-divider { visibility: hidden; }
  /* The change line sits over the strip's right end: the install link steps
     aside while it shows (it says the install amount itself, QA P1-02). */
  .app-shell .app-footer:has(.footer-change-note:not([hidden]):not(.is-fading)) .foot-install-note { visibility: hidden; }
}
@media (prefers-reduced-motion: reduce) { .app-shell .footer-change-note { transition: none; } }

@media (orientation: portrait) and (min-width: 700px) {
  .app-shell .app-footer {
    height: auto;
    flex-wrap: wrap;
    row-gap: var(--space-2, 8px);
    padding: var(--space-2, 8px) var(--space-4, 16px) var(--space-3, 12px);
  }
  .app-shell .footer-cost-value { font-size: var(--t-xl); }
  .app-shell .foot-divider { display: none; }
  .app-shell .footer-nav-buttons { flex: 1 0 100%; order: 10; }
  /* The install link (QA P1-02): its own line under the money strip, so the categories keep their room. */
  .app-shell .foot-install-note { order: 9; flex: 0 1 100%; max-width: max-content; margin-left: 0; }
  .app-shell .footer-nav-buttons > .footer-next-btn { flex: 1 1 auto; }
}

/* Phone portrait (< 700px): the same two rows — Breakdown and the total on
   top, the actions full width below. On one row at 390 the total shrank to
   nothing and overflowed left over "Breakdown", and Next ran off the right
   edge (review 2026-09-30). The Review labels stay short here. Keep
   FOOTER_STACK_QUERY in app.js (any portrait) in step with these two blocks. */
@media (orientation: portrait) and (max-width: 699.98px) {
  .app-shell .app-footer {
    height: auto;
    flex-wrap: wrap;
    column-gap: var(--space-3, 12px);
    row-gap: var(--space-2, 8px);
    padding: var(--space-2, 8px) var(--space-3, 12px) var(--space-3, 12px);
  }
  .app-shell .foot-divider { display: none; }
  .app-shell .footer-cost { flex: none; }
  .app-shell .footer-nav-buttons { flex: 1 0 100%; order: 10; min-width: 0; }
  .app-shell .footer-nav-buttons > .footer-next-btn { flex: 1 1 auto; min-width: 0; }
}

/* --- Material-card prices (H3, 2026-09-30) ---------------------------
   What a picker card costs for THIS deck: a pre-tax change to the
   subtotal ("+$1,240" / "−$380"), "Selected" on the current card.
   Filled by paintCardPrices() (cardPrices.js); quiet dots until priced.
   One short line that wraps rather than clipping at phone width. */
.card-price {
  display: block;
  margin-top: 2px;
  font-size: var(--t-xs, 12px);
  font-weight: 600;
  line-height: 1.25;
  font-variant-numeric: tabular-nums;
  color: var(--ink-2, #5A6A75);
  white-space: normal;
  overflow-wrap: anywhere;
  text-align: center;
}
.card-price[data-tone="pending"] { color: var(--ink-3, #5F6C76); font-weight: 400; letter-spacing: 2px; }
.card-price[data-tone="selected"] { color: var(--accent-700, #1F4F4F); }
.card-price[data-tone="down"] { color: var(--ok, #2F7D4F); }
.card-price[data-tone="request"] { font-weight: 500; }
.card-price-note { display: block; font-size: 11px; font-weight: 400; color: var(--warn, #9A6212); letter-spacing: 0; }
.toggle-row-text .card-price { margin: 2px 0 0; text-align: left; }
.toggle-row-text .card-price-note { display: inline; margin-left: 4px; }
/* A priced text-only choice (the segmented wood type): two lines, name
   over price, like the size picker's tag — 44px, a full touch target. */
.app-shell .visual-selector-grid:not(:has(.visual-option-icon)) > .visual-option:has(> .card-price:not([hidden])) {
  height: auto;
  min-height: 44px;
  flex-direction: column;
  gap: 1px;
  line-height: 1.2;
  padding-top: 4px;
  padding-bottom: 4px;
}
.app-shell .visual-selector-grid:not(:has(.visual-option-icon)) > .visual-option > .card-price { margin: 0; white-space: nowrap; }

/* --- Selection cards with prices (owner feedback 2026-09-30) ---------
   Composite Brand / Line, field fastener and railing Brand are tappable
   cards (choiceCards.js), not drop-downs: the house .visual-option card
   (one radius, one border, accent ring when selected), text only — no
   decorative icons (the input-type rule). A Line card carries a strip of
   its colours. A compact grid that wraps: at most three across (four for
   the brand lists), two on a phone or the desktop side panel. Every card
   is a real <button> (keyboard focus) and at least 56px tall. */
.app-shell .choice-cards {
  --cc-cols: 3;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(max(132px, calc((100% - (var(--cc-cols) - 1) * var(--space-2, 8px)) / var(--cc-cols))), 1fr));
  gap: var(--space-2, 8px);
  align-items: stretch;
}
.app-shell .choice-cards[data-choice-cards-for="compositeBrand"],
.app-shell .choice-cards[data-choice-cards-for="railingBrandSelect"] { --cc-cols: 4; }
.app-shell .choice-cards:empty { display: none; }
.app-shell .choice-cards > .choice-card {
  font: inherit;
  width: 100%;
  min-height: 56px;
  margin: 0;
  padding: var(--space-2, 8px) var(--space-3, 12px);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  text-align: center;
  color: var(--ink);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.app-shell .choice-cards > .choice-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.app-shell .choice-cards > .choice-card .visual-option-name { font-size: var(--t-sm, 14px); overflow-wrap: anywhere; }
.app-shell .choice-cards > .choice-card > .card-price { margin-top: 0; }
.choice-card-chips {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 3px;
  max-width: 100%;
}
.choice-chip {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .2);
}
.choice-chip-more { font-size: 11px; line-height: 14px; color: var(--ink-3, #5F6C76); }



/* ==================================================================
   KIOSK / VISUAL BATCH  (QA 2026-10-02 U2-03 / P2-04 / U1-09) — one block at
   the END of the file so it merges cleanly. Scoped to `.touch`
   (html class = `(pointer: coarse)`, see §10): a mouse screen is untouched.
   `node scripts/touchAudit.mjs <url>` must report 0 controls under 44px.
   ================================================================== */

/* "Fix" in the Quote card's one-line checklist sat 4px from the next link and
   was 17x17. Real padding (cancelled by the vertical margin so the line keeps
   its height) makes it a 45px box; wrapped lines get a full target of air. */
.touch .app-shell .quote-checklist-line { row-gap: 28px; column-gap: var(--space-2, 8px); }
.touch .app-shell .quote-checklist-inline { gap: 0; }
.touch .app-shell .quote-checklist-inline .quote-checklist-fix { padding: 14px; margin: -14px 0; min-height: 0; }
.touch .app-shell .quote-checklist-inline .quote-checklist-fix::before { display: none; }

/* A field label with an (i) right under another control (Include fascia →
   Fascia style) left the two hit areas 40px apart: the label row is a finger
   tall, the (i) centred in it. */
.touch :is(.field-label, .visual-selector-label, .config-section-label):has(> .info-tip) {
  display: flex; align-items: center; flex-wrap: wrap; min-height: var(--touch-min, 44px);
}

/* The first row of an open accordion starts 8px under its header; give the (i)
   on it the room its 44px hit area needs. */
.touch .config-section.expanded .config-section-body > .visual-selector:first-child,
.touch .config-section.expanded .config-section-body > .field:first-child { padding-top: 14px; }

/* "Place Breaker Board" was cut off in its button (Pylex stair step and the
   framing breaker section): wrap instead of truncating. */
.touch #structureAddBreakerBtn,
.touch #addBreakerBtn {
  white-space: normal; height: auto; min-height: var(--touch-min, 44px);
  line-height: 1.25; text-align: center; overflow: visible; text-overflow: clip;
}
.touch #structureAddBreakerBtn > span,
.touch #addBreakerBtn > span { white-space: normal; overflow: visible; text-overflow: clip; }

/* Railing style / colour / picket profile wait for a brand (2.1 m5, railingBrandGate.js). */
.rail-needs-brand { opacity: .45; pointer-events: none; cursor: not-allowed; }
