/* public/pricing-tool.css
   Shared page-local styles for the public Online Pricing Tool's two pages
   (quote.html - full quiz, book.html - quick book-now). Both are
   unauthenticated public pages (no shell.css/appbar, per
   build-consistency-contract.md 1.1's public-page exception), so this is
   NOT loaded on any /tools/ page and is separate from design-system.css.

   Every color/spacing/radius flows through a design-system.css v1 token -
   this file assumes design-system.css is already linked by the host page.
   No design-system-v2 classes (.ds-tier-card etc.) appear anywhere - v1
   tokens only, per the build brief. Kept as its own file (rather than
   duplicated in each page's own <style> block) because both quote.html and
   book.html render the identical category grid / question pills /
   confirmation screen - one fix here fixes both pages.

   2026-07-20 design pass (Richard: "there are no colors, logos, nothing" -
   the first build used flat v1 neutrals only and never touched the real
   Home Therapist brand identity). This pass adds:
     - the real logo (mascot + wordmark, public/icons/logo-mascot.png /
       logo-lockup.png - trimmed/exported from public/icons/source/*.png,
       see git history for the export script) in the top bar and footer
     - a colorful gradient hero band, reusing VERBATIM the exact recipe
       design-system.css v1's own .ds-card-hero already uses (same tokens,
       same rgba stops - this is an approved, already-shipped pattern, not
       a new invented color) - see design-system.css .ds-card-hero for the
       source of truth if this ever needs to be kept in sync
     - colored icon tiles on the category grid (royal/sky/mint/gold
       rotation via :nth-child - CATEGORIES in pricing-tool-quiz.js always
       renders in the same fixed 5-item order on both pages, so this needs
       no JS/data change)
     - a real bronze/silver/gold tier ladder on the AC results cards. The
       bronze/silver values below are NOT invented here - they are pre-
       adopted verbatim from plans/design-system-v2.md section 8's already-
       Richard-approved tier-ladder tokens (--tier-value-* and
       --tier-premium-* families), defined locally as `--pt-*` custom
       properties because v2's CSS
       hasn't been rolled out sitewide yet (see design-system-v2.md's
       banner - that rollout is a separate, unscoped, ungreenlit project).
       Elite reuses the existing LIVE v1 --brand-gold-a/-b tokens directly,
       same as the rest of the file. When v2 does roll out sitewide, these
       `--pt-tier-*` vars can be deleted in favor of the real `--tier-*`
       tokens - the values already match.
     - a branded footer trust bar (logo + license numbers + reviews +
       financing partners), matching callhometherapist.com's credibility
       signals instead of a plain gray legal line. */

:root {
  /* Pre-adopted from design-system-v2.md section 8 (Richard-approved, not
     yet shipped sitewide) - bronze "Value" + silver "Premium" tier-ladder
     colors. Gold "Elite" reuses the existing live --brand-gold-a/-b below. */
  --pt-tier-value-a: #C89A5D;
  --pt-tier-value-b: #9C7444;
  --pt-tier-premium-a: #DEE2E8;
  --pt-tier-premium-b: #AEB6C1;
}

* { box-sizing: border-box; }
body { margin: 0; padding: 0; font-family: var(--font-sans); background: var(--bg); color: var(--ink); min-height: 100vh; }
/* 2026-07-21 polish pass (Richard: desktop had ~260px of dead space on each
   side at common laptop/monitor widths). Widened from 760 to 960 - enough
   for the category grid to breathe in one row, still narrow enough that the
   address/contact form steps don't feel like a form floating in a football
   field (Angi/Stripe/HomeAdvisor-style flows all cap similarly even on wide
   monitors - full-bleed single-column forms are their own antipattern). */
.qt-wrap { max-width: 960px; margin: 0 auto; padding: var(--s-5) var(--s-4) var(--s-16); }
a { color: inherit; }
[hidden] { display: none !important; }

/* ── 16px floor on every text-entry control (iOS focus-zoom) ──────────────
   🔴 2026-08-12 mobile funnel audit. Do NOT lower this back to --text-base.

   Mobile WebKit zooms the whole viewport in when a focused text field
   computes under 16px, and it does not zoom back out on blur. Every text
   control on these four pages inherited .ds-input's --text-base (15px) from
   design-system.css, so the audit's live check of the production page found
   all 9 inputs at 15px. 98.6% of this funnel's traffic (4,539 of 4,607
   clicks) opens inside the Facebook or Instagram in-app browser, which is
   WebKit, so this fired on essentially every paid visitor rather than on
   some edge case: they tap "Street Address", the page jumps to a magnified
   state, and they type the remaining four address fields and then four
   contact fields on a page they now have to pan sideways to read.

   Scoped HERE on purpose, not in design-system.css. .ds-input is shared by
   12 pages including staff tools and the mobile app, so raising it at source
   is a design-system change Richard has not greenlit (and the v2 rollout is
   its own separate, ungreenlit project). pricing-tool.css is loaded by
   exactly four pages - ac-replacement.html, quote.html, book.html,
   out-of-area.html - which is precisely the cold-paid-traffic funnel and
   nothing else, so the blast radius is exactly the pages with the problem.

   --text-md IS the design system's 16px step (1rem, and nothing in this
   funnel overrides the root font-size), so this stays on the token scale
   instead of hardcoding a pixel value.

   This is a FLOOR, not a restyle: it declares font-size and nothing else.
   Padding, colour, border and radius all stay with the design system, so it
   is theme-neutral by construction (measured identical in light and dark).
   The one knock-on is height: line-height is 1.4, so each control grows
   1.39px, 43px to 44.39px, which incidentally clears the 44px touch-target
   floor these pages were just under. Measured at 375px and at 320px on the
   address and contact steps: control widths, right edges and document scroll
   width are byte-identical to before, because the grids are already
   single-column under 520px and every control is width:100% + border-box.
   Total step growth is ~7px of vertical scroll. Nothing reflows.

   The element qualifiers are deliberate: booking-widget.js (mounted by
   quote.html and book.html for the address and contact steps) appends its
   own <style> to document.head at mount time, i.e. AFTER this file in the
   cascade, so a bare `.ds-input` here could one day be silently outranked by
   an equal-specificity rule injected at runtime. Qualifying by element wins
   regardless of order and matches every control these pages actually render
   (booking-widget renders the State picker as .ds-select, hence its
   inclusion). The consent checkbox on ac-replacement.html is intentionally
   left alone: WebKit only zooms for text-entry controls, never on checkbox
   focus, and it is not a .ds-* control anyway. */
