/* ==========================================================================
   Marine Dashboard — shared visual system
   Phase 1 of docs/reference/gui-restyle-and-rehlko-module-plan.md, session 1.

   ONE file, no page-specific rules. Every page links this and composes the
   classes below; nothing in a page file hard-codes a colour or a font family.

   MONOCHROME BY DECISION (plan §7, decision 1). The Rehlko configurator this
   system is lifted from carries a chromatic accent; this tool does not. Ink does
   the accent's job. The only colours that are not ink/paper/steel are the two
   STATUS colours, --ok and --warn, and they are never used for selection or
   emphasis. A rule ported from the mockup that names the accent token of the
   source system is renamed to --accent* at port time, never aliased -- and the
   plan's acceptance check greps this file for those source names, so they do not
   appear here even in a comment.

   WHY WARM PAPER AND SQUARE CORNERS. This is trade software read on a tablet
   at a marina, often in bright daylight, by someone standing next to a
   customer. The warm ground (#EFEDE6) is quieter under glare than white, the
   square corners and the mono face for every identifier make the screens read
   as a parts document rather than a consumer app, and that is exactly the
   register PRODUCT.md asks for: instrumentation a dealer trusts.
   ========================================================================== */

/* --------------------------------------------------------------------------
   0. Faces — self-hosted (plan §7, decision 4)
   --------------------------------------------------------------------------
   Downloaded from Google Fonts on 2026-08-31, latin subset only, at exactly the
   weights this system uses: 174 KB for all nine files. Self-hosted rather than
   CDN-linked because printing stations and offline demos must not depend on a
   network the marina may not have -- which is the same reason the tool caches
   its reference data.

   Both families are SIL Open Font License 1.1; see assets/fonts/README.md.

   font-display: swap, so text paints immediately in the fallback and re-renders
   when the face arrives. The fallback stacks in §1 are chosen to be close in
   metrics (Arial Narrow for the condensed face, the system sans and mono for the
   others), so the swap is a refinement rather than a reflow.
   -------------------------------------------------------------------------- */

@font-face {
  font-family: "Barlow";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: local("Barlow"), url("fonts/barlow-400.woff2") format("woff2");
}
@font-face {
  font-family: "Barlow";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: local("Barlow Medium"), url("fonts/barlow-500.woff2") format("woff2");
}
@font-face {
  font-family: "Barlow";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: local("Barlow SemiBold"), url("fonts/barlow-600.woff2") format("woff2");
}
@font-face {
  font-family: "Barlow Condensed";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: local("Barlow Condensed Medium"), url("fonts/barlow-condensed-500.woff2") format("woff2");
}
@font-face {
  font-family: "Barlow Condensed";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: local("Barlow Condensed SemiBold"), url("fonts/barlow-condensed-600.woff2") format("woff2");
}
@font-face {
  font-family: "Barlow Condensed";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: local("Barlow Condensed Bold"), url("fonts/barlow-condensed-700.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: local("IBM Plex Mono"), url("fonts/ibm-plex-mono-400.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: local("IBM Plex Mono Medium"), url("fonts/ibm-plex-mono-500.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: local("IBM Plex Mono SemiBold"), url("fonts/ibm-plex-mono-600.woff2") format("woff2");
}

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */

:root {
  /* Surfaces */
  --paper: #EFEDE6;          /* page ground: warm grey, not white */
  --card: #FFFFFF;           /* panels, inputs, selected chips */
  --head: #14181B;           /* solid header band */

  /* Ink */
  --ink: #14181B;            /* primary text, panel top rules, active states */
  --steel: #5C666D;          /* secondary text, labels, part numbers, hints */
  --line: #D8D4C8;           /* every border and divider, warm-tinted */

  /* Accent == ink. Deliberately the same value; see the header note. */
  --accent: #14181B;
  --accent-tint: #E6E9EC;    /* selected row / selected option background */
  --accent-line: #B9BFC6;    /* tag borders inside a selected chip */

  /* Status — the only chromatic values in the system */
  --ok: #1F6E43;             /* N/C prices, won, passed checks */
  --warn: #A6620B;           /* flags, on hold, pending */
  --danger: #8E1F1F;         /* form errors only; never a surface */

  /* Nameplate gradient stops */
  --plate-hi: #E9EBEE;
  --plate-lo: #B9BFC6;

  /* On-ink text, for the header band and filled buttons */
  --on-ink: #FFFFFF;
  --on-ink-dim: #B9BFC6;
  --on-ink-faint: #9AA1A8;

  /* Type. Each family names its fallback so the system reads correctly
     before the self-hosted woff2 files land (plan §7, decision 4). Arial
     Narrow is the condensed face every Windows station already has. */
  --disp: "Barlow Condensed", "Arial Narrow", "Liberation Sans Narrow", sans-serif;
  --body: "Barlow", system-ui, -apple-system, "Segoe UI", Arial, sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;

  /* Spacing rhythm — used rather than ad-hoc pixel values */
  --gap-1: 6px;
  --gap-2: 10px;
  --gap-3: 14px;
  --gap-4: 18px;
  --gap-5: 24px;
  --gap-6: 32px;

  /* Semantic layering. Never write a bare z-index in a page file. */
  --z-sticky: 100;
  --z-drawer: 200;
  --z-backdrop: 300;
  --z-modal: 400;
  --z-toast: 500;

  /* Motion: state changes only, 120–180ms, ease-out. */
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --t-fast: 120ms;
  --t-base: 180ms;
}

/* --------------------------------------------------------------------------
   2. Page frame
   -------------------------------------------------------------------------- */

.theme,
.theme body {
  background: var(--paper);
  color: var(--ink);
}

body.theme {
  margin: 0;
  font-family: var(--body);
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

/* The paper ruling: subtle enough to miss, present enough to feel like stock.
   Fixed and non-interactive, so it never intercepts a tap. */
body.theme::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  opacity: .35;
  background-image: repeating-linear-gradient(0deg, transparent 0 23px, rgba(20, 24, 27, .025) 23px 24px);
}

/* Everything the page draws sits above the ruling. */
body.theme > * { position: relative; z-index: 1; }

.wrap {
  max-width: 1220px;
  margin: 0 auto;
  padding: 0 20px 80px;
}

/* Header — solid ink band. This is the anchor that replaces the accent colour. */
.app-head {
  background: var(--head);
  color: var(--on-ink);
  padding: 24px 24px 18px;
  display: flex;
  flex-wrap: wrap;
  gap: var(--gap-3);
  align-items: flex-end;
  justify-content: space-between;
}

.app-head .brand h1 {
  font-family: var(--disp);
  font-weight: 700;
  font-size: clamp(28px, 4.5vw, 44px);
  text-transform: uppercase;
  letter-spacing: .02em;
  line-height: 1;
  margin: 0;
  text-wrap: balance;
}
.app-head .brand h1 em { font-style: normal; color: var(--on-ink-faint); }
.app-head .brand p {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--on-ink-dim);
  margin: 6px 0 0;
  letter-spacing: .03em;
}

/* Mono meta block, right side of the band: account, role, quote id. */
.head-meta {
  font-family: var(--mono);
  font-size: 12px;
  text-align: right;
  color: var(--on-ink-dim);
  line-height: 1.7;
  display: flex;
  flex-wrap: wrap;
  gap: var(--gap-2);
  align-items: center;
  justify-content: flex-end;
}
.head-meta b { color: var(--on-ink); font-weight: 600; }

/* App navigation: one bordered strip of display-face links under the band. */
.nav {
  display: flex;
  border: 1px solid var(--line);
  background: var(--card);
  margin: 0 0 var(--gap-4);
  width: max-content;
  max-width: 100%;
  overflow-x: auto;
}
.nav a {
  font-family: var(--disp);
  font-size: 15px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .05em;
  padding: 11px 16px;
  color: var(--steel);
  text-decoration: none;
  border-right: 1px solid var(--line);
  white-space: nowrap;
}
.nav a:last-child { border-right: 0; }
.nav a:hover { color: var(--ink); }
.nav a.on {
  color: var(--ink);
  box-shadow: inset 0 -3px 0 var(--accent);
}

/* Brand bar: the two supplied marks on white, above the ink band. Kept on white
   because that is the ground both lockups were drawn for -- the John Deere Marine
   Power mark is black type with a yellow deer and would need a reversed variant to
   sit on ink, which is not ours to make. */
.brandbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap-4);
  background: var(--card);
  border-bottom: 1px solid var(--line);
  padding: 14px 24px;
}
.brandbar-mark { display: block; height: 56px; width: auto; }
.brandbar-mark-partner { height: 64px; }
@media (max-width: 640px) {
  .brandbar { padding: 10px 16px; }
  .brandbar-mark { height: 40px; }
  .brandbar-mark-partner { height: 46px; }
}

/* Standalone hint paragraph. .step-head .hint is the right-aligned variant inside a
   panel header; this is the same voice used as intro copy under a heading. */
.hint {
  font-size: 13px;
  color: var(--steel);
  margin: 0 0 var(--gap-4);
  max-width: 65ch;
  text-wrap: pretty;
}

/* The language toggle is rendered by shared/shared-language-toggle.js into any
   .language-toggle container, on every page. Styled here so it belongs to the system
   rather than to whichever page happens to mount it. */
.theme .language-toggle { display: inline-flex; align-items: center; gap: var(--gap-1); }
.theme .language-toggle-label {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--steel);
}
.theme .language-toggle-select {
  font-family: var(--mono);
  font-size: 11.5px;
  padding: 7px 8px;
  border: 1px solid var(--line);
  border-radius: 0;
  background: var(--card);
  color: var(--ink);
  width: auto;
  min-height: 0;
}

/* --------------------------------------------------------------------------
   3. Panels
   -------------------------------------------------------------------------- */

/* The 3px top rule is the primary structural device of this system. */
.step {
  background: var(--card);
  border: 1px solid var(--line);
  border-top: 3px solid var(--ink);
  padding: 18px 20px 20px;
  margin-bottom: var(--gap-4);
}

.step-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--gap-3);
  margin-bottom: var(--gap-3);
}
.step-head .no {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 600;
  color: var(--card);
  background: var(--ink);
  padding: 3px 7px;
  letter-spacing: .05em;
}
.step-head h2 {
  font-family: var(--disp);
  font-size: 22px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .03em;
  margin: 0;
}
.step-head .hint {
  font-size: 13px;
  color: var(--steel);
  margin-left: auto;
  text-align: right;
}

/* Plain card, for surfaces where the nameplate would be too much
   (dashboard, admin) — plan §7, decision 3. */
.sel-panel {
  background: var(--card);
  border: 1px solid var(--line);
  border-top: 3px solid var(--ink);
  padding: 16px 18px;
}

/* Section heading inside the page body, under the band. Smaller than a panel's h2
   because it labels a group of panels rather than sitting on one. */
.page-title {
  font-family: var(--disp);
  font-size: 20px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
  margin: 0 0 var(--gap-3);
}

.notes {
  font-family: var(--mono);
  font-size: 11.5px;
  line-height: 1.9;
  color: var(--steel);
  border-top: 1px solid var(--line);
  padding-top: var(--gap-3);
  margin-top: var(--gap-5);
}

/* Feature list inside a panel: what a workflow covers, or what an option
   includes. Dashed dividers rather than bullets -- the same rule the option
   groups use, so a list of features and a list of parts read as one family. */
.feat {
  list-style: none;
  margin: 0 0 var(--gap-4);
  padding: 0;
  border-top: 1px dashed var(--line);
}
.feat li {
  font-size: 13.5px;
  padding: 7px 0;
  border-bottom: 1px dashed var(--line);
  color: var(--ink);
}

/* Subordinate row: something the tool names but cannot open yet. Deliberately
   not a panel -- a planned workflow must not read as an available one. */
.row-muted {
  display: flex;
  align-items: center;
  gap: var(--gap-3);
  border: 1px solid var(--line);
  background: var(--card);
  padding: 10px 14px;
}
.row-muted .rm-name {
  font-family: var(--disp);
  font-size: 15px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .03em;
  color: var(--steel);
}
.row-muted .rm-desc {
  font-size: 12.5px;
  color: var(--steel);
  margin: 2px 0 0;
}
.row-muted .rm-end { margin-left: auto; flex: none; }

/* --------------------------------------------------------------------------
   4. Forms and controls
   -------------------------------------------------------------------------- */

/* This one rule sets the tone of every form in the tool. Scoped to .field so
   it cannot reach a label that is itself a control (the mockup hit exactly
   that collision with a toggle switch). */
.field { margin-bottom: var(--gap-3); }
.field > label {
  display: block;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--steel);
  margin-bottom: var(--gap-1);
}

/* Section 9.3 excludes checkboxes and radios from the field rule because they
   are glyphs the browser draws, not boxes -- but the browser draws them in ITS
   accent colour, which is a saturated blue. On an achromatic system that is the
   only chromatic thing on the screen, and it is on every page with a checkbox,
   not just this one. `accent-color` is the single property that reaches inside
   a native control, so it is the whole fix. */
.theme input[type="checkbox"],
.theme input[type="radio"] { accent-color: var(--ink); }

.tin,
.tarea,
.vsel {
  font-family: var(--body);
  font-size: 14px;
  padding: 9px 10px;
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink);
  width: 100%;
  border-radius: 0;
}
.tin.mono, .tarea.mono { font-family: var(--mono); font-size: 15px; }
.tarea { min-height: 96px; resize: vertical; }
.vsel { min-width: 170px; }

/* Placeholders carry the same contrast duty as body text: steel, not a
   default light grey (PRODUCT.md asks for the higher end of AA). */
.tin::placeholder, .tarea::placeholder { color: var(--steel); opacity: 1; }

.tin:hover, .tarea:hover, .vsel:hover { border-color: var(--steel); }
.tin:disabled, .tarea:disabled, .vsel:disabled {
  background: var(--paper);
  color: var(--steel);
  cursor: not-allowed;
}

/* Segmented control: any mutually exclusive filter with six or fewer options. */
.seg { display: inline-flex; border: 1px solid var(--line); }
.seg button {
  width: auto;
  border-radius: 0;
  font-family: var(--body);
  font-size: 13.5px;
  font-weight: 500;
  padding: 9px 14px;
  background: var(--paper);
  border: 0;
  border-right: 1px solid var(--line);
  cursor: pointer;
  color: var(--ink);
}
.seg button:last-child { border-right: 0; }
.seg button:hover { background: var(--card); }
.seg button.active { background: var(--ink); color: var(--on-ink); }

/* One button style, two weights. Every primary action in the tool is
   .mini-btn.primary; there is no separate large-button style. */
