/*
 * Buildr installer PWA — Order / Confirm / Done (CR-BUILDR-A001-23).
 *
 * Class names/values are card-verbatim from ds-bundle-app-r1/app/{order,
 * order-draft,order-confirm,order-done}.html.
 *
 * CR-BUILDR-A001-30: the "two fixed SKUs, so there is nothing to choose"
 * exception this header used to claim is GONE. CR-BUILDR-A001-31 seeded the
 * real catalog and Step 1 now draws all five of the card's product controls,
 * so the option-group rules below (`.opt-btn .cap`, the swatch row, the
 * `cols-4`/`cols-5` grids) are the card's own, copied verbatim rather than
 * omitted. Nothing was removed to make room for them.
 *
 * CR-BUILDR-A001-26 adds the payment step's three families, each copied
 * verbatim from a RATIFIED r2 card and each rendered by that card's own body:
 * `.pay-card` (+ `.pay-amt`) and `.nof-hold` from `ds-bundle-app-r2/app/
 * order-pay.html`, and the `.paid-block` family (`.paid-at .paid-amt
 * .paid-note .receipt-link`) from `ds-bundle-app-r2/app/order-done-paid.html`.
 * No token was added — the r2 bundle uses r1's stylesheet byte-for-byte. The
 * two edits to EXISTING rules are both card-ruled: `.cta-caption` gains the
 * card's `line-height: 1.5` (the caption now sets on two lines) and `.success`
 * drops from 64px to 48px of top padding to make room for the paid block.
 */

.topbar { /* shared with shell.css — see that file for the definition; this
             app has a single stylesheet load order, not a name collision. */ }

.io-app-bar {
  height: 44px; flex-shrink: 0; background: var(--paper); border-bottom: 1px solid var(--dust);
  display: flex; align-items: center; justify-content: center; position: relative;
}
.io-app-bar-badge { width: 24px; height: 24px; display: block; }
.io-app-bar-step {
  position: absolute; right: 16px; font-family: var(--font-mono); font-size: 10px; font-weight: 600;
  letter-spacing: .08em; color: var(--ink-mute);
}
.io-app-bar-back {
  position: absolute; left: 8px; width: 40px; height: 40px; display: flex; align-items: center;
  justify-content: center; color: var(--ink-soft); background: none; border: none; cursor: pointer;
}
.io-app-bar-back svg { width: 20px; height: 20px; stroke: currentColor; fill: none; stroke-width: 2.4; stroke-linecap: round; }
.io-step-dots { display: flex; justify-content: center; gap: 6px; padding: 12px 0 4px; background: var(--paper); flex-shrink: 0; }
.io-step-dot { width: 24px; height: 4px; border-radius: 2px; background: var(--dust); }
.io-step-dot.io-now { background: var(--navy); }
.io-step-dot.io-done { background: var(--ink); }

.io-body { flex: 1; padding: 18px 18px 8px; }
.h-title { font-size: 19px; font-weight: 800; color: var(--ink); margin: 0 0 4px; }
.h-sub { font-size: 13px; color: var(--ink-soft); margin: 0 0 18px; line-height: 1.5; }

/* ---- Confirm & pay, pickup date first (CR-BUILDR-A016-6, ratified card
   app/order-pay-r3.html) — the slim bar that replaces the app top bar and
   the title block on THIS screen only. ---- */
.io-app-bar-close {
  position: absolute; left: 4px; top: 0; width: 44px; height: 44px; display: flex; align-items: center;
  justify-content: center; color: var(--ink-soft); background: none; border: none; cursor: pointer;
}
.io-app-bar-close svg { width: 20px; height: 20px; stroke: currentColor; fill: none; stroke-width: 2.4; stroke-linecap: round; }
.io-app-bar-title { font-size: var(--t-body); font-weight: 800; color: var(--ink); }

/* the hero: the shipped pickup card, its sentence split in two so the date
   can be set large. Same words, same order. */
.pickup-hero { padding: 14px 16px; margin-bottom: 12px; flex-wrap: wrap; row-gap: 0; }
.pickup-hero strong { margin-bottom: 0; }
/* the place gets the card's full width under a hairline, so the address
   stays on one line at 375 */
.pickup-place { flex: 0 0 100%; margin-top: 10px; border-top: 1px solid var(--dust); }
.pickup-hero .pickup-k { display: block; }
.pickup-hero .pickup-date { display: block; font-size: var(--t-h1); font-weight: 800; line-height: 1.1; letter-spacing: -.02em; color: var(--ink); margin: 2px 0 0; }
/* while GET /api/fulfillment/options is in flight the hero holds its size
   and says nothing it does not know */
.pickup-wait { border-color: var(--dust); }
.pickup-wait svg { stroke: var(--chalk); }
.pickup-wait .pickup-k { color: var(--ink-mute); }
/* each block is exactly the line box of the text it stands for (1.1em /
   1.5em), so nothing moves when the answer lands */
