/*
 * New Order — Step 1 (CR-A116-18). Classes lifted verbatim from the frozen
 * card (kpm/products/ynetplus/cr/attachments/order-flow-r1.html rev4
 * `#step1`) — same selectors, same values, drawing on tokens.css (identical
 * :root variable names to the card's own, see tokens.css header). Additions
 * beyond the static mockup are called out inline; each is a functional
 * necessity (interactivity/accessibility), never a visual deviation.
 *
 * 🔴 No qty/stepper, no Subtotal/HST row styling exists here on purpose —
 * the 2026-08-12 card correction deleted them; see shared/price-rail.js.
 */

/* =====================================================================
 * Shared wizard contract (CR-A116-38) — the seam between this CSS and the
 * three step modules (step-product.js, step-job-site.js, step-delivery.js).
 * Class names are FIXED by the CR so all four workstreams (this file +
 * modal.css, and the three JS files) stay file-disjoint; do not rename.
 * Values below are taken verbatim from the ratified cards' own error-box /
 * qq-foot / qq-foot-slot / btn-back / btn-primary rules (new-order-1-
 * product.html, order-jobsite-r1.html, new-order-3-delivery.html) — one
 * shape, used on all three steps (Drew's ruling #2: Back and Next live in
 * one consistent place across the wizard).
 * ===================================================================== */

/* ---------- .nof-error — the ONE error language, every failure, every
   step. Geometry/colors copied from new-order-3-delivery.html's .error-box,
   the card's own "corrected" version (its BUILD NOTES: "build it exactly
   as-is, don't route it through another container's error class") — this
   fixes the real defect where step 3's failure message rendered as
   unstyled black text because the old error style was scoped to a
   container step 3 never used. #FECACA has no token in tokens.css — same
   one-off border color .qq-foot .order-error already hardcodes below. */
.nof-error {
  background: var(--danger-soft);
  border: 1px solid #FECACA;
  border-radius: var(--r-md);
  padding: 14px 16px;
  display: flex;
  gap: 10px;
  align-items: flex-start;
}
.nof-error__icon {
  width: 20px; height: 20px; border-radius: 50%; background: var(--danger);
  display: flex; align-items: center; justify-content: center; flex-shrink: 0; margin-top: 1px;
}
.nof-error__icon svg { width: 11px; height: 11px; stroke: #fff; stroke-width: 3; fill: none; }
.nof-error strong { display: block; font-size: 13px; font-weight: 700; color: var(--danger); margin-bottom: 2px; }
.nof-error p { margin: 0; font-size: 13px; color: var(--danger); line-height: 1.5; }

/* ---------- .nof-footer — a step's own footer row. [[BF-611]] 2026-08-24:
   now rendered INTO the shared modal shell's real handle.foot (see the
   `.modal-foot.new-order-modal-foot` rule further below) rather than inline
   in the scrolling body — the "shell contributes NO footer" framing this
   comment used to open with is no longer true; kept only as the step
   footers' own class/markup, unchanged.
   Back sits left, Next sits right via margin-left:auto on .nof-footer__next
   — this (rather than justify-content:space-between plus an empty
   placeholder div) is how Next stays glued to the same right-hand slot on
   step 1, where .nof-footer__back is simply absent rather than rendered-
   empty: an auto-margin doesn't care whether anything precedes it. */
/* [[BF-549]] 2026-08-19 — sticky bottom, mirrors .ctx-ladder's sticky top
   above (see that rule's header note for the full -18px derivation: it
   cancels .modal-body's own 18px padding, modal.css, uniformly for every
   step sharing that one scrollport). background/border-top already present
   below are enough to occlude scrolled content underneath once stuck — no
   new color/border needed here, unlike .step3-footer-row below which had
   neither. Covers both consumers unchanged: step 1's `.qq-foot.nof-footer`
   combo (qq-foot's own later padding/background rule still wins the
   cascade, untouched) and step 2's plain `.nof-footer` inside #jsFooter. */
.nof-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
  padding: 14px 20px;
  border-top: 1px solid var(--line);
  background: var(--field);
  position: sticky;
  bottom: -18px;
  z-index: 10;
}
.nof-footer__back {
  height: 48px; padding: 0 18px; border: 1.5px solid var(--line); border-radius: var(--r-md);
  background: var(--bg); color: var(--ink-soft); font-size: 14px; font-weight: 700;
  display: flex; align-items: center; gap: 6px; cursor: pointer; font-family: var(--font); flex-shrink: 0;
}
.nof-footer__back:hover { background: var(--field); }

.nof-footer__next {
  margin-left: auto;
  height: 48px; padding: 0 22px; border: none; border-radius: var(--r-md); background: var(--blue); color: #fff;
  font-size: 14px; font-weight: 700; display: flex; align-items: center; justify-content: center; gap: 8px;
  cursor: pointer; transition: background var(--dur-1) var(--ease); font-family: var(--font);
}
.nof-footer__next:hover { background: var(--blue-deep); }

/* CR-A116-41 fix (Drew's UAT 2026-08-19): the footer's right side is an
   explicit SLOT — a right-docked column holding the primary button with its
   reason/caption line beneath it. Replaces the flex-basis:100% wrap trick,
   which in practice left the reason line ON the button row (misaligning Next
   against Back) and, in step 1, put the caption inside the blue-painted
   wrapper. The slot always reserves the caption row's height so the footer
   never changes size when a reason appears or clears (nothing moves). */
.nof-footer__slot { margin-left: auto; display: flex; flex-direction: column; align-items: flex-end; gap: 4px; }
.nof-footer__slot .nof-reason, .nof-footer__slot .cta-caption { flex-basis: auto; margin: 0; height: 16px; line-height: 16px; text-align: right; overflow: hidden; }
/* The slot is a column (button + caption row), so centering the footer's
   items would sink Back to the slot's vertical middle — Back must align with
   the Next BUTTON, not the slot. Top-align and let both buttons share row 1. */
.nof-footer:has(.nof-footer__slot) { align-items: flex-start; }