.mini-btn {
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: .04em;
  text-transform: uppercase;
  border: 1px solid var(--line);
  background: var(--paper);
  color: var(--ink);
  padding: 9px 14px;
  cursor: pointer;
  /* Both declared against assets/app.css, which styles bare `button`/`select`/`input`
     with width:100% and border-radius:10px. theme.css loads after it, but a class only
     wins for the properties it actually declares -- exactly the collision the restyle
     plan warns about in section 3.3. Anything here that a bare element rule also sets
     has to be restated, or a button silently stretches to fill its row. */
  width: auto;
  border-radius: 0;
  font-weight: 500;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--gap-1);
  transition: border-color var(--t-fast) var(--ease), background-color var(--t-fast) var(--ease);
}
/* Restore the [hidden] attribute against the display:inline-flex above -- the same trap
   assets/app.css documents at .field small[hidden], and the same fix: scoped, not a global
   [hidden]{...!important}.

   This one was live. index.html sets `manageUsersLink.hidden = !user.is_admin`, but the UA
   sheet's [hidden]{display:none} is a bare attribute selector that any class selector
   outranks, so every non-admin -- including every dealer -- was shown a working "Manage
   Users" link in the account strip. api/admin/users.php's require_admin() refused them, so
   this was a visible control that could not do anything rather than a data leak, but it
   invited every dealer to click it and be told no. Found 2026-09-07 while gating the
   Reports card next to it. .step, which the Reports card uses, declares no display and was
   never affected. */
.mini-btn[hidden] { display: none; }

.mini-btn:hover { border-color: var(--steel); background: var(--card); }
.mini-btn:active { transform: translateY(1px); }
.mini-btn.primary { background: var(--ink); color: var(--on-ink); border-color: var(--ink); }
.mini-btn.primary:hover { background: #23292E; border-color: #23292E; }
.mini-btn[disabled],
.mini-btn[aria-disabled="true"] {
  opacity: .45;
  cursor: not-allowed;
  pointer-events: none;
}
.mini-btn.block { width: 100%; }

.search-in {
  display: flex;
  align-items: center;
  gap: var(--gap-2);
  border: 1px solid var(--line);
  background: var(--card);
  padding: 0 10px;
  flex: 1;
  min-width: 220px;
  max-width: 420px;
}
.search-in input {
  border: 0;
  border-radius: 0;
  outline: 0;
  font-family: var(--body);
  font-size: 14px;
  padding: 9px 0;
  width: 100%;
  background: none;
  color: var(--ink);
}
.search-in svg { width: 14px; height: 14px; stroke: var(--steel); flex: none; }

/* 34×18 square toggle: used where a checkbox reads too small (permissions). */
.tog {
  position: relative;
  display: inline-block;
  width: 34px;
  height: 18px;
  flex: none;
}
.tog input {
  position: absolute;
  inset: 0;
  opacity: 0;
  margin: 0;
  cursor: pointer;
}
.tog i {
  display: block;
  width: 100%;
  height: 100%;
  background: var(--paper);
  border: 1px solid var(--line);
  transition: background-color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
.tog i::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 14px;
  height: 14px;
  background: var(--steel);
  transition: transform var(--t-fast) var(--ease), background-color var(--t-fast) var(--ease);
}
.tog input:checked + i { background: var(--ink); border-color: var(--ink); }
.tog input:checked + i::after { transform: translateX(16px); background: var(--card); }
.tog input:focus-visible + i { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Field-ready: on touch, every control clears 44px (PRODUCT.md). */
@media (pointer: coarse) {
  .mini-btn, .seg button, .tin, .vsel, .nav a { min-height: 44px; }
  .search-in { min-height: 44px; }
}

/* --------------------------------------------------------------------------
   5. Focus — visible on every interactive element, no exceptions
   -------------------------------------------------------------------------- */

.theme :focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.tin:focus-visible, .tarea:focus-visible, .vsel:focus-visible, .search-in:focus-within {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
}
/* On the ink band, an ink outline would be invisible. */
.app-head :focus-visible { outline-color: var(--on-ink); }

/* --------------------------------------------------------------------------
   6. Status vocabulary
   -------------------------------------------------------------------------- */

.pill {
  display: inline-block;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .07em;
  text-transform: uppercase;
  padding: 3px 7px;
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--steel);
}
.pill.sent { background: var(--ink); border-color: var(--ink); color: var(--on-ink); }
.pill.won { background: var(--ok); border-color: var(--ok); color: var(--on-ink); }
.pill.rev { background: var(--warn); border-color: var(--warn); color: var(--on-ink); }

.badge {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: .07em;
  text-transform: uppercase;
  padding: 2px 6px;
  flex: none;
}
.badge.req { color: var(--card); background: var(--accent); }
.badge.optn { color: var(--steel); border: 1px solid var(--line); background: var(--card); }
.badge.one { color: var(--ink); border: 1px solid var(--accent-line); background: var(--accent-tint); }

.msg {
  font-family: var(--body);
  font-size: 13px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink);
}
.msg.err { border-color: var(--danger); color: var(--danger); }
/* page-quote-dashboard.js toggles `error-state` on its message element rather than a
   class of this system's own; treated as the error variant so the two agree. */
.msg.error-state { border-color: var(--danger); color: var(--danger); }
.msg.ok { border-color: var(--ok); color: var(--ok); }

/* --------------------------------------------------------------------------
   7. Data display
   -------------------------------------------------------------------------- */

table.q {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
}
table.q thead th {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--steel);
  text-align: left;
  font-weight: 500;
  border-bottom: 2px solid var(--ink);
  padding: 8px 10px;
}
table.q tbody td {
  padding: 11px 10px;
  border-bottom: 1px dashed var(--line);
  vertical-align: top;
}
table.q tbody tr:hover td { background: var(--paper); }
table.q .id { font-family: var(--mono); font-weight: 600; }
table.q .amt { font-family: var(--mono); text-align: right; white-space: nowrap; }
table.q .mdl {
  font-family: var(--disp);
  font-size: 16px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .02em;
}
table.q .sub { color: var(--steel); font-size: 12px; }

.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--gap-2);
}
.stat {
  background: var(--card);
  border: 1px solid var(--line);
  border-top: 3px solid var(--ink);
  padding: 14px 16px 16px;
}
.stat .k {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--steel);
}
.stat .v {
  font-family: var(--disp);
  font-size: 38px;
  font-weight: 600;
  line-height: 1.05;
  margin-top: 4px;
}
.stat .v small { font-family: var(--mono); font-size: 14px; font-weight: 400; color: var(--steel); }
.stat .foot { font-family: var(--mono); font-size: 11px; color: var(--steel); margin-top: 6px; }
.stat.acc .v { font-weight: 700; }

.spec { width: 100%; border-collapse: collapse; font-size: 14px; }
.spec th {
  width: 38%;
  text-align: left;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--steel);
  font-weight: 400;
  padding: 7px 8px;
  border-bottom: 1px dashed var(--line);
  vertical-align: top;
}
.spec td { padding: 7px 8px; border-bottom: 1px dashed var(--line); }

/* --------------------------------------------------------------------------
   8. Layout helpers
   -------------------------------------------------------------------------- */

.cfg-grid {
  display: grid;
  grid-template-columns: 1fr 400px;
  gap: var(--gap-4);
  align-items: start;
}
@media (max-width: 940px) { .cfg-grid { grid-template-columns: 1fr; } }

.fgrid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--gap-3) var(--gap-4);
}
.fgrid .span2 { grid-column: span 2; }
@media (max-width: 760px) {
  .fgrid { grid-template-columns: repeat(2, 1fr); }
  .fgrid .span2 { grid-column: span 2; }
}
@media (max-width: 520px) {
  .fgrid { grid-template-columns: 1fr; }
  .fgrid .span2 { grid-column: span 1; }
}

/* --------------------------------------------------------------------------
   9. Quote workflow frame
   --------------------------------------------------------------------------
   The six quote pages (engine, transmission, controls, engine-only, generator
   package, misc) share one shell and one set of rendered components. Two of
   those components -- the module nav and the bundle tabs -- are drawn by
   SHARED renderers (`app.renderModuleNav` in assets/quote-state.js,
   `bundles.renderTabs` in assets/propulsion-bundles.js), so their class names
   belong to all six pages at once, not to whichever page a session is
   restyling.

   Everything in this section therefore styles the class names those pages and
   renderers ALREADY emit, and renames nothing. A rename would restyle five
   pages that still link only app.css. The pages convert one per session by
   linking this stylesheet and adding `.theme` to <body>; until a page does
   that, none of these rules can reach it, because app.css is the only sheet it
   loads.

   Restyle plan section 3.2 step 3: the option pattern is settled here, in the
   inventory-variant rows, and controls/misc reuse it unchanged.
   -------------------------------------------------------------------------- */

/* 9.1 Shell ---------------------------------------------------------------- */

.theme .quote-shell {
  display: grid;
  grid-template-columns: 236px minmax(0, 1fr);
  gap: var(--gap-4);
  align-items: start;
  margin-top: var(--gap-4);
}
.theme .quote-main-panel { min-width: 0; }

/* The workflow list is the one piece of persistent orientation a rep has while
   building a quote, so it is a bordered instrument panel rather than a tinted
   sidebar: square, sticky, condensed heading. */
.theme .quote-workflow-nav {
  position: sticky;
  top: var(--gap-3);
  background: var(--card);
  border: 1px solid var(--line);
  border-top: 3px solid var(--ink);
  border-radius: 0;
  /* app.css drops rgba(15,23,42,.05) on this panel, and 9.2 restates
     `box-shadow: none` on `.card` for exactly that reason; this rule did not,
     so the one panel that is on screen the whole time a quote is being built
     has carried a soft blue-tinted shadow since session 3. */
  box-shadow: none;
  padding: var(--gap-3) 0 var(--gap-2);
}
.theme .quote-workflow-nav h3 {
  font-family: var(--disp);
  font-size: 15px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--steel);
  margin: 0 var(--gap-3) var(--gap-2);
}
.theme .site-pages-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: block;
  border-top: 1px solid var(--line);
}
.theme .site-pages-list li { border-bottom: 1px dashed var(--line); }
.theme .site-pages-list li:last-child { border-bottom: 0; }

/* A 3px left rule marks the current page, mirroring the 3px top rule that marks
   a panel. Colour alone is not carrying it: the face changes weight too. */
.theme .site-page-link {
  display: block;
  padding: 9px var(--gap-3);
  border: 0;
  border-left: 3px solid transparent;
  border-radius: 0;
  text-decoration: none;
  font-size: 13.5px;
  line-height: 1.35;
  color: var(--steel);
  transition: background-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.theme .site-page-link:hover {
  background: var(--paper);
  border-color: transparent;
  color: var(--ink);
}
.theme .site-page-link.active {
  background: var(--accent-tint);
  border-left-color: var(--ink);
  color: var(--ink);
  font-weight: 600;
}
.theme .site-nav-divider {
  height: 1px;
  background: var(--line);
  margin: var(--gap-2) var(--gap-3);
}

@media (max-width: 940px) {
  .theme .quote-shell { grid-template-columns: 1fr; }
  .theme .quote-workflow-nav { position: static; }
}

/* 9.2 Card ----------------------------------------------------------------- */

/* `.card` + `.card-h` + `.card-b` is the workflow pages own panel markup. It
   gets the `.step` treatment -- 3px top rule, nameplate header -- so a quote
   page and a restyled page read as one system without either side renaming.
   `border-radius` and `overflow` are restated because app.css sets both. */
.theme .card {
  background: var(--card);
  border: 1px solid var(--line);
  border-top: 3px solid var(--ink);
  border-radius: 0;
  overflow: visible;
  box-shadow: none;
  margin-bottom: var(--gap-4);
}
.theme .card-h {
  font-family: var(--disp);
  font-size: 20px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .03em;
  color: var(--ink);
  background: transparent;
  padding: var(--gap-3) var(--gap-4) var(--gap-2);
  border-bottom: 1px solid var(--line);
}
.theme .card-b { padding: var(--gap-4); }

/* 9.3 Fields, hints, actions ----------------------------------------------- */

/* app.css styles `.field small` and `.hint` together at the same specificity as
   this rule; theme.css loads after it, so this wins on order for the pages that
   link it and cannot reach the pages that do not. */
.theme .field small,
.theme .hint {
  display: block;
  font-size: 12px;
  line-height: 1.45;
  color: var(--steel);
  margin-top: var(--gap-1);
}
.theme .field small[hidden], .theme .hint[hidden] { display: none; }
.theme .field-error {
  display: block;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .04em;
  color: var(--danger);
  margin-top: var(--gap-1);
}

/* The page selects are bare tags in the markup, so they are matched here rather
   than given `.vsel` -- same metrics, no markup churn.

   Written as "every input EXCEPT the ones that are not boxes" rather than as a
   list of types. The list form was wrong the moment quote intake arrived: it
   named text, number, select and textarea, and intake's date field kept
   app.css's 8px radius and blue-grey border because `date` was not on it.
   Checkboxes and radios are excluded because they are glyphs, not fields, and
   file inputs because the control is the button the browser draws. */
.theme .field > select,
.theme .field > input:not([type="checkbox"]):not([type="radio"]):not([type="file"]),
.theme .field > textarea {
  font-family: var(--body);
  font-size: 14px;
  padding: 9px 10px;
  border: 1px solid var(--line);
  border-radius: 0;
  background: var(--card);
  color: var(--ink);
  width: 100%;
  box-shadow: none;
}
/* admin/dealers.html's own <style> block set this and the block is now gone.
   Left off, a textarea resizes in BOTH axes and a dealer dragging one wider
   pushes the grid it sits in. */
.theme .field > textarea { resize: vertical; }

.theme .field > select:hover:enabled,
.theme .field > input:hover:enabled,
.theme .field > textarea:hover:enabled { border-color: var(--steel); }
.theme .field > select:disabled,
.theme .field > input:disabled,
.theme .field > textarea:disabled {
  background: var(--paper);
  color: var(--steel);
  cursor: not-allowed;
}

.theme .actions {
  display: flex;
  justify-content: space-between;
  gap: var(--gap-2);
  flex-wrap: wrap;
  margin-top: var(--gap-5);
  padding-top: var(--gap-3);
  border-top: 1px solid var(--line);
}
.theme .actions .right { display: flex; gap: var(--gap-2); flex-wrap: wrap; }

/* 9.4 Progress ------------------------------------------------------------- */

/* Steps are drawn by the shared progress renderer for all six pages. Mono,
   square, ink-filled when current: the `.pill` vocabulary at control size. */
.theme .progress {
  display: flex;
  flex-wrap: wrap;
  gap: var(--gap-1);
  margin-bottom: var(--gap-3);
}
.theme .progress-step {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .06em;
  text-transform: uppercase;
  padding: 6px 9px;
  border: 1px solid var(--line);
  border-radius: 0;
  background: var(--card);
  color: var(--steel);
}
.theme .progress-step.done {
  background: var(--accent-tint);
  border-color: var(--accent-line);
  color: var(--ink);
}
.theme .progress-step.active {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--on-ink);
}

/* 9.5 Banner --------------------------------------------------------------- */

/* Same metrics as `.msg`; the two names exist because the workflow pages had a
   banner before this system had a message. Status colour is the border only --
   a tinted surface would be the second chromatic ground in a monochrome tool. */