.pickup-skel { display: block; height: 1.5em; padding: 3px 0; background: var(--board); background-clip: content-box; border-radius: var(--r-sm); }
.pickup-date.pickup-skel { width: 200px; height: 1.1em; padding: 4px 0; }
.pickup-name.pickup-skel { width: 64px; }

/* the compact summary: Home's order row grammar (tag / product / mono size)
   inside the shipped .sum-card */
.sum-compact { padding: 12px 14px; margin-bottom: 0; display: flex; flex-direction: column; gap: 3px; }
.sum-compact .tag { background: var(--paper); }
.sum-name { font-size: 15px; font-weight: 700; color: var(--ink); line-height: 1.3; }
.sum-foot-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.sum-open { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: 13.5px; font-weight: 600; color: var(--ink); }
/* the body gives the top slot a little less air than the shipped 18px */
.confirm-screen .io-body { padding-top: 14px; }

/* the no-scroll layout: the app top bar and #viewHost's own padding are this
   screen's 138px of dead chrome (the slim .io-app-bar above replaces the
   bar; .confirm-screen already supplies its own flex column and foot). The
   `.confirm-screen { height: 100dvh; min-height: 0; }` override that
   actually contains the screen lives further down, immediately after the
   existing `min-height: 100dvh` declaration it must win over by source
   order (CSS-precedence trap — same selector, same specificity).
   CR-BUILDR-A016-2: `.order-screen` joins these selectors — same bar, same
   hidden top bar, same zeroed #viewHost padding — EXTENDED here, never
   duplicated. Order takes NONE of Confirm's `height: 100dvh` rules below:
   Order scrolls, the bar is not pinned. */
.installer-shell:has(.confirm-screen, .order-screen) .topbar { display: none; }
#viewHost:has(.confirm-screen, .order-screen) { padding: 0; }
.confirm-screen .io-body { min-height: 0; overflow-y: auto; }
.confirm-screen .io-app-bar, .order-screen .io-app-bar { height: calc(44px + env(safe-area-inset-top, 0px)); padding-top: env(safe-area-inset-top, 0px); }
.confirm-screen .io-app-bar-close, .order-screen .io-app-bar-close { top: env(safe-area-inset-top, 0px); }
/* Order-only: #viewHost's padding is zeroed above, so the form's bottom room
   (previously #viewHost's own 48px) has to come from somewhere on THIS
   screen — Order scrolls, so this is padding-bottom on the scrolling body,
   never height/overflow (those stay Confirm-only). */
.order-screen .io-body { padding-bottom: calc(24px + env(safe-area-inset-bottom, 0px)); }

.field-label-row { margin-bottom: 20px; }
.field-label-row label {
  font-family: var(--font-mono); font-size: 10.5px; font-weight: 600; color: var(--ink-mute);
  text-transform: uppercase; letter-spacing: .08em; display: block; margin-bottom: 8px;
}
.field-label-row label .opt { font-weight: 500; text-transform: none; letter-spacing: 0; color: var(--ink-mute); }
.tag-input {
  width: 100%; height: 64px; padding: 0 14px; border: 2px solid var(--dust); border-radius: var(--r-md);
  background: var(--paper); font-size: 15px; color: var(--ink); font-family: var(--font-ui); box-sizing: border-box;
}
.tag-input::placeholder { color: var(--chalk); font-weight: 500; }
.tag-input:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }

/* Drew, 2026-09-14: a group label belongs to the buttons under it, not to
   the section above — wide gap between groups, tight gap label-to-buttons. */
.qq-body { display: flex; flex-direction: column; gap: 26px; }
/* the four option groups live inside #ord-groups (one flex child), so they
   need the same rhythm between themselves as .qq-body gives its children */
#ord-groups { display: flex; flex-direction: column; gap: 26px; }
.qq-group-label {
  font-family: var(--font-mono); font-size: 10.5px; font-weight: 600; letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-mute); margin-bottom: 6px;
}
.btn-grid { display: grid; gap: var(--tap-gap); grid-template-columns: repeat(2, 1fr); }
.btn-grid.cols-4, .btn-grid.cols-2 { grid-template-columns: repeat(2, 1fr); }
/* Drew, 2026-09-14: all five panel counts on ONE row — smaller buttons, no
   second row, less scrolling. */