input.ds-input,
select.ds-select,
textarea.ds-textarea {
  font-size: var(--text-md);
}

/* ── Top bar (always visible) - logo lockup + call button ───────────────── */
.qt-topbar { display: flex; align-items: center; justify-content: space-between; gap: var(--s-3); margin-bottom: var(--s-5); flex-wrap: wrap; }
.qt-brand { display: inline-flex; align-items: center; gap: 10px; font-weight: 800; font-size: var(--text-md); letter-spacing: var(--tracking-tight); }
/* Full legal name (2026-07-22, Richard) wraps to 2 lines at phone widths -
   a smaller size keeps it tidy without truncating the name. */
@media (max-width: 480px) { .qt-brand { font-size: var(--text-sm); } }
.qt-brand-mark { height: 34px; width: auto; display: block; }
.qt-topbar-phone { display: inline-flex; align-items: center; gap: 6px; font-weight: 700; color: var(--accent-ink, var(--accent)); text-decoration: none; font-size: var(--text-sm); min-height: 44px; padding: 0 4px; border-radius: var(--r-full); transition: background var(--dur-fast) var(--ease); }
.qt-topbar-phone:hover { background: var(--brand-royal-soft); }
.qt-topbar-phone svg { width: 16px; height: 16px; }

/* ── Hero band - the exact .ds-card-hero recipe from design-system.css
   (radial gold bloom + royal-to-navy gradient, white text), reused
   verbatim for a full marketing hero card instead of a small dashboard
   card. ──────────────────────────────────────────────────────────────── */