.theme .banner {
  font-family: var(--body);
  font-size: 13px;
  line-height: 1.45;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 0;
  background: var(--card);
  box-shadow: none;
  color: var(--ink);
  margin-bottom: var(--gap-3);
}
.theme .banner-ok { border-color: var(--ok); color: var(--ok); }
.theme .banner-warn { border-color: var(--warn); color: var(--warn); }

/* 9.6 Summary readout ------------------------------------------------------ */

/* What the rep has locked in. It sits inside a panel, so it takes a left rule
   rather than a second top rule, and the empty state is dashed: nothing is
   locked yet, and the border should say so before the text does. */
.theme .summary-card {
  background: var(--paper);
  border: 1px solid var(--line);
  border-left: 3px solid var(--ink);
  border-radius: 0;
  /* Same omission, same app.css rule: it names `.summary-card` alongside
     `.card` and `.section`. Three rules in this file restate the border and the
     radius that rule sets; only `.card` restated the shadow. Every quote page
     with a summary carried it. */
  box-shadow: none;
  padding: var(--gap-3);
  margin-top: var(--gap-3);
}
.theme .summary-card.empty {
  background: transparent;
  border: 1px dashed var(--line);
  color: var(--steel);
}
.theme .summary-card h3 {
  font-family: var(--disp);
  font-size: 16px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
  margin: 0 0 var(--gap-2);
}
.theme .summary-card.empty p { font-size: 13px; color: var(--steel); margin: 0; }
.theme .summary-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--gap-2);
}
@media (max-width: 620px) { .theme .summary-grid { grid-template-columns: 1fr; } }
.theme .kv {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 0;
  padding: var(--gap-2);
}
.theme .kv .k {
  display: block;
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--steel);
  margin-bottom: 3px;
}
.theme .kv .v { font-size: 13.5px; line-height: 1.4; color: var(--ink); word-break: break-word; }
.theme .kv .v.muted { color: var(--steel); }
.theme .kv.span-2 { grid-column: span 2; }
@media (max-width: 620px) { .theme .kv.span-2 { grid-column: span 1; } }

/* A price is an identifier a rep reads aloud to a customer, so it is mono. The
   `!important`s answer app.css own `!important`s on the same four properties --
   specificity cannot win against those, only order plus equal force. */
.theme .price-input-wrap { display: inline-flex; align-items: center; gap: var(--gap-1); }
.theme .price-input-wrap .price-prefix {
  font-family: var(--mono);
  font-size: 13px;
  color: var(--steel);
}
.theme .price-input {
  font-family: var(--mono);
  font-weight: 500;
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink);
  max-width: 160px;
  padding: 7px 9px !important;
  font-size: 13.5px !important;
  border-radius: 0 !important;
  width: 100% !important;
}
.theme .price-edit-note {
  display: block;
  font-size: 11.5px;
  color: var(--steel);
  margin-top: var(--gap-1);
}

/* 9.7 Option rows ---------------------------------------------------------- */

/* THE OPTION PATTERN (plan section 3.2, step 3). One bordered row per choice,
   the whole row a hit target, selection shown by an ink left rule AND the tint
   -- never by colour alone. `:has()` is what lets this markup respond to its
   own radio without a class from JS; where it is unsupported the row still
   reads correctly, it simply does not tint. Controls and misc reuse this. */
.theme .radio-stack { display: grid; gap: var(--gap-1); }
.theme .radio-row {
  display: flex;
  gap: var(--gap-2);
  align-items: flex-start;
  border: 1px solid var(--line);
  border-left: 3px solid transparent;
  background: var(--card);
  padding: 10px var(--gap-3);
  cursor: pointer;
  transition: border-color var(--t-fast) var(--ease), background-color var(--t-fast) var(--ease);
}
.theme .radio-row:hover { border-color: var(--steel); border-left-color: var(--steel); }
.theme .radio-row:has(input:checked) {
  background: var(--accent-tint);
  border-color: var(--accent-line);
  border-left-color: var(--ink);
}
.theme .radio-row:has(input:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.theme .radio-row input[type="radio"] { margin-top: 3px; flex: none; }

/* The heading above a group of option rows: a rule and a condensed label, never
   a nested card. Same device app.css already used, in the system face. */
.theme .inventory-variants-block {
  margin-top: var(--gap-4);
  border-top: 1px solid var(--line);
  padding-top: var(--gap-3);
}
.theme .inventory-variants-block h4 {
  font-family: var(--disp);
  font-size: 15px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--steel);
  margin: 0 0 var(--gap-2);
}

/* app.css uses --muted for secondary text throughout; this system uses --steel,
   which is the same job at the contrast PRODUCT.md asks for. */
.theme .muted { color: var(--steel); }

/* 9.8 Module nav and bundle tabs ------------------------------------------- */

/* Both are drawn by the shared renderers named at the top of this section, so
   they carry one vocabulary: mono, square, ink when active. */
.theme .module-nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--gap-1);
  margin-top: var(--gap-4);
  padding-top: var(--gap-3);
  border-top: 1px dashed var(--line);
}
.theme .module-nav-link,
.theme .bundle-tab,
.theme .bundle-tab-add {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .05em;
  text-transform: uppercase;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: auto;
  min-width: 0;
  padding: 9px 13px;
  border: 1px solid var(--line);
  border-radius: 0;
  background: var(--card);
  color: var(--steel);
  text-decoration: none;
  cursor: pointer;
  transition: border-color var(--t-fast) var(--ease), background-color var(--t-fast) var(--ease);
}
.theme .module-nav-link:hover,
.theme .bundle-tab:hover,
.theme .bundle-tab-add:hover { border-color: var(--steel); color: var(--ink); }
.theme .module-nav-link.active,
.theme .bundle-tab.active {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--on-ink);
}
.theme .bundle-tab-add { border-style: dashed; }

.theme .bundle-tabs-wrap { margin-bottom: var(--gap-3); }
.theme .bundle-tab-bar { display: flex; flex-wrap: wrap; gap: var(--gap-1); align-items: center; }
.theme .bundle-actions-row {
  display: flex;
  align-items: center;
  gap: var(--gap-2);
  flex-wrap: wrap;
  margin-top: var(--gap-2);
}

/* Field-ready, same rule as section 4: on touch every one of these clears 44px. */
@media (pointer: coarse) {
  .theme .site-page-link,
  .theme .module-nav-link,
  .theme .bundle-tab,
  .theme .bundle-tab-add,
  .theme .radio-row { min-height: 44px; }
  .theme .module-nav-link,
  .theme .bundle-tab,
  .theme .bundle-tab-add { padding: 12px 14px; }
}

/* 9.9 Collisions with app.css ---------------------------------------------- */

/* Both of these were found by sweeping the rendered page for a computed colour
   with chroma in it, not by reading the stylesheet -- neither is visible in the
   markup this session edited.

   app.css gives the workflow-nav heading a 2px gold underline: the accent the old
   system used for orientation. Ink does that job here -- the 3px top rule on the
   panel, the left rule on the active row -- so the underline goes rather than
   changing colour. Removed in a rule of its own because 9.1 is about type and
   this is about a collision. */
.theme .quote-workflow-nav h3 { border-bottom: 0; }

/* The power-curve PDF link in the engine summary is rendered by page-engine.js and
   carries no class, so it fell back to the browser default blue -- the loudest
   colour anywhere on a monochrome page. Underline carries the affordance instead.
   Buttons and nav chips are excluded: both already style themselves. */
.theme .card a:not(.mini-btn):not(.module-nav-link) {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}
.theme .card a:not(.mini-btn):not(.module-nav-link):hover { color: var(--steel); }


/* Two more of the same kind, and the reason they needed hunting rather than
   reading: app.css paints its gold on HOVER and FOCUS states, which no static
   sweep of the rendered page can see.

   The focus ring. app.css rings text inputs in gold at 35% alpha; section 5 of
   this file rings everything in ink, and wins for buttons, links and bare
   selects. It does NOT win for `input[type="number"]`, because a type-attribute
   selector plus :focus outscores `.theme :focus-visible` -- so the one control
   on this page that took a typed value, the net price, kept a gold ring at a
   contrast no keyboard user should have to find. Named explicitly here. */
.theme .price-input:focus-visible,
.theme .field > input:focus-visible,
.theme .field > select:focus-visible,
.theme .field > textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* The hover shadow. app.css gives .module-nav-link a gold inset underline on
   hover. The rule in 9.8 restates its border and colour but declares no shadow,
   and a property a rule does not declare is a property it does not win -- the
   same collision the plan warns about in section 3.3. */
.theme .module-nav-link:hover,
.theme .bundle-tab:hover,
.theme .bundle-tab-add:hover { box-shadow: none; }

/* --------------------------------------------------------------------------
   10. Marine controls
   --------------------------------------------------------------------------

   Scoped to `#configSvg`, and that scope is load-bearing rather than tidy.

   The six layout diagrams in `assets/*.svg` carry their colours in a `<style>`
   block INSIDE each SVG, and the same six files are also the printed
   controls-diagram supplement in `forms/summary.html`. One palette, two media.
   Editing the sources -- or writing an unscoped `.controls-diagram` rule here --
   would repaint the printed supplement too, which plan section 2.3 ("Printed
   quotes: nothing") and section 3.4 both forbid. `#configSvg` exists only on
   `forms/controls.html`, so everything below is screen-only by construction and
   the print path is byte-unchanged.

   Specificity is deliberate for the same reason: the SVG's own rules are
   `.controls-diagram .node` (0,2,0) and, being injected into the document AFTER
   this stylesheet, they win every tie. `.theme #configSvg` puts an id in front
   of ours, so these rules win without `!important` -- except against app.css's
   one `!important`, noted in 10.4.
   -------------------------------------------------------------------------- */

/* 10.1 State palette ------------------------------------------------------- */

/* OWNER DECISION, Dave 2026-09-01, and a deliberate departure from plan section
   3.3's "no red, no chromatic accent" -- recorded here rather than quietly,
   because section 3.4 makes the absence of red an acceptance check.

   The brief: a component still to be chosen reads RED, a chosen one reads EI
   yellow, so a user can tell at a glance both that they must select something
   and that the configurations are selectable at all. The tokens are defined on
   this scope, not in section 1, so no other page can pick them up and
   `--danger` keeps its "form errors only" meaning everywhere else. */
.theme #configSvg {
  /* Not --danger. That token means "this input is wrong"; this one means "this
     is yours to fill in", and the two must not be the same red if they ever
     share a screen. */
  --cd-todo: #B91C1C;

  /* EI yellow as the printed packet uses it (ei_proposal.js's --yellow), not
     app.css's older screen gold #F4C02A. It is a FILL here, with an ink outline
     carrying the edge: #EFD64B on white is about 1.4:1, so a hairline of it
     would be a colour only someone looking for it would find. Lines get the
     darker sibling -- same hue, at a lightness a 3px stroke can hold. */
  --cd-done: #EFD64B;
  --cd-done-line: #8A6D0B;
}

/* 10.2 The diagram container (plan section 2.2, `.lp`) --------------------- */

.theme #configSvg.layout-preview {
  border: 1px solid var(--line);
  border-radius: 0;
  background: var(--card);
  padding: var(--gap-4);
}
.theme .layout-preview-wrap { gap: var(--gap-2); }

/* 10.3 Diagram furniture --------------------------------------------------- */

/* Everything that is not a state goes monochrome: zones, hardware, fixed boxes,
   labels. Part numbers take the mono face -- they are the one thing on this
   diagram a person reads character by character. */
.theme #configSvg .controls-diagram { font-family: var(--body); }
.theme #configSvg .zone { stroke: var(--line); stroke-width: 1.5; }
.theme #configSvg .zone-label {
  fill: var(--steel);
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: .08em;
}
.theme #configSvg .hw { fill: var(--paper); stroke: var(--line); }
.theme #configSvg .hw.has-photo { fill: var(--card); }
.theme #configSvg .hw-label { fill: var(--ink); font-family: var(--disp); font-size: 16px; }
.theme #configSvg .hw-sub,
.theme #configSvg .node-sub { fill: var(--steel); }
.theme #configSvg .node { fill: var(--card); stroke: var(--ink); stroke-width: 1.5; }
.theme #configSvg .node-label { fill: var(--ink); font-family: var(--disp); font-size: 16px; }
.theme #configSvg .run { stroke: var(--ink); }
.theme #configSvg .run-label { fill: var(--ink); }
.theme #configSvg .slot { fill: var(--steel); font-family: var(--mono); font-size: 11px; }
.theme #configSvg .slot-part { fill: var(--ink); font-family: var(--mono); font-weight: 600; }

/* The key badge and the key glyph both said gold. The glyph says it in
   presentation attributes, which any stylesheet outranks; the badge says it in
   the SVG's own rule, which is why this needs the id scope. */
.theme #configSvg .key-badge { fill: var(--ink); }
.theme #configSvg .key-badge-text { fill: var(--on-ink); font-family: var(--mono); }
.theme #configSvg .key-glyph { stroke: var(--ink); }

/* 10.4 The three states ---------------------------------------------------- */

/* Two colours, three states. Both unchosen states are red so the owner's
   two-colour read holds at a glance; REQUIRED keeps its dashed stroke, so the
   distinction that actually blocks a package -- must choose, versus may choose
   -- survives being the same colour. Colour is never the only carrier: dash,
   weight, the check mark and the hint text all still say it. */
.theme #configSvg .is-selectable .node {
  stroke: var(--cd-todo);
  stroke-width: 2;
  stroke-dasharray: none;
}
.theme #configSvg .is-selectable .run { stroke: var(--cd-todo); stroke-width: 2.5; }
.theme #configSvg .is-selectable .run-label { fill: var(--cd-todo); }

.theme #configSvg .is-required .node,
.theme #configSvg .is-required .run { stroke: var(--cd-todo); }
.theme #configSvg .is-required .run-label { fill: var(--cd-todo); }

.theme #configSvg .select-hint,
.theme #configSvg .required-flag {
  fill: var(--cd-todo);
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .02em;
}
/* The pencil marks anything clickable in every state, including chosen ones --
   it is the "you may change this" affordance, not a to-do marker, so it takes
   steel once the row is done and red while it is not. */
.theme #configSvg .edit-glyph { fill: var(--cd-todo); opacity: 1; }
.theme #configSvg .selected .edit-glyph { fill: var(--steel); }

/* Chosen: EI yellow fill inside an ink outline. Same device as the option rows
   in 9.7 -- a tint plus a heavier edge -- with the brand colour doing the
   tinting. The panel photo sits on top of the fill, so the yellow reads as a
   surround rather than washing the photo. */
.theme #configSvg .selected .node {
  stroke: var(--ink);
  stroke-width: 2.5;
  stroke-dasharray: none;
  fill: var(--cd-done);
}
.theme #configSvg .selected .run {
  stroke: var(--cd-done-line);
  stroke-width: 3;
  stroke-dasharray: none;
}
.theme #configSvg .selected .run-label { fill: var(--ink); }
.theme #configSvg .selected .check-mark { stroke: var(--cd-done-line); }
.theme #configSvg .selected .slot-part { fill: var(--ink); }