.btn-grid.cols-5 { grid-template-columns: repeat(4, 1fr) 1.7fr; gap: 6px; }
.btn-grid.cols-5 .opt-btn { padding: 8px 2px; font-size: 15px; }
.btn-grid.cols-5 .opt-btn .cap { font-size: 9.5px; }
.opt-btn {
  min-height: var(--tap-min); border: 2px solid var(--dust); border-radius: var(--r-md); background: var(--paper);
  color: var(--ink-soft); font-size: 15px; line-height: 1.4; font-weight: 700; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 2px; cursor: pointer; padding: 8px 6px;
  position: relative; font-family: var(--font-ui); text-align: center;
}
.opt-btn .cap { font-size: 10.5px; line-height: 1.4; font-weight: 600; color: var(--ink-mute); }
/* Drew, 2026-09-14: a chosen option FILLS — navy ground, white text — no
   tick badge. The selection is the colour; everything unchosen stays paper. */
.opt-btn.selected { background: var(--navy); border-color: var(--navy); color: #fff; }
.opt-btn.selected .cap { color: rgba(255, 255, 255, .82); }
.opt-btn:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }

/* Edge colour swatches, order.html. Fills are data-driven per color_code
   (SWATCH_STYLES in order.js) — never three hardcoded colour classes, because
   the live catalog can carry more codes than the card draws examples of. */
.swatch-row { display: flex; gap: 14px; flex-wrap: wrap; }
.swatch-btn {
  display: flex; flex-direction: column; align-items: center; gap: 6px; min-width: 64px;
  min-height: var(--tap-min); border: none; background: none; padding: 6px; cursor: pointer;
  font-family: var(--font-ui);
}
.swatch { width: 40px; height: 40px; border-radius: 50%; display: flex; align-items: center; justify-content: center; box-sizing: border-box; }
/* Drew, 2026-09-14: the navy ring read as decoration, not as a choice. The
   chosen swatch carries a tick INSIDE it — white on a dark fill, dark on a
   light fill (difference blend), so it reads on every catalog colour. */
.swatch svg { width: 20px; height: 20px; stroke: #fff; stroke-width: 3; fill: none; stroke-linecap: round; stroke-linejoin: round; mix-blend-mode: difference; }
/* Drew, 2026-09-14: the ring alone was invisible on a phone, above all on
   the white swatch. Once a colour is chosen, every OTHER swatch fades hard;
   the chosen one stays at full strength — the fade moves with the choice. */
.swatch-row:has(.swatch-btn.selected) .swatch-btn:not(.selected) { opacity: .3; }
.swatch-btn { transition: opacity 150ms ease; }
.swatch-label { font-size: 12px; font-weight: 700; color: var(--ink-mute); }
.swatch-btn.selected .swatch-label { color: var(--navy); }
.swatch-btn:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }

.dim-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; scroll-margin-top: calc(12px + env(safe-area-inset-top, 0px)); }
/* fraction chips (Drew, 2026-09-14; CR-BUILDR-A016-3, 2026-09-19: plain
   "1/8 … 7/8" text, not the single-character glyphs — those glyphs' digits
   rendered ~8px tall and grey, unreadable on a job site. 19px in the UI
   face (not mono) reads clearest at this size; weight stays 700. */
.frac-row { display: flex; gap: 4px; margin-top: 10px; }
.frac-btn {
  flex: 1; min-width: 0; min-height: 48px; border: 2px solid var(--dust); border-radius: var(--r-md);
  background: var(--paper); color: var(--ink); font-family: var(--font-ui); font-size: 19px; font-weight: 700;
  font-variant-numeric: tabular-nums; cursor: pointer; padding: 0;
}
.frac-btn:active { background: var(--navy); border-color: var(--navy); color: #fff; }
.frac-btn:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }
/* DISABLED = the default (Drew, 2026-09-19): an enabled chip on an empty
   field invited a tap that wrote a bare fraction. The app's EXISTING
   disabled grammar for a button, value for value: .btn-cta:disabled /
   .blocked (shell.css:227) — board ground, dust edge, ink-mute text.
   Declared AFTER :active so a disabled chip can never flash navy. No
   opacity: the 2px edge and the 19px bold figures stay whole. */
.frac-btn:disabled { background: var(--board); border-color: var(--dust); color: var(--ink-mute); cursor: not-allowed; }
.field-label {
  font-family: var(--font-mono); font-size: 10.5px; font-weight: 600; letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-mute); margin-bottom: 6px; display: block;
}
.dim-input {
  width: 100%; height: var(--tap-min); padding: 0 14px; border: 2px solid var(--dust); border-radius: var(--r-md);
  background: var(--paper); font-family: var(--font-mono); font-size: 18px; color: var(--ink);
  font-variant-numeric: tabular-nums; box-sizing: border-box;
}
.dim-input.filled { border-color: var(--navy); }
.dim-input:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }
.field-hint { font-family: var(--font-mono); font-size: 10.5px; line-height: 1.4; color: var(--ink-mute); margin: 5px 0 0; }