.nof-footer__next .icon { stroke: #fff; }

/* [[BF-611]] 2026-08-24 — New Order modal footer pinning. new-order-modal.js
   now passes its modal shell's real `handle.foot` (marker class added there:
   `.new-order-modal-foot`) as each step's footHost instead of leaving
   `handle.foot` hidden while every step rendered its own footer inline in
   the scrolling body. `.modal-foot` (modal.css) carries its own box
   treatment (padding/border-top) — doubling up with the step footer's own
   (`.nof-footer`/`.step3-footer-row`, both already fully styled: padding,
   border-top, background) would draw two stacked boxes. This neutralizes
   the shell's own box so the step's footer supplies the only visible one,
   and makes it a plain block so the step footer's own width/layout rules
   (already correct on their own — see .nof-footer__slot's margin-left:auto
   above) aren't fighting `.modal-foot`'s flex/justify-content:flex-end
   sizing a non-full-width child. */
.modal-foot.new-order-modal-foot {
  display: block;
  padding: 0;
  border-top: none;
}
.modal-foot.new-order-modal-foot > .nof-footer,
.modal-foot.new-order-modal-foot > .step3-footer-row {
  position: static;
}
/* .step3-footer-row (unlike .nof-footer/.qq-foot) has never carried its own
   horizontal padding — it relied on the 18px inset .modal-body gave every
   scrolling child (see the "No padding added" note on its own rule below).
   Moved out to .modal-foot, that ambient inset is gone, so this supplies its
   own — 20px, matching the other two footers' existing convention — and
   drops the margin-top that used to space it from the content flowing above
   it in the same scrollport (irrelevant now; it's the footer's own region). */
.modal-foot.new-order-modal-foot > .step3-footer-row {
  padding: 0 20px;
  margin-top: 0;
}

/* ---------- .nof-reason — the short muted line beneath a blocked primary.
   flex-basis:100% forces it onto its own row inside .nof-footer's wrapping
   flex row (a 2-item row wraps a 3rd item without needing an extra wrapper
   element); margin-left:auto then docks that new row's content under the
   right-aligned Next button — same visual result as the card's own
   .block-reason, without a wrapper class the CR's contract doesn't name. */
.nof-reason {
  flex-basis: 100%;
  max-width: 230px;
  margin-left: auto;
  margin-top: 2px;
  text-align: right;
  font-size: 11.5px;
  color: var(--muted);
}

/* ---------- .is-blocked / .is-submitting — modifiers on a primary CTA.
   Compounded against every primary-button class this app actually uses
   (.nof-footer__next here, plus the pre-existing .btn-primary/.btn-place)
   so the modifier works regardless of which base class a given step's CTA
   carries — bare `.is-blocked`/`.is-submitting` stay as a fallback. */
.is-blocked,
.nof-footer__next.is-blocked,
.btn-primary.is-blocked,
.btn-place.is-blocked {
  background: #E5E7EB;
  color: var(--muted);
  cursor: not-allowed;
  pointer-events: none;
}
.is-blocked .icon,
.nof-footer__next.is-blocked .icon,
.btn-primary.is-blocked .icon,
.btn-place.is-blocked .icon {
  stroke: var(--muted);
}

.is-submitting,
.nof-footer__next.is-submitting,
.btn-primary.is-submitting,
.btn-place.is-submitting {
  background: var(--blue-deep);
  color: #fff;
  cursor: not-allowed;
  pointer-events: none;
}
.spinner-ring {
  width: 14px; height: 14px; border-radius: 50%; border: 2px solid rgba(255, 255, 255, .35);
  border-top-color: #fff; flex-shrink: 0; animation: nof-spin .7s linear infinite;
}
@keyframes nof-spin { to { transform: rotate(360deg); } }

/* ---------- .nof-reserve — generic marker for "this block holds its
   worst-case height, empty space is fine, the box itself never resizes"
   (Drew's ruling #1). Exact heights are per-block, not per this utility —
   see .price-block/.site-summary/.unit-reserved below, each already
   carrying its own fixed height from its own card — so .nof-reserve only
   adds the two properties every such block needs regardless of its
   height: a hard box-sizing and clipped overflow, so nothing inside can
   force the box taller than its reserved number. */
.nof-reserve { box-sizing: border-box; overflow: hidden; }

.nof-footer__back:focus-visible,
.nof-footer__next:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
}

.qq-card-wrap {
  display: flex;
  justify-content: center;
  /* BF-live-2026-08-17 (Drew): was var(--sp-4) [16px] top/bottom — doubled
     up with modal-body's own 18px padding (modal.css) once this card moved
     inside the modal shell (CR-A116-30). Tightened to the next token down;
     the card's own internal spacing (untouched here) still reads as a
     distinct block. */
  padding: var(--sp-2) 0;
}

.qq-card {
  width: 100%;
  /* 2026-08-17 (Drew, live directive — modal wider+shorter): was 640px.
     The modal shell's `l` size widened 720 -> 900px (modal.css) specifically
     so Step 1 could trade vertical stacking for horizontal room instead of
     just adding side margin; this card now uses that room via .qq-two-col
     below (Edge style+Edge colour, Width/Height+Price paired side-by-side)
     rather than growing wider for its own sake. */
  max-width: 800px;
  /* [[BF-494]] 2026-08-17 — FRAMELESS inside the modal. This block used to
     draw `background: var(--bg); border: 1px solid var(--line); border-radius:
     var(--r-lg); box-shadow: var(--shadow-pop)`, which was correct while
     Step 1 was a standalone PAGE. CR-A116-30 moved the flow into the shared
     modal shell (assets/js/shared/modal.js), and the shell already supplies
     exactly that frame — so the build drew a bordered, shadowed card INSIDE
     a bordered, shadowed modal.
     The ratified card nulls it for this context verbatim
     (new-order-1-product.html:100):
       .qq-card{width:100%; background:transparent; border:none; border-radius:0;}
     `overflow: hidden` went with it — it only existed to clip child borders
     against the (now absent) rounded corner.
     Scope note: `.qq-card` has NO non-modal caller left. Its only consumers
     are step-product.js (this step), step-delivery.js's shimmer/error states
     and new-order-modal.js's own loader — all three render inside the modal,
     so the null is applied on the base rule rather than under an `.m-shell`-
     style guard that could never discriminate anything. The shimmer keeps its
     own visible surface explicitly — see .qq-card.shimmer-card below. */
  background: transparent;
  border: none;
  border-radius: 0;
  box-shadow: none;
}
/* Pairs two adjacent .order-choice-group sections (or a section + the price
   slot) side-by-side instead of stacked — the concrete mechanism for
   "trade vertical space for the newly-available horizontal space" (Drew,
   2026-08-17). Only used where both halves are non-empty (step-product.js's
   twoUp() helper falls back to plain concatenation otherwise, so a lone
   section never sits half-width with dead space next to it). Collapses back
   to one column under 760px available width (tablet shell + phone) — same
   threshold philosophy as modal.css's own `l` tablet breakpoint. */