/* app.css line 1379 paints BOTH the chosen node and the chosen run gold with
   `!important`. Among important declarations specificity still decides, and
   (1,5,0) beats its (0,3,0) -- but only if this one is important too. This is
   the session's app.css collision, the same shape as the four in 9.9. */
.theme #configSvg .hot.selected .node { stroke: var(--ink) !important; }
.theme #configSvg .hot.selected .run { stroke: var(--cd-done-line) !important; }

/* The focus ring on a node goes from the old blue to ink, matching section 5. */
.theme #configSvg .hot:focus-visible .focus-ring { stroke: var(--accent); }

/* A RUN has no .focus-ring element -- only nodes do. Its focus indicator IS the
   SVG's weight bump, and this session broke it: `.selected .run` sets 3px at
   (1,3,0) and the SVG asks for 4px at (0,3,0), so a CHOSEN run went to focus
   and hover without changing at all. Restated here a class heavier so it wins
   in every state, chosen or not. Hover and focus-visible together, never hover
   alone -- these pages are used on tablets, where there is no hover. */
.theme #configSvg .hot:hover .run,
.theme #configSvg .hot:focus-visible .run { stroke-width: 4.5; }
.theme #configSvg .hot:hover .node,
.theme #configSvg .hot:focus-visible .node { stroke-width: 3.5; }

/* The three arrowheads live in `<defs>` as marker paths carrying a `fill`
   presentation attribute. No state rule reaches them: a marker is referenced
   from the run but is NOT inside the `.hot` group that carries the class, so
   `.selected .run` styles the line and leaves its head behind. Found by
   sweeping computed colour, not by reading -- the heads were still #0F6E56,
   #B45309 and #065F46 while every line around them had changed. Id is the only
   handle they have. */
.theme #configSvg marker#cd-arrow path { fill: var(--ink); }
.theme #configSvg marker#cd-arrow-required path { fill: var(--cd-todo); }
.theme #configSvg marker#cd-arrow-done path { fill: var(--cd-done-line); }

/* Steel measures 4.02:1 on the yellow fill -- fine on the page ground it was
   designed for, short of 4.5 here, and this text is 11-12px. Only what sits
   INSIDE a chosen node moves to ink; a chosen run's label and part number sit
   on white and keep steel, which is why this needs `:has()` to tell a node
   group from a run group rather than one flat `.selected` rule. */
.theme #configSvg .selected:has(.node) .node-sub,
.theme #configSvg .selected:has(.node) .slot,
.theme #configSvg .selected:has(.node) .edit-glyph { fill: var(--ink); }
.theme #configSvg .selected:has(.node) .check-mark { stroke: var(--ink); }

/* 10.5 Page furniture ------------------------------------------------------ */

/* This page's group box used to be styled here, as `.section.section-flat`,
   because controls.html was the only page in the workflow that had one.
   forms/generator-package.html has seven of the SAME box wearing only the
   `.section` half of that name, so the treatment generalised and moved to
   11.8. Nothing about this page changed: every `.section` here also carries
   `.section-flat`, and 11.8 makes the declarations 10.5 already made. */

/* The subtotal is one of the two nameplates this page is allowed (plan section
   2.2: quote totals and build sheets only). Its markup is a bare div, so the
   plate is built from type and a rule rather than the full screwed panel. */
.theme .controls-total-amount {
  font-family: var(--mono);
  font-size: 30px;
  font-weight: 600;
  color: var(--ink);
  letter-spacing: -.01em;
}

/* 10.6 Collisions with app.css --------------------------------------------- */

/* The diagram's option menu is a popover app.css gives a 12px radius and a
   blue-tinted price; both are pre-theme vocabulary. */
.theme .diagram-option-menu {
  border-radius: 0;
  border: 1px solid var(--ink);
  background: var(--card);
  box-shadow: 0 8px 24px rgb(20 24 27 / .16);
}
.theme .diagram-option-price { color: var(--steel); font-family: var(--mono); }

/* app.css rounds .choice-card. The card is the option row from 9.7 wearing a
   different class name, so it gets that rule's treatment rather than becoming a
   second pattern that means the same thing. */
.theme .choice-card {
  border-radius: 0;
  border: 1px solid var(--line);
  border-left: 3px solid transparent;
  background: var(--card);
  margin-bottom: var(--gap-1);
  transition: border-color var(--t-fast) var(--ease), background-color var(--t-fast) var(--ease);
}
.theme .choice-card:hover { border-color: var(--steel); border-left-color: var(--steel); }
.theme .choice-card:has(input:checked) {
  background: var(--accent-tint);
  border-color: var(--accent-line);
  border-left-color: var(--ink);
}
.theme .choice-card:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The colour is not decoration here. app.css sets `.choice-title` to
   `--ei-steel` (#2F3F4F), a blue-grey from the pre-theme palette, and this
   rule declared only type -- so the group headings on this page and on
   generator-package.html both kept it. A property a rule does not declare is a
   property it does not win. */
.theme .choice-title {
  font-family: var(--disp);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: .01em;
  color: var(--ink);
}
.theme .choice-desc { color: var(--steel); }

/* --------------------------------------------------------------------------
   11. The rest of the quote workflow
   --------------------------------------------------------------------------

   Quote intake, transmission, engine only and misc options. These four pages
   were in NO session of the plan's seven -- the sequence in section 3.2 names
   login, dashboard, engine, controls, generators, admin users and summary, and
   these carry the same shell as the pages it does name. Added on the owner's
   instruction so the workflow does not end up half-restyled.

   Nearly all of it is section 9 already: shell, card, progress, banner, fields,
   actions, module nav and bundle tabs arrive styled the moment a page links this
   file. What is left is the handful of components that live only on these four.
   -------------------------------------------------------------------------- */

/* 11.1 Workflow section picker (quote intake) ------------------------------ */

/* The panel that decides which modules a quote contains. It sits ABOVE the
   card rather than inside one, so it is a bordered plate in its own right --
   the one place on intake where a second ground is correct, because it is not
   part of the form below it. */
.theme .workflow-selector-panel {
  border: 1px solid var(--line);
  border-radius: 0;
  border-top: 3px solid var(--ink);
  background: var(--card);
  padding: var(--gap-4);
  margin-bottom: var(--gap-4);
}
.theme .workflow-selector-head h3 {
  font-family: var(--disp);
  font-size: 15px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--ink);
  margin: 0 0 var(--gap-3);
}
.theme .workflow-choice-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--gap-1);
}
/* Each module is an option row -- the same pattern 9.7 settled, so a checkbox
   that adds a PAGE to the quote reads the same way as a checkbox that adds a
   part to it. */
.theme .workflow-choice {
  display: flex;
  border-radius: 0;
  align-items: baseline;
  gap: var(--gap-2);
  flex-wrap: wrap;
  border: 1px solid var(--line);
  border-left: 3px solid transparent;
  background: var(--card);
  padding: 10px var(--gap-3);
  cursor: pointer;
  transition: border-color var(--t-fast) var(--ease), background-color var(--t-fast) var(--ease);
}
.theme .workflow-choice:hover { border-color: var(--steel); border-left-color: var(--steel); }
.theme .workflow-choice:has(input:checked) {
  background: var(--accent-tint);
  border-color: var(--accent-line);
  border-left-color: var(--ink);
}
.theme .workflow-choice:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.theme .workflow-choice input { flex: none; }
.theme .workflow-choice > span { font-weight: 500; }
/* "Always included" -- a statement about the row, not a hint under it, so it
   sits inline in mono rather than breaking to its own line. */
.theme .workflow-choice-note {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--steel);
  margin-left: auto;
}

/* 11.2 Field groups and required marks (quote intake) ---------------------- */

/* Same divider treatment section 10.5 gives `.section-flat`: a rule and a
   condensed label, never a nested card. Written as its own rule because the two
   classes never appear on the same page and merging them would tie intake's
   layout to a change made for controls. */
.theme .field-group + .field-group {
  border-top: 1px solid var(--line);
  padding-top: var(--gap-4);
  margin-top: var(--gap-4);
}
.theme .field-group > h4 {
  font-family: var(--disp);
  font-size: 15px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--steel);
  margin: 0 0 var(--gap-3);
}
.theme .required-legend {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .03em;
  color: var(--steel);
  margin: 0 0 var(--gap-4);
}
/* --danger earns its "form errors only" meaning here: this IS the form's error
   vocabulary, marking the fields that can fail validation. */
.theme .required-mark { color: var(--danger); font-weight: 700; }

/* 11.3 Optional add-on panels (transmission) ------------------------------- */

/* A section the user switches on, holding a form that did not exist until they
   did. The toggle is an option row; what it reveals is indented under an ink
   rule so the containment is visible without a box. */
.theme .optional-addon-panel {
  /* app.css gives this a full 1px border at a 14px radius. Declaring only the
     top rule left the other three sides and the corners standing, so the toggle
     sat in a nested box -- the same trap section 9.9 records: a property a rule
     does not declare is a property it does not win. */
  border: 0;
  border-top: 1px solid var(--line);
  border-radius: 0;
  background: none;
  padding: var(--gap-4) 0 0;
  margin-top: var(--gap-4);
}
.theme .transmission-addon-toggle {
  display: flex;
  border-radius: 0;
  align-items: center;
  gap: var(--gap-2);
  border: 1px solid var(--line);
  border-left: 3px solid transparent;
  background: var(--card);
  padding: 10px var(--gap-3);
  cursor: pointer;
  font-weight: 500;
}
.theme .transmission-addon-toggle:hover { border-color: var(--steel); border-left-color: var(--steel); }
.theme .transmission-addon-toggle:has(input:checked) {
  background: var(--accent-tint);
  border-color: var(--accent-line);
  border-left-color: var(--ink);
}
.theme .transmission-addon-toggle:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.theme .optional-addon-section {
  border-left: 3px solid var(--line);
  padding-left: var(--gap-4);
  margin-top: var(--gap-3);
}
.theme .optional-addon-section[hidden] { display: none; }

/* 11.4 Charges and repeating rows (misc, engine only) ---------------------- */

.theme .charges-block { margin-bottom: var(--gap-5); }
.theme .charges-block > h3 {
  font-family: var(--disp);
  font-size: 15px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--steel);
  margin: 0 0 var(--gap-1);
}
.theme .engine-lines-container { display: grid; gap: var(--gap-3); }

/* `.actions` is the page's footer bar and carries a top rule; `.actions-inline`
   is an "add another" button sitting mid-form, which is not a footer and must
   not draw one. app.css only zeroes its margin, so the rule had to go here. */
.theme .actions.actions-inline {
  border-top: 0;
  padding-top: 0;
  margin-top: var(--gap-3);
  justify-content: flex-start;
}

/* 11.5 Match count and status row (transmission) --------------------------- */

.theme .status-row {
  display: flex;
  gap: var(--gap-3);
  align-items: center;
  flex-wrap: wrap;
  margin: var(--gap-3) 0;
}
/* app.css tints .badge in the old blue at a 999px radius. It is a count, so it
   takes the mono outlined vocabulary the module nav and progress steps use. */
.theme .badge {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .06em;
  text-transform: uppercase;
  padding: 5px 9px;
  border-radius: 0;
  border: 1px solid var(--ink);
  background: var(--card);
  color: var(--ink);
}

/* 11.6 Attachments (quote intake) ------------------------------------------ */

.theme .attachment-table-wrap { overflow-x: auto; }
.theme .attachment-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}
.theme .attachment-table th {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--steel);
  text-align: left;
  padding: 8px 10px;
  border-bottom: 2px solid var(--ink);
  white-space: nowrap;
}
.theme .attachment-table td {
  padding: 9px 10px;
  border-bottom: 1px dashed var(--line);
  vertical-align: middle;
}
.theme .attachment-table tbody tr:hover { background: var(--accent-tint); }
.theme .empty-state {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .04em;
  color: var(--steel);
  border: 1px dashed var(--line);
  padding: var(--gap-4);
  text-align: center;
}
.theme .empty-state[hidden] { display: none; }

/* 11.7 Collisions with app.css --------------------------------------------- */

/* The destructive control on intake keeps BOTH its classes in the markup, so it
   is not reachable by the .mini-btn swap the other buttons took. It is the one
   button on these pages that discards work, and outlining it in --danger is the
   only chromatic thing on the page -- which is the point. */
.theme .button-danger {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .06em;
  text-transform: uppercase;
  padding: 8px 12px;
  border: 1px solid var(--danger);
  border-radius: 0;
  background: var(--card);
  color: var(--danger);
  cursor: pointer;
  width: auto;
}
.theme .button-danger:hover { background: var(--danger); color: var(--on-ink); }

/* On the ink band --danger measures 2.01:1 -- the reset control was the least
   legible thing on the page it is most dangerous to misread. The band's own
   vocabulary carries it at rest and the colour returns on hover and focus, at
   which point it is against the button's own fill rather than the band. */
.theme .app-head .button-danger {
  border-color: var(--on-ink-faint);
  background: transparent;
  color: var(--on-ink);
}
.theme .app-head .button-danger:hover,
.theme .app-head .button-danger:focus-visible {
  background: var(--danger);
  border-color: var(--danger);
  color: var(--on-ink);
}

/* Buttons written by a page CONTROLLER rather than by the markup. The .mini-btn
   swap the four pages took reaches only what is in the HTML files, so the
   "Remove", "Remove Bundle" and "Add" controls that page-generator-engine-only.js
   and propulsion-bundles.js create at runtime kept app.css's 8px radius and
   blue-grey border. Restyling the class those renderers already emit costs no JS
   change and fixes every page at once -- the same move section 9 made for the
   module nav and bundle tabs. `.compact` is app.css's small-button modifier and
   carries the radius on its own, so it is named too.

   Excluded: .mini-btn (already this vocabulary) and .button-danger, which is the
   one control that must NOT look like the others. */
.theme button.secondary:not(.mini-btn):not(.button-danger),
.theme .button-link.secondary:not(.mini-btn):not(.button-danger),
.theme button.compact:not(.mini-btn):not(.button-danger) {
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: .04em;
  text-transform: uppercase;
  font-weight: 500;
  border: 1px solid var(--line);
  border-radius: 0;
  background: var(--paper);
  color: var(--ink);
  padding: 9px 14px;
  width: auto;
  cursor: pointer;
  text-decoration: none;
  box-shadow: none;
}
.theme button.secondary:not(.mini-btn):not(.button-danger):hover,
.theme .button-link.secondary:not(.mini-btn):not(.button-danger):hover,
.theme button.compact:not(.mini-btn):not(.button-danger):hover {
  border-color: var(--steel);
  background: var(--card);
}

/* The PRIMARY weight of the same pair, and the same reasoning one step further.
   app.css paints `button.primary` with a stack of carbon-fibre gradients -- all
   black, so a chroma sweep never flags it, which is how it survived every page
   restyled so far. Three places emit it and only three: admin/dealers.js and
   admin/users-render.js each write a Save, and forms/summary.html's Print Quote
   (session 7). Naming the class they already use restyles all three, including
   the dealers screen behind us, with no JS change.

   `background` is written as the shorthand deliberately: the longhand `-color`
   alone leaves app.css's four background-images painting on top of the fill. */