.price-block {
  background: var(--board); border: 1px solid var(--dust); border-radius: var(--r-md); padding: 12px 16px;
  display: flex; flex-direction: column; justify-content: center; min-height: 76px; box-sizing: border-box;
}
.price-pending { display: flex; align-items: center; justify-content: center; gap: 8px; font-size: 13px; color: var(--ink-mute); }
.price-row { display: flex; align-items: baseline; justify-content: space-between; font-size: 14px; color: var(--ink-soft); padding: 4px 0; }
.price-row.total .label { font-size: 15px; font-weight: 700; color: var(--ink); }
.price-row.total .amt { font-size: 26px; font-weight: 800; color: var(--ink); font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.price-row.muted { color: var(--ink-mute); font-size: 12px; }

.qq-foot { padding: 12px 0 16px; }
.btn-primary {
  width: 100%; height: var(--tap-min); border: 2px solid var(--navy-edge); border-radius: var(--r-md);
  background: var(--navy); color: #fff; font-size: 16.5px; font-weight: 800; display: flex; align-items: center;
  justify-content: center; gap: 8px; cursor: pointer; font-family: var(--font-ui);
}
.btn-primary:disabled { opacity: .6; cursor: not-allowed; }
.btn-primary .icon { stroke: #fff; }
.btn-primary:focus-visible { outline: 2px solid var(--navy-edge); outline-offset: 2px; }
.cta-caption { font-family: var(--font-mono); font-size: 11px; color: var(--ink-mute); text-align: center; margin: 8px 0 0; line-height: 1.5; }

.nof-error {
  background: var(--paper); border: 2px solid var(--danger-deep); border-radius: var(--r-md); padding: 14px;
  margin-bottom: 16px; display: flex; gap: 12px; align-items: flex-start;
}
.nof-error svg { stroke: var(--danger-deep); flex-shrink: 0; margin-top: 1px; }
.nof-error strong { display: block; font-size: 14.5px; color: var(--danger-deep); margin-bottom: 2px; }
.nof-error p { margin: 0; font-size: 13px; color: var(--ink-soft); }
/* hold-toned banner: a stop that is not the installer's error (came back from
   Checkout without paying) — order-pay.html, CR-BUILDR-A001-26. */
.nof-error.nof-hold { border-color: var(--hold-deep); }
.nof-error.nof-hold svg { stroke: var(--hold-deep); }
.nof-error.nof-hold strong { color: var(--hold-deep); }

/* collapsed pre-answered summary, order-draft.html (Back from Confirm) */
.step1-summary {
  display: flex; align-items: center; gap: 12px; background: var(--board); border: 1px solid var(--dust);
  border-radius: var(--r-md); padding: 13px 14px;
}
.step1-summary-label {
  font-family: var(--font-mono); font-size: 10px; font-weight: 600; letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-mute);
}
.step1-summary-value { flex: 1; font-size: 14px; font-weight: 700; color: var(--ink); }
.step1-summary-change {
  font-size: 13px; font-weight: 700; color: var(--navy); background: var(--paper); border: 2px solid var(--navy);
  padding: 6px 12px; border-radius: var(--r-md); cursor: pointer; font-family: var(--font-ui); min-height: 40px;
}

/* ---- Confirm (order-confirm.html) ---- */
/* CR-BUILDR-A006-6: `.foot` (below) inherits `position: sticky; bottom: 0`
   from shell.css's shared rule (:116) — order-flow.css's own `.foot` rule
   never resets `position`, so that sticky offset always applies here too.
   That's harmless on the auth screens because they render inside their own
   full-height flex column (shell.css's `.signup-screen` / `.login-page-wrap`),
   where the foot's static position already sits at the viewport's bottom
   edge and sticky is just a safety net once the page scrolls. Confirm has no
   such wrapper — `.io-app-bar`/`.io-step-dots`/`.io-body`/`.foot` used to
   render straight into the plain, unstyled `.installer-view` mount node, so
   `.io-body`'s own `flex: 1` (below) had no flex container to act in. On a
   pickup order the combined content is shorter than one viewport, so the
   sticky foot snapped up to the bottom of the screen and painted the Pay
   button over the tail of `.io-body` — the pickup card's address — instead
   of sitting below it. This wrapper is that missing flex column. */
.confirm-screen { display: flex; flex-direction: column; min-height: 100vh; min-height: 100dvh; }
/* CR-BUILDR-A016-6, adoption row 12: `min-height: 100dvh` above always made
   the page 138px taller than the phone once the sticky top bar and
   #viewHost's own padding are added — this rule wins by source order,
   containing the screen to the viewport exactly instead of merely flooring
   it there. `.io-body`'s own `overflow-y: auto` (above) is what lets the
   one named exception (a notice on a 375x667 phone) scroll inside itself
   rather than push the foot off-screen. */
.confirm-screen { height: 100dvh; min-height: 0; }

.sum-card { background: var(--board); border: 1px solid var(--dust); border-radius: var(--r-md); padding: 16px; margin-bottom: 16px; }
.sum-row { display: flex; justify-content: space-between; font-size: 14.5px; padding: 8px 0; gap: 12px; }
.sum-row .k { color: var(--ink-mute); flex-shrink: 0; }
.sum-row .v { color: var(--ink); font-weight: 700; text-align: right; min-width: 0; overflow-wrap: anywhere; }
.sum-row .v.num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
/* CR-BUILDR-A016-10 — the receipt row's key/action pairing; no colour of its
   own (the button carries its own chrome via .sum-edit-btn). */
.sum-row-act { align-items: center; padding: 4px 0; }
.sum-row-muted .v { color: var(--ink-mute); font-weight: 600; font-size: 12.5px; }
/* Drew, 2026-09-14: the way back lives on the card — a question and a link,
   never a second navy button competing with Pay. */
.sum-edit { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 10px; padding-top: 12px; border-top: 1px solid var(--dust); }
.sum-edit-q { font-size: 12.5px; color: var(--ink-mute); }
.sum-edit-btn {
  min-height: 44px; padding: 0 14px; border: 2px solid var(--dust); border-radius: var(--r-md); background: var(--paper);
  color: var(--navy); font-size: 13.5px; font-weight: 700; font-family: var(--font-ui); cursor: pointer;
}
.sum-edit-btn:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }
/* CR-BUILDR-A006-16 — the destructive verb in destructive red, otherwise the
   ratified small outlined button; same move as .acct-btn.danger
   (shell.css:73). Card ref: ds-bundle-app-r5/app/home-r2.html:137. */