.qt-hero-band {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-xl);
  padding: var(--s-6) var(--s-5);
  margin-bottom: var(--s-5);
  color: #fff;
  background:
    radial-gradient(800px 300px at 120% -20%, rgba(244,217,123,0.55) 0%, transparent 55%),
    linear-gradient(135deg, var(--brand-royal) 0%, var(--brand-royal-ink) 55%, #1B3B6B 100%);
  box-shadow: 0 14px 32px -12px rgba(61,124,201,0.55), 0 4px 10px -4px rgba(212,169,74,0.25);
}
.qt-hero-band::before {
  content: ""; position: absolute; inset: 0;
  background: radial-gradient(400px 180px at 0% 100%, rgba(0,160,223,0.25) 0%, transparent 60%);
  pointer-events: none;
}
.qt-hero-band::after {
  content: ""; position: absolute; top: 0; left: 0; right: 0; height: 3px;
  background: var(--brand-gold-grad);
}
.qt-hero-mascot {
  position: absolute; right: -8px; bottom: -14px; height: 132px; width: auto;
  opacity: 0.94; pointer-events: none; filter: drop-shadow(0 10px 18px rgba(0,0,0,0.25));
}
@media (max-width: 620px) { .qt-hero-mascot { display: none; } }
.qt-hero { position: relative; z-index: 1; text-align: center; margin-bottom: var(--s-6); }
.qt-hero-eyebrow { font-size: var(--text-xs); letter-spacing: 0.14em; text-transform: uppercase; color: rgba(255,255,255,0.75); font-weight: 700; margin-bottom: var(--s-2); }
.qt-hero-title { font-size: 1.9rem; font-weight: 800; letter-spacing: var(--tracking-tight); line-height: 1.15; margin-bottom: var(--s-2); color: #fff; }
.qt-hero-sub { color: rgba(255,255,255,0.82); font-size: var(--text-md); max-width: 480px; margin: 0 auto var(--s-3); line-height: 1.5; }
.qt-review-badge { display: inline-flex; align-items: center; gap: 6px; font-size: var(--text-sm); color: #fff; background: rgba(255,255,255,0.14); backdrop-filter: blur(6px); border: 1px solid rgba(255,255,255,0.22); border-radius: var(--r-full); padding: 6px 14px; }
.qt-review-badge .stars { color: var(--brand-gold-b); letter-spacing: 1px; }

/* ── How it works - numbered steps rotate through the brand trio ─────────── */
.qt-steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s-3); margin: var(--s-6) 0; }
@media (max-width: 620px) { .qt-steps { grid-template-columns: 1fr; } }
.qt-step { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); padding: var(--s-4); text-align: center; }
.qt-step-num { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: 50%; background: var(--accent); color: #fff; font-weight: 800; font-size: var(--text-sm); margin-bottom: var(--s-2); }
.qt-step:nth-child(1) .qt-step-num { background: var(--brand-royal); }
.qt-step:nth-child(2) .qt-step-num { background: var(--brand-sky); }
.qt-step:nth-child(3) .qt-step-num { background: var(--brand-mint-ink); }
.qt-step-title { font-weight: 700; margin-bottom: 4px; }
.qt-step-text { font-size: var(--text-sm); color: var(--ink-2); line-height: 1.4; }

/* ── Back link + step eyebrow (category icon + title above a sub-step) ──── */
.qt-back { display: inline-flex; align-items: center; gap: 6px; background: none; border: 0; color: var(--ink-2); font: inherit; font-size: var(--text-sm); font-weight: 600; cursor: pointer; padding: 8px 0; min-height: 44px; margin-bottom: var(--s-2); }
.qt-back:hover { color: var(--accent-ink, var(--accent)); }
.qt-step-eyebrow { display: flex; align-items: center; gap: var(--s-2); margin-bottom: var(--s-4); }
.qt-step-eyebrow .qt-cat-icon { width: 36px; height: 36px; }
.qt-step-eyebrow .qt-cat-icon svg { width: 20px; height: 20px; }
.qt-step-eyebrow-title { font-size: 1.3rem; font-weight: 800; }

/* ── Action panel (2026-07-21 polish pass) - wraps the category-picker
   heading + grid in a tinted surface so it visually pops forward as "the
   part you click," distinct from the passive hero/how-it-works reading
   above it (Richard: "I don't see the need to click on anything"). Shared
   by quote.html's category step and book.html's service-picker step. ───── */
.qt-action-panel { background: var(--surface-2); border-radius: var(--r-xl); padding: var(--s-5) var(--s-4); }
.qt-action-title { font-size: 1.2rem; font-weight: 800; text-align: center; margin: 0 0 4px; }
.qt-action-sub { text-align: center; font-size: var(--text-sm); color: var(--ink-2); margin: 0 0 var(--s-4); }

/* ── Category grid - each tile rotates through a brand color so the grid
   reads as colorful/inviting rather than one flat monochrome repeat. Fixed
   1-then-3-column breakpoint (was a hardcoded 2-column grid always) so
   quote.html's 3 categories land in one clean row instead of leaving an
   orphan card alone on its own row (Richard: "feels unfinished") - also
   improves book.html's 5-category grid (was 2+2+1, now 3+2). A first pass
   used `repeat(auto-fit, minmax(240px,1fr))`, but a fresh-eyes review
   caught that auto-fit's continuous width math still lands on 2 columns
   for any viewport ~556-807px (iPad portrait at 768px included) -
   reproducing the exact bug this was meant to fix. A discrete breakpoint
   tied to the tool's actual (fixed, known) category counts has no such gap
   - below 700px it's always 1 column (safe), at/above it's always exactly
   3, with nothing in between. ────────────────────────────────────────────── */
.qt-cat-grid { display: grid; grid-template-columns: 1fr; gap: var(--s-3); }
@media (min-width: 700px) { .qt-cat-grid { grid-template-columns: repeat(3, 1fr); } }
.qt-cat-card { position: relative; overflow: hidden; display: flex; align-items: center; gap: var(--s-3); text-align: left; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); padding: var(--s-4) var(--s-4) var(--s-4) calc(var(--s-4) + 4px); cursor: pointer; font: inherit; color: var(--ink); min-height: 76px; transition: border-color .12s, box-shadow .12s, transform .12s; }
.qt-cat-card::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 4px; }
.qt-cat-card:hover, .qt-cat-card:focus-visible { border-color: var(--accent); box-shadow: var(--sh-md); transform: translateY(-1px); }
.qt-cat-icon { flex: 0 0 auto; width: 44px; height: 44px; border-radius: var(--r-md); background: var(--accent-soft); color: var(--accent-ink, var(--accent)); display: flex; align-items: center; justify-content: center; }
.qt-cat-icon svg { width: 24px; height: 24px; }
.qt-cat-card:nth-child(1) .qt-cat-icon { background: var(--brand-sky-soft); color: var(--brand-sky-ink); }
.qt-cat-card:nth-child(2) .qt-cat-icon { background: var(--brand-mint-soft); color: var(--brand-mint-ink); }
.qt-cat-card:nth-child(3) .qt-cat-icon { background: var(--brand-royal-soft); color: var(--brand-royal-ink); }
.qt-cat-card:nth-child(4) .qt-cat-icon { background: rgba(212,169,74,0.16); color: var(--brand-gold-a); }
.qt-cat-card:nth-child(5) .qt-cat-icon { background: var(--brand-sky-soft); color: var(--brand-sky-ink); }
.qt-cat-card:nth-child(1)::before { background: var(--brand-sky); }
.qt-cat-card:nth-child(2)::before { background: var(--brand-mint); }
.qt-cat-card:nth-child(3)::before { background: var(--brand-royal); }
.qt-cat-card:nth-child(4)::before { background: var(--brand-gold-a); }
.qt-cat-card:nth-child(5)::before { background: var(--brand-sky); }
.qt-cat-copy { flex: 1 1 auto; min-width: 0; }
/* Label + blurb used to both be inline <span>s with no line break between
   them, so they visually ran together as one string (e.g. "AC Systems & Air
   DuctsNew system, replacement, or duct work") - confirmed live, not just a
   screenshot artifact. display:block puts each on its own line. */
.qt-cat-label { display: block; font-weight: 700; font-size: var(--text-base); }
.qt-cat-blurb { display: block; font-size: var(--text-xs); color: var(--ink-2); margin-top: 2px; line-height: 1.35; }
.qt-cat-chev { flex: 0 0 auto; width: 20px; height: 20px; color: var(--muted); transition: color .12s, transform .12s; }
.qt-cat-card:hover .qt-cat-chev, .qt-cat-card:focus-visible .qt-cat-chev { color: var(--accent-ink, var(--accent)); transform: translateX(2px); }