.theme button.primary:not(.mini-btn),
.theme .button-link.primary:not(.mini-btn) {
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: .04em;
  text-transform: uppercase;
  font-weight: 500;
  border: 1px solid var(--ink);
  border-radius: 0;
  background: var(--ink);
  color: var(--on-ink);
  padding: 9px 14px;
  width: auto;
  cursor: pointer;
  text-decoration: none;
  box-shadow: none;
}
.theme button.primary:not(.mini-btn):hover,
.theme .button-link.primary:not(.mini-btn):hover {
  background: #23292E;
  border-color: #23292E;
}

/* The dev-only tool strips are hidden in production but visible to staff, and
   app.css sizes their file input as a full-width form field. */
.theme .inline-tools { display: flex; gap: var(--gap-2); flex-wrap: wrap; align-items: center; }
.theme .inline-tools[hidden] { display: none; }
.theme .inline-tools input[type="file"] { width: auto; font-family: var(--mono); font-size: 11px; }

/* app.css gives the intake form's own stack a card background; the sections
   inside it already carry theirs. */
.theme .quote-intake-form-stack { display: grid; gap: var(--gap-4); background: none; }

/* 11.8 Group sections and option grids (generator package) ----------------- */

/* `.section` is the group box on forms/generator-package.html -- seven of them
   inside one card -- and it is the same box `.section.section-flat` names on
   forms/controls.html. 10.5 styled the two-class form when controls.html was
   the only page that had one; the treatment was always generic, so it lives
   here now and asks only for `.section`. Every `.section` on controls.html
   also carries `.section-flat`, so that page is unaffected.

   A group divider, not a nested card: the card is already the `.card` around
   it, and a bordered box inside a bordered box is the "two grounds" mistake
   9.1 avoids everywhere else. */
.theme .section {
  border: 0;
  border-radius: 0;
  background: none;
  /* app.css gives .section a shadow as well as a border. Dropping the border
     and the background does not drop the shadow -- a box-shadow paints around
     the border box whether or not anything fills it -- so controls.html has
     been dropping a soft shadow with no box under it since session 4. */
  box-shadow: none;
  padding: 0;
  margin: 0 0 var(--gap-5);
}
/* The rule DIVIDES groups, so it belongs to every section after the first, not
   to all of them with the first switched off. :first-of-type cannot say that
   here -- it matches by element name, and on controls.html the first div
   inside .card-b is #bundleTabs -- so the sibling combinator says it instead. */
.theme .section ~ .section {
  border-top: 1px solid var(--line);
  padding-top: var(--gap-4);
}
.theme .section > h3 {
  font-family: var(--disp);
  font-size: 15px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--steel);
  margin: 0 0 var(--gap-3);
}

/* `.field.span-2` is declared by NO stylesheet in this repo. app.css declares
   `.kv.span-2`, which is the summary readout's cell and a different element,
   so the widest group on the page ("Additional Items") has never spanned the
   two-column field grid. The media query is not decoration: `span 2` inside
   the single-column grid app.css switches to at 720px would create an implicit
   second column and push the page sideways. */
.theme .field.span-2 { grid-column: span 2; }
@media (max-width: 720px) { .theme .field.span-2 { grid-column: span 1; } }

/* Every label in a check grid is a choice you click -- the same job the option
   row of 9.7 does, in different markup -- so it takes that rule's treatment
   rather than becoming a second pattern that means the same thing, exactly as
   `.choice-card` does in 10.6. Selection reads from an ink left rule AND the
   tint, never from colour alone.

   The label colour is the reason this cannot be left to 9.3. That rule is
   `.theme .field > label`, a CHILD combinator aimed at the field's own label,
   while app.css's `.field label` is a descendant selector -- so all 113 option
   labels and their spans on this page were still painting in app.css's
   #2F3F4F. The markup gives no hint of it; only the rendered DOM does. */
/* `.choice-title` does two different jobs under one name. On controls.html it
   is the title of an option inside a `.choice-card`, which is what 10.6 styles
   it as. Here it is the LABEL of a check grid -- the same job the `<label>` of
   the select beside it does -- and it was announcing itself in 16px display
   type next to an 11px mono label for no difference in meaning. The child
   combinator tells the two apart: page-controls.js emits its copy inside a
   `.choice-card`, never as a child of `.field`. */
.theme .field > .choice-title {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--steel);
  margin-bottom: var(--gap-1);
}

.theme .check-grid { display: grid; gap: var(--gap-1); margin-top: var(--gap-1); }
/* `.compact-check-grid` is declared by no stylesheet either, so every option
   group on the page stacked one per row and the form ran to seven screens. */
.theme .check-grid.compact-check-grid {
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
.theme .check-grid label {
  display: flex;
  gap: var(--gap-2);
  align-items: center;
  border: 1px solid var(--line);
  border-left: 3px solid transparent;
  background: var(--card);
  padding: 8px var(--gap-2);
  font-family: var(--body);
  font-size: 13.5px;
  color: var(--ink);
  cursor: pointer;
  transition: border-color var(--t-fast) var(--ease), background-color var(--t-fast) var(--ease);
}
.theme .check-grid label:hover { border-color: var(--steel); border-left-color: var(--steel); }
.theme .check-grid label:has(input:checked) {
  background: var(--accent-tint);
  border-color: var(--accent-line);
  border-left-color: var(--ink);
}
.theme .check-grid label:has(input:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* app.css spaces the single-choice checkbox with a right margin; the flex gap
   is doing that now, and a second one reads as a misalignment against the
   radio groups beside it. */
.theme .check-grid label input { flex: none; margin: 0; }

/* --------------------------------------------------------------------------
   12. Admin screens
   --------------------------------------------------------------------------

   BOTH admin screens, since session 6. They share every component below, and
   session 6 was indeed close to "link the stylesheet and delete the page-local
   <style> block" -- what it added is the capability divider and the credential
   banner at the end of this section, both unique to `admin/users.html`.

   Both admin pages were built with their OWN <style> block rather than page
   classes in app.css, which is how they drifted from each other and from the
   rest of the app. Everything that is a colour, a border, a radius or a type
   decision moves here; what stays in the page is layout only.

   These pages have no `.wrap`, so the band and the content need their own
   gutter -- `.admin-wrap` is that, and nothing else.
   -------------------------------------------------------------------------- */

.theme .admin-wrap { max-width: 1240px; margin: 0 auto; }

/* The admin card is a plain `.card` with an `<h2>` inside, not the
   `.card-h`/`.card-b` pair the quote pages use, so section 9.2's header rule
   never reaches it. Same plate, different markup. */
.theme .admin-wrap .card {
  border: 1px solid var(--line);
  border-top: 3px solid var(--ink);
  border-radius: 0;
  background: var(--card);
  padding: var(--gap-5);
  margin-bottom: var(--gap-5);
  box-shadow: none;
  overflow: visible;
}
.theme .admin-wrap .card > h2 {
  font-family: var(--disp);
  font-size: 19px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--ink);
  margin: 0 0 var(--gap-2);
}
.theme .admin-wrap .card > p.hint { margin: 0 0 var(--gap-4); max-width: 78ch; }

/* At 390px the dealers table is 980px wide and pushed the whole PAGE sideways --
   the plan's section 3.4 asks every page to render at that width. A wide table
   scrolls inside its own box; the page body never does. */
.theme .table-scroll { overflow-x: auto; }

/* The editor opens as a row inside the table rather than a drawer beside it.
   BOTH screens do -- the plan reserved its `.drawer` for users.html on the
   understanding that its editor was a separate form, and it is not:
   users-render.js writes a `tr.editor-row` with a colspan exactly as
   dealers.js does. The separate form on that page is ADD USER, which is
   already a `.card`. So there is no `.drawer` in this stylesheet and nothing
   is missing one. The row IS the editor, so it reads as an inset: a paper
   ground and an ink left rule marking which record is open. */
.theme .editor-row > td {
  background: var(--paper);
  border-left: 3px solid var(--ink);
}
.theme .editor-section > h3 {
  font-family: var(--disp);
  font-size: 15px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--steel);
  margin: 0 0 var(--gap-1);
}

/* A dealer's status is a status, so it takes colour -- the one thing section 6
   allows chroma for. Outlined rather than filled: these sit in a table column,
   where a filled chip at every row would out-shout the names beside them. */
.theme .badge.active { border-color: var(--ok); color: var(--ok); }
.theme .badge.inactive { border-color: var(--steel); color: var(--steel); }

/* Errors are the other place --danger is correct. Mono, because what it prints
   is validation output rather than prose. Kept `display:none` until the page
   fills it -- admin/dealers.js toggles this element, and a rule that forgets
   that would print an empty red gap above every table. */
.theme .form-error {
  display: none;
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: .03em;
  line-height: 1.5;
  color: var(--danger);
  border: 1px solid var(--danger);
  padding: var(--gap-2) var(--gap-3);
  margin-bottom: var(--gap-3);
  white-space: pre-wrap;
}

/* The checkbox rows in the editor are labelled controls, not option rows -- a
   permission toggle beside its own text, with no card around it. */
.theme .checkbox-row label { font-weight: 400; margin: 0; }

/* The capability block is `admin/users.html`'s own group divider -- a rule
   across the form with a labelled group under it. Same heading vocabulary as
   the dealers editor's `.editor-section > h3`; the two screens now share one
   admin heading, one step down from the card's `<h2>`. */
.theme .caps > h3,
.theme .editor-row .caps > h3 {
  font-family: var(--disp);
  font-size: 15px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--steel);
  margin: 0 0 var(--gap-1);
}

/* The temporary-password banner. It is shown ONCE, is never shown again, and
   the thing on it that matters is a string the admin has to read character by
   character -- so the panel is the ink-topped plate every admin card uses and
   the credential itself is the only mono, boxed element on the page.

   Its old rule was `background: var(--accent)`, app.css's pale gold. In this
   system --accent IS --ink, so left alone the banner would have painted near
   black behind near black text. A token that survives a port under the same
   name but a different meaning is worse than one that is missing.

   `display` is deliberately not declared: admin/users.js toggles this element
   between none and block, and a rule that set it would either publish an empty
   banner on load or fight the script. The page keeps the `display:none`. */
.theme .cred-banner {
  background: var(--card);
  border: 1px solid var(--line);
  border-top: 3px solid var(--ink);
  border-radius: 0;
  padding: var(--gap-4) var(--gap-4) var(--gap-3);
  box-shadow: none;
}
.theme .cred-banner h3 {
  font-family: var(--disp);
  font-size: 15px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--ink);
  margin: 0 0 var(--gap-2);
}
.theme .cred-banner code {
  font-family: var(--mono);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: .06em;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 0;
  padding: var(--gap-2) var(--gap-3);
}
.theme .cred-banner p {
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--steel);
  margin: var(--gap-2) 0 0;
}

/* --------------------------------------------------------------------------
   13. Summary screen chrome
   --------------------------------------------------------------------------

   `forms/summary.html` only. It is the one page in the tool where the theme
   and the PRINTED document share a document, so read this before touching it:

   - Everything here styles chrome that `@media print` hides. Nothing in this
     section may reach inside `main.quote-page`, which is the quote itself.
   - That page declares `--ink`, `--line` and `--accent` on `.quote-page`
     rather than on `:root`, because this stylesheet defines all three at
     `:root` with different values. The sheet keeps its own; the chrome out
     here takes ours. Do not "tidy" those three back up to `:root`.
   - The page also restates `body.theme`'s typography in both media for the
     same reason: a print rule written as `body` cannot outrank `body.theme`.
   -------------------------------------------------------------------------- */

/* This page's body is Arial/12px, because that is what the printed quote is set
   in and every sheet element inherits from it. The chrome therefore has to ASK
   for the theme's text face -- on every other page it simply inherits it. Named
   on the four chrome roots rather than on `body`, which the sheet inherits. */
.theme .app-head.screen-actions,
.theme .print-options-panel,
.theme .layout-fallback-notice,
.theme #marginInternalNote { font-family: var(--body); }

/* The three panels are one plate each, the same ink-topped card the admin
   screens use. `.print-options-panel` is the outer look for the Quote
   Specifications panel and the mark-up fieldset's container too -- the page
   deliberately reuses the class rather than duplicating the box. */
.theme .print-options-panel {
  background: var(--card);
  border: 1px solid var(--line);
  border-top: 3px solid var(--ink);
  border-radius: 0;
  padding: var(--gap-4);
  box-shadow: none;
}
.theme .print-options-panel h3 {
  font-family: var(--disp);
  font-size: 15px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--ink);
  margin: 0 0 var(--gap-2);
}
.theme .print-option-row { font-size: 13.5px; color: var(--ink); font-weight: 500; }
.theme .print-option-subrow { font-size: 12.5px; color: var(--ink); font-weight: 400; }

/* Status lines under an option are machine output -- a count of curves found,
   a reason a box is unavailable -- so they take the mono label vocabulary. */
.theme .print-option-status {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .03em;
  color: var(--steel);
}
.theme .print-option-status.warn { color: var(--danger); }

/* The mark-up fieldset. A fieldset's border is a box AROUND a group, not a
   card inside a card, so it is a hairline with a mono legend sitting in it. */
.theme .margin-options { border: 1px solid var(--line); border-radius: 0; }
.theme .margin-options legend {
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--steel);
}
.theme .margin-row { font-size: 12.5px; color: var(--ink); }
.theme .margin-row-label { font-family: var(--mono); font-size: 11.5px; font-weight: 600; letter-spacing: .03em; }
.theme .margin-row-unit,
.theme .margin-row-figures,
.theme .margin-options .muted,
.theme .quote-specs-panel .muted { color: var(--steel); font-size: 11.5px; }
.theme .margin-row-figures { font-family: var(--mono); }
.theme .margin-custom-input {
  font-family: var(--mono);
  font-size: 12px;
  border: 1px solid var(--line);
  border-radius: 0;
  background: var(--card);
  color: var(--ink);
  padding: 3px 6px;
}

/* Shown to an engines_inc viewer INSTEAD of the mark-up panel, so it explains a
   number they cannot otherwise account for. An inset, not an alert: it reports
   state rather than asking for anything. */
.theme #marginInternalNote {
  background: var(--paper);
  border: 1px solid var(--line);
  border-left: 3px solid var(--ink);
  border-radius: 0;
  font-size: 12.5px;
  color: var(--steel);
}

/* The multi-bundle fallback notice is the one thing on this page that is a
   WARNING -- the dealer's own layout was set aside for this quote -- and
   section 6 allows chroma exactly there. Amber on the rule, not on the ground:
   a filled amber panel above a white quote sheet reads as an error state. */
.theme .layout-fallback-notice {
  background: var(--card);
  border: 1px solid var(--line);
  border-left: 3px solid var(--warn);
  border-radius: 0;
  padding: var(--gap-3);
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--ink);
}
.theme .layout-fallback-notice strong { color: var(--ink); font-weight: 600; }

/* The Quote Specifications bar is a control, and the panel's whole resting
   state. Card ground so it reads as pressable against the plate behind it. */