.sum-edit-btn.danger { color: var(--danger-deep); }
/* CR-BUILDR-A006-16 — the Pay CTA inside the unpaid detail card sits 6px
   under the last row, the same air the Confirm card's own .sum-edit row
   leaves above itself. Card ref: home-r2.html:129. */
.sum-cta { margin-top: 6px; }
/* CR-BUILDR-A006-16 — the Account card's confirmation grammar (title, one
   sentence, red CTA, Keep beside it), scoped to the detail card so it reads
   at list scale instead of full-page scale. Card ref: home-r2.html:140-141. */
.sum-card .h-title { font-size: 17px; font-weight: 800; color: var(--ink); margin: 2px 0 4px; }
.sum-card .h-sub { font-size: 13px; color: var(--ink-soft); margin: 0 0 14px; line-height: 1.5; }
/* A full sentence (not a short label) stands alone in .k, e.g. orders.js's
   vendor-status note — it must be free to shrink and wrap like the rest of
   the row, not pinned to its unwrapped width like a normal label. */
.sum-row-note .k { flex: 1 1 auto; min-width: 0; }
.sum-div { height: 1px; background: var(--dust); margin: 6px 0; }

.pickup-card {
  border: 2px solid var(--go-deep); background: var(--paper); border-radius: var(--r-md); padding: 16px;
  margin-bottom: 18px; display: flex; gap: 12px; align-items: flex-start;
}
.pickup-card svg { stroke: var(--go-deep); flex-shrink: 0; margin-top: 1px; }
.pickup-card strong { display: block; font-size: 14.5px; color: var(--go-deep); margin-bottom: 2px; }
.pickup-card p { margin: 0; font-size: 13px; color: var(--ink-soft); }
.pickup-at {
  display: block; margin-top: 8px; font-family: var(--font-mono); font-size: 10px; font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink-mute);
}
.pickup-name { display: block; font-size: 14.5px; font-weight: 700; color: var(--ink); }
.pickup-card.pickup-card-warning { border-color: var(--hold-deep); }
.pickup-card.pickup-card-warning svg { stroke: var(--hold-deep); }
.pickup-card.pickup-card-warning strong { color: var(--hold-deep); }

/* payment received (order-pay.html frame 4, CR-BUILDR-A001-26) — the same
   outline grammar as the pickup card, go-deep because it went through. */
.pay-card {
  border: 2px solid var(--go-deep); background: var(--paper); border-radius: var(--r-md); padding: 14px 16px;
  margin-bottom: 16px; display: flex; gap: 12px; align-items: flex-start;
}
.pay-card svg { stroke: var(--go-deep); flex-shrink: 0; margin-top: 1px; }
.pay-card strong { display: block; font-size: 14.5px; color: var(--go-deep); margin-bottom: 2px; }
.pay-card .pay-amt { display: block; font-size: 15px; font-weight: 700; color: var(--ink); }
.pay-card p { margin: 4px 0 0; font-size: 13px; color: var(--ink-soft); }

