/* ============================================================================
   plan.css — Plan + clearance tool.
   Requires assets/css/keca-tokens.css to be loaded FIRST.
   (That file was app/css/tokens.css until the two parallel token files were
   merged; the --keca-* names it declares are unchanged.)

   Deliberately does NOT depend on assets/css/keca-base.css — that file carries
   the viewer pages' element defaults, and this stylesheet sets its own.

   COMPATIBILITY LAYER
   ----------------------------------------------------------------------------
   keca-tokens.css is a shared contract file owned jointly with the 3D viewer
   component. Rather than scatter raw colors through this file — which would
   guarantee the two pages diverge — every color here goes through a --pl-*
   alias defined ONCE in the block below, mapped from the shared token with a
   fallback.

   That fallback is the only place a literal color appears in this file. It
   exists so that a keca-tokens.css which is missing, renamed or still being
   written produces a plain readable page instead of black-on-black. Replace
   values in keca-tokens.css; do not edit colors below this block.
   ========================================================================= */

:root {
  --pl-bg:            var(--keca-bg, #f7f7f5);
  --pl-surface:       var(--keca-surface, #ffffff);
  --pl-surface-alt:   var(--keca-surface-alt, #efefec);
  --pl-surface-sunk:  var(--keca-surface-sunk, #e0e0dc);

  --pl-ink:           var(--keca-text, #121211);
  --pl-ink-muted:     var(--keca-text-muted, #5a5a56);
  /* Fallback darkened from #7d7d78 (4.1:1) to meet WCAG AA 4.5:1 if
     tokens.css ever fails to load — see the note on --keca-text-faint. */
  --pl-ink-faint:     var(--keca-text-faint, #5c5c58);
  --pl-ink-invert:    var(--keca-text-invert, #ffffff);

  --pl-rule:          var(--keca-rule, #c9c9c4);
  --pl-rule-strong:   var(--keca-rule-strong, #121211);
  --pl-rule-faint:    var(--keca-rule-faint, #e0e0dc);

  --pl-focus:         var(--keca-focus, #121211);
  --pl-focus-halo:    var(--keca-focus-halo, #ffffff);
  --pl-alert:         var(--keca-alert, #121211);
  --pl-warn:          var(--keca-warn, #5a5a56);

  --pl-font-display:  var(--keca-font-display, "Montserrat", Arial, sans-serif);
  --pl-font-body:     var(--keca-font-body, "Nunito Sans", "Segoe UI", Arial, sans-serif);
  --pl-font-mono:     var(--keca-font-mono, ui-monospace, Consolas, monospace);

  --pl-s1: var(--keca-s-1, 0.25rem);
  --pl-s2: var(--keca-s-2, 0.5rem);
  --pl-s3: var(--keca-s-3, 0.75rem);
  --pl-s4: var(--keca-s-4, 1rem);
  --pl-s5: var(--keca-s-5, 1.5rem);
  --pl-s6: var(--keca-s-6, 2rem);

  --pl-track-display: var(--keca-tracking-display, 0.14em);
  --pl-track-label:   var(--keca-tracking-label, 0.09em);
  --pl-tap:           var(--keca-tap-min, 44px);
}

/* ---------------------------------------------------------------------------
   BASE
   ------------------------------------------------------------------------ */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  padding: 0 var(--pl-s4) var(--pl-s6);
  background: var(--pl-bg);
  color: var(--pl-ink);
  font-family: var(--pl-font-body);
  font-size: 1rem;
  line-height: 1.55;
}

/* Square corners. Everywhere. Always. */
input, select, textarea, button, table, td, th, details, .region, .banner, .toast {
  border-radius: 0;
}

h1, h2, h3, h4, .region-h, .panel-sub, .app-brand, label, summary, th, .btn {
  font-family: var(--pl-font-display);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--pl-track-label);
  line-height: 1.2;
}

p { margin: 0 0 var(--pl-s3); }

/* 0.92em of an 11px label came out at 10.1px. max() holds the 11px floor
   without changing how .mono scales inside larger running text. */
.mono { font-family: var(--pl-font-mono); font-size: max(0.92em, var(--keca-fs-micro, 0.6875rem)); text-transform: none; }

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  background: var(--pl-ink);
  color: var(--pl-ink-invert);
  padding: var(--pl-s2) var(--pl-s4);
  font-family: var(--pl-font-display);
  text-transform: uppercase;
  letter-spacing: var(--pl-track-label);
}
.skip-link:focus { left: var(--pl-s2); top: var(--pl-s2); }

:focus-visible {
  outline: var(--keca-focus-width, 2px) solid var(--pl-focus);
  outline-offset: var(--keca-focus-offset, 2px);
}

/* ---------------------------------------------------------------------------
   HEADER / FOOTER
   ------------------------------------------------------------------------ */

.app-head {
  border-bottom: 2px solid var(--pl-rule-strong);
  padding: var(--pl-s5) 0 var(--pl-s3);
  margin-bottom: var(--pl-s4);
}
.app-head-id { display: flex; align-items: baseline; gap: var(--pl-s3); flex-wrap: wrap; }
.app-brand {
  margin: 0;
  font-size: 0.75rem;
  letter-spacing: var(--pl-track-display);
  color: var(--pl-ink-muted);
}
.app-title { margin: 0; font-size: 1.5rem; letter-spacing: var(--pl-track-display); }

/* THE WAY BACK TO kecausa.com (2026-09-21). This tool is also served inside the
   main site at kecausa.com/specifier/, and nothing in it led back out: the nav
   below only reaches the other tools. First in .app-head-id, so it sits before
   the brand on the brand's own baseline. Quiet, underlined so it reads as a
   link, and the 44px target is on its own box, as .app-nav a does it. */
.site-exit {
  display: inline-flex;
  align-items: center;
  min-height: var(--pl-tap);
  font-family: var(--pl-font-display);
  font-weight: 500;
  font-size: var(--keca-fs-micro, 0.6875rem);
  letter-spacing: 0.08em;
  text-transform: none;
  white-space: nowrap;
  color: var(--pl-ink-muted);
  text-decoration: underline;
  text-decoration-color: var(--pl-rule);
  text-underline-offset: 0.35em;
}
.site-exit:hover { color: var(--pl-ink); text-decoration-color: var(--pl-ink); }
/* The hairline between "the website" and "this tool": a fixed-height
   pseudo-element, so it stays a short rule and not a 44px one. */
.site-exit::after {
  content: "";
  flex: 0 0 1px;
  width: 1px;
  height: 1.25rem;
  margin-left: var(--pl-s3);
  background: var(--pl-rule);
}
@media print { .site-exit { display: none !important; } }

.app-sub {
  margin: var(--pl-s2) 0 0;
  max-width: 68ch;
  font-size: 0.875rem;
  color: var(--pl-ink-muted);
}

.app-foot {
  margin-top: var(--pl-s6);
  padding-top: var(--pl-s4);
  border-top: 1px solid var(--pl-rule);
  font-size: 0.8125rem;
  color: var(--pl-ink-muted);
  max-width: 80ch;
}

/* ---------------------------------------------------------------------------
   BANNER
   ------------------------------------------------------------------------ */

.banner {
  border: 1px solid var(--pl-rule-strong);
  border-left-width: 6px;
  background: var(--pl-surface);
  padding: var(--pl-s3) var(--pl-s4);
  margin-bottom: var(--pl-s4);
}
.banner[data-level="warn"] { background: var(--pl-surface-alt); }
.banner[data-level="error"] {
  background: var(--pl-surface-sunk);
  border-left-width: 10px;
}
.banner-title {
  margin: 0;
  font-family: var(--pl-font-display);
  text-transform: uppercase;
  letter-spacing: var(--pl-track-label);
  font-size: 0.8125rem;
}
.banner-body {
  margin: var(--pl-s2) 0 0;
  font-size: 0.8125rem;
  color: var(--pl-ink-muted);
  white-space: pre-wrap;
}
/* The data-provenance banner runs to several hundred pixels on a phone, which
   pushed the drawing off the first screen. Cap and scroll it there; the text
   is unchanged, and it is never truncated — only scrolled. */
@media (max-width: 1023px) {
  .banner-body { max-height: 9.5rem; overflow-y: auto; overscroll-behavior: contain; }
}
.banner-actions { display: flex; gap: var(--pl-s2); margin-top: var(--pl-s3); flex-wrap: wrap; }
.banner-actions:empty { display: none; }

/* ---------------------------------------------------------------------------
   LAYOUT
   ------------------------------------------------------------------------ */

.layout {
  display: grid;
  gap: var(--pl-s4);
  /* minmax(0, 1fr), NOT the implicit `auto`.
     An auto-sized grid track grows to the MAX-CONTENT of its widest item, and
     two panels here contain a table with a min-width (the takeoff at 30rem,
     the request schedule at 44rem) precisely so their columns stay readable
     and scroll sideways inside .table-scroll. With an auto track, that
     min-width propagated outwards instead: the single mobile column grew to
     ~790 px, every panel with it, and the WHOLE PAGE scrolled horizontally on
     a phone — the plan drawing, the toolbar and the action bar all pushed off
     the right edge. minmax(0, …) caps the track at the container so the
     overflow stays inside the one element built to scroll. */
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas:
    "settings"
    "plan"
    "catalog"
    "spec"
    "takeoff"
    "quote"
    "session";
}

.region-settings { grid-area: settings; }
.region-plan     { grid-area: plan; }
.region-catalog  { grid-area: catalog; }
.region-spec     { grid-area: spec; }
.region-takeoff  { grid-area: takeoff; }
.region-quote    { grid-area: quote; }
.region-session  { grid-area: session; }

@media (min-width: 1024px) {
  .layout {
    grid-template-columns: 300px minmax(0, 1fr) 340px;
    grid-template-areas:
      "settings settings settings"
      "catalog  plan     spec"
      "catalog  takeoff  spec"
      "quote    quote    quote"
      "session  session  session";
    align-items: start;
  }
}

.region {
  background: var(--pl-surface);
  border: 1px solid var(--pl-rule);
  padding: var(--pl-s4);
  /* A grid item defaults to min-width:auto, which refuses to shrink below its
     content. Paired with the minmax(0, 1fr) track above, this is what lets a
     wide table scroll inside its own container instead of widening the page. */
  min-width: 0;
}

.region-h {
  margin: 0 0 var(--pl-s3);
  font-size: 0.8125rem;
  letter-spacing: var(--pl-track-display);
  color: var(--pl-ink);
}

/* <details> panels: the summary IS the heading.
   min-height is the shared tap-target token, not an arbitrary 1.5rem: these
   summaries are the primary controls on a phone and were 24 px tall, which
   scrapes past WCAG 2.5.8 and fails the project's own 44 px target. */
.region-h--summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: var(--pl-s2);
  min-height: var(--pl-tap);
  padding: var(--pl-s1) 0;
  margin: 0;
}
.region-h--summary::-webkit-details-marker { display: none; }
.region-h--summary::before {
  content: "+";
  font-family: var(--pl-font-mono);
  font-size: 1rem;
  line-height: 1;
  width: 1em;
}
details[open] > .region-h--summary::before { content: "\2212"; }
.region-h-count {
  margin-left: auto;
  font-family: var(--pl-font-body);
  font-size: 0.75rem;
  text-transform: none;
  letter-spacing: 0;
  color: var(--pl-ink-muted);
}
.region-body { padding-top: var(--pl-s3); }

.region-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--pl-s3);
  flex-wrap: wrap;
  margin-bottom: var(--pl-s3);
}
.region-bar .region-h { margin: 0; }

.panel-sub {
  margin: var(--pl-s4) 0 var(--pl-s2);
  font-size: 0.75rem;
  letter-spacing: var(--pl-track-label);
  color: var(--pl-ink);
  border-bottom: 1px solid var(--pl-rule);
  padding-bottom: var(--pl-s1);
}
.panel-sub-note {
  font-family: var(--pl-font-body);
  text-transform: none;
  letter-spacing: 0;
  font-weight: 400;
  color: var(--pl-ink-faint);
}
.panel-note { font-size: 0.75rem; color: var(--pl-ink-muted); margin: 0 0 var(--pl-s3); }
.panel-empty { font-size: 0.8125rem; color: var(--pl-ink-muted); font-style: italic; }

/* ---------------------------------------------------------------------------
   FORMS
   ------------------------------------------------------------------------ */

.settings-grid {
  display: grid;
  gap: var(--pl-s4);
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}

.field { display: flex; flex-direction: column; gap: var(--pl-s1); min-width: 0; }
.field label, .field-legend {
  font-family: var(--pl-font-display);
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: var(--pl-track-label);
  color: var(--pl-ink-muted);
}
.field-help { font-size: 0.6875rem; color: var(--pl-ink-faint); margin: 0; }

input[type="number"],
input[type="search"],
input[type="text"],
input[type="file"],
select,
textarea {
  font-family: var(--pl-font-body);
  font-size: 1rem;
  color: var(--pl-ink);
  background: var(--pl-surface);
  border: 1px solid var(--pl-rule-strong);
  padding: 0.5rem 0.6rem;
  min-height: var(--pl-tap);
  width: 100%;
  max-width: 100%;
}
/* NO FONT-SIZE HERE. The shared rule above sets 1rem = 16px, which is the
   threshold below which iOS Safari zooms the whole page in on focus and does
   not zoom back out. This rule used to override it with 0.8125rem (13px), and
   it applies to both textareas on the page: #io-output (the export box, which
   is focusable even though it is readonly) and #import-text. The page
   correctly does not set maximum-scale, so a user who tapped either one was
   left panning a zoomed-in layout tool — and #io-output is exactly where the
   panel's own copy sends an iPhone user whose download was blocked. */
textarea { min-height: 6rem; font-family: var(--pl-font-mono); }

/* min-width: 0 on both. A flex item's default min-width:auto floors an
   <input type="number"> at its intrinsic width — roughly 180px at 16px with
   0.6rem of padding each side plus the stepper — so the two room-size inputs
   could not shrink. At 375px the container is ~309px wide and the row wanted
   ~370px, which pushed content past the viewport and gave the whole page a
   horizontal scrollbar the moment "Project settings" was opened (which
   plan-main.js collapses on phones, so the user must open it). On desktop the
   same row overflowed its ~222px grid track and collided with the adjacent
   "Circulation clearance" field. */
.field-pair { min-width: 0; }
.pair { display: flex; align-items: center; gap: var(--pl-s2); min-width: 0; }
.pair > input { min-width: 0; flex: 1 1 0; }
.pair-x { color: var(--pl-ink-faint); flex: none; }
.input-unit { display: flex; align-items: center; gap: var(--pl-s2); }
.input-unit .unit { font-size: 0.75rem; color: var(--pl-ink-muted); }

/* Room size as feet + inches, one axis per row: "W [12] ft [6.0] in". The
   same min-width:0 story as .pair above — two number inputs plus two unit
   badges in a ~222 px grid track would otherwise overflow it. */
.ftin-row { display: flex; align-items: center; gap: var(--pl-s2); min-width: 0; }
.ftin-row .input-unit { flex: 1 1 0; min-width: 0; }
.ftin-row .input-unit > input { flex: 1 1 0; min-width: 0; width: auto; }
.ftin-row .unit { flex: none; }
.ftin-axis {
  flex: none;
  width: 1.1em;
  font-family: var(--pl-font-display);
  font-size: 0.6875rem;
  color: var(--pl-ink-faint);
}

/* The millimetres beside an inch figure in the takeoff: read, not scanned. */
.dim-mm { color: var(--pl-ink-faint); font-size: 0.8em; white-space: nowrap; margin-left: 0.35em; }

.field-check { flex-direction: row; flex-wrap: wrap; align-items: center; gap: var(--pl-s2); }
.field-check input[type="checkbox"] {
  /* 24 px, the WCAG 2.5.8 minimum target. It was 18 px. */
  width: 1.5rem; height: 1.5rem;
  min-height: 0;
  accent-color: var(--pl-ink);
  flex: none;
}
/* The label carries `for=`, so the whole label is a hit area for the box.
   Giving it the full tap height makes the effective target 44 px on a phone
   rather than the 24 px of the box alone — the same result .field-check-inline
   gets by wrapping its input. Without this the label is only 13 px tall. */
.field-check label {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  min-height: var(--pl-tap);
  cursor: pointer;
}
.field-check .field-help { flex: 1 1 100%; }

/* ---------------------------------------------------------------------------
   BUTTONS
   ------------------------------------------------------------------------ */

.btn {
  font-size: 0.6875rem;
  letter-spacing: var(--pl-track-label);
  background: var(--pl-surface);
  color: var(--pl-ink);
  border: 1px solid var(--pl-rule-strong);
  padding: 0.55rem 0.8rem;
  min-height: var(--pl-tap);
  cursor: pointer;
}
.btn:hover:not(:disabled) { background: var(--pl-ink); color: var(--pl-ink-invert); }
.btn:disabled { opacity: 0.4; cursor: not-allowed; }
.btn-sm { min-height: 2rem; padding: 0.35rem 0.6rem; }
.btn-row { display: flex; gap: var(--pl-s2); flex-wrap: wrap; margin-top: var(--pl-s3); }
.toolbar { display: flex; gap: var(--pl-s1); }
.toolbar .btn { min-width: var(--pl-tap); }

/* ---------------------------------------------------------------------------
   PLAN SURFACE
   ------------------------------------------------------------------------ */

/* TOUCH: THE DRAWING MUST NOT SWALLOW THE PAGE SCROLL.
   ---------------------------------------------------------------------------
   `touch-action: none` on the surface and the SVG meant a one-finger swipe
   starting anywhere on the drawing did nothing at all. In the single-column
   phone layout the drawing is the full column width and ~373px tall, leaving a
   ~33px strip either side, so a thumb landing on it left the user unable to
   reach the catalog, takeoff or session panels below — and it disabled the
   browser's pinch-zoom over the drawing too, while plan-view.js handles only
   one pointer, so there was no zoom of any kind except the toolbar buttons.

   `pan-y pinch-zoom` here, `none` on .piece below. touch-action intersects
   from the hit target up to the scroll container, so the permissive value has
   to sit on the container and the restrictive one on the child:
     - finger on a PIECE  -> none -> full two-axis drag, unchanged.
     - finger on the SHEET -> vertical swipe scrolls the page (the escape
       route), horizontal drag still pans the plan, pinch zooms the page.
   A browser-initiated scroll now cancels the gesture, which plan-view.js
   handles in onPointerCancel. Vertical panning of the sheet by touch is given
   up deliberately: the toolbar's Fit / +/- cover it, and being unable to
   scroll the page at all does not have a workaround. */
.plan-surface {
  border: 1px solid var(--pl-rule-strong);
  background: var(--pl-surface-alt);
  touch-action: pan-y pinch-zoom;
  overscroll-behavior: contain;
  height: clamp(320px, 56vh, 460px);
  overflow: hidden;
}
@media (min-width: 1024px) {
  .plan-surface { height: clamp(420px, 62vh, 660px); }
}

.plan-svg { display: block; width: 100%; height: 100%; touch-action: pan-y pinch-zoom; }
/* A placed piece is the one thing that needs both axes from a single finger. */
.plan-svg .piece { touch-action: none; }
.plan-svg text { font-family: var(--pl-font-body); }

/* Non-interactive layers must not swallow the background pan gesture. */
.plan-svg .layer-grid,
.plan-svg .layer-dims,
.plan-svg .layer-halos { pointer-events: none; }

.plan-svg .floor { fill: var(--pl-surface); stroke: none; }
.plan-svg .wall-band {
  fill: var(--pl-surface-sunk);
  stroke: var(--pl-rule-strong);
  stroke-width: var(--keca-stroke-wall, 3px);
}
.plan-svg .grid-line { stroke: var(--pl-rule-faint); stroke-width: var(--keca-stroke-thin, 1px); fill: none; }

.plan-svg .dim-ext,
.plan-svg .dim-line { stroke: var(--pl-ink-faint); stroke-width: var(--keca-stroke-thin, 1px); fill: none; }
.plan-svg .dim-tick { stroke: var(--pl-ink); stroke-width: var(--keca-stroke-med, 1.5px); fill: none; }
.plan-svg .dim-text {
  fill: var(--pl-ink);
  font-family: var(--pl-font-display);
  letter-spacing: var(--pl-track-label);
  dominant-baseline: alphabetic;
}
.plan-svg .sheet-note {
  fill: var(--pl-ink-faint);
  font-family: var(--pl-font-display);
  letter-spacing: var(--pl-track-label);
}
.plan-svg .scalebar-line,
.plan-svg .scalebar-tick { stroke: var(--pl-ink); stroke-width: var(--keca-stroke-med, 1.5px); }
.plan-svg .scalebar-text {
  fill: var(--pl-ink-muted);
  font-family: var(--pl-font-display);
  letter-spacing: var(--pl-track-label);
}

/* --- pieces --- */
.plan-svg .piece { cursor: grab; outline: none; }
.plan-svg .piece:active { cursor: grabbing; }
.plan-svg .piece.is-undrawable { display: none; }

.plan-svg .piece-body {
  fill: var(--pl-surface-alt);
  stroke: var(--pl-rule-strong);
  stroke-width: var(--keca-stroke-med, 1.5px);
}
.plan-svg .piece-hatch { fill: none; stroke: none; }
.plan-svg .piece-axis {
  stroke: var(--pl-ink);
  stroke-width: var(--keca-stroke-wall, 3px);
  stroke-linecap: butt;
}
.plan-svg .piece-focus { fill: none; stroke: none; }
.plan-svg .piece-label {
  fill: var(--pl-ink);
  pointer-events: none;
  paint-order: stroke fill;
  stroke: var(--pl-surface);
  stroke-width: 0.22em;
  stroke-linejoin: round;
  font-weight: 600;
}

/* Conflict states never rely on color alone — hatch and dash carry them. */
.plan-svg .piece.is-overlap .piece-hatch { fill: url(#keca-overlap-hatch); }
.plan-svg .piece.is-overlap .piece-body { stroke-width: var(--keca-stroke-heavy, 2px); }
.plan-svg .piece.is-tight .piece-body {
  stroke-width: var(--keca-stroke-heavy, 2px);
  stroke-dasharray: 7 4;
}
.plan-svg .piece.is-outside .piece-body { stroke-dasharray: 2 3; }
.plan-svg .piece.is-selected .piece-body { stroke-width: var(--keca-stroke-wall, 3px); }

.plan-svg .hatch-bg { fill: none; }
.plan-svg .hatch-line { stroke: var(--pl-ink-faint); stroke-width: var(--keca-stroke-thin, 1px); }

/* Focus ring uses stroke, not outline — outline on SVG is unreliable across
   engines and is clipped by the viewBox in some of them.

   `.is-focused` is applied by plan-view.js from real focusin/focusout events.
   It is not a duplicate of the :focus rules: focusing an SVG <g tabindex="0">
   is supported unevenly (Safari historically did not focus SVG elements at
   all), and a keyboard user with no visible focus indicator is stranded. */
.plan-svg .piece.is-focused .piece-focus,
.plan-svg .piece:focus-visible .piece-focus,
.plan-svg .piece:focus .piece-focus {
  stroke: var(--pl-focus);
  stroke-width: var(--keca-stroke-wall, 3px);
  stroke-dasharray: 6 4;
}

/* --- halos --- */
.plan-svg .piece-halo {
  fill: none;
  stroke: var(--pl-ink-faint);
  stroke-width: var(--keca-stroke-thin, 1px);
  stroke-dasharray: 4 5;
}
.plan-svg .piece-halo.is-hidden { display: none; }
.plan-svg .piece-halo.is-selected { stroke: var(--pl-ink-muted); }
.plan-svg .piece-halo.is-tight,
.plan-svg .piece-halo.is-overlap {
  stroke: var(--pl-alert);
  stroke-width: var(--keca-stroke-med, 1.5px);
  stroke-dasharray: 9 4;
}

/* ---------------------------------------------------------------------------
   SELECTION BAR / STATUS
   ------------------------------------------------------------------------ */

.sel-bar {
  display: flex;
  align-items: center;
  gap: var(--pl-s3);
  flex-wrap: wrap;
  margin-top: var(--pl-s3);
}
.sel-name {
  margin: 0;
  font-family: var(--pl-font-display);
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: var(--pl-track-label);
  color: var(--pl-ink-muted);
  flex: 1 1 10rem;
  min-width: 0;
}
.sel-actions { display: flex; gap: var(--pl-s1); flex-wrap: wrap; }

.status-line {
  margin: var(--pl-s3) 0 0;
  padding: var(--pl-s3);
  border: 1px solid var(--pl-rule-strong);
  border-left-width: 6px;
  background: var(--pl-surface);
  font-size: 0.8125rem;
  font-weight: 600;
}
.status-line[data-state="fail"] {
  background: var(--pl-surface-sunk);
  border-left-width: 10px;
}

/* Persistent, unmissable, and it wraps — unlike the in-drawing note, which is
   sized in constant screen pixels and cannot be allowed to grow. */
.plan-caveat {
  margin: var(--pl-s2) 0 0;
  font-size: 0.6875rem;
  color: var(--pl-ink-muted);
}

.conflicts { margin-top: var(--pl-s3); }
.conflict-list { margin: 0; padding: 0; list-style: none; font-size: 0.8125rem; }
.conflict-list li {
  padding: var(--pl-s1) 0;
  border-bottom: 1px solid var(--pl-rule-faint);
  display: flex;
  gap: var(--pl-s2);
  align-items: baseline;
}
.conflict-list li.more { color: var(--pl-ink-muted); font-style: italic; }
.verdict-tag {
  font-family: var(--pl-font-display);
  font-size: 0.625rem;
  letter-spacing: var(--pl-track-label);
  border: 1px solid var(--pl-rule-strong);
  padding: 0 0.35rem;
  flex: none;
}
.conflict-list li[data-verdict="OVERLAP"] .verdict-tag {
  background: var(--pl-ink);
  color: var(--pl-ink-invert);
}

/* Secondary disclosure summaries — legend, provenance, output box.
   These render at 11 px, which gave a 13 px-tall tap target: under the 24 px
   WCAG 2.5.8 minimum and unusable with a thumb. The padding is the target;
   the text size is unchanged. */
.legend > summary,
.prov > summary,
.sub-details > summary {
  /* Padding, not display:flex — flex would strip the native disclosure
     triangle, which is the only affordance these have. */
  min-height: 2.25rem;
  padding: var(--pl-s2) 0;
}

.legend { margin-top: var(--pl-s3); font-size: 0.8125rem; }
.legend > summary {
  cursor: pointer;
  font-size: 0.6875rem;
  color: var(--pl-ink-muted);
}
.legend-body ul { margin: var(--pl-s2) 0 0; padding-left: 1.1rem; }
.legend-body li { margin-bottom: var(--pl-s1); color: var(--pl-ink-muted); }

/* ---------------------------------------------------------------------------
   CATALOG
   ------------------------------------------------------------------------ */

/* The line and category selectors above the list. Two columns wherever the
   panel is wide enough for two readable select boxes, one below that — a
   category name like "Barstools & counter stools" needs the room. */
.cat-filters {
  display: grid;
  gap: var(--pl-s2);
  grid-template-columns: 1fr;
  margin-bottom: var(--pl-s2);
}
@media (min-width: 420px) {
  .cat-filters { grid-template-columns: 1fr 1fr; }
}

.cat-list { list-style: none; margin: 0; padding: 0; max-height: 46vh; overflow-y: auto; }
@media (min-width: 1024px) { .cat-list { max-height: 52vh; } }

/* Reveals the next page of rows. Full width so it cannot be mistaken for one
   of the row-level buttons inside the list above it. */
.cat-more {
  width: 100%;
  margin-top: var(--pl-s2);
}

/* "3 more match, but cannot be placed." Never styled as an error: nothing has
   gone wrong, a filter is simply doing its job and saying so. */
.cat-hidden-note {
  margin: var(--pl-s2) 0 0;
  padding: var(--pl-s2);
  border-left: 2px solid var(--pl-rule-strong);
  background: var(--pl-surface-alt);
  font-size: 0.75rem;
  line-height: 1.5;
  color: var(--pl-ink-muted);
}
.cat-hidden-note button {
  display: block;
  margin-top: var(--pl-s1);
  font: inherit;
  color: var(--pl-ink);
  background: none;
  border: 0;
  border-bottom: 1px solid var(--pl-ink);
  padding: 0;
  min-height: 0;
  cursor: pointer;
}

/* The "verify" button and the revealed reason are siblings of the place/spec
   buttons, so the row wraps and the reason takes a full line of its own. */
.cat-row { display: flex; flex-wrap: wrap; align-items: stretch; border-bottom: 1px solid var(--pl-rule-faint); }
.cat-row > .tag--why { flex: none; align-self: center; margin: 0 var(--pl-s2); }
.cat-row > .cat-why { flex: 1 1 100%; margin: 0 0 var(--pl-s2); }
.cat-empty { padding: var(--pl-s3) 0; color: var(--pl-ink-muted); font-style: italic; }

.cat-place {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  text-align: left;
  background: none;
  border: 0;
  padding: var(--pl-s2) var(--pl-s1);
  min-height: var(--pl-tap);
  cursor: pointer;
  font-family: var(--pl-font-body);
  color: var(--pl-ink);
}
.cat-place:hover:not(:disabled) { background: var(--pl-surface-alt); }
.cat-place:disabled { cursor: not-allowed; opacity: 0.55; }
.cat-name {
  font-family: var(--pl-font-display);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: var(--pl-track-label);
}
.cat-dims { font-size: 0.6875rem; color: var(--pl-ink-muted); }

.cat-spec {
  flex: none;
  border: 0;
  border-left: 1px solid var(--pl-rule-faint);
  background: none;
  color: var(--pl-ink-muted);
  font-family: var(--pl-font-display);
  /* 0.625rem was 10px — the smallest text on the page, on the control that
     opens a product’s published specification. Raised to --keca-fs-micro, the
     11px floor this project set. */
  font-size: var(--keca-fs-micro, 0.6875rem);
  text-transform: uppercase;
  letter-spacing: var(--pl-track-label);
  padding: 0 var(--pl-s3);
  min-width: var(--pl-tap);
  min-height: var(--pl-tap);
  cursor: pointer;
}
.cat-spec:hover { background: var(--pl-ink); color: var(--pl-ink-invert); }

/* ---------------------------------------------------------------------------
   SPEC PANEL
   ------------------------------------------------------------------------ */

.spec-head { border-bottom: 2px solid var(--pl-rule-strong); padding-bottom: var(--pl-s2); }
.spec-name { margin: 0; font-size: 1rem; letter-spacing: var(--pl-track-display); }
.spec-sku { margin: var(--pl-s1) 0 0; font-size: 0.6875rem; color: var(--pl-ink-muted); }

.elev { margin: var(--pl-s4) 0; }
.elev-svg {
  display: block;
  width: 100%;
  height: 200px;
  background: var(--pl-surface-alt);
  border: 1px solid var(--pl-rule);
}
.elev-envelope { fill: var(--pl-surface); stroke: var(--pl-rule-strong); stroke-width: var(--keca-stroke-heavy, 2px); }
.elev-ground { stroke: var(--pl-ink); stroke-width: var(--keca-stroke-med, 1.5px); }
.elev-dim { stroke: var(--pl-ink); stroke-width: var(--keca-stroke-thin, 1px); }
.elev-ext { stroke: var(--pl-ink-faint); stroke-width: var(--keca-stroke-hair, 0.5px); }
.elev-text {
  fill: var(--pl-ink);
  font-family: var(--pl-font-display);
  letter-spacing: var(--pl-track-label);
}
.elev-caption { font-size: 0.6875rem; color: var(--pl-ink-muted); margin: var(--pl-s2) 0 0; }
.elev-seat {
  margin: var(--pl-s2) 0 0;
  padding: var(--pl-s2);
  border: 1px dashed var(--pl-rule);
  font-size: 0.6875rem;
  display: flex;
  gap: var(--pl-s2);
  align-items: baseline;
  flex-wrap: wrap;
}
.elev-seat-label {
  font-family: var(--pl-font-display);
  text-transform: uppercase;
  letter-spacing: var(--pl-track-label);
}
.elev-seat-why { color: var(--pl-ink-faint); }
.elev-missing { margin: var(--pl-s4) 0; }

.spec-table { width: 100%; border-collapse: collapse; font-size: 0.8125rem; }
.spec-table th, .spec-table td {
  text-align: left;
  padding: 0.3rem 0.35rem;
  border-bottom: 1px solid var(--pl-rule-faint);
  vertical-align: baseline;
}
.spec-table th {
  font-size: 0.6875rem;
  color: var(--pl-ink-muted);
  font-weight: 600;
  width: 45%;
}
.spec-table td.num { font-variant-numeric: tabular-nums; }
.spec-table td.alt { color: var(--pl-ink-faint); font-size: 0.75rem; text-align: right; }
.spec-table .unit { text-transform: none; letter-spacing: 0; color: var(--pl-ink-faint); }
/* A manufacturer statement that runs to several lines keeps its breaks. */
.spec-table td.prose { white-space: pre-line; line-height: 1.5; }
/* The manufacturer publishes no value for this field — a stated fact, set in
   the faint ink but never as the Pending word. */
.spec-table .notpub { color: var(--pl-ink-faint); }

/* The single visual convention for "no value has been supplied". */
.is-pending {
  color: var(--pl-ink-faint);
  border-bottom: 1px dotted var(--pl-ink-faint);
  cursor: help;
  font-style: italic;
}

/* 0.5625rem was 9px. --keca-fs-micro (11px) is the smallest step the token
   scale defines, and this tag is what warns a specifier not to trust a
   dimension. */
.tag {
  display: inline-block;
  font-family: var(--pl-font-display);
  font-size: var(--keca-fs-micro, 0.6875rem);
  text-transform: uppercase;
  letter-spacing: var(--pl-track-label);
  border: 1px solid var(--pl-rule-strong);
  padding: 0 0.3rem;
  cursor: help;
  vertical-align: middle;
}

/* THE REASON MUST BE REACHABLE WITHOUT A HOVER.
   suspectReason() was written specifically so the warning names its actual
   cause — duplicate bounding box, implausible footprint, unverified axes — and
   all of that lived in a `title`, which does not exist on a touch screen. This
   variant is a real button that reveals the sentence as visible text. */
.tag--why {
  background: none;
  color: inherit;
  cursor: pointer;
  min-height: 0;
  font-weight: inherit;
}
.tag--why:hover, .tag--why[aria-expanded="true"] {
  background: var(--pl-ink);
  color: var(--pl-ink-invert);
}
.cat-why {
  font-size: 0.75rem;
  line-height: 1.45;
  color: var(--pl-ink-muted);
  margin: var(--pl-s1) 0 0;
  padding-left: var(--pl-s2);
  border-left: 2px solid var(--pl-rule-strong);
}
.cat-why[hidden] { display: none; }

/* Header navigation — plan.html was previously a dead end. */
.app-nav { display: flex; flex-wrap: wrap; gap: var(--pl-s3); margin-top: var(--pl-s2); }
.app-nav a {
  font-family: var(--pl-font-display);
  font-size: var(--keca-fs-micro, 0.6875rem);
  text-transform: uppercase;
  letter-spacing: var(--pl-track-label);
  color: var(--pl-ink);
  text-decoration: none;
  border-bottom: 1px solid var(--pl-rule-strong);
  padding-bottom: 2px;
  min-height: var(--pl-tap);
  display: inline-flex;
  align-items: center;
}
.app-nav a:hover { border-bottom-color: var(--pl-ink); }

/* Keyboard reference in the legend. */
.legend-keys { margin: 0 0 var(--pl-s3); padding-left: 1.1rem; }
.legend-keys li { margin-bottom: 0.25rem; }
kbd {
  font-family: var(--pl-font-mono);
  font-size: 0.8125rem;
  border: 1px solid var(--pl-rule-strong);
  border-bottom-width: 2px;
  padding: 0 0.3rem;
  background: var(--pl-surface);
  white-space: nowrap;
}
.field-check-inline {
  display: flex;
  align-items: center;
  gap: var(--pl-s2);
  margin-bottom: var(--pl-s1);
  font-size: 0.8125rem;
  min-height: var(--pl-tap);
}
.field-check-inline input[type="checkbox"] {
  width: 1.5rem; height: 1.5rem; min-height: 0; accent-color: var(--pl-ink); flex: none;
}

.prov { margin-top: var(--pl-s4); border-top: 1px solid var(--pl-rule); padding-top: var(--pl-s2); }
.prov > summary {
  cursor: pointer;
  font-family: var(--pl-font-display);
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: var(--pl-track-label);
  color: var(--pl-ink-muted);
}
.prov-body { padding-top: var(--pl-s2); }
.prov-list { display: grid; grid-template-columns: auto 1fr; gap: 0.15rem var(--pl-s3); margin: 0; font-size: 0.75rem; }
.prov-list dt { font-family: var(--pl-font-display); text-transform: uppercase; letter-spacing: var(--pl-track-label); font-size: 0.625rem; color: var(--pl-ink-muted); }
.prov-list dd { margin: 0; }
.prov-flags { margin: var(--pl-s3) 0 0; padding-left: 0; list-style: none; font-size: 0.6875rem; color: var(--pl-ink-muted); }
.prov-flags li { margin-bottom: var(--pl-s2); }

/* ---------------------------------------------------------------------------
   TAKEOFF
   ------------------------------------------------------------------------ */

.table-scroll { overflow-x: auto; min-width: 0; max-width: 100%; }
.takeoff-table { width: 100%; border-collapse: collapse; font-size: 0.8125rem; min-width: 30rem; }
.takeoff-table th, .takeoff-table td {
  text-align: left;
  padding: 0.35rem 0.4rem;
  border-bottom: 1px solid var(--pl-rule-faint);
}
.takeoff-table thead th {
  font-size: 0.625rem;
  color: var(--pl-ink-muted);
  border-bottom: 2px solid var(--pl-rule-strong);
  white-space: nowrap;
}
.takeoff-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.takeoff-table .qty { font-weight: 700; }

.io-text { width: 100%; }
.sub-details { margin: var(--pl-s3) 0; }
.sub-details > summary {
  cursor: pointer;
  font-family: var(--pl-font-display);
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: var(--pl-track-label);
  color: var(--pl-ink-muted);
}

/* ---------------------------------------------------------------------------
   SPECIFICATION REQUEST

   The document a designer hands to KECA. It is styled as a DOCUMENT — ruled
   header, blank fill-in lines, a schedule table — rather than as another
   dashboard panel, because it is read and printed as one.

   Everything here reuses the page's existing type scale, tokens and .spec-table
   rules. No new colour, no new radius, no shadow: square, monochrome, same as
   the rest of the tool and the same as kecausa.com.
   ------------------------------------------------------------------------ */

.quote-body { margin-bottom: var(--pl-s4); }

.quote-doc {
  border: 1px solid var(--pl-rule);
  padding: var(--pl-s4);
  background: var(--pl-surface);
}

.quote-head {
  border-bottom: 2px solid var(--pl-rule-strong);
  padding-bottom: var(--pl-s2);
  margin-bottom: var(--pl-s3);
}
.quote-eyebrow {
  margin: 0;
  font-family: var(--pl-font-display);
  font-weight: 600;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: var(--pl-track-display);
}
.quote-stamp {
  margin: var(--pl-s1) 0 0;
  font-size: 0.6875rem;
  color: var(--pl-ink-muted);
}

/* The two lines the tool refuses to collect, drawn as ruled blanks so the
   document reads as complete rather than as though a field failed to load. */
.quote-blanks {
  display: grid;
  grid-template-columns: auto minmax(6rem, 1fr);
  align-items: end;
  gap: var(--pl-s2) var(--pl-s3);
  margin: 0 0 var(--pl-s2);
}
.quote-blank-label {
  font-family: var(--pl-font-display);
  font-size: 0.625rem;
  text-transform: uppercase;
  letter-spacing: var(--pl-track-label);
  color: var(--pl-ink-muted);
  white-space: nowrap;
}
.quote-blank-rule {
  border-bottom: 1px solid var(--pl-rule-strong);
  height: 1.1rem;
}

.quote-verdict {
  margin: 0 0 var(--pl-s3);
  padding: var(--pl-s2) var(--pl-s3);
  font-size: 0.8125rem;
  border: 1px solid var(--pl-rule);
  border-left: 4px solid var(--pl-rule);
}
/* PASS and FAIL are separated by BORDER WEIGHT and by the word itself, never
   by colour alone — WCAG 1.4.1 and the house monochrome rule. */
.quote-verdict[data-state="pass"] { border-left-color: var(--pl-ink-muted); }
/* "NOT CHECKED" is a third state, not a soft pass. A DASHED rule reads as
   "incomplete" in black and white and at print resolution, and matches the
   dashed outline the drawing already uses for a figure that is not KECA's. */
.quote-verdict[data-state="unchecked"] {
  border-left-width: 6px;
  border-left-style: dashed;
  border-left-color: var(--pl-ink-muted);
  background: var(--pl-surface-alt);
}
.quote-verdict[data-state="fail"] {
  border-left-width: 6px;
  border-left-color: var(--pl-alert);
  background: var(--pl-surface-alt);
}

.quote-conflicts {
  margin: 0 0 var(--pl-s3);
  padding-left: 1.1rem;
  font-size: 0.75rem;
  color: var(--pl-ink-muted);
}
.quote-conflicts li { margin-bottom: 0.15rem; }
.quote-conflicts li.more { font-style: italic; }

.quote-table { min-width: 44rem; }
.quote-table th[scope="row"] { vertical-align: top; }
.quote-table td { vertical-align: top; font-variant-numeric: tabular-nums; }
.quote-table .num { text-align: right; }
.quote-table .qty { font-weight: 700; }

.quote-name {
  display: block;
  font-family: var(--pl-font-display);
  font-size: 0.75rem;
  letter-spacing: var(--pl-track-label);
}
.quote-sku {
  display: inline-block;
  font-family: var(--pl-font-mono);
  font-size: 0.6875rem;
  text-transform: none;
  letter-spacing: 0;
  color: var(--pl-ink-muted);
}
.quote-cat {
  display: block;
  font-family: var(--pl-font-body);
  font-weight: 400;
  font-size: 0.6875rem;
  text-transform: none;
  letter-spacing: 0;
  color: var(--pl-ink-muted);
}
/* One axis on its own line, used only when a product is missing one of W/D/H
   so that the remaining figures can never be skimmed as a complete triple. */
.quote-axis { display: block; white-space: nowrap; }
.quote-axis-l {
  display: inline-block;
  min-width: 1.1em;
  font-family: var(--pl-font-display);
  font-size: 0.625rem;
  color: var(--pl-ink-muted);
}

/* The imperial restatement of a published metric figure. Subordinate on
   purpose — it is the same number, said again in another unit. */
.quote-alt {
  color: var(--pl-ink-faint);
  font-size: 0.6875rem;
  white-space: nowrap;
}
.quote-foot { margin-top: var(--pl-s4); }

/* ---------------------------------------------------------------------------
   TOAST
   ------------------------------------------------------------------------ */

.toast {
  position: fixed;
  left: var(--pl-s4);
  right: var(--pl-s4);
  bottom: var(--pl-s4);
  z-index: 50;
  margin: 0;
  padding: var(--pl-s3);
  background: var(--pl-ink);
  color: var(--pl-ink-invert);
  font-size: 0.8125rem;
  border: 1px solid var(--pl-ink);
  max-width: 44rem;
  pointer-events: none;
}
.toast:empty { display: none; }
@media (min-width: 1024px) { .toast { left: auto; right: var(--pl-s5); } }

/* ---------------------------------------------------------------------------
   REDUCED MOTION
   ------------------------------------------------------------------------ */

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; scroll-behavior: auto !important; }
}

/* ---------------------------------------------------------------------------
   PRINT — the drawing plus the schedule plus the caveat. Nothing else.
   ------------------------------------------------------------------------ */

@media print {
  body { padding: 0; background: #fff; font-size: 10pt; }

  .skip-link,
  .banner,
  .region-settings,
  .region-catalog,
  .region-session,
  .toolbar,
  .sel-bar,
  .legend,
  .toast,
  .btn-row { display: none !important; }

  .app-head { border-bottom: 1pt solid #000; }
  .layout { display: block; }
  .region { border: 0; padding: 0; margin-bottom: 12pt; break-inside: avoid; }
  .region-h--summary::before { content: none; }

  /* <details> must print open regardless of screen state. */
  details > .region-body { display: block !important; }

  .plan-surface { height: 150mm; border: 1pt solid #000; }
  .takeoff-table { min-width: 0; }
  .app-foot { border-top: 1pt solid #000; color: #000; }

  /* --- the specification request ---------------------------------------- */
  /* The raw text copy of the request is for the clipboard, not for paper. The
     document above it says the same thing, laid out. */
  #quote-text-wrap { display: none !important; }

  .quote-doc { border: 1pt solid #000; padding: 6pt; }
  .quote-head { border-bottom: 1.5pt solid #000; }
  .quote-table { min-width: 0; font-size: 8.5pt; }
  .quote-blank-rule { border-bottom: 0.75pt solid #000; }
  /* A schedule that breaks across pages mid-row is unreadable on paper. */
  .quote-table tr { break-inside: avoid; }
  .quote-verdict { break-inside: avoid; }

  /* REQUEST-ONLY MODE.
     Set on <body> by the "Print / PDF" button in the request panel and cleared
     on afterprint. It prints the request as a standalone document — no
     drawing, no takeoff table, no spec panel — because that is what gets sent
     to a supplier. A plain Ctrl+P is unaffected and still prints the drawing
     plus the schedule, exactly as before. */
  body.print-request-only .region-plan,
  body.print-request-only .region-spec,
  body.print-request-only .region-takeoff { display: none !important; }
  body.print-request-only .quote-doc { border: 0; padding: 0; }
}

/* ==========================================================================
   PHONE TYPE FLOOR — measured on this page at 375 px
   --------------------------------------------------------------------------
   Every selector below carries a SENTENCE or a NUMBER a specifier reads, not
   a two-word tracked label, and every one rendered under 14 px at 375 px:

       .field-help          11 px  "Project setting, not a fixed rule"
       .plan-caveat         11 px  "Confirm critical clearances"
       .cat-dims            11 px  the W x D of each catalog row
       .panel-note          12 px  including "Nothing is transmitted"
       .takeoff-table thead 10 px  SKU / Qty / W / D / H — the column that says
                                   WHICH dimension a number is
       .takeoff-table body  13 px  the quantities themselves
       .status-line         13 px  the PASS / FAIL circulation verdict
       .banner-title/body   13 px  how much of the catalog has no spec data
       .app-foot            13 px  "Read before specifying"
       .panel-empty         13 px
       .legend              13 px  how to read the drawing
       .conflict-list       13 px  which two pieces are too close

   The uppercase micro-labels (.region-h, .btn, .sel-name, .cat-spec) stay at
   the documented 11 px step of the token scale: those are tracked labels, and
   raising them reflows the toolbar. Running text is the thing being fixed.

   Phone only — desktop density is untouched.
   ========================================================================== */
@media (max-width: 767px) {
  .field-help,
  .plan-caveat,
  .cat-dims,
  .panel-note,
  .panel-empty,
  .status-line,
  .banner-title,
  .banner-body,
  .app-foot,
  .legend,
  .conflict-list,
  .legend-body li,
  .takeoff-table { font-size: var(--keca-fs-base, 0.875rem); }

  /* The takeoff header row is the label for six columns of millimetres. It was
     the smallest type on the page at 10 px. It scrolls inside .table-scroll, so
     widening it costs nothing outside that one element. */
  .takeoff-table thead th { font-size: var(--keca-fs-small, 0.75rem); }
}

/* ==========================================================================
   DESIGNER-SUPPLIED DIMENSIONS
   --------------------------------------------------------------------------
   488 of the 775 products publish no width, no depth, or neither, and those
   figures do not exist. The designer supplies them. Everything in this block
   exists to keep ONE distinction visible: which numbers are KECA's published
   specification and which are the designer's own.

   MONOCHROME DISCIPLINE APPLIES HERE MORE THAN ANYWHERE.
   There is no hue in this palette and none is added. The entered state is
   carried by a DASH PATTERN, a LEFT RULE, a BOX and — always — WORDS. Nothing
   below relies on colour to make its point, which is both the house style and
   a WCAG 1.4.1 requirement; it also means the distinction survives the
   black-and-white print of the specification request, which is how a
   submittal actually travels.
   ========================================================================== */

/* --- the inline mark on a value ---------------------------------------
   Deliberately NOT .is-pending's dotted underline: pending means "nobody
   supplied this" and entered means "you supplied this", and a specifier must
   never have to work out which of the two a mark means. Dashed, not dotted,
   and the same dash the plan draws. */
.is-entered {
  border-bottom: 2px dashed var(--pl-ink);
  font-style: normal;
  font-weight: 600;
  padding-bottom: 1px;
  cursor: help;
}

/* The words that travel with the mark. A tag alone would be decoration. */
.tag--entered {
  border-style: dashed;
  border-width: 1.5px;
  text-transform: none;
  letter-spacing: 0;
  font-family: var(--pl-font-body);
  font-size: var(--keca-fs-micro, 0.6875rem);
  white-space: normal;
}

/* A whole row that carries an entered figure, in the takeoff and in the
   request schedule. A left rule, so it reads at a glance down a column. */
tr.is-entered-row > th:first-child,
tr.is-entered-row > td:first-child { border-left: 3px dashed var(--pl-ink); }

/* --- the plan ----------------------------------------------------------
   A SECOND outline, inset inside the footprint, not a dash on the body.
   .piece-body's dash already carries a clearance verdict (TIGHT is 7-4,
   OUTSIDE is 2-3); overloading one stroke with two unrelated meanings would
   make a tight published chair and an OK entered chair look alike. Two
   concentric outlines cannot be read as one. */
.plan-svg .piece-entered {
  fill: none;
  stroke: var(--pl-ink);
  stroke-width: var(--keca-stroke-med, 1.5px);
  stroke-dasharray: 12 6;
}

/* --- the catalog row --------------------------------------------------- */

/* "Enter dimensions to place" — the action that replaced a dead end for the
   488. Full width on its own line inside the row, because it is a sentence
   and truncating it to "Enter dimen…" would lose the only word that says what
   happens next. 44px minimum, like every other tap target on this page. */
.cat-enter {
  flex: 1 1 100%;
  order: 3;
  text-align: left;
  background: none;
  color: var(--pl-ink);
  border: 1px dashed var(--pl-rule-strong);
  font-family: var(--pl-font-display);
  font-size: var(--keca-fs-micro, 0.6875rem);
  text-transform: uppercase;
  letter-spacing: var(--pl-track-label);
  padding: 0.5rem var(--pl-s2);
  min-height: var(--pl-tap);
  margin: 0 0 var(--pl-s2);
  cursor: pointer;
}
.cat-enter:hover { background: var(--pl-ink); color: var(--pl-ink-invert); }

/* "edit", on a product that already carries an entered figure. Sits in the
   row beside "spec" rather than on a line of its own — it is a short label,
   and that row is no longer a dead end needing an explanation. */
.cat-row .cat-enter:not(.cat-enter--needed) {
  flex: none;
  order: 0;
  width: auto;
  margin: 0;
  border-width: 0 0 0 1px;
  border-left-style: solid;
  border-left-color: var(--pl-rule-faint);
  padding: 0 var(--pl-s3);
  min-width: var(--pl-tap);
  color: var(--pl-ink-muted);
}

/* The words beside an entered figure in the catalog list. */
.cat-entered-mark {
  font-family: var(--pl-font-body);
  font-size: var(--keca-fs-micro, 0.6875rem);
  color: var(--pl-ink-muted);
  text-transform: none;
  letter-spacing: 0;
}

li.cat-row.is-entered-row { border-left: 3px dashed var(--pl-ink); }
li.cat-row.is-entered-row .cat-place { padding-left: var(--pl-s2); }

/* --- the specification panel ------------------------------------------- */

/* The loudest block on the panel, on purpose: this is where a specifier
   decides whether a number is safe to put on a submittal. */
.entered-panel {
  margin: var(--pl-s4) 0;
  padding: var(--pl-s3);
  border: 2px dashed var(--pl-rule-strong);
  background: var(--pl-surface-alt);
}
.entered-panel-h { margin-top: 0; }
.entered-list { list-style: none; margin: 0 0 var(--pl-s2); padding: 0; }
.entered-list li {
  display: flex;
  justify-content: space-between;
  gap: var(--pl-s2);
  padding: var(--pl-s1) 0;
  border-bottom: 1px solid var(--pl-rule-faint);
}
.entered-axis {
  font-family: var(--pl-font-display);
  font-size: var(--keca-fs-micro, 0.6875rem);
  text-transform: uppercase;
  letter-spacing: var(--pl-track-label);
}
.entered-value { font-variant-numeric: tabular-nums; font-weight: 700; }
.entered-note {
  font-size: var(--keca-fs-small, 0.75rem);
  line-height: 1.5;
  color: var(--pl-ink);
  margin: 0;
}
.entered-actions { margin-top: var(--pl-s3); }

.prov-entered {
  border-left: 3px dashed var(--pl-ink);
  padding-left: var(--pl-s2);
  color: var(--pl-ink);
}

/* The elevation envelope, when its width or its height is the designer's. */
.elev-svg.is-entered .elev-envelope { stroke-dasharray: 12 6; }
.elev-entered { display: block; margin-top: var(--pl-s1); color: var(--pl-ink); }

/* --- the specification request ----------------------------------------- */

.quote-entered {
  margin: var(--pl-s4) 0;
  padding: var(--pl-s3);
  border: 2px dashed var(--pl-rule-strong);
}
.quote-entered-h { margin-top: 0; }
.quote-entered-list { list-style: none; margin: 0 0 var(--pl-s2); padding: 0; }
.quote-entered-list li {
  display: flex;
  flex-wrap: wrap;
  gap: var(--pl-s1) var(--pl-s3);
  align-items: baseline;
  padding: var(--pl-s1) 0;
  border-bottom: 1px solid var(--pl-rule-faint);
}
.quote-entered-name {
  font-family: var(--pl-font-display);
  font-size: var(--keca-fs-small, 0.75rem);
  text-transform: uppercase;
  letter-spacing: var(--pl-track-label);
}
.quote-entered-sku { color: var(--pl-ink-muted); }
.quote-entered-vals { font-variant-numeric: tabular-nums; font-weight: 700; }

/* ==========================================================================
   THE ENTRY FORM
   --------------------------------------------------------------------------
   MUST WORK AT 375 px. Inputs inherit the page's 1rem (16px) rule — below that
   iOS Safari zooms the page in on focus and does not zoom back out — and the
   44px minimum tap height. The dialog is capped at 92vh and scrolls inside
   itself, so the photograph and the published-dimensions block never push the
   Save button off a phone screen.
   ========================================================================== */

.ed-dialog {
  width: min(30rem, calc(100vw - 2rem));
  max-height: 92vh;
  padding: 0;
  border: 2px solid var(--pl-rule-strong);
  border-radius: var(--keca-radius, 0);
  background: var(--pl-surface);
  color: var(--pl-ink);
  font-family: var(--pl-font-body);
  overflow: auto;
}
.ed-dialog::backdrop { background: rgba(28, 27, 27, 0.55); }
/* Fallback for engines without showModal(): plan-main.js sets the plain `open`
   attribute, and a non-modal <dialog> is laid out in normal flow, so it is
   centred here by hand rather than appearing wherever it happens to sit. */
.ed-dialog[open] { position: fixed; inset: 50% auto auto 50%; transform: translate(-50%, -50%); }

.ed-form { padding: var(--pl-s4); }

.ed-title {
  margin: 0;
  font-family: var(--pl-font-display);
  font-size: var(--keca-fs-h2, 1.25rem);
  letter-spacing: var(--pl-track-display);
  text-transform: uppercase;
}
.ed-product {
  display: flex;
  flex-wrap: wrap;
  gap: var(--pl-s1) var(--pl-s3);
  align-items: baseline;
  margin: var(--pl-s2) 0 var(--pl-s4);
  padding-bottom: var(--pl-s2);
  border-bottom: 2px solid var(--pl-rule-strong);
}
.ed-name {
  font-family: var(--pl-font-display);
  font-size: var(--keca-fs-body, 1rem);
  text-transform: uppercase;
  letter-spacing: var(--pl-track-label);
}
.ed-sku, .ed-cat { font-size: var(--keca-fs-small, 0.75rem); color: var(--pl-ink-muted); }

.ed-context { display: grid; gap: var(--pl-s3); margin-bottom: var(--pl-s4); }
@media (min-width: 26rem) {
  .ed-context { grid-template-columns: 8rem 1fr; align-items: start; }
}

.ed-photo { margin: 0; }
.ed-photo img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--pl-rule);
  background: var(--pl-surface-alt);
}
.ed-photo figcaption {
  font-size: var(--keca-fs-micro, 0.6875rem);
  color: var(--pl-ink-faint);
  margin-top: var(--pl-s1);
}

.ed-sub {
  margin: 0 0 var(--pl-s2);
  font-family: var(--pl-font-display);
  font-size: var(--keca-fs-micro, 0.6875rem);
  text-transform: uppercase;
  letter-spacing: var(--pl-track-label);
  color: var(--pl-ink-muted);
}
.ed-pub-row {
  display: flex;
  justify-content: space-between;
  gap: var(--pl-s2);
  padding: var(--pl-s1) 0;
  border-bottom: 1px solid var(--pl-rule-faint);
  font-size: var(--keca-fs-small, 0.75rem);
}
.ed-pub-label { color: var(--pl-ink-muted); }
.ed-pub-value { font-variant-numeric: tabular-nums; }
.ed-pub-note {
  margin: var(--pl-s2) 0 0;
  font-size: var(--keca-fs-micro, 0.6875rem);
  color: var(--pl-ink-faint);
  line-height: 1.5;
}

/* The honesty statement. Boxed, above the inputs, so it is read before
   anything is typed rather than after. */
.ed-warning {
  margin: 0 0 var(--pl-s4);
  padding: var(--pl-s3);
  border-left: 3px solid var(--pl-rule-strong);
  background: var(--pl-surface-alt);
  font-size: var(--keca-fs-small, 0.75rem);
  line-height: 1.55;
}

.ed-fields { display: grid; gap: var(--pl-s4); }
.ed-field label { display: block; }
.ed-optional {
  text-transform: none;
  letter-spacing: 0;
  color: var(--pl-ink-faint);
  font-style: italic;
}

.ed-error {
  margin: var(--pl-s3) 0 0;
  padding: var(--pl-s2);
  border: 1px solid var(--pl-rule-strong);
  background: var(--pl-surface-alt);
  font-size: var(--keca-fs-small, 0.75rem);
  line-height: 1.5;
}
.ed-error[hidden] { display: none; }

.ed-actions { margin-top: var(--pl-s4); }
.ed-actions .btn { flex: 1 1 auto; }

@media (max-width: 767px) {
  .ed-warning,
  .ed-pub-row,
  .entered-note,
  .ed-error { font-size: var(--keca-fs-base, 0.875rem); }
  /* One control per line on a phone: three buttons side by side at 375 px
     puts "Clear entered dimensions" on three wrapped lines inside a 100 px
     box. */
  .ed-actions { flex-direction: column; }
}

/* On a phone the form is one column, so a full-width photograph pushed the
   input the form exists to collect below the fold — measured at 375 px: the
   dialog was 747 px tall and "Overall width" started at ~430 px. Capped and
   letterboxed, the photograph still does its job (is this the chair I mean?)
   without being the reason somebody has to scroll to type. */
@media (max-width: 25.99rem) {
  .ed-photo img { max-height: 7.5rem; object-fit: contain; }
}