.theme .quote-specs-panel > summary {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 0;
  transition: border-color var(--t-fast) var(--ease);
}
.theme .quote-specs-panel > summary:hover { border-color: var(--steel); }
.theme .quote-specs-summary-chevron {
  border: 1px solid var(--line);
  border-radius: 0;
  background: var(--paper);
}
.theme .quote-specs-summary-chevron::before {
  border-right: 2px solid var(--ink);
  border-bottom: 2px solid var(--ink);
}
.theme .quote-specs-summary-title {
  font-family: var(--disp);
  font-size: 14px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--ink);
}
.theme .quote-specs-summary-hint { font-size: 11.5px; font-weight: 400; color: var(--steel); text-transform: none; letter-spacing: 0; }

/* The override counter. Outlined at rest, filled once anything is overridden --
   the state change is what proves the panel is live. Mono, because it counts. */
.theme .quote-specs-summary-badge {
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: .06em;
  text-transform: uppercase;
  padding: 4px 9px;
  border: 1px solid var(--line);
  border-radius: 0;
  background: var(--card);
  color: var(--steel);
}
.theme .quote-specs-summary-badge.is-edited {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--on-ink);
}
.theme .quote-specs-context {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 0;
  font-size: 11.5px;
  color: var(--steel);
}
.theme .quote-specs-context strong { color: var(--ink); font-weight: 600; }
.theme .quote-specs-group-title {
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--steel);
  border-bottom: 1px solid var(--line);
}
.theme .quote-spec-field label {
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--steel);
}
.theme .quote-spec-field input,
.theme .quote-spec-field textarea {
  font-family: var(--body);
  font-size: 12.5px;
  border: 1px solid var(--line);
  border-radius: 0;
  background: var(--card);
  color: var(--ink);
  padding: 6px 8px;
}

/* --------------------------------------------------------------------------
   14. Motion
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .theme *,
  .theme *::before,
  .theme *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  .mini-btn:active { transform: none; }
}

/* --------------------------------------------------------------------------
   15. The internal price-list configurators
   --------------------------------------------------------------------------

   Phase 2, session B of docs/reference/gui-restyle-and-rehlko-module-plan.md.

   TWO PAGES WEAR THIS SECTION, NOT ONE. It was written for rehlko.html and was
   scoped `.rk-page` while that was the only page it dressed. marathon.html --
   the Power Source Midwest / Marathon price list, built to the same arrangement
   -- is the second, and it is not a variant of the first: both are internal,
   engines_inc-only configurators that pick a machine, price its options, take a
   margin and print an Engines, Inc. sheet. They share one component set because
   they ARE one component set.

   So the scope is `.pricer-page`, a class both pages carry, and this section is
   declared once. The alternative was a second copy of sections 15 and 16 under a
   `.mn-page` scope -- a thousand lines of CSS whose whole job would be to stay
   identical to the thousand lines above it, on a printed document where "the
   same format as the Rehlko module" is the requirement. A copy that must not
   drift is worse than a scope that cannot.

   WHAT IS STILL PER-PAGE: ids. Every id stays prefixed for its own page (`rk-`,
   `mn-`), because the two page modules each resolve theirs through their own
   `$()` helper. Classes are shared; ids are not.

   The plan's section 4.2 says the module "uses theme.css unchanged" because
   "its markup already matches the Phase 1 class names". That was true of the
   NAMES and not of the RULES: section 2.2's component catalog lists .chip,
   details.sec, .opt, .plate, .plate.bom, .margin-panel and .price-stack, and
   Phase 1 declared none of them -- no page in its seven sessions had a chip
   grid or a nameplate, so nothing was there to style. `.step`, `.step-head`,
   `.seg`, `.field`, `.vsel`, `.search-in`, `.mini-btn`, `.badge`, `.sel-panel`,
   `.notes` and `.cfg-grid` DO already exist above, and this section does not
   restate them.

   So the rules below are ported from the source configurator's own <style>
   block -- the one section 4.2 says to drop -- rather than invented. Every
   reference to the source system's accent token, or its darker pair, is renamed
   to --accent / --ink at port time as
   section 3.3 requires (never aliased), which in this monochrome system means
   the chip's active ring, the required badge, the selection tint and the total
   are ink rather than chromatic. The two chromatic survivors are status colours
   section 1 already reserves: --ok on an N/C price and on the profit figure.

   EVERYTHING HERE IS SCOPED TO .pricer-page, and that is not tidiness. The source
   names are short and generic -- .opt, .sec, .part, .price, .plate, .notes --
   and `.part` is live markup in forms/summary.html, which is the PRINT layer.
   An unscoped `.part { }` in this file would reach a printed quote, which
   section 2.3's last row forbids outright. Scoping makes that impossible by
   construction rather than by my having checked.
   -------------------------------------------------------------------------- */

/* 15.1 Header meta and filter row ------------------------------------------ */

/* The source's right-hand `.quote-tag` block: effective date, revision date and
   the catalogue's own counts. It sits in .head-meta on the ink band, so it
   takes the band's dim-on-ink treatment rather than the source's steel. */
.pricer-page .quote-tag {
  font-family: var(--mono);
  font-size: 12px;
  text-align: right;
  color: var(--on-ink-dim);
  line-height: 1.7;
}
.pricer-page .quote-tag b { color: var(--on-ink); font-weight: 600; }

.pricer-page .filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--gap-5);
  align-items: flex-end;
  margin-bottom: var(--gap-4);
}

/* 15.2 kW filter and the clear affordance ---------------------------------- */

.pricer-page .kw-in { display: flex; align-items: center; gap: 8px; }
.pricer-page .kw-in input {
  font-family: var(--mono);
  font-size: 16px;
  width: 110px;
  padding: 7px 10px;
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink);
}
.pricer-page .kw-in span { font-family: var(--mono); font-size: 12px; color: var(--steel); }
.pricer-page .btn-clear {
  font-family: var(--body);
  font-size: 12.5px;
  background: none;
  border: 0;
  color: var(--steel);
  cursor: pointer;
  text-decoration: underline;
  padding: 8px 0;
}
.pricer-page .btn-clear:hover { color: var(--ink); }

/* 15.3 Model chips --------------------------------------------------------- */

.pricer-page .chips {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(196px, 1fr));
  gap: var(--gap-2);
}
/* The "nothing meets that kW" note, which marathon.html renders INTO the chip grid
   rather than beside it -- it replaces the chips, so it belongs where they were. As a
   grid item it would otherwise sit in the first 196px column and wrap to five words a
   line. Rehlko's grid never holds one (its own filters dim rather than remove), so this
   rule costs that page nothing. */
.pricer-page .chips .no-match { grid-column: 1 / -1; padding: var(--gap-3) 0; }
.pricer-page .chip {
  border: 1px solid var(--line);
  background: var(--paper);
  padding: 10px 12px;
  cursor: pointer;
  text-align: left;
  font-family: var(--body);
  color: var(--ink);
  transition: border-color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
.pricer-page .chip:hover { border-color: var(--steel); }
/* Source: accent border + accent inset ring + accent name. Ink does all three. */
.pricer-page .chip.active {
  border-color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--ink);
  background: var(--card);
}
.pricer-page .chip .nm {
  font-family: var(--disp);
  font-size: 18px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .02em;
}
.pricer-page .chip .sub { font-size: 11.5px; color: var(--steel); margin-top: 2px; line-height: 1.35; }
.pricer-page .chip .kw {
  font-family: var(--mono);
  font-size: 11px;
  margin-top: var(--gap-1);
  display: flex;
  gap: var(--gap-1);
  flex-wrap: wrap;
}
.pricer-page .chip .kw i {
  font-style: normal;
  background: var(--card);
  border: 1px solid var(--line);
  padding: 1px 6px;
}
/* Source tinted this border a pale wash of its accent. --accent-line is the token
   that already carries "tag border inside a selected chip" (section 1). */
.pricer-page .chip.active .kw i { border-color: var(--accent-line); }
.pricer-page .chip.dim { opacity: .42; }
.pricer-page .chip .tagc {
  display: inline-block;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: .06em;
  color: var(--card);
  background: var(--steel);
  padding: 1px 5px;
  margin-left: var(--gap-1);
  vertical-align: 2px;
}
/* The COM tag. Source filled it with its accent; ink is the emphasis here. */
.pricer-page .chip .tagc.c { background: var(--ink); }

/* 15.4 Option groups ------------------------------------------------------- */

.pricer-page .cfg-tools {
  display: flex;
  flex-wrap: wrap;
  gap: var(--gap-3);
  align-items: flex-end;
  margin-bottom: var(--gap-3);
}

.pricer-page details.sec { border: 1px solid var(--line); margin-bottom: var(--gap-2); background: var(--card); }
.pricer-page details.sec[open] { border-color: #C4BFB1; }
.pricer-page details.sec summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: var(--gap-2);
  padding: 10px 14px;
  background: var(--paper);
  border-bottom: 1px solid transparent;
  user-select: none;
}
.pricer-page details.sec[open] summary { border-bottom: 1px solid var(--line); }
.pricer-page details.sec summary::-webkit-details-marker { display: none; }
.pricer-page .sec-caret {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--steel);
  transition: transform var(--t-fast) var(--ease);
  flex: none;
}
.pricer-page details.sec[open] .sec-caret { transform: rotate(90deg); }
.pricer-page .sec-name {
  font-family: var(--disp);
  font-size: 16.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .03em;
}

/* Section 11.5's `.theme .badge` restyles app.css's count badge, and it is both
   LATER in this file and the same specificity as section 6's `.badge.req` /
   `.optn` / `.one`, so on any page carrying `.theme` it wins and the three
   variants collapse into one identical outlined box. This page carries `.theme`
   for the page frame, so the variants are restated here at a specificity 11.5
   cannot reach. Restated rather than reordered: 11.5 is doing a real job on the
   pages that load app.css, and moving it would change them. */
.pricer-page .badge {
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: .07em;
  text-transform: uppercase;
  padding: 2px 6px;
  border: 0;
  border-radius: 0;
  flex: none;
}
.pricer-page .badge.req { color: var(--card); background: var(--accent); }
.pricer-page .badge.optn { color: var(--steel); border: 1px solid var(--line); background: var(--card); }
.pricer-page .badge.one { color: var(--ink); border: 1px solid var(--accent-line); background: var(--accent-tint); }

.pricer-page .sec-meta {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--steel);
  white-space: nowrap;
}
/* The group subtotal. Source: the darker accent. */
.pricer-page .sec-meta b { color: var(--ink); }
.pricer-page .sec-note { font-size: 12.5px; color: var(--steel); padding: 10px 14px 0; font-style: italic; }
.pricer-page .sec-body { padding: 4px 6px 8px; }
.pricer-page .subgrp {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--steel);
  padding: 10px 8px 2px;
  border-bottom: 1px dashed var(--line);
}

/* 15.5 Option rows --------------------------------------------------------- */

.pricer-page .opt {
  display: flex;
  align-items: center;
  gap: var(--gap-2);
  padding: 7px 8px;
  border-bottom: 1px dashed var(--line);
}
.pricer-page .opt:last-child { border-bottom: 0; }
.pricer-page .opt:hover { background: #FAF8F2; }
.pricer-page .opt input {
  width: 15px;
  height: 15px;
  accent-color: var(--accent);
  flex: none;
  cursor: pointer;
}
.pricer-page .opt label { flex: 1; cursor: pointer; font-size: 13.5px; line-height: 1.35; }
.pricer-page .opt .part { font-family: var(--mono); font-size: 11px; color: var(--steel); }
.pricer-page .opt .price { font-family: var(--mono); font-size: 13px; font-weight: 500; white-space: nowrap; }
/* A status colour, and one section 1 already reserves for exactly this: "N/C
   prices, won, passed checks". It stays green. */
.pricer-page .opt .price.nc { color: var(--ok); font-weight: 600; font-size: 11.5px; letter-spacing: .04em; }
/* Source tinted a selected row a pale wash of its accent. --accent-tint is the
   token that already means "selected row" (section 1). */
.pricer-page .opt.on { background: var(--accent-tint); }
.pricer-page .opt.locked { opacity: .4; }
.pricer-page .opt.locked:hover { background: none; }
.pricer-page .opt.locked label,
.pricer-page .opt.locked input { cursor: not-allowed; }

.pricer-page .price-stack { display: flex; flex-direction: column; align-items: flex-end; line-height: 1.15; }
.pricer-page .price-stack .list {
  font-family: var(--mono);
  font-size: 10px;
  color: #A8A395;
  text-decoration: line-through;
}

.pricer-page .no-match { padding: var(--gap-4); color: var(--steel); font-size: 13.5px; }

.pricer-page .qty-row {
  display: flex;
  align-items: center;
  gap: var(--gap-3);
  padding: var(--gap-3) 2px 0;
  flex-wrap: wrap;
}
.pricer-page .qty-row input {
  font-family: var(--mono);
  font-size: 16px;
  width: 84px;
  padding: 6px 10px;
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink);
}

/* 15.6 The nameplate ------------------------------------------------------- */

/* Section 2.2: "This is the signature element -- quote totals and build sheets
   only", and "two per page is the ceiling". This page has exactly two: the list
   quote and the build sheet. The gradient and the four screws are achromatic,
   so the element ports unchanged; only the total's colour was red. */