.foot { flex-shrink: 0; padding: 16px 18px calc(16px + env(safe-area-inset-bottom)); border-top: 1px solid var(--dust); background: var(--paper); }
.btn-cta {
  width: 100%; height: var(--tap-min); border: 2px solid var(--navy-edge); border-radius: var(--r-md);
  background: var(--navy); color: #fff; font-size: 17px; font-weight: 800; display: flex; align-items: center;
  justify-content: center; gap: 8px; cursor: pointer; font-family: var(--font-ui);
}
.btn-cta:disabled { opacity: .7; cursor: not-allowed; }
.io-btn-secondary { background: var(--paper); color: var(--ink-soft); border: 2px solid var(--chalk); margin-top: 10px; }

/* ---- Save for later / leave sheet (CR-BUILDR-A016-4, ratified card
   app/order-r3.html frames O4/O5) — reuses the app's ONE sheet grammar
   (.sheet-scrim / .sheet, shell.css:125-150, already `position: fixed` and
   already reading `--col-left`/`--col-w` per CR-BUILDR-A016-9's contract).
   These two rules are the only sheet CSS this CR adds: the destructive verb
   in destructive red (the .acct-btn.danger move, shell.css:80) and the
   keyboard-open dock to the TOP so the optional field can never sit under
   the keyboard (card's own .sheet.sheet-top; the real `.kb-open` class is
   set document-wide from visualViewport, app.js:1633). No new token, no new
   hex, no new sheet component. */
.io-btn-secondary.danger { color: var(--danger-deep); }
.sheet-link { display: flex; justify-content: center; margin-top: 4px; }
/* shell.css's `.sheet-scrim` is a flex column with `align-items: flex-end` —
   `.sheet` docks to the bottom as a flex child, not via its own `bottom: 0`.
   Docking to the top is therefore the scrim's alignment flipping, plus the
   sheet's own corners/border/padding turning around to match (card's
   .sheet.sheet-top, drawn absolute; here real fixed positioning does the
   same job through the flex parent). */
.kb-open .sheet-scrim:has(.sheet.sheet-top) { align-items: flex-start; }
.sheet.sheet-top {
  border-top: 0; border-bottom: 2px solid var(--dust);
  border-radius: 0 0 var(--r-lg) var(--r-lg);
  padding-top: calc(20px + env(safe-area-inset-top, 0px));
}

/* ---- Home order list, one-shot status line (CR-BUILDR-A006-13) ----
   Card r5 order-pay-r2.html's own definition, byte-for-byte — the same rule
   the sibling home-r2.html card repeats for its own "something just
   happened" line (pull-to-refresh, delete). No new token. */
.list-status { font-family: var(--font-mono); font-size: 11px; letter-spacing: .04em; color: var(--ink-mute);
  min-height: 16px; line-height: 16px; margin: -4px 0 0; }

/* ---- Done (order-done.html) ----
   CR-BUILDR-A022-6 (ratified card app/order-done-r3.html) replaces the
   centred column below with a left-aligned, card-based layout cloning
   Confirm & pay's own hero — see the card's own "PROPOSED" block for the
   six-rule rationale this comment does not repeat. `.pickup-block` and
   `.pickup-addr` (the pre-r3 pickup line under the check mark) are
   superseded by the shared `.pickup-card.pickup-hero` (order-flow.css:323
   above) via `pickup-hero.js`; `.receipt-link` gets one adjustment per the
   adoption map's own row (align-self: center -> flex-start, the screen
   being left-aligned now) where it is defined below.

   FIX ROUND 1 (/proof rejection 1) — `.success { overflow-y: auto }` alone
   never engaged: nothing between it and the viewport was height-bounded
   (`.installer-shell` is `min-height: 100vh`, `#viewHost` is `flex: 1` with
   no `min-height: 0`), so the DOCUMENT grew instead, and the `position:
   sticky` foot painted over the tail of the content — "View Stripe receipt"
   sat under the foot on the Mirror state and had its last 10px clipped on
   Panel. Unlike Confirm (which hides the app topbar and zeros #viewHost's
   padding for a 100dvh screen of its own), Done KEEPS the real topbar —
   the ratified card draws it — so Done gets its own containment, scoped by
   `.done-screen` (order-done.js's own wrapper class, never the bare
   `.io-success-screen` the unauthenticated `#/paid` return page also uses).

   FIX ROUND 2 (/proof rejection 2, [[proof-report-2]]) — round 1 rewrote
   `.success` / `.success h2` (and `.success-icon`'s centring margin)
   UNSCOPED, and both classes are the SAME ones `app.js renderPaidReturn()`
   renders on the unauthenticated `#/paid` return page (CR-BUILDR-A019-8) —
   that page shares `.io-success-screen > .success > .success-icon/h2/p`
   verbatim, outside #viewHost entirely, never touched by the containment
   rules above. Round 1's restyle centred-column -> left-aligned-card
   silently reskinned it too: 117k+ changed px at 428x926, both with and
   without an order number (proof-report-2's own measurement). Every rule
   below that is Done-only now lives under `.done-screen` (its own
   ancestor selector, or `.done-screen .success` etc.), and the base
   `.success` / `.success-icon` / `.success h2` / `.success .ref` rules are
   restored to `origin/main` BYTE-FOR-BYTE (diffed below) — `#/paid` gets
   its centred column back, unconditionally.

   Round 2 also drops the magic `calc(100dvh - 70px)` #viewHost height:
   proof measured the REAL topbar at 117-129px with safe-area insets (not
   70px), and found the discrepancy harmless only because `#viewHost`'s
   own `flex: 1` inside a `min-height: 0` flex column already absorbs
   whatever the topbar's real height is — the explicit height was
   redundant AND fragile (silently wrong on every notched device) the
   moment `min-height: 0` was in place. Removed; `#viewHost` now sizes
   itself from the same flex mechanics `.installer-shell:has(.done-screen)`
   already provides, with no hard-coded number to go stale. */