.qq-two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; align-items: start; }
.qq-two-col > .order-choice-group,
.qq-two-col > #step1PriceSlot { min-width: 0; }
@media (max-width: 760px) {
  .qq-two-col { grid-template-columns: 1fr; gap: var(--sp-2); }
}
/* BF-live-2026-08-17: qq-head/qq-body/qq-group-label vertical spacing
   tightened one token step each (Drew — modal body needed internal
   scrolling to see the price box on a normal laptop viewport). Horizontal
   padding, font-sizes, and the 44px tap targets below are untouched — this
   is a density pass only, no visual redesign. */
.qq-head { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-2) 20px; border-bottom: 1px solid var(--line); }
.qq-head .badge {
  width: 40px; height: 40px; border-radius: var(--r-md); background: var(--blue-soft);
  color: var(--blue-deep); display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.qq-head h2 { font-size: 16px; font-weight: 700; color: var(--ink); margin: 0; }
.qq-head p { font-size: 13px; color: var(--muted); margin: 2px 0 0; }
.qq-body { padding: var(--sp-2) 20px; display: flex; flex-direction: column; gap: var(--sp-2); }

/* =====================================================================
 * Context ladder (CR-A116-45) — Drew's live-UAT ruling, 2026-08-19: one
 * shared, read-only PRODUCT/SITE strip, pinned as the FIRST element inside
 * each wizard step's own body container, directly under the modal header.
 * Rendered by the one shared helper in new-order-modal.js
 * (wizard.ladderHtml()/wizard.patchLadder()) — consumed unmodified by all
 * three step modules, not three hand-copies. Geometry/colours verbatim from
 * the ratified cards (new-order-1-product.html / order-jobsite-r1.html /
 * new-order-3-delivery.html, Claude Design "YNET+ Client App").
 *
 * Fixed 44px content row, NEVER wraps, NEVER changes height as a cell
 * fills — white-space:nowrap + real ellipsis (min-width:0 on the flex text
 * cells) is what keeps that promise regardless of how long an address or
 * product description gets.
 *
 * Bleed math (the negative-margin trick that lets the strip span edge-to-
 * edge under the header): the cards' own mockup uses one simplified body
 * wrapper: production nests three DIFFERENT real containers with three
 * different real paddings, so each host below cancels exactly its OWN
 * padding — same per-host override pattern the cards themselves use for
 * `.gal-card .ctx-ladder`.
 *
 * [[BF-549]] 2026-08-19 (Drew's live UAT: «اون لدر که اضافه شده، باعث شده
 * دگمه های پایین برن پایین‌تر و مودال اسکرول بخوره» — the ladder pushed the
 * footer below the fold). Sticky top pins the ladder under modal.css's
 * `.modal-head`; sticky bottom on each step's own footer (below) pins IT
 * to the shell's bottom edge — `.modal-body` (modal.css) stays the ONLY
 * scrolling element, only the content between the two now moves.
 * `top: -18px` cancels `.modal-body`'s own `padding: 18px` (modal.css
 * literal, matched here, not invented — same cross-file-literal pattern
 * job-site.css's own comments already use): sticky's offset is resolved
 * against the nearest SCROLLING ancestor's padding edge (`.modal-body`),
 * not against any of the non-scrolling wrappers in between (`.qq-card-wrap`
 * / `.job-site-card-wrap`'s own `padding: var(--sp-2) 0`, `.qq-card` /
 * `.job-site-card`'s own zero padding) — those wrappers only change WHEN
 * stickiness engages during scroll, never WHERE it rests once engaged. That
 * makes this one offset correct for all three hosts below unchanged,
 * despite their differing padding — verified against modal.css's own
 * "`.modal-body` is already the only scrolling element" note, not assumed.
 * z-index clears `.places-dropdown` (job-site.css, z-index:5 — the create-
 * site address autocomplete, the only other stacked element that can
 * scroll near this strip). */
.ctx-ladder {
  height: 44px; flex-shrink: 0; display: flex; align-items: stretch;
  background: var(--field); border-bottom: 1px solid var(--line);
  position: sticky; top: -18px; z-index: 10;
}
.ctx-cell { flex: 1; min-width: 0; display: flex; align-items: center; gap: var(--sp-2); overflow: hidden; }
.ctx-cell + .ctx-cell { margin-left: 18px; padding-left: 18px; border-left: 1px solid var(--line); }
.ctx-badge {
  flex-shrink: 0; display: inline-flex; align-items: center; height: 18px; padding: 0 7px;
  border-radius: var(--r-sm); font-size: 9.5px; font-weight: 800; letter-spacing: .06em;
  text-transform: uppercase; color: #fff;
}
.ctx-badge.product { background: var(--violet); }
.ctx-badge.site { background: var(--teal); }
.ctx-val {
  min-width: 0; font-size: 12.5px; font-weight: 600; color: var(--ink-soft);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ctx-val.placeholder { color: var(--faint); font-weight: 500; }

/* Step 1 host: .qq-card-wrap > .qq-card > .qq-body (padding: var(--sp-2) 20px, above). */
.qq-body .ctx-ladder { margin: calc(-1 * var(--sp-2)) -20px 0; padding: 0 20px; }

/* Step 2 host: .job-site-card > .card-body (padding: var(--sp-3) 22px —
   job-site.css's own existing literal, matched here, not invented). */
.job-site-card .card-body .ctx-ladder { margin: calc(-1 * var(--sp-3)) -22px 0; padding: 0 22px; }

/* Step 3 host: .step3-wrap (padding: var(--sp-2) 0, below) — replaces the
   deleted .identity-bar in this exact slot. No horizontal bleed needed
   (the host already has 0 horizontal padding); var(--sp-6) reserves the
   same gap below the strip the old identity-bar's own margin-bottom did. */
.step3-wrap .ctx-ladder { margin: calc(-1 * var(--sp-2)) 0 var(--sp-6); padding: 0 20px; }

.qq-group-label {
  font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted); margin-bottom: var(--sp-1); display: flex; align-items: center; gap: 6px;
}

.btn-grid { display: grid; gap: 8px; }
.btn-grid.cols-4 { grid-template-columns: repeat(4, 1fr); }
.btn-grid.cols-5 { grid-template-columns: repeat(5, 1fr); }
.btn-grid.cols-2 { grid-template-columns: repeat(2, 1fr); }
.opt-btn {
  min-height: 44px; border: 1.5px solid var(--line); border-radius: var(--r-md); background: var(--field);
  color: var(--ink-soft); font-size: 13px; line-height: 1.45; font-weight: 600; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 2px; cursor: pointer; padding: 8px 4px;
  transition: border-color var(--dur-1) var(--ease), background var(--dur-1) var(--ease);
  position: relative; font-family: var(--font);
  /* pixel-round2 §4.2: <div> in the card -> <button> in the build inherits
     Chromium's UA text-align: center; this element's own align-items
     already centers via flex, so the visual is identical, but the leaked
     text-align was still a measured mismatch. text-align is inherited, so
     this one declaration also closes .opt-btn .cap below (BF-460-shaped
     "fix per class" instruction — one rule, not a per-element patch). */
  text-align: left;
}
.opt-btn .cap { font-size: 10px; line-height: 1.45; font-weight: 500; color: var(--faint); }
.opt-btn.selected { border-color: var(--blue); background: var(--blue-soft); color: var(--blue-deep); }
.opt-btn.selected .cap { color: var(--blue); }
.opt-btn.selected .check {
  position: absolute; top: 4px; right: 4px; width: 14px; height: 14px; border-radius: 50%;
  background: var(--blue); display: flex; align-items: center; justify-content: center;
}
.opt-btn.selected .check svg { width: 9px; height: 9px; stroke: #fff; stroke-width: 3; fill: none; }
/* :focus-visible ring — not on the static card (a mockup draws no focus
   states); required by carried-forward CR-A116-2 Round-5 Ruling #10 /
   QA-Scenario 5 (a11y regression) — same treatment shell.css already gives
   .nav-item. */
.opt-btn:focus-visible,
.swatch-btn:focus-visible,
.btn-primary:focus-visible,
.dim-input:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
}

.swatch-row { display: flex; gap: 12px; flex-wrap: wrap; }
/* Card draws .swatch-btn as a non-interactive <div> wrapper around a
   circle + label; here the WHOLE wrapper is the real <button> (one clear
   ≥44px tap target that still shows the circle + label exactly as drawn),
   rather than only the 36px circle being clickable. */
.swatch-btn {
  display: flex; flex-direction: column; align-items: center; gap: 6px; min-width: 56px;
  min-height: 44px; border: none; background: none; padding: 4px; cursor: pointer; font-family: var(--font);
  font-size: 16px; line-height: 1.45; color: var(--ink);
}
.swatch { width: 36px; height: 36px; border-radius: 50%; position: relative; display: block; }
.swatch-btn.selected .swatch { box-shadow: 0 0 0 3px var(--blue-soft); }
.swatch-btn.selected .swatch::after {
  content: ''; position: absolute; inset: 0; border-radius: 50%; box-shadow: 0 0 0 2px #fff inset;
}
.swatch-label { font-size: 11px; font-weight: 600; color: var(--muted); }
.swatch-btn.selected .swatch-label { color: var(--blue-deep); }

.dim-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.field-label {
  font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted); margin-bottom: 5px; display: block;
}
.dim-input {
  width: 100%; padding: var(--sp-2) 12px; border: 1.5px solid var(--line); border-radius: var(--r-md);
  background: var(--field); font-family: var(--mono); font-size: 15px; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.dim-input.filled { border-color: var(--blue); background: #fff; }
/* Scoped under .dim-row (not a bare `.field-hint`) — settings.css defines
   its OWN `.field-hint` (11.5px/1.5) and, loaded after this file in
   index.html's global stylesheet list, was winning ties on same-specificity
   selectors across every page, including this one (measured: 11.5px/17.25px
   built vs this file's intended 11px/15.95px — CR-A116-integration-pixel-
   report §3.4). Scoping raises specificity without touching settings.css's
   own page. */
.dim-row .field-hint { font-size: 11px; line-height: 1.45; color: var(--faint); margin-top: var(--sp-1); }

/* Fixed 92px in every state — idle / pending / ready / problem (CR-A116-38
   shared contract, Drew's ruling #1: "nothing inside the modal resizes").
   Height/box-sizing/flex/overflow added here (new-order-1-product.html's
   own .price-block); padding stays the file's existing var(--sp-2) 16px
   density-pass value (BF-live-2026-08-17), not the card's un-tightened
   12px 16px — untouched on purpose, this edit is the height mechanic only. */
.price-block {
  height: 92px;
  box-sizing: border-box;
  background: var(--band); border: 1px solid var(--line); border-radius: var(--r-md); padding: var(--sp-2) 16px;
  display: flex; flex-direction: column; justify-content: center; overflow: hidden;
}
/* "Problem" sub-state — the block still reserves 92px, but the .nof-error
   inside fills it edge-to-edge instead of sitting inside the band/border
   frame (verbatim new-order-1-product.html: `.price-block.problem`). */
.price-block.problem { padding: 0; background: transparent; border: none; }
.price-block.problem .nof-error { height: 100%; box-sizing: border-box; }
/* Idle / pending sub-states (verbatim new-order-1-product.html gallery). */
.price-idle { font-size: 12.5px; color: var(--muted); text-align: center; }
.price-pending { display: flex; align-items: center; justify-content: center; gap: 8px; font-size: 12.5px; color: var(--muted); }
.price-pending .spin-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--blue); opacity: .55; }
.price-row { display: flex; align-items: baseline; justify-content: space-between; font-size: 13px; color: var(--ink-soft); padding: 4px 0; }
.price-row .amt { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.price-row.muted { color: var(--muted); font-size: 12px; }
.price-row.total { padding-top: var(--sp-1); }
.price-row.total .label { font-size: 14px; font-weight: 700; color: var(--ink); }
.price-row.total .amt { font-size: 24px; font-weight: 800; color: var(--navy); font-family: var(--mono); }
/* Pending/idle placeholder glyph — not a stale price, not blank (INV-6). */
.price-row .amt.price-pending { color: var(--faint); font-weight: 700; }
.price-block-pending { opacity: .85; }

/* No-match / error sub-state — reuses .note-box (already defined for this
   exact purpose on the card's own .substates-row demo). */
.price-block-warning { background: none; border: none; padding: 0; }
.note-box {
  border-radius: var(--r-md); padding: 12px 14px; display: flex; gap: 10px; align-items: flex-start; font-size: 13px;
}
.note-box.amber { background: var(--amber-soft); border: 1px solid #FDE68A; color: var(--amber); }
/* CR-A116-41 (R2 — dead-product honesty): a neutral/informational variant —
   nothing failed here, the catalog moved, so this deliberately does not
   reuse .amber (warning) or .nof-error (red/failure). Same tokens the app's
   primary accent already uses (tokens.css --blue/--blue-soft), border color
   follows .amber's own precedent of a literal one-off hex not in tokens.css
   (#FECACA/#FDE68A) — #BFDBFE is the matching light-blue border. */
.note-box.info { background: var(--blue-soft); border: 1px solid #BFDBFE; color: var(--blue-deep); }
.note-box .icon { margin-top: 2px; }
.note-box strong { display: block; font-weight: 700; margin-bottom: 2px; color: inherit; }
.note-box span.sub { color: var(--ink-soft); font-weight: 400; display: block; }

.qq-foot { padding: var(--sp-2) 20px; border-top: 1px solid var(--line); background: var(--field); }
.qq-foot .order-error {
  font-size: 12.5px; color: var(--danger); background: var(--danger-soft); border: 1px solid #FECACA;
  border-radius: var(--r-md); padding: 8px 12px; margin-bottom: 10px;
}
/* Scoped under .qq-foot — `.btn-primary` is also defined (differently:
   `padding: 0 24px`, `display: inline-flex`) by client-table.css and
   dashboard.css, both loaded LATER in index.html's global stylesheet list,
   so their padding was winning ties over this file's full-width footer CTA
   everywhere, incl. Step 1's "Next — Job site" (measured padding-x: 6px
   card vs 24px built — CR-A116-integration-pixel-report §3.4). Scoping
   raises specificity without touching the other two files' own contexts. */
.qq-foot .btn-primary {
  width: 100%; height: 48px; padding: 1px 6px; border: none; border-radius: var(--r-md); background: var(--blue); color: #fff;
  font-size: 14px; font-weight: 700; display: flex; align-items: center; justify-content: center; gap: 8px;
  cursor: pointer; transition: background var(--dur-1) var(--ease); font-family: var(--font);
}
.qq-foot .btn-primary:hover { background: var(--blue-deep); }
.qq-foot .btn-primary:disabled { cursor: not-allowed; opacity: .6; }
.qq-foot .btn-primary .icon { stroke: #fff; }
.cta-caption { font-size: 11px; color: var(--faint); text-align: center; margin-top: var(--sp-1); font-family: var(--mono); }

/* CR-A116-38 item 2 — the collapsed, pre-answered product entry row (a
   default-applied selection is already locked in, only "Change" reopens the
   picker). No shared-contract class covers this shape and nothing already
   in this file matched a "locked value + Change chip" row, so step-product.js
   originally rendered it with inline styles (every value an existing design
   token, nothing invented) and flagged it in the forge report for promotion
   here. `#step1ProductSummary`/`#step1ChangeProduct` (step-product.js) are
   stable ids kept as JS hooks; the classes below are the promoted styling. */
.step1-summary {
  display: flex; align-items: center; gap: 12px; background: var(--field); border: 1px solid var(--line);
  border-radius: var(--r-md); padding: 11px 13px;
}
.step1-summary-label { font-size: 10.5px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.step1-summary-value { flex: 1; font-size: 13.5px; font-weight: 600; color: var(--ink); }
.step1-summary-change {
  font-size: 12px; font-weight: 700; color: var(--blue-deep); background: var(--blue-soft); border: 1px solid #BFDBFE;
  padding: 5px 11px; border-radius: var(--r-md); cursor: pointer; font-family: var(--font);
}

/* [[BF-494]]: .qq-card is frameless now (see above), so the loading
   placeholder — which is nothing BUT a surface — needs its own or it renders
   as 420px of invisible nothing. Uses --field (the same neutral the shimmer
   blocks themselves use) rather than restoring the old popped card frame:
   this is a placeholder inside the modal, not a second card. Consumers:
   step-product.js's renderSkeleton(), step-delivery.js's own shimmer, and
   new-order-modal.js's initial loader. */
.qq-card.shimmer-card { min-height: 420px; background: var(--field); border-radius: var(--r-lg); }
.qq-shimmer-block { background: var(--field); border-radius: var(--r-md); animation: qq-pulse 1.4s ease-in-out infinite; }
@keyframes qq-pulse { 0%, 100% { opacity: .55; } 50% { opacity: 1; } }

/* ---------- phone reflow (~480px, matches shell.css's own breakpoint) ---------- */
@media (max-width: 480px) {
  .btn-grid.cols-4 { grid-template-columns: repeat(2, 1fr); }
  .btn-grid.cols-5 { grid-template-columns: repeat(3, 1fr); }
  .dim-row { grid-template-columns: 1fr 1fr; }
}

/* =====================================================================
 * Step 2 — Job site (CR-A116-38). Classes lifted verbatim from the
 * ratified card (order-jobsite-r1.html), same selectors/values, drawing
 * on tokens.css. Scope here is the two reserved-height blocks the CR names
 * (site-summary 56px, unit-reserved 96px) — NOT the full step-2 surface
 * (results-slot/mini-search/empty-state/confirmed-tag live in job-site.css).
 * The search typewriter placeholder and the unit-slot shimmer are each
 * implemented by step-job-site.js its own working way (an animated
 * `placeholder` attribute rewrite, and the pre-existing `.skel`/
 * `.skeleton-row` classes respectively) — gap-reconciliation review
 * (CR-A116-38) found this file's own card-accurate `.tw`/`.tw-txt`/
 * `.tw-caret` and `.skel-chip`/`.skel-chip-row` rules unreachable
 * (zero references) and removed them; do not reintroduce them here.
 * ===================================================================== */

/* ---------- search — closed until the client types (Drew's ruling);
   idle placeholder cycles an example query, typewriter-style. */
.search-wrap { position: relative; }
.search-input {
  width: 100%; padding: 11px 14px 11px 38px; border: 1.5px solid var(--blue); border-radius: var(--r-md);
  background: #fff; font-size: 14px; color: var(--ink); box-shadow: 0 0 0 3px var(--blue-soft); font-family: var(--font);
}
.search-input.idle { border-color: var(--line); box-shadow: none; color: var(--faint); }
.search-wrap .icon { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: var(--faint); }

/* ---------- selected-site summary — FIXED 56px, always two lines
   («دو خط ثابت», Drew's ruling), nothing picked / a single address / a
   building+unit are all the same box height. */
.site-summary {
  height: 56px; box-sizing: border-box; border: 1px solid var(--line); border-radius: var(--r-md); background: var(--field);
  padding: 9px 12px; display: flex; align-items: center; justify-content: space-between; gap: 10px; overflow: hidden;
}
.site-summary .ss-txt { min-width: 0; }
.site-summary .l1 { font-size: 13px; font-weight: 700; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.site-summary .l2 { font-size: 11.5px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 1px; }
.site-summary.empty .l1 { color: var(--faint); font-weight: 600; }
.site-summary.empty .l2 { color: var(--faint); }
.site-summary .btn-change {
  flex-shrink: 0; background: none; border: none; padding: 2px 0; margin: 0; font-family: var(--font);
  font-size: 12px; font-weight: 600; color: var(--blue); cursor: pointer; text-decoration: underline; text-underline-offset: 2px;
}

/* ---------- unit picker — FIXED 96px reserved slot, shimmer before a unit
   exists (Drew's ruling). Above 24 units the same slot becomes a mini
   search+list (order-jobsite-r1.html state 06) — not built in this pass;
   the slot's own height/frame already accommodates it unchanged. */
.unit-reserved {
  height: 96px; box-sizing: border-box; border: 1px solid var(--line); border-radius: var(--r-md); background: #fff;
  padding: 12px; display: flex; flex-direction: column; justify-content: center; gap: 8px;
}
.unit-reserved .ur-label {
  font-size: 10.5px; font-weight: 800; letter-spacing: .05em; text-transform: uppercase; color: var(--muted);
  display: flex; align-items: baseline; gap: 5px;
}
.unit-reserved .ur-label .num { font-family: var(--mono); font-weight: 700; color: var(--muted); }
.unit-chip-row { display: flex; gap: 8px; flex-wrap: wrap; }
.unit-chip {
  min-width: 44px; height: 36px; padding: 0 12px; border-radius: var(--r-md); border: 1.5px solid var(--line);
  background: #fff; color: var(--ink-soft); font-size: 13px; font-weight: 700; display: flex; align-items: center;
  justify-content: center; cursor: pointer; font-family: var(--font);
}
.unit-chip.selected { border-color: var(--blue); background: var(--blue); color: #fff; }
.unit-chip.new-chip { border-style: dashed; color: var(--muted); font-weight: 600; font-size: 12px; gap: 6px; }
.unit-chip.new-chip .icon { width: 13px; height: 13px; }
.unit-chip:focus-visible, .site-summary .btn-change:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
}

/* Reduced motion — tokens.css's blanket near-zero-duration rule covers the
   rest; the submit spinner is explicit here since it's an infinite loop, not
   a one-shot transition. */
@media (prefers-reduced-motion: reduce) {
  .spinner-ring { animation: none; }
}

/* =====================================================================
 * Step 3 — Delivery & review (CR-A116-21). Classes lifted verbatim from
 * the frozen card (kpm/products/ynetplus/cr/attachments/order-flow-r1.html
 * rev4 #step3), same selectors/values, drawing on tokens.css. Additions
 * beyond the static mockup are called out inline — each is a functional
 * necessity (interactivity/a11y/real data states), never a visual
 * deviation. See step-delivery.js's own header for the disclosed gaps
 * (terms box, pickup warehouse address, locked-day design) this CSS
 * supports rendering honestly rather than fabricating.
 * ===================================================================== */

/* BF-live-2026-08-17: step3-wrap/identity-bar/option-grid/option-card
   vertical spacing tightened (Drew — modal body needed internal scrolling
   to see the Place Order button on a normal laptop viewport). Same
   redundant-double-padding fix as step1's qq-card-wrap (modal-body already
   pads 18px) plus one token step down on inter-block margins. Font-sizes
   and the 44px day-pill/button tap targets are untouched.
   max-width bumped 640 -> 800 in the same pass, matching step-product.js's
   .qq-card (see that file's own 2026-08-17 header note): the modal's `l`
   size widened 720 -> 900px (modal.css) specifically so wizard steps could
   trade vertical stacking for horizontal room. Step 3 didn't get a
   two-column reflow like Step 1's twoUp() — its content doesn't pair as
   cleanly — but .review-strip's own flex-wrap was folding to 2 rows purely
   because 640px wasn't wide enough for its 4 fields + total, not because
   the content needs 2 rows. The extra width alone resolves that wrap. */
.step3-wrap { max-width: 800px; margin: 0 auto; padding: var(--sp-2) 0; }

/* CR-A116-45 — .identity-bar (+ .ro-tag) removed entirely: the context
   ladder (.ctx-ladder, above) now occupies this exact slot. No other
   consumer existed (grep-confirmed before deletion). */

.option-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-6); margin-bottom: var(--sp-2); }
.option-card {
  border: 2px solid var(--line); border-radius: var(--r-lg); background: var(--bg); padding: var(--sp-2) var(--sp-3); position: relative;
  transition: border-color var(--dur-1) var(--ease);
}
.option-card.selected { border-color: var(--blue); box-shadow: 0 0 0 3px var(--blue-soft); }
.option-card .oc-head { display: flex; align-items: center; gap: 12px; margin-bottom: 4px; }
/* Functional necessity: the delivery card's mode-select is scoped to its
   header only (the date-strip below it owns its own <button> day-pills —
   buttons cannot nest). A real <button>, not a <div>, for keyboard/a11y. */
/* Buttons don't inherit font/color from the page by default (UA stylesheet
   resets them) — the card's own mockup uses a plain <div> here, so without
   this reset every measured child (h4/.oc-sub) sits a few px shorter than
   the frozen card despite matching font-size (line-height cascades from a
   DIFFERENT base). Caught by the mandatory pixel self-check (CR-A116-21). */
.oc-head-btn { width: 100%; border: none; background: none; padding: 0; text-align: left; cursor: pointer; font: inherit; color: inherit; line-height: inherit; min-height: 44px; }
.option-card .oc-icon {
  width: 40px; height: 40px; border-radius: var(--r-md); background: var(--blue-soft); color: var(--blue-deep);
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.option-card.selected .oc-icon { background: var(--blue); color: #fff; }
.option-card.selected .oc-icon .icon { stroke: #fff; }
.option-card h4 { font-size: 15px; font-weight: 700; color: var(--ink); margin: 0; display: flex; align-items: center; }
.option-card .oc-sub { font-size: 12px; color: var(--muted); margin: 1px 0 0; }
.option-card .radio-check {
  position: absolute; top: 18px; right: 18px; width: 20px; height: 20px; border-radius: 50%;
  border: 2px solid var(--line); background: #fff;
}
.option-card.selected .radio-check { border-color: var(--blue); background: var(--blue); display: flex; align-items: center; justify-content: center; }
.option-card.selected .radio-check svg { width: 11px; height: 11px; stroke: #fff; stroke-width: 3; fill: none; }
/* Pickup card has no interactive descendants — the whole card is the real
   <button>; reset default button chrome to keep the card's own visuals. */
.option-card-btn { width: 100%; border: 2px solid var(--line); text-align: left; cursor: pointer; font: inherit; color: inherit; line-height: inherit; }
.option-card-btn.selected { border-color: var(--blue); }
.pickup-new-badge {
  font-size: 10px; font-weight: 800; color: var(--teal); background: #F0FDFA; border: 1px solid #99F6E4;
  padding: 2px 6px; border-radius: 999px; vertical-align: middle; margin-left: 4px;
}

.date-strip { display: flex; gap: 8px; margin-top: var(--sp-1); }
/* min-height 44px -> 50px (CR-A116-38): matches new-order-3-delivery.html's
   own .day-pill exactly ("day-pill height is fixed across every state —
   selected / available — the date changes, the row's height never does").
   44px was the original a11y tap-target floor; 50px still clears it, so
   this is a safe increase, not a regression — enabled/selected already
   shared this rule unconditionally, so the pill never changed height
   between states even at 44px; this just aligns the number to the card. */
.day-pill {
  flex: 1; text-align: center; padding: 8px 4px; border-radius: var(--r-md); border: 1.5px solid var(--line);
  background: var(--field); min-height: 50px; cursor: pointer; font: inherit; color: inherit; line-height: inherit;
}
.day-pill .wd { font-size: 10px; font-weight: 700; text-transform: uppercase; color: var(--faint); letter-spacing: .04em; }
.day-pill .dd { font-family: var(--mono); font-size: 14px; font-weight: 700; color: var(--faint); margin-top: 2px; }
.day-pill.enabled .wd, .day-pill.enabled .dd { color: var(--ink-soft); }
.day-pill.selected { border-color: var(--blue); background: var(--blue); }
.day-pill.selected .wd, .day-pill.selected .dd { color: #fff; }
/* Card draws this state as `.disabled`; the CR's own invariant language
   calls it "locked" — same visual treatment, and a real disabled <button>
   (not just opacity) so it is unfocusable/unclickable, not merely styled
   to look inert. */
.day-pill.disabled, .day-pill[disabled] { opacity: .45; cursor: not-allowed; }
.step3-legend-copy { font-size: 11.5px; color: var(--muted); margin: var(--sp-1) 0 0; }
.strip-legend { display: flex; gap: 16px; margin-top: var(--sp-2); font-size: 11px; color: var(--faint); flex-wrap: wrap; }
.strip-legend .lg { display: flex; align-items: center; gap: 5px; }
.lg .dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
.lg .dot.sel { background: var(--blue); }
.lg .dot.ok { background: var(--ink-soft); }
.lg .dot.no { background: var(--faint); opacity: .5; }

.pickup-note { margin-top: var(--sp-2); background: var(--field); border: 1px solid var(--line); border-radius: var(--r-md); padding: var(--sp-2) 14px; }
.pickup-note .pd { font-family: var(--mono); font-size: 15px; font-weight: 700; color: var(--ink); }
.pickup-note .ps { font-size: 12px; color: var(--muted); margin-top: 2px; }
.step3-empty-note { font-size: 12.5px; color: var(--muted); margin: 10px 0 0; }

.terms-box {
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--r-lg); padding: 18px 20px;
  margin-bottom: var(--sp-6); display: flex; align-items: center; justify-content: space-between; gap: 16px;
}
.terms-box .tb-label { font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.terms-box .tb-value { font-size: 14px; color: var(--ink); margin-top: 6px; font-weight: 600; }

.review-strip { background: var(--navy); border-radius: var(--r-lg); padding: var(--sp-3) 24px; color: #fff; display: flex; align-items: center; gap: 24px; flex-wrap: wrap; }
.review-strip .ri { display: flex; flex-direction: column; gap: 2px; }
.review-strip .ri .rl { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: #93A3C7; }
.review-strip .ri .rv { font-size: 13px; font-weight: 600; color: #fff; }
.review-strip .rdiv { width: 1px; align-self: stretch; background: rgba(255, 255, 255, .15); }
/* [[BF-514]] (Drew, 2026-08-18): on step 3, changing the selected delivery
   date made this strip flip between a one-row and a two-row layout — .rtotal
   hopped from sitting inline on the right onto its own line and the card
   resized under the cursor.
   ROOT CAUSE: the strip is a wrapping flex row, so its wrap decision is a
   function of its TOTAL content width — and the delivery-date cell is the one
   cell whose text changes while the user is looking at it. Measured on the
   live component's own .rv font (600 13px, the system stack above), the cell
   ranges from 56px ("Fri, Jul 1") to 82px ("Wed, May 30") across the whole
   calendar — a 26px swing, against a step that sits ~10px from its wrap
   threshold at the 800px .step3-wrap width. Reproduced: with job sites
   "2250 Homelands Drive" / "2465 Hurontario Street" the strip measured 100px
   tall (Total inline, top 12px) for some dates and 160px (Total on its own
   line, top 71px) for others AT THE SAME 800px width — a 60px resize caused
   only by which pill was clicked.
   FIX: take the DATE out of that decision rather than take the wrap out.
   Reserving the widest string the `EEE, MMM d` format can ever produce makes
   the strip's width identical for every selectable date, so the wrap decision
   can no longer change under the cursor. 92px = the measured 82px worst case
   plus headroom, because this font stack resolves to a DIFFERENT face per OS
   (Segoe UI / Roboto rather than the -apple-system face measured here) and a
   min-width tuned to one platform's metrics would let the swing return on
   another. Verified across 7 job sites x 6 dates x every width 390-800px:
   49 date-dependent reflows before, 0 after — and 92px costs nothing that
   84px did not (both produce byte-identical row counts at every width tested).
   flex-wrap is deliberately KEPT: between 481px and this step's 800px it is
   the strip's only escape valve, and forcing nowrap there would push a long
   job-site address out of the navy card. The <=480px block at the end of this
   file already turns the strip into a column, so wrapping stays the deliberate
   narrow-width behaviour it was meant to be rather than an always-on accident.
   The ratified card draws this strip as a single row with no wrap; correcting
   the card is tracked separately under [[BF-503]] and is NOT done here. */
.review-strip .ri:has(+ .rtotal) { min-width: 92px; }
/* The money block never gets squashed to buy room for a tight line. */
.review-strip .rtotal { margin-left: auto; text-align: right; flex-shrink: 0; }
.review-strip .rtotal .rl { color: #93A3C7; }
.review-strip .rtotal .amt { font-family: var(--mono); font-size: 24px; font-weight: 800; color: #fff; }
/* Addition beyond the frozen card's #step3 pixels — Technical Spec item 5
   + INV-8b require the verbatim HST line "where the card places it", but
   the static #step3 mockup does not draw it at all (it only appears in
   Step 1's price block). Placed here, next to the order-time Total, using
   the review-strip's OWN existing muted-navy token color (#93A3C7, already
   used for .rl in this component) rather than inventing a new one. Flagged
   in the forge report pixel table as a deliberate, CR-instructed addition
   for Jonathan/Drew to confirm placement in a future design pass. */
.review-strip .rtotal-tax-note { font-size: 10.5px; color: #93A3C7; margin-top: 3px; font-weight: 400; }

.email-confirm-check {
  display: flex; align-items: center; gap: 10px; margin-top: var(--sp-2); padding: var(--sp-2) 16px; background: var(--field);
  border: 1px solid var(--line); border-radius: var(--r-md); max-width: 520px;
}
.email-confirm-check label { display: flex; align-items: center; gap: 10px; font-size: 13px; color: var(--ink-soft); cursor: pointer; }
.email-confirm-check b { color: var(--ink-soft); }
.email-confirm-check .ecc-muted { color: var(--muted); }
/* Real <input type="checkbox"> for keyboard operability + native
   :focus-visible — visually hidden but still tabbable/announced; a
   decorative box mirrors the card's custom checkmark graphic next to it. */
.email-confirm-check input[type="checkbox"] {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.email-confirm-check input[type="checkbox"]:focus-visible ~ * ,
.email-confirm-check label:focus-within .ecc-box { outline: 2px solid var(--blue); outline-offset: 2px; }
.ecc-box {
  width: 20px; height: 20px; border-radius: 4px; background: var(--line); display: flex; align-items: center;
  justify-content: center; flex-shrink: 0;
}
.ecc-box svg { width: 13px; height: 13px; stroke: #fff; stroke-width: 3; fill: none; opacity: 0; }
.ecc-box.checked { background: var(--blue); }
.ecc-box.checked svg { opacity: 1; }

/* 2026-08-17 (Drew, live directive): pairs the email-confirm checkbox and
   the Place-order button into one row instead of two stacked full-width
   blocks (each previously carrying its own margin-top) — same "trade
   vertical space for horizontal room" mechanism as step-product.js's
   .qq-two-col, scoped to just this one footer pairing rather than a full
   step-3 content reflow (the width bump above already handles review-strip's
   own wrapping; this handles the remaining ~55-64px of real overflow measured
   live on 1440x900/1280x800 with real fulfillment data). email-confirm-check
   keeps its own visual box; only the outer margin-top is absorbed by the row. */
/* [[BF-549]] 2026-08-19 — sticky bottom, same -18px derivation as
   .nof-footer above (steps 1/2 share one scrollport with step 3:
   .modal-body, modal.css). Unlike .nof-footer, this row never carried its
   own background/border (step3-wrap is frameless, nothing behind it but
   the modal's own white) — background/border-top are NEW here, added only
   because sticky content now needs to occlude whatever scrolls underneath
   it; var(--bg) is the exact color already showing through (modal-shell's
   own background, modal.css), so this is invisible at rest and only does
   work once something scrolls past. No padding added — height is
   unchanged, margin-top above still does the only spacing job it did
   before. */
.step3-footer-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-top: var(--sp-2); flex-wrap: wrap; position: sticky; bottom: -18px; z-index: 10; background: var(--bg); border-top: 1px solid var(--line); }
.step3-footer-row .email-confirm-check { margin-top: 0; flex: 1; min-width: 280px; }
.step3-footer-row .review-cta { margin-top: 0; }
@media (max-width: 640px) {
  .step3-footer-row { flex-direction: column; align-items: stretch; }
  .step3-footer-row .review-cta { justify-content: flex-end; }
}

.review-cta { display: flex; align-items: center; gap: 14px; margin-top: var(--sp-2); flex-wrap: wrap; }
.btn-place {
  background: var(--blue); color: #fff; border: none; border-radius: var(--r-md); padding: 0 24px; height: 48px;
  font-size: 14px; font-weight: 700; display: flex; align-items: center; gap: 8px; cursor: pointer; font-family: var(--font);
}
.btn-place:disabled { cursor: not-allowed; opacity: .6; }
.btn-place .icon { stroke: #fff; }
.review-cta-caption { font-size: 12px; color: var(--muted); }

.day-pill:focus-visible,
.oc-head-btn:focus-visible,
.option-card-btn:focus-visible,
.btn-place:focus-visible,
.email-confirm-check label:focus-within {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
}

@media (max-width: 480px) {
  .option-grid { grid-template-columns: 1fr; }
  .review-strip { flex-direction: column; align-items: flex-start; gap: 10px; }
  .review-strip .rdiv { display: none; }
  .review-strip .rtotal { margin-left: 0; text-align: left; }
}