.pricer-page .plate-wrap { position: sticky; top: 16px; }
.pricer-page .plate {
  position: relative;
  padding: 26px 26px 22px;
  color: #22262A;
  background: linear-gradient(160deg, var(--plate-hi) 0%, #D5D9DE 45%, var(--plate-lo) 100%);
  border: 1px solid #9AA1A8;
  border-radius: 6px;
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / .8),
    inset 0 -2px 4px rgb(0 0 0 / .18),
    0 6px 18px rgb(20 24 27 / .18);
}
.pricer-page .plate::before,
.pricer-page .plate::after,
.pricer-page .plate .screw-a,
.pricer-page .plate .screw-b {
  content: "";
  position: absolute;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #F2F4F6, #8B9299 70%, #5C666D);
  box-shadow: inset 0 0 0 1px rgb(0 0 0 / .25);
}
.pricer-page .plate::before { top: 8px; left: 8px; }
.pricer-page .plate::after { top: 8px; right: 8px; }
.pricer-page .plate .screw-a { bottom: 8px; left: 8px; }
.pricer-page .plate .screw-b { bottom: 8px; right: 8px; }
.pricer-page .plate h3 {
  font-family: var(--disp);
  text-transform: uppercase;
  font-weight: 700;
  font-size: 19px;
  letter-spacing: .14em;
  text-align: center;
  border-bottom: 2px solid #565D63;
  padding-bottom: 8px;
  margin: 0 0 var(--gap-3);
  text-shadow: 0 1px 0 rgb(255 255 255 / .55);
}
.pricer-page .plate .rows { font-family: var(--mono); font-size: 12px; }
.pricer-page .plate .r {
  display: flex;
  justify-content: space-between;
  gap: var(--gap-3);
  padding: 5px 0;
  border-bottom: 1px dotted #9AA1A8;
}
.pricer-page .plate .r .k {
  color: #4A5157;
  letter-spacing: .04em;
  text-transform: uppercase;
  font-size: 10.5px;
  padding-top: 2px;
  flex: none;
}
.pricer-page .plate .r .v {
  font-weight: 600;
  text-align: right;
  text-shadow: 0 1px 0 rgb(255 255 255 / .5);
  overflow-wrap: anywhere;
}
.pricer-page .plate .total {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-top: var(--gap-3);
  padding-top: var(--gap-2);
  border-top: 2px solid #565D63;
}
.pricer-page .plate .total .k {
  font-family: var(--disp);
  text-transform: uppercase;
  font-weight: 600;
  letter-spacing: .1em;
  font-size: 15px;
}
/* The one figure the whole page exists to produce. Source: the darker accent. */
.pricer-page .plate .total .v {
  font-family: var(--mono);
  font-weight: 600;
  font-size: 23px;
  color: var(--ink);
  text-shadow: 0 1px 0 rgb(255 255 255 / .6);
}
.pricer-page .plate .per { font-family: var(--mono); font-size: 11px; color: #4A5157; text-align: right; margin-top: 2px; }
.pricer-page .plate-empty {
  font-family: var(--mono);
  font-size: 12.5px;
  color: #4A5157;
  text-align: center;
  padding: 26px 0;
}

/* 15.7 The build sheet ----------------------------------------------------- */

.pricer-page .plate.bom { margin-top: var(--gap-3); padding-bottom: var(--gap-4); }
.pricer-page .plate.bom h3 { margin-bottom: 8px; }
.pricer-page .bom-list {
  font-family: var(--mono);
  font-size: 11px;
  max-height: 340px;
  overflow: auto;
  padding-right: 2px;
}
.pricer-page .bom-grp {
  font-size: 9.5px;
  letter-spacing: .12em;
  color: #4A5157;
  text-transform: uppercase;
  padding: 8px 0 3px;
  border-bottom: 1px solid #7E868D;
  font-weight: 600;
}
.pricer-page .bom-r {
  display: flex;
  justify-content: space-between;
  gap: var(--gap-2);
  padding: 4px 0;
  border-bottom: 1px dotted #9AA1A8;
}
.pricer-page .bom-r .d { color: #2A2F34; line-height: 1.35; overflow-wrap: anywhere; }
.pricer-page .bom-r .d small { display: block; font-size: 9px; color: #5A6167; letter-spacing: .03em; }
.pricer-page .bom-r .pn {
  font-weight: 600;
  white-space: nowrap;
  text-align: right;
  color: var(--ink);
  text-shadow: 0 1px 0 rgb(255 255 255 / .5);
}
.pricer-page .bom-foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: var(--gap-2);
  padding-top: 8px;
  border-top: 2px solid #565D63;
}
.pricer-page .bom-foot .cnt {
  font-family: var(--mono);
  font-size: 10.5px;
  color: #4A5157;
  letter-spacing: .05em;
  text-transform: uppercase;
}
.pricer-page .bom-copy {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .05em;
  text-transform: uppercase;
  border: 1px solid #7E868D;
  background: linear-gradient(180deg, #EDEFF2, #C9CED4);
  color: #22262A;
  padding: 4px 10px;
  cursor: pointer;
  border-radius: 3px;
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .7);
}
.pricer-page .bom-copy:hover { filter: brightness(1.04); }
.pricer-page .bom-copy:active { box-shadow: inset 0 1px 3px rgb(0 0 0 / .25); }

/* 15.8 Margin panel and selected-options list ------------------------------ */

/* Section 3 already declares .sel-panel with the system's 3px ink top rule; the
   source gave it a plain 1px border. The system's version wins -- this is the
   restyle, not a reproduction -- so only what section 3 does not cover is here. */
.pricer-page .sel-panel { margin-top: var(--gap-3); }
.pricer-page .sel-panel h4,
.pricer-page .margin-panel h4 {
  font-family: var(--disp);
  font-size: 15px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .05em;
  margin: 0 0 8px;
}
.pricer-page .sel-list { max-height: 300px; overflow: auto; font-size: 12.5px; }
.pricer-page .sel-item {
  display: flex;
  justify-content: space-between;
  gap: var(--gap-2);
  padding: 5px 0;
  border-bottom: 1px dashed var(--line);
}
.pricer-page .sel-item:last-child { border-bottom: 0; }
.pricer-page .sel-item .d { color: var(--ink); line-height: 1.3; }
.pricer-page .sel-item .d small { display: block; font-family: var(--mono); font-size: 10.5px; color: var(--steel); }
.pricer-page .sel-item .p { font-family: var(--mono); white-space: nowrap; font-weight: 500; }
/* Source: an accent-coloured X. A remove control is not a status, so it is steel
   until hover. */
.pricer-page .sel-item .rm {
  background: none;
  border: 0;
  color: var(--steel);
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  padding: 0 2px;
  flex: none;
}
.pricer-page .sel-item .rm:hover { color: var(--ink); }
.pricer-page .sel-empty { color: var(--steel); font-size: 12.5px; }

/* Source: a 3px top rule in its accent. Every other panel in this system
   takes its 3px top rule in ink, so this one does too. */
.pricer-page .margin-panel {
  background: var(--card);
  border: 1px solid var(--line);
  border-top: 3px solid var(--ink);
  margin-bottom: var(--gap-3);
  padding: 14px 16px;
}
.pricer-page .margin-panel .m-in { display: flex; align-items: center; gap: 8px; }
.pricer-page .margin-panel input {
  font-family: var(--mono);
  font-size: 16px;
  width: 84px;
  padding: 6px 10px;
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink);
}
.pricer-page .margin-panel .m-in span { font-family: var(--mono); font-size: 13px; color: var(--steel); }
.pricer-page .m-quick { display: flex; gap: var(--gap-1); margin-top: 8px; flex-wrap: wrap; }
.pricer-page .m-quick button {
  font-family: var(--mono);
  font-size: 11px;
  border: 1px solid var(--line);
  background: var(--paper);
  color: var(--ink);
  padding: 4px 9px;
  cursor: pointer;
}
.pricer-page .m-quick button:hover { border-color: var(--steel); }
.pricer-page .m-quick button.active { background: var(--ink); color: var(--on-ink); border-color: var(--ink); }
.pricer-page .m-calc { font-family: var(--mono); font-size: 11.5px; color: var(--steel); margin-top: var(--gap-2); line-height: 1.8; }
.pricer-page .m-calc b { color: var(--ink); }
/* The other status-colour survivor: a profit figure, same family as N/C. */
.pricer-page .m-calc .profit { color: var(--ok); font-weight: 600; }

/* 15.9 Field-ready --------------------------------------------------------- */

/* Same rule as section 4: on touch every control on this page clears 44px. */
@media (pointer: coarse) {
  .pricer-page .chip,
  .pricer-page .m-quick button,
  .pricer-page .bom-copy,
  .pricer-page details.sec summary { min-height: 44px; }
  .pricer-page .kw-in input,
  .pricer-page .qty-row input,
  .pricer-page .margin-panel input { min-height: 44px; }
  .pricer-page .opt { padding: 10px 8px; }
}

@media (max-width: 640px) {
  .pricer-page .quote-tag { text-align: left; }
}

/* --------------------------------------------------------------------------
   16. The configurator printed quote
   --------------------------------------------------------------------------

   The sheet rehlko.html and marathon.html both print -- one format, declared
   once, for the reason section 15's header gives.

   The sheet rehlko.html prints. Section 4.4 item 4 of
   docs/reference/gui-restyle-and-rehlko-module-plan.md deferred this ("later,
   separate decision: quote-state hand-off and a print format"); this is the
   print format half of it, and only that half -- nothing here writes to
   assets/quote-state.js or to a saved quote.

   WHY IT IS NOT forms/summary.html
   --------------------------------
   That page prints a SAVED quote through assets/summary-layouts/*.js, and every
   one of those layouts is keyed to a dealer. A Rehlko configuration is none of
   those things: it is Engines Inc's own internal configurator, it has no dealer,
   no quote row and no quote number, and section 2.3 of the plan forbids touching
   the print layer. So this is a second, self-contained sheet that borrows
   summary.html's STRUCTURE -- letterhead, project grid, line table, notes/totals
   pair, legal footer -- without borrowing a line of its CSS. Nothing in
   forms/summary.html changed, and nothing here can reach it.

   WHICH IS ALSO WHY EVERY SELECTOR BELOW STARTS WITH .pricer-page, exactly as
   section 15's do, and for a sharper reason than section 15 had. Section 15's
   risk was a generic name (.opt, .part, .price) colliding with live print markup.
   This section's names ARE print markup, and this file is loaded by
   forms/summary.html. An unscoped .sheet-table or .sheet-totals here would not
   merely collide with the printed quote -- on a page whose own sheet uses the
   same vocabulary it would be a rule of it.
   tools/js-tests/rehlko-page.test.mjs reads every selector from section 15's
   heading to the end of this file and fails on any that escapes .pricer-page.

   NO @page RULE HERE, AND IT CANNOT MOVE HERE. @page is document-scoped: there
   is no selector on it, so an @page block in this file would re-margin the
   printed quote on forms/summary.html and on every other page that links this
   stylesheet. rehlko.html declares its own in the one place that can only affect
   rehlko.html -- its inline <style> block. See the comment there.

   COLOUR. Tokens only, no literal hex -- partly because section 15's colour test
   reads to the end of the file, and partly for a reason that bites here
   specifically: this system's --accent IS --ink (section 1, the monochrome
   decision), so a table header written the way forms/summary.html writes its own
   -- background: var(--accent), colour inherited -- would print ink on ink. That
   page escapes it by redeclaring --accent as a pale grey on .quote-page itself.
   Here the header band names --accent-tint directly, which is that same pale grey
   as a token this system already owns, and the text colour is stated rather than
   inherited.
   -------------------------------------------------------------------------- */

/* 16.1 The print options panel (screen only) -------------------------------- */

/* THE BORDER-BOX RESET THIS PAGE NEVER HAD.
   Every other screen in the tool gets `* { box-sizing: border-box }` from
   assets/app.css line 18; forms/summary.html, which loads no app.css, declares its own
   at the top of its <style> block. rehlko.html loads NEITHER -- that is the deliberate
   decision in its own head comment -- so it was the one page running on the CSS default
   of content-box, and had been since the module was built.
   The symptom: `.theme .field > input` is width:100% with 10px of horizontal padding
   and a border, so under content-box every such input rendered 22px WIDER than the box
   containing it. Invisible while the fields were in a single column; the moment STEP 3
   put two side by side in a grid, each input overhung its cell by exactly the 22px
   column gap and the two columns appeared to touch with no gutter at all.
   Scoped to .pricer-page rather than added at :root: every other page already has this from
   app.css, and a bare `*` rule here would be a global change made to fix one page. */
.pricer-page,
.pricer-page *,
.pricer-page *::before,
.pricer-page *::after { box-sizing: border-box; }

/* NOT `.sheet-fields` -- that name belongs to the PRINTED sheet's field grid in
   section 16.2, and both rules matched `.pricer-page .sheet-fields` at the same
   specificity, so the sheet's (being later in the file) silently won here: this panel
   was laid out with the sheet's `gap: 3px 22px` instead of its own, giving the form a
   3px row gap. Two different things must not share a class name just because both are
   about printing. */
.pricer-page .print-fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--gap-2) var(--gap-3);
  margin-bottom: var(--gap-3);
}
.pricer-page .print-fields .print-field-wide { grid-column: 1 / -1; }
.pricer-page .sheet-opts {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--gap-3);
  padding-top: var(--gap-2);
  border-top: 1px solid var(--line);
}
.pricer-page .sheet-opts .field { margin: 0; }
.pricer-page .sheet-check {
  display: flex;
  align-items: center;
  gap: var(--gap-1);
  font-family: var(--mono);
  font-size: 12px;
  color: var(--ink);
  cursor: pointer;
}
.pricer-page .sheet-check input { width: 16px; height: 16px; margin: 0; cursor: pointer; }
.pricer-page .sheet-actions { display: flex; gap: var(--gap-2); margin-left: auto; }
/* Said in the panel rather than on the sheet: the sheet is the customer's copy
   and must never have to explain itself. Same job .m-calc does for the margin
   panel one column over. */
.pricer-page .sheet-hint {
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--steel);
  line-height: 1.7;
  margin-top: var(--gap-2);
}
.pricer-page .sheet-hint b { color: var(--ink); }

/* 16.1b The quote hand-off (screen only) ------------------------------------ */

.pricer-page .quote-handoff { display: flex; flex-direction: column; gap: var(--gap-2); }
.pricer-page .quote-handoff .sheet-hint { margin-top: 0; }
.pricer-page .quote-list { display: flex; flex-direction: column; gap: 1px; }
.pricer-page .quote-list:empty { display: none; }
/* Deliberately the same row shape as .sel-item in section 15.8 -- a genset on the
   quote and an option on the build are the same kind of thing to a reader: something
   added, priced, and removable. Restated rather than shared because .sel-item's rules
   are the ported configurator's and this row is not part of that port. */
.pricer-page .quote-item {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: var(--gap-2);
  align-items: center;
  padding: 7px 9px;
  background: var(--card);
  border: 1px solid var(--line);
}
.pricer-page .quote-item .d { font-size: 13px; color: var(--ink); min-width: 0; }
.pricer-page .quote-item .d small {
  display: block;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--steel);
  overflow-wrap: anywhere;
}
.pricer-page .quote-item .p { font-family: var(--mono); font-size: 13px; color: var(--ink); white-space: nowrap; }
.pricer-page .quote-item .rm {
  border: 1px solid var(--line);
  background: var(--paper);
  color: var(--steel);
  font-size: 12px;
  line-height: 1;
  padding: 5px 7px;
  cursor: pointer;
}
.pricer-page .quote-item .rm:hover { border-color: var(--steel); color: var(--ink); }
/* 16.2 The sheet — the shipset packet's visual system ----------------------- */

/* Owner decision, 2026-09-02: the printed Rehlko sheet takes the look of the v2
   proposal packet (`assets/summary-layouts/ei_proposal.js`, the shipset variant).
   The vocabulary below is ported from that file's styleText(): the letterhead with a
   document type on the right, the panel-and-bar hero, the four-cell stat band, the
   two-column field grid, the ruled section headings, the item table with group rows,
   the inverted total bar and cells, and the fine-print footer.

   PORTED, NOT SHARED, and that was the choice between the two options put to the
   owner. ei_proposal.js writes its own <style> block scoped to `#eiProposalLayout`
   into a host inside `.quote-page`, which exists only on forms/summary.html. Reaching
   it from here would have meant giving this page the print layer's own markup and a
   synthetic quote state, and any accommodation it needed would have landed in the
   deployed v2 packet — which §2.3 of the plan forbids. This way rehlko.html still
   cannot touch a customer quote.

   The cost is honest and worth stating: this is a SECOND implementation of that
   design. If the packet's look changes, this does not follow.

   THE SIX TOKENS ARE REDECLARED ON THE SHEET, not at :root, exactly as
   forms/summary.html redeclares --ink/--line/--accent on `.quote-page` and for the
   same reason. theme.css §1 owns those names for the whole tool at values chosen for
   a warm-paper screen; the packet's are a different, cooler set chosen for paper. Put
   at :root they would repaint every screen in the tool. Declared here they reach
   exactly the rules below, because everything the sheet prints is inside .pricer-sheet.
   Named `--sheet-*` rather than shadowing `--ink` so a reader of any single rule can
   see which system it belongs to. */