.installer-shell:has(.done-screen) { height: 100dvh; min-height: 0; }
.installer-shell:has(.done-screen) #viewHost { min-height: 0; padding-bottom: 0; display: flex; flex-direction: column; flex: 1; }
.installer-shell:has(.done-screen) .installer-view { flex: 1; min-height: 0; display: flex; flex-direction: column; }
/* base rules — BYTE-FOR-BYTE origin/main, shared with the unauthenticated
   `#/paid` return page (app.js renderPaidReturn(), CR-BUILDR-A019-8).
   NEVER edit these directly again; add a `.done-screen …` override below
   instead, the same way the rest of this file already scopes Confirm-only
   rules under `.confirm-screen`. */
.io-success-screen { flex: 1; display: flex; flex-direction: column; justify-content: space-between; }
.success { padding: 48px 24px 0; text-align: center; }
.success-icon {
  width: 80px; height: 80px; border-radius: 50%; background: var(--paper); border: 2px solid var(--go-deep);
  display: flex; align-items: center; justify-content: center; margin: 0 auto 20px;
}
.success-icon svg { width: 36px; height: 36px; stroke: var(--go-deep); fill: none; stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; }
.success h2 { font-size: 21px; font-weight: 800; color: var(--ink); margin: 0 0 8px; }
.success p { font-size: 14px; color: var(--ink-soft); margin: 0 0 4px; line-height: 1.5; }
.success .ref {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-weight: 700;
  color: var(--ink); font-size: 16px; margin: 16px 0; letter-spacing: .02em;
}
/* Done-only overrides (FIX ROUND 2) — everything round 1 needed from the
   base rules above, re-applied under `.done-screen` so `#/paid` never sees
   them. `.done-screen .success` needs BOTH `min-height: 0` (so it can
   shrink inside the now flex-bounded chain) and `overflow-y: auto` (so it
   is the thing that actually scrolls, per the CR's own "the body scrolls
   on a notched phone" rule) alongside the left-aligned, zero-padding card
   layout the ratified r3 card draws. */
.done-screen.io-success-screen { min-height: 0; }
.done-screen .success { padding: 0; text-align: left; overflow-y: auto; min-height: 0; }
.done-screen .success-icon { margin: 0; }
.done-screen .success h2 { margin: 0; }
.done-screen .success .ref { margin: 0; }
/* FIX ROUND 1 (CR-BUILDR-A022-6, /proof rejection 1) — `.pickup-hero`'s
   base `margin-bottom: 12px` is SHARED with Confirm & pay (order-flow.css:63)
   and must not change (Confirm's own pixel-identity invariant). This
   `.done-screen`-scoped override only ever matches inside Done's own
   subtree — Confirm never carries `.done-screen` — so it closes part of
   the Mirror-state overflow without touching Confirm at all. */
.done-screen .pickup-hero { margin-bottom: var(--sp-2); }
/* the success row: the ratified 80px disc shrunk to a 36px icon beside
   "Order placed" on one line, so the pickup hero is the first big thing.
   FIX ROUND 1 — margin-bottom 10px -> --sp-2 (8px): the card's own vertical
   rhythm is tighter than the first pass drew it; every value below this
   point that changed in this round swaps a bespoke px for the nearest
   token that already existed (Jonathan's own note), never a new one. */
.success-head { display: flex; align-items: center; gap: var(--sp-2); margin: 0 0 var(--sp-2); }
.success-head .success-icon { width: 36px; height: 36px; flex-shrink: 0; margin: 0; }
.success-head .success-icon svg { width: 18px; height: 18px; }
/* the door board: Confirm's own .sum-card holding DOOR -> material -> parts
   line, a hairline, then OPENING -> the typed size, both big values sharing
   the hero date's own grammar (t-h1 / 800 / 1.1 / -.02em / ink). */