/* ── Qualifying questions (single-select) ─────────────────────────────────
   2026-07-27 readability pass (Richard, design-pass item 2: "I look into this
   and this conversation has a huge note on it. Little letters, barely
   readable"). Measured on the live page before this change: the note rendered
   at 12px (--text-xs) in mid-grey and the answer chips at 13px (--text-sm),
   under a 16px question label. On a public page a customer reads once, on a
   phone, in a hurry, that is below the readable floor.

   Everything here steps UP: the question is the biggest thing on screen, the
   answers are comfortably tappable, and the help copy is real body text
   rather than fine print. /quote can afford the space because it now shows
   ONE question per screen (item 1).

   Blast radius, deliberate: this file is shared by quote.html, book.html and
   ac-replacement.html. .qt-q-note / .qt-option-btn therefore also grow on
   /book and on the AC landing page's own fallback render - reviewed in both
   and wanted in both, since neither was any more readable than /quote was.
   .qt-q-help / .qt-q-reassure / .qt-option-img are quote-only today (book.js
   renders no help block), and simply never match elsewhere. */
/* 🔴 One question per page must read as a single centred column, not as a
   full-width form. /quote's wrap is 960px, and a lone question stretched
   across all of it leaves the bordered help block sitting at its own 52ch
   reading measure (477px measured) directly beneath a 928px Continue button,
   which reads as broken rather than deliberate.

   Found by LOOKING at the rendered desktop page, not by reading the CSS: at
   375px every element is already narrower than the column, so mobile looked
   correct and hid it completely. The old .qt-q-note had the same 480px cap and
   the same mismatch, but as plain grey text nobody noticed - turning it into a
   bordered, filled card is what made the raggedness visible.

   Scoped to #qt-step-questions (quote.js's paged flow) deliberately. /book's
   step is #bk-step-questions and still renders every question at once, where
   the full width is right, so it keeps the per-element reading measure below
   and this rule never touches it. */
#qt-step-questions { max-width: 34rem; margin-left: auto; margin-right: auto; }
#qt-step-questions .qt-q-help,
#qt-step-questions .qt-q-reassure,
#qt-step-questions .qt-q-note { max-width: none; }

/* No max-width here on purpose: the reading measure for the paged flow is set
   once on #qt-step-questions above, which correctly leaves /book's
   all-questions-at-once step at full width. A cap on .qt-q-group itself would
   be redundant on /quote and would silently narrow /book too. */
.qt-q-group { margin-bottom: var(--s-5); }
.qt-q-label { font-weight: 800; font-size: 1.25rem; line-height: 1.3; letter-spacing: -0.01em; margin-bottom: var(--s-4); }
/* Explanatory copy under a question (e.g. how ductwork/plenums are counted)
   - builds trust in the number the customer is about to see, per Richard.
   Legacy single-paragraph form, kept for the questions not yet rewritten
   into a .qt-q-help block. Same content, now at a readable size. */
.qt-q-note { font-size: var(--text-base); color: var(--ink-2); line-height: 1.6; margin-top: var(--s-3); max-width: 52ch; }

/* Titled help block (item 3 - "write this as if it was going to be answered
   by a 5 year old"). One short, checkable instruction per line beats one
   dense paragraph: a homeowner standing at their outdoor unit can follow a
   list, not prose. */
/* Tinted with the brand blue rather than --surface-2. Verified in a real
   browser: --surface-2 (#F1F5F9) against this page's --bg (#F6F8FB) is a
   five-value difference, so the panel did not read as a panel at all - only
   its 1px border separated it, and the help block looked like loose text that
   had drifted. The royal tint also carries meaning: this is the "here is how
   to answer" box, not another card of choices. */
.qt-q-help { margin-top: var(--s-4); max-width: 52ch; background: var(--brand-royal-soft); border: 1px solid var(--accent-soft); border-radius: var(--r-md); padding: var(--s-3) var(--s-4); }
.qt-q-help-title { font-weight: 700; font-size: var(--text-base); color: var(--ink); margin-bottom: var(--s-2); }
.qt-q-help-list { margin: 0; padding-left: 1.15em; }
.qt-q-help-list li { font-size: var(--text-base); color: var(--ink-2); line-height: 1.55; margin-bottom: 6px; }
.qt-q-help-list li:last-child { margin-bottom: 0; }

/* Reassurance line (item 5 - Richard: "we also can add things like those
   reassuring questions to the client"). Deliberately quiet and italic so it
   reads as a friendly aside, not as another instruction to process. */
/* --ink-3 (4.87:1 on white), NOT --muted: --muted is #8A94A7, which measures
   3.05:1 and fails WCAG AA for normal text. Using it here would have quietly
   reintroduced the exact "barely readable" problem this pass exists to fix,
   on the one line meant to put the customer at ease. */
.qt-q-reassure { margin-top: var(--s-3); font-size: var(--text-base); font-style: italic; color: var(--ink-3); max-width: 52ch; line-height: 1.5; }

.qt-q-options { display: flex; flex-wrap: wrap; gap: var(--s-2); }
/* Long answers (e.g. "Bare or patchy, I can clearly see the beams") wrap into
   ragged one-per-row pills anyway - stack them full width on purpose so they
   read as a clean list of choices. */
.qt-q-options.is-stacked { flex-direction: column; align-items: stretch; }
/* justify-content, not just text-align: .qt-option-btn is an inline-flex box
   that centres its own label, and that centring silently beat the text-align
   here - a full-width row with its text floating in the middle. Caught in a
   real browser, not in review. */
.qt-q-options.is-stacked .qt-option-btn { width: 100%; justify-content: flex-start; text-align: left; border-radius: var(--r-md); }