.pricer-page .pricer-sheet {
  /* The Engines, Inc. values from ei_proposal.js's ENGINES_INC_TOKENS, verbatim. */
  --sheet-ink: #17171A;
  --sheet-steel: #58585A;
  --sheet-hair: #E8E8EA;
  --sheet-rule: #D8D8DB;
  --sheet-panel: #F5F5F6;
  --sheet-yellow: #EFD64B;
  --sheet-on-ink: #FFFFFF;
  margin: 0 0 var(--gap-6);
}
.pricer-page .pricer-sheet[hidden] { display: none; }

/* One sheet of Letter paper. */
.pricer-page .sheet-page {
  width: 8.5in;
  min-height: 11in;
  margin: 0 auto var(--gap-5);
  padding: 0.5in 0.55in 0.6in;
  background: var(--card);
  color: var(--sheet-ink);
  box-shadow: 0 8px 22px rgba(20, 24, 27, .18);
  /* The packet's faces: Arial for the body, Arial Narrow for headings and figures. */
  font-family: Arial, "Liberation Sans", sans-serif;
  font-size: 9.5pt;
  line-height: 1.38;
  box-sizing: border-box;
  position: relative;
}
.pricer-page .sheet-page h1,
.pricer-page .sheet-page h2,
.pricer-page .sheet-page h3,
.pricer-page .sheet-page .sheet-narrow,
.pricer-page .sheet-page .sheet-num {
  font-family: "Arial Narrow", "Liberation Sans Narrow", Arial, sans-serif;
}

/* Letterhead ---------------------------------------------------------------- */
.pricer-page .sheet-letterhead {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  border-bottom: 2px solid var(--sheet-ink);
  padding-bottom: 8px;
}
.pricer-page .sheet-letterhead img { height: 46px; width: auto; }
.pricer-page .sheet-doctype { text-align: right; }
.pricer-page .sheet-doctype h1 {
  margin: 0;
  font-size: 16pt;
  letter-spacing: .04em;
  text-transform: uppercase;
  font-weight: 700;
  color: var(--sheet-ink);
}
.pricer-page .sheet-doctype .ref { margin-top: 2px; font-size: 10pt; color: var(--sheet-steel); letter-spacing: .06em; }

/* Hero ---------------------------------------------------------------------- */
/* 6px, not 4px, and the reason is the packet's: measured under a mono printer, the
   bar greys to within a few levels of an ordinary table rule, and its WIDTH is what
   still tells them apart. See the .eip-hero comment in ei_proposal.js. */
.pricer-page .sheet-hero {
  margin-top: 14px;
  padding: 12px 14px;
  background: var(--sheet-panel);
  border-left: 6px solid var(--sheet-yellow);
}
.pricer-page .sheet-hero .k { font-size: 7.5pt; letter-spacing: .12em; text-transform: uppercase; color: var(--sheet-steel); }
.pricer-page .sheet-hero .v { font-size: 19pt; font-weight: 700; letter-spacing: .01em; line-height: 1.1; }
.pricer-page .sheet-hero .sub { margin-top: 3px; font-size: 9pt; color: var(--sheet-steel); }

/* Stat band ----------------------------------------------------------------- */
.pricer-page .sheet-stats {
  display: flex;
  margin-top: 10px;
  border-top: 1px solid var(--sheet-rule);
  border-bottom: 1px solid var(--sheet-rule);
}
.pricer-page .sheet-stat { flex: 1 1 0; padding: 8px 10px; border-right: 1px solid var(--sheet-hair); min-width: 0; }
.pricer-page .sheet-stat:last-child { border-right: 0; }
.pricer-page .sheet-stat .k { font-size: 7pt; letter-spacing: .1em; text-transform: uppercase; color: var(--sheet-steel); }
.pricer-page .sheet-stat .v { font-size: 11pt; font-weight: 700; margin-top: 2px; overflow-wrap: anywhere; }

/* Section heading ----------------------------------------------------------- */
.pricer-page .sheet-h {
  margin: 16px 0 6px;
  font-size: 11pt;
  text-transform: uppercase;
  letter-spacing: .07em;
  border-bottom: 1px solid var(--sheet-ink);
  padding-bottom: 3px;
  color: var(--sheet-ink);
  font-weight: 700;
}

/* Field grid ---------------------------------------------------------------- */
.pricer-page .sheet-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 3px 22px; margin-top: 8px; }
.pricer-page .sheet-field { display: flex; gap: 6px; font-size: 9pt; min-width: 0; }
.pricer-page .sheet-field .k { color: var(--sheet-steel); min-width: 92px; flex: 0 0 auto; }
.pricer-page .sheet-field .v { font-weight: 700; overflow-wrap: anywhere; }

/* 16.3 Tables --------------------------------------------------------------- */

.pricer-page .sheet-table { width: 100%; border-collapse: collapse; margin-top: 6px; table-layout: fixed; }
.pricer-page .sheet-table th {
  font-size: 7.5pt;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--sheet-steel);
  text-align: left;
  border-bottom: 1.5px solid var(--sheet-ink);
  padding: 4px 6px;
  font-weight: 400;
}
.pricer-page .sheet-table td {
  padding: 5px 6px;
  vertical-align: top;
  border-bottom: 1px solid var(--sheet-hair);
  font-size: 9pt;
  overflow-wrap: anywhere;
}
.pricer-page .sheet-table .num { text-align: right; white-space: nowrap; }
.pricer-page .sheet-table .mid { text-align: center; white-space: nowrap; }
.pricer-page .sheet-table .code { font-size: 8.5pt; }
.pricer-page .sheet-table tr.grp td {
  background: var(--sheet-panel);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  font-size: 8pt;
  border-bottom: 1px solid var(--sheet-rule);
  padding: 5px 6px;
}
.pricer-page .sheet-table tr.sub td {
  border-bottom: 1px solid var(--sheet-rule);
  border-top: 1px solid var(--sheet-rule);
  font-weight: 700;
  font-size: 8.5pt;
}
.pricer-page .sheet-table .nc { color: var(--sheet-steel); }
.pricer-page .sheet-table .note { color: var(--sheet-steel); font-size: 8.5pt; margin-top: 2px; }

/* Column widths, by table. table-layout is fixed, so each set must sum to 100: an
   over-100 total is silently rescaled and the columns stop being the widths they say.
   In CSS rather than inline on each <col> because an inline `style="width:17%"` puts a
   percent sign into the rendered customer sheet, which is the one thing the sheet
   guarantees it never states. */
.pricer-page .t-equip col:nth-child(1) { width: 22%; }
.pricer-page .t-equip col:nth-child(2) { width: 78%; }
.pricer-page .t-cost col:nth-child(1) { width: 20%; }
.pricer-page .t-cost col:nth-child(2) { width: 48%; }
.pricer-page .t-cost col:nth-child(3) { width: 16%; }
.pricer-page .t-cost col:nth-child(4) { width: 16%; }
.pricer-page .t-build col:nth-child(1) { width: 22%; }
.pricer-page .t-build col:nth-child(2) { width: 56%; }
.pricer-page .t-build col:nth-child(3) { width: 22%; }

/* The Marathon sheet's three tables, which carry one column fewer than the Rehlko
   sheet's above and therefore cannot borrow their widths -- under `table-layout: fixed`
   a three-width set applied to a two-column table leaves the third width unused and the
   first two at the wrong ratio.

   The missing column is the part number. TP-6066 gives one for every line it prices;
   the Power Source Midwest quote gives none for its options -- the regulators are named
   products and the modifications are described in words -- so that column has nothing
   to put in it. See equipmentPage() in assets/page-marathon.js: the answer to an empty
   column on a customer's document is to drop the column, not to invent codes for it. */
.pricer-page .t-mn-equip col:nth-child(1) { width: 74%; }
.pricer-page .t-mn-equip col:nth-child(2) { width: 26%; }
.pricer-page .t-mn-cost col:nth-child(1) { width: 52%; }
.pricer-page .t-mn-cost col:nth-child(2) { width: 26%; }
.pricer-page .t-mn-cost col:nth-child(3) { width: 22%; }
.pricer-page .t-mn-build col:nth-child(1) { width: 74%; }
.pricer-page .t-mn-build col:nth-child(2) { width: 26%; }

/* 16.4 Totals, terms and the footer ----------------------------------------- */

.pricer-page .sheet-total-cells { display: flex; margin-top: 14px; border: 1px solid var(--sheet-ink); }
.pricer-page .sheet-total-cell { flex: 1 1 0; padding: 9px 12px; border-right: 1px solid var(--sheet-hair); min-width: 0; }
.pricer-page .sheet-total-cell:last-child { border-right: 0; background: var(--sheet-ink); color: var(--sheet-on-ink); }
.pricer-page .sheet-total-cell .k { font-size: 7pt; letter-spacing: .1em; text-transform: uppercase; color: var(--sheet-steel); }
.pricer-page .sheet-total-cell:last-child .k { color: var(--sheet-hair); }
.pricer-page .sheet-total-cell .v { font-size: 13pt; font-weight: 700; margin-top: 2px; }
.pricer-page .sheet-total-bar {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-top: 14px;
  padding: 9px 12px;
  background: var(--sheet-ink);
  color: var(--sheet-on-ink);
}
.pricer-page .sheet-total-bar .k { font-size: 8.5pt; letter-spacing: .1em; text-transform: uppercase; }
.pricer-page .sheet-total-bar .v { font-size: 15pt; font-weight: 700; }

.pricer-page .sheet-notes { white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-word; margin-top: 10px; font-size: 9pt; }

/* The internal sheet's stamp. Bordered rather than filled so it survives a printer
   set to drop backgrounds — the one element here whose meaning depends on being seen. */
.pricer-page .sheet-stamp {
  margin-top: 12px;
  padding: 6px 10px;
  border: 2px solid var(--sheet-ink);
  font-size: 8.5pt;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  text-align: center;
}

.pricer-page .sheet-terms p { margin: 0 0 7px; font-size: 9pt; }
/* The draft-terms banner, in the packet's own shape (ei_proposal.js's
   .eip-placeholder): bordered, not filled, for the same printer reason as the stamp. */
.pricer-page .sheet-placeholder {
  margin: 10px 0;
  padding: 7px 10px;
  border: 2px solid var(--sheet-ink);
  font-size: 8pt;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  text-align: center;
}

.pricer-page .sheet-foot {
  margin-top: 18px;
  border-top: 1px solid var(--sheet-rule);
  padding-top: 7px;
  font-size: 7pt;
  color: var(--sheet-steel);
  line-height: 1.45;
}
.pricer-page .sheet-foot .addr { margin-top: 5px; }
/* The sheet NAME, where the packet prints "Page N of M".
   NOT a page number, deliberately. The packet earns its numbering with a measuring
   paginator (~400 lines of calibrated constants that pack a flow into sheets and count
   them). This sheet lets the browser break its own pages, so a number written here
   would be right until an equipment list ran to two leaves and then quietly wrong — and
   a wrong page number on a customer's quote is worse than none. The sheet name tells a
   reader which document they are holding, which is the job the number was doing. */
.pricer-page .sheet-foot .sheetname { margin-top: 5px; font-size: 7.5pt; letter-spacing: .06em; text-transform: uppercase; }
.pricer-page .sheet-marks {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 18px;
  flex-wrap: nowrap;
  margin-top: 7px;
}
.pricer-page .sheet-marks img { object-fit: contain; max-height: 26px; max-width: 74px; }
.pricer-page .sheet-marks img.jd { max-width: 38px; max-height: 32px; }

/* 16.5 Print ---------------------------------------------------------------- */

@media print {
  /* `.pricer-page`, not `body`: theme.css's own `body.theme` rule sets the display face
     and 15px/1.5, and a media query adds no specificity, so a bare `body` here would
     lose to it and the sheet would print in the screen theme's type. The same trap,
     and the same fix, as forms/summary.html's print block. */
  .pricer-page {
    background: var(--card);
    font-family: Arial, "Liberation Sans", sans-serif;
    font-size: 9.5pt;
    line-height: 1.38;
  }
  /* The theme's paper ruling is a fixed-position ::before, which some engines paint
     onto the first printed sheet. Switched off rather than left to chance. */
  .pricer-page::before { display: none !important; }

  /* Everything that is not the sheet. The configurator is the tool, not the document. */
  .pricer-page > .app-head,
  .pricer-page > .wrap { display: none !important; }

  /* Shown whether or not the preview toggle was ever pressed, so Ctrl+P and the Print
     button put the same thing on paper. [hidden] is restated because that is the state
     the page is in for everyone who did not press Preview. */
  .pricer-page .pricer-sheet,
  .pricer-page .pricer-sheet[hidden] { display: block !important; }

  .pricer-page .sheet-page {
    width: auto;
    min-height: auto;
    margin: 0;
    padding: 0;
    box-shadow: none;
  }
  /* Every sheet after the first starts its own leaf. An adjacent-sibling rule rather
     than a class, so a sheet added later needs no rule of its own, and a run that
     renders only one sheet forces no trailing blank page. */
  .pricer-page .sheet-page + .sheet-page { break-before: page; page-break-before: always; }

  /* A row split across a leaf loses the part number from its description, and a
     heading stranded at the foot of a page is a heading for nothing. */
  .pricer-page .sheet-table tr,
  .pricer-page .sheet-letterhead,
  .pricer-page .sheet-hero,
  .pricer-page .sheet-stats,
  .pricer-page .sheet-total-cells,
  .pricer-page .sheet-total-bar,
  .pricer-page .sheet-foot { break-inside: avoid; page-break-inside: avoid; }
  .pricer-page .sheet-h,
  .pricer-page .sheet-placeholder { break-after: avoid; page-break-after: avoid; }
  /* The table head repeats on every leaf a long equipment list runs onto. This is the
     browser's own version of the packet's "continued" marker; without it a reader
     picking up the second leaf has unlabelled columns. */
  .pricer-page .sheet-table thead { display: table-header-group; }

  /* The panels, bands and bars are structure, not decoration: without them the sheet
     reads as one undifferentiated block, and the inverted total cell would print white
     text on white. The partner marks make the same request forms/summary.html makes
     for its own. */
  .pricer-page .sheet-hero,
  .pricer-page .sheet-table tr.grp td,
  .pricer-page .sheet-total-bar,
  .pricer-page .sheet-total-cell:last-child,
  .pricer-page .sheet-stamp,
  .pricer-page .sheet-placeholder,
  .pricer-page .sheet-marks img {
    print-color-adjust: exact;
    -webkit-print-color-adjust: exact;
  }
}