.done-door { display: flex; flex-direction: column; gap: 2px; padding: var(--sp-3) 16px; margin-bottom: var(--sp-2); }
/* FIX ROUND 3 (/proof rejection 3) — shell.css's bare `.tag` is
   `background: var(--board)`, the same grey as `.sum-card`/`.done-door`
   behind it, so the job-site chip had no visible fill (card draws it
   white — same move as Confirm's own `.sum-compact .tag { background:
   var(--paper) }`). Fill only; the card's own `margin-bottom: 4px` is
   deliberately NOT taken here — measured, it costs 3px and puts the
   receipt link 0.75px under the sticky foot on the Mirror state. */
.done-door .tag { background: var(--paper); margin-bottom: 1px; }
.done-k {
  font-family: var(--font-mono); font-size: 10px; font-weight: 600; letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-mute); display: block;
}
.done-big { display: block; font-size: var(--t-h1); font-weight: 800; line-height: 1.1; letter-spacing: -.02em; color: var(--ink); margin: 2px 0 0; }
.done-big.done-material { text-transform: uppercase; letter-spacing: .01em; }
.done-big.num { font-weight: 700; letter-spacing: 0; }
.done-sub { font-size: 15px; font-weight: 600; color: var(--ink-soft); line-height: 1.3; margin-top: 2px; }
.done-div { height: 1px; background: var(--dust); margin: 6px 0 4px; }
.done-ref { display: flex; flex-direction: column; gap: 2px; margin: 0 0 var(--sp-2); }
.done-ref .ref { font-size: 20px; margin: 0; }

/* CR-BUILDR-A019-8 — the signed-out `#/paid` return page (card 15). The
   number block reuses `.success .ref`/`.num` verbatim (below); these five
   rules are the minimum the card needs beyond what this file already
   ships, byte-for-byte off card 15's own `<style>` (proof report 1, B3):
   `.ref-at` (the card's own label atom, replacing `.pickup-at` per the
   fix-round instruction — same look, `.pickup-block .pickup-at`'s own
   declarations, just not borrowed under a foreign class name), the
   `.close-block` divider (same shape as `.paid-block` above: a hairline
   rule then centered text) holding the close instruction and the smaller
   muted WhatsApp line, and the `.mark` row for the BUILDR mark at the
   foot — no new token, no new hex; every value below is one already
   declared in `tokens.css`. */
.ref-at {
  font-family: var(--font-mono); font-size: 10px; font-weight: 600; letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-mute); display: block; margin-bottom: 2px;
}
.close-block { margin: 18px 24px 0; padding-top: 14px; border-top: 1px solid var(--dust); text-align: center; }
.close-line { font-size: 14px; color: var(--ink-soft); margin: 0 0 4px; line-height: 1.5; }
.close-line strong { color: var(--ink); font-weight: 700; }
.wa-note { font-size: 12.5px; color: var(--ink-mute); margin: 0; }
.paid-return-mark {
  display: flex; align-items: center; justify-content: center; gap: 8px; padding: 14px 24px 0; opacity: .85;
}

/* paid online (order-done-paid.html, CR-BUILDR-A001-26) — the receipt's
   bookkeeping, lowest on the screen, hairline above it.
   FIX ROUND 1 (CR-BUILDR-A022-6, /proof rejection 1) — margin-top 18px ->
   --sp-3 (12px), padding-top 14px -> --sp-3 (12px): closing the gap that
   pushed "View Stripe receipt" under the sticky foot at 428x926, same
   existing-token rule as the Done-only classes above. This block is never
   rendered on Confirm & pay (confirm.js has no `.paid-block` markup), so
   this change cannot touch Confirm's pixel-identity. */
.paid-block {
  display: flex; flex-direction: column; gap: 2px; margin: var(--sp-3) 0 0; padding-top: var(--sp-3);
  border-top: 1px solid var(--dust);
}
.paid-at {
  font-family: var(--font-mono); font-size: 10px; font-weight: 600; letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-mute);
}
.paid-amt { font-size: 16px; font-weight: 700; color: var(--ink); }
.paid-note { font-size: 12.5px; color: var(--ink-mute); }
.receipt-link {
  /* CR-BUILDR-A022-6, adoption map: align-self: center -> flex-start — the
     screen is left-aligned now, not centred. */
  display: inline-flex; align-items: center; justify-content: center; align-self: flex-start;
  min-height: 44px; padding: 8px 6px; margin-top: 2px;
  font-size: 12.5px; font-weight: 700; color: var(--navy); text-decoration: underline; text-underline-offset: 3px;
  /* CR-BUILDR-A016-10 — was an `<a>`, now a `<button>` (openExternal(), not
     an href) so the in-app browser is reachable the same way on both
     screens. No color/typography change; only the tag's own default chrome
     is reset. No new token, no new hex. */
  background: none; border: none; font-family: var(--font-ui); cursor: pointer;
}

@media (prefers-reduced-motion: reduce) {
  .spinner { animation: none; }
}