.qt-option-btn { display: inline-flex; align-items: center; justify-content: center; gap: 10px; min-height: 52px; padding: 12px 20px; border-radius: var(--r-full); border: 2px solid var(--border); background: var(--surface); color: var(--ink); font: inherit; font-size: var(--text-base); font-weight: 600; line-height: 1.35; cursor: pointer; transition: border-color .12s, background .12s, color .12s; }
.qt-option-btn:hover { border-color: var(--accent); }
.qt-option-btn.is-selected { background: var(--accent); border-color: var(--accent); color: #fff; }

/* Photo answers (item 4 - "we could even add photos to that one... a closet
   looks like this"). Only ever applied once quote.js has PRELOADED the image
   and confirmed it loads, so a missing file degrades to the plain text chip
   above rather than a broken-image box. See promoteOptionImages(). */
.qt-q-options.is-photo-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-3); }
.qt-q-options.is-photo-grid .qt-option-btn { flex-direction: column; gap: 0; padding: 0; overflow: hidden; border-radius: var(--r-md); text-align: center; }
.qt-option-img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; display: block; background: var(--surface-2); }
.qt-option-btn.has-img .qt-option-label { display: block; padding: 10px 12px; width: 100%; }

.qt-continue-row { margin-top: var(--s-6); }
.qt-continue-row .ds-btn { width: 100%; }

/* ── Confirmation screen ───────────────────────────────────────────────── */
.qt-confirm { text-align: center; padding: var(--s-6) 0; }
.qt-confirm-icon { width: 64px; height: 64px; border-radius: 50%; background: var(--success-soft); color: var(--success); display: flex; align-items: center; justify-content: center; margin: 0 auto var(--s-4); }
.qt-confirm-icon svg { width: 32px; height: 32px; }
.qt-confirm-title { font-size: 1.5rem; font-weight: 800; margin-bottom: var(--s-2); }
.qt-confirm-msg { color: var(--ink-2); font-size: var(--text-md); max-width: 460px; margin: 0 auto var(--s-5); line-height: 1.5; }
/* 2026-07-29 - the two rules below exist for public/out-of-area.html, the one
   shared out-of-area screen every public funnel surface lands on. They live
   here, with the rest of the .qt-confirm family, rather than in that page's
   own <style> block: this file loads BEFORE any page-local block, so a
   duplicate .qt-confirm-* rule in a page would silently win the cascade (see
   the .qt-hero-eyebrow and .qt-tier-* notes elsewhere in this file for the two
   times that has already bitten us).
   .is-warn swaps the success-green icon pair for the warn pair. An out-of-area
   answer is not a success, and the green checkmark it would otherwise render
   is precisely what made a refused booking read as a confirmed one. */
.qt-confirm-icon.is-warn { background: var(--warn-soft); color: var(--warn); }
/* Centered, wrapping action row - on a phone the two buttons stack and each
   keeps the 48px height .ds-btn-lg gives it, so both stay above the 44px
   touch-target floor. */
.qt-confirm-actions { display: flex; flex-wrap: wrap; gap: var(--s-3); align-items: center; justify-content: center; }
/* On a phone, two centered auto-width buttons stack ragged. Full width is
   both tidier and a bigger tap target, and it matches what
   .qt-continue-row .ds-btn already does on every other step of these flows. */
@media (max-width: 480px) {
  .qt-confirm-actions { flex-direction: column; align-items: stretch; }
  .qt-confirm-actions .ds-btn { width: 100%; }
}

/* ── 2026-07-22 design pass (Richard: competitor research on climacoreair.com/
   modernize.com/topshelfheating.com/costflowai.com + flo-riteac.com's
   hvacquote.ai-powered tool - "make it feel alive, not a black box"). Two
   keyframes below are DEFINED here (shared file) but their APPLICATION is
   scoped to quote.html-only container IDs (#qt-cat-grid, #qt-q-groups,
   #qt-booking-mount) or to classes ONLY quote.js's own markup ever emits
   (.qt-cat-price, .qt-cta-glow, .qt-tier-savings, .qt-tier-rebate,
   .qt-results-trust, .qt-progress-*, .qt-calculating*) - book.html's
   renderer (public/book.js) never outputs any of these class names or IDs,
   so a keyframe definition sitting in this shared file has nothing to match
   there. Per Richard: "/quote ONLY, forget about /book until I say we're
   done" - verify this scoping holds before ever touching this block. ────── */

/* 2026-07-28 (Richard): this was qt-bounce-in, a 450ms spring that overshot in
   BOTH dimensions - translateY(-2px) and scale(1.01) at the 60% mark - before
   settling back. On a one-question-per-screen flow that fires on every single
   answer, and he read the overshoot for exactly what it is: "that kind of
   movement that the text does when you change, it's kinda like it jumps out.
   I just prefer just to show straight or a softer animation."
   Now a plain fade with a 4px rise: no overshoot, no scale, half the duration.
   Content still arrives rather than snapping in, but nothing ever travels past
   its final position, which is the part that read as a jump. */
@keyframes qt-fade-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}
/* quote.js renders its category grid into #qt-cat-grid (book.js uses
   #bk-cat-grid) and its question groups into #qt-q-groups (book.js uses
   #bk-q-groups) - scoping by container ID keeps this off book.html even
   though `.qt-cat-card`/`.qt-q-group` are shared classes. */
#qt-cat-grid .qt-cat-card, #qt-q-groups .qt-q-group {
  animation: qt-fade-in .22s ease-out both;
}
@media (prefers-reduced-motion: reduce) {
  #qt-cat-grid .qt-cat-card, #qt-q-groups .qt-q-group { animation: none; }
}

/* A gentle, endless "breathe" pulse for the one primary CTA per screen that
   most deserves the eye (flo-riteac.com's "GET FREE QUOTE" button uses the
   same scale+glow recipe). Gold, not the generic accent - reserved for a
   genuine premium/action moment per design-system-v2.md 5.3, and a pricing
   CTA is exactly that. Applied only via this class - quote.js opts specific
   buttons in (the recommended tier's CTA, the final booking CTA scoped
   through #qt-booking-mount below); book.js's markup never emits this class. */
@keyframes qt-breathe {
  0%, 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(212,169,74,0); }
  50% { transform: scale(1.025); box-shadow: 0 0 0 5px rgba(212,169,74,0.25), 0 0 22px 4px rgba(212,169,74,0.35); }
}
.qt-cta-glow { animation: qt-breathe 2.6s ease-in-out infinite; }
.qt-cta-glow:hover, .qt-cta-glow:focus-visible { animation-play-state: paused; }
#qt-booking-mount #bw-book-btn { animation: qt-breathe 2.6s ease-in-out infinite; }
#qt-booking-mount #bw-book-btn:hover, #qt-booking-mount #bw-book-btn:focus-visible { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) {
  .qt-cta-glow, #qt-booking-mount #bw-book-btn { animation: none; }
}

/* ── Step progress indicator (quote.html only - new markup, see quote.html) */
.qt-progress-row { display: flex; align-items: center; gap: var(--s-3); margin-bottom: var(--s-4); }
.qt-progress-track { flex: 1 1 auto; height: 6px; border-radius: var(--r-full); background: var(--surface-2); overflow: hidden; }
.qt-progress-fill { height: 100%; width: 0; border-radius: var(--r-full); background: linear-gradient(90deg, var(--brand-royal), var(--brand-gold-a)); transition: width .35s ease; }
.qt-progress-label { flex: 0 0 auto; font-size: var(--text-xs); color: var(--ink-2); font-weight: 600; white-space: nowrap; }
@media (max-width: 480px) { .qt-progress-label { display: none; } }

/* ── Real "starting at" price badge on each category card (quote.js only -
   book.js never populates this, book never shows a price by design). ────── */
.qt-cat-price { display: block; font-size: var(--text-xs); font-weight: 700; color: var(--brand-gold-a); margin-top: 4px; min-height: 1em; }

/* ── "Calculating your price" transition (quote-only - shown briefly in
   #qt-results-mount while fetchPricing()'s response is in flight). ───────── */
.qt-calculating { text-align: center; padding: var(--s-8) 0; }
.qt-calculating-spinner { width: 40px; height: 40px; margin: 0 auto var(--s-4); border-radius: 50%; border: 4px solid var(--border); border-top-color: var(--brand-gold-a); animation: qt-spin .8s linear infinite; }
.qt-calculating-text { color: var(--ink-2); font-size: var(--text-md); font-weight: 600; }
@keyframes qt-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .qt-calculating-spinner { animation-duration: 2.4s; } }

/* ── Results trust line + real savings/rebate block (quote.js AC tiers) ──── */
.qt-results-trust { text-align: center; font-size: var(--text-sm); color: var(--ink-2); margin: 0 0 var(--s-4); }
.qt-results-trust .stars { color: var(--brand-gold-a); letter-spacing: 1px; }

/* ── Insulation toggle (item 13, funnel-architecture.md section 11 - "Add
   the insulation toggle the heat calc has... can drop a 4 ton to a 3.5
   ton"). Segmented-control style, matching the pill-CTA treatment this page
   already uses (see quote.html's .ds-btn-duo override note) rather than a
   plain <select>, since there are only 2-3 options and a visible toggle
   makes the "this changes your price" relationship obvious at a glance.
   quote.js only, never rendered on /book (book.js has no tier picker at
   all, so nothing here can affect it). ───────────────────────────────────── */
.qt-ins-toggle { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; margin: 0 0 var(--s-4); }
.qt-ins-option { font-size: var(--text-sm); font-weight: 600; padding: 8px 16px; border-radius: var(--r-full); border: 1px solid var(--border); background: var(--surface); color: var(--ink-2); cursor: pointer; min-height: 40px; }
.qt-ins-option:hover { border-color: var(--pt-tier-premium-a); }
.qt-ins-option.is-selected { background: var(--accent); border-color: var(--accent); color: #fff; }
.qt-tier-savings { background: var(--success-soft); border-radius: var(--r-md); padding: 8px 10px; margin-top: 6px; }
.qt-tier-savings-line { display: flex; justify-content: space-between; gap: 6px; font-size: var(--text-xs); color: var(--ink); }
.qt-tier-savings-line strong { color: var(--success); white-space: nowrap; }
/* The ONE savings line per AC card (2026-09 owner decision: the average
   monthly electricity saving, no payback, no yearly figure). */
.qt-tier-savings-head { font-size: var(--text-sm); font-weight: 800; color: var(--success-ink, var(--success)); text-align: center; margin-bottom: 4px; line-height: 1.35; }
.qt-tier-savings-head.is-none { color: var(--ink-2); font-weight: 700; }
/* Why a card's size differs from the recommendation (Elite in whole tons, a
   size missing from the price book stepped up). */
.qt-tier-size-reason { font-size: var(--text-xs); color: var(--ink-3); font-style: italic; line-height: 1.4; margin: -2px 0 4px; }
/* A home one 5-ton cannot carry (TON-04): priced as the systems it needs. */
.qt-needs-multi { max-width: 40rem; margin: 0 auto var(--s-3); padding: var(--s-2) var(--s-3); text-align: center; font-size: var(--text-sm); line-height: 1.5; color: var(--ink); background: var(--warn-soft); border: 1px solid var(--border); border-radius: var(--r-md); }
.qt-tier-rebate { font-size: var(--text-xs); color: var(--ink-2); text-align: center; margin-top: 6px; line-height: 1.4; }

/* ── Results: AC tier cards - real bronze / silver / gold ladder. Badge
   fill follows the design-system-v2.md §5.3 "gold bubble" recipe (flat
   fill + near-black text + matching border + inset highlight), applied to
   all three tiers for a consistent premium ladder feel: flat fill, never a
   gradient under text (a gradient under small badge text was tried and
   rejected in the v2 review for hurting legibility). Bronze/silver values
   are --pt-tier-* vars, defined above. Gold reuses the existing live
   --brand-gold-a/-b tokens directly.
   Moved here from quote.html's own inline <style> 2026-07-27
   (funnel-architecture.md section 11, "Live defects" - turning the
   redirect-to-tools flag off showed partly unstyled tier cards to live
   traffic). services/public-ac-replacement.js's OWN inline fallback
   renderer (public/ac-replacement.js renderPricingTiers) emits this EXACT
   markup too - it is the live fallback whenever the redirect flag is off or
   a handoff fails to mint - but ac-replacement.html never loaded quote.html's
   inline styles, only this shared file. Consolidating the whole group here
   is what makes both pages' renderers work. Do not move any of this back
   into a page-local <style> block, and do not add a book.html-only
   override - book.js never emits these classes (it has no tier picker), so
   nothing here can affect /book. ─────────────────────────────────────────── */
.qt-tier-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s-3); margin-bottom: var(--s-5); align-items: start; }
@media (max-width: 700px) { .qt-tier-row { grid-template-columns: 1fr; } }
/* 2026-07-27 audit finding M1. Below 700px the row collapses to ONE full-width
   column, where .qt-tier-card.premium's scale(1.04) makes the card ~4% wider
   than its container and pushes its border past the page gutter. Scaling up is
   a 3-up trick - it needs sibling columns to have slack to grow into. On mobile
   the card is already full width, so it earns its prominence from the border,
   badge and shadow instead, and only keeps the vertical lift. */
@media (max-width: 700px) { .qt-tier-card.premium { transform: translateY(-4px); } }
.qt-tier-card { background: var(--surface); border: 2px solid var(--border); border-radius: var(--r-lg); padding: var(--s-4); display: flex; flex-direction: column; gap: 8px; position: relative; }
.qt-tier-card.value { border-color: var(--pt-tier-value-a); }
/* 2026-07-27 fix (item 10, funnel-architecture.md section 11 - "The three
   price cards look the same. Make Premium pop: bigger, bordered, visually
   dominant."). The mechanism already existed (distinct border color,
   --sh-md, a 4px lift, the Most Popular badge, the glowing CTA) but every
   card shared the same 2px border and the lift alone read as "slightly
   nudged," not dominant. Real dominance now: a visibly thicker 3px border
   (vs 2px on Value/Elite), a real scale (not just a lift, so it reads
   bigger, not just "moved"), and z-index so the larger card overlaps its
   neighbors instead of the grid clipping it. align-items:start on the row
   above keeps all three cards top-aligned despite Premium's extra height
   from the scale. */
.qt-tier-card.premium { border-color: var(--pt-tier-premium-a); border-width: 3px; box-shadow: var(--sh-md); transform: translateY(-4px) scale(1.04); z-index: 1; }
.qt-tier-card.elite { border-color: var(--brand-gold-a); }
.qt-tier-badge { align-self: center; font-size: 0.68rem; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; padding: 4px 14px; border-radius: var(--r-full); box-shadow: inset 0 1px 0 rgba(255,255,255,.6); }
.qt-tier-badge.value { background: var(--pt-tier-value-b); color: #2E1B0A; border: 1px solid var(--pt-tier-value-a); }
.qt-tier-badge.premium { background: var(--pt-tier-premium-b); color: #1C242E; border: 1px solid var(--pt-tier-premium-a); }
.qt-tier-badge.elite { background: var(--brand-gold-b); color: #241A06; border: 1px solid var(--brand-gold-a); }
.qt-tier-rec { align-self: center; font-size: 0.62rem; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; color: var(--brand-gold-a); border: 1px solid var(--brand-gold-a); padding: 2px 10px; border-radius: var(--r-sm); }
.qt-tier-brand { text-align: center; font-weight: 700; font-size: var(--text-md); margin-top: 2px; }

/* ── Goodman / Daikin toggle on a tier card (2026-07-28, Richard) ────────
   Sits exactly where the static brand line was, so a card with one brand and a
   card with two occupy the same vertical rhythm. Deliberately mirrors
   .qt-ins-option, the insulation control the customer has already met on this
   same screen, rather than introducing a second visual language for "pick one".
   Tokens only - the accent-on-selected uses --accent-ink for its foreground
   rather than a raw #fff, so it holds contrast in both themes. */
.qt-tier-brands { display: flex; justify-content: center; gap: 6px; margin-top: 2px; }
.qt-brand-option {
  font: inherit; font-size: var(--text-sm); font-weight: 700;
  padding: 6px 14px; min-height: 36px;
  border-radius: var(--r-full); border: 1px solid var(--border);
  background: var(--surface); color: var(--ink-2); cursor: pointer;
  transition: background .12s, border-color .12s, color .12s;
}
.qt-brand-option:hover, .qt-brand-option:focus-visible { border-color: var(--accent); }
.qt-brand-option.is-selected {
  background: var(--accent); border-color: var(--accent);
  /* Matches .qt-ins-option.is-selected above, deliberately. --accent-ink is the
     ink for a TINTED background (design-system.css pairs it with --accent-soft)
     and on a solid accent fill it renders dark blue on medium blue - measured
     at rgb(30,79,138) on rgb(61,124,201), which is close to unreadable. The
     insulation control two rules up already solved this on this exact accent;
     diverging from the control sitting right next to it would be worse than
     the raw value. */
  color: #fff;
}

/* Fewer than three cards must CENTRE, not stretch or hang left (Richard:
   "you make sure that the cards are properly centered and settled... just
   center properly and show whatever we have. So make it smaller is the idea").
   A gas-furnace job has no Elite and a package job has only Value, so this is
   the normal state for two whole categories of customer, not an edge case.
   auto-flow keeps each card at its natural width instead of one card stretching
   across the full row, which is what "make it smaller" means here. */
/* Above the phone breakpoint only. Below 700px .qt-tier-row is already a
   single stacked column, and an unscoped grid-auto-flow:column here would
   override that and shove two cards side by side on a phone. */
@media (min-width: 701px) {
  .qt-tier-row.is-narrow {
    grid-template-columns: none;
    grid-auto-flow: column;
    /* Explicit equal columns. Without this the columns size to their CONTENT,
       so a card carrying a brand toggle came out wider than one carrying a
       plain brand line - two cards that should read as a matched pair sat 4px
       off centre and at different widths. Measured, not assumed. */
    grid-auto-columns: minmax(0, 300px);
    justify-content: center;
  }
  .qt-tier-row.is-narrow .qt-tier-card { width: 100%; }
  /* Same reasoning as the mobile rule above (2026-07-27 audit finding M1):
     "scaling up is a 3-up trick - it needs sibling columns to have slack to
     grow into". A centred row has fixed columns and no slack, so premium's
     scale(1.04) overflowed its column by 6px a side and pushed the whole row
     visibly off centre. Measured at 158px left vs 152px right. It keeps the
     lift, the thicker border, the badge and the shadow, which is what actually
     marks it as recommended. */
  .qt-tier-row.is-narrow .qt-tier-card.premium { transform: translateY(-4px); }
}
.qt-tier-line { display: flex; justify-content: space-between; gap: 6px; padding: 4px 0; border-bottom: 1px solid var(--border-subtle); font-size: var(--text-sm); }
.qt-tier-line:last-of-type { border-bottom: 0; }
.qt-tier-line-lbl { color: var(--ink-2); }
.qt-tier-line-val { font-weight: 700; text-align: right; }
.qt-tier-price { text-align: center; font-size: 1.15rem; font-weight: 800; color: var(--accent-ink, var(--accent)); margin-top: 4px; }
.qt-tier-financing { text-align: center; font-size: var(--text-xs); color: var(--ink-2); }
/* .ds-btn defaults to --btn-h:40px, under the 44px touch-target floor
   (session-rules.md 5 / build-consistency-contract.md 1.3) - bump it via
   the system's own override variable, same mechanism .ds-btn-lg uses,
   rather than hardcoding a height outside the token layer. */
.qt-tier-cta { margin-top: 6px; --btn-h: 44px; }
/* 2026-07-27 (item 11) - one honest line saying WHICH kind of savings number is
   on screen. Sits between the tier row and the financing disclaimer, and reads
   as a note rather than a warning: this is transparency, not an error state. */
/* 2026-07-27 readability pass: these two were 0.74rem/0.68rem (roughly 12px
   and 11px) in --muted, which measures 3.05:1 on white and fails WCAG AA.
   The savings-basis line is real content a customer is meant to read before
   trusting a number, so it moves up to body size at a passing contrast. The
   financing line stays visually quieter (it is a disclaimer, and should not
   compete with the price) but is no longer unreadably small. */
.qt-savings-basis { font-size: var(--text-sm); color: var(--ink-3); text-align: center; line-height: 1.55; margin: var(--s-4) auto 0; max-width: 46rem; }
.qt-financing-disclaimer { font-size: var(--text-xs); color: var(--ink-3); text-align: center; line-height: 1.5; margin: var(--s-3) 0 var(--s-5); }

/* ── Tier unit photo (item 6) ────────────────────────────────────────────
   Inserted by quote.js promoteTierImages() ONLY after the file is confirmed
   to load, so a card with no photo yet is byte-identical to today's. */
.qt-tier-img { width: 100%; aspect-ratio: 4 / 3; object-fit: contain; display: block; background: var(--surface-2); border-radius: var(--r-sm); margin-top: 4px; }

/* ── "What your price already includes" (item 8) ──────────────────────────
   Sits under the three tier cards, where the customer is deciding. Answers
   the question every quote raises ("what is actually in that number?") in
   plain scope terms rather than leaving them to assume the worst. */
.qt-included { margin: var(--s-6) 0 var(--s-4); background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); padding: var(--s-5); }
.qt-included-title { font-size: 1.1rem; font-weight: 800; text-align: center; margin: 0 0 var(--s-4); }
.qt-included-grid { display: grid; grid-template-columns: 1fr; gap: var(--s-4); }
@media (min-width: 720px) { .qt-included-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.qt-included-item { display: flex; align-items: flex-start; gap: 10px; }
.qt-included-item svg { flex: 0 0 auto; width: 20px; height: 20px; color: var(--success); margin-top: 2px; }
.qt-included-item-title { font-weight: 700; font-size: var(--text-base); line-height: 1.35; }
.qt-included-item-text { font-size: var(--text-base); color: var(--ink-2); line-height: 1.55; margin-top: 2px; }

/* ── Footer - logo lockup + trust bar, matching callhometherapist.com's
   credibility signals instead of a plain gray legal line. ────────────────── */
.qt-footer { margin-top: var(--s-10); padding-top: var(--s-5); border-top: 1px solid var(--border); text-align: center; }
.qt-footer-logo { height: 40px; width: auto; margin: 0 auto var(--s-3); display: block; }
.qt-footer-trust { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 6px 14px; font-size: var(--text-xs); color: var(--ink-2); margin-bottom: var(--s-3); }
.qt-footer-trust .stars { color: var(--brand-gold-a); letter-spacing: 1px; }
.qt-footer-legal { font-size: var(--text-xs); color: var(--muted); line-height: 1.7; }
