/* =============================================================================
   web-brand — the three demo brand sites (gutters / roofing / landscaping).

   SPEC 2026-09-15 D11: this stylesheet is web-amgutter's app/static/style.css
   (the source of 850gutters.com) copied WHOLESALE — Ethan: "copy the whole
   850gutters.com website" — followed by two web-brand blocks:

     1. the D5 colour schemes: the reference's v4 palette is the `blue`
        default in :root; `html[data-scheme="..."]` re-tokens --brand,
        --brand-btn (every one keeps >= 4.5:1 with white text — pinned by
        tests/test_scheme_picker.py), --brand-deep, --ink, --slate and
        --brand-on-dark (the wordmark's colour on the two dark panels,
        >= 3:1 against both — same test);
     2. everything web-brand's own pages (the wizard, the legal pages, the
        refusal pages) need to render exactly as they did: the same class
        names, appended AFTER the reference so they win on order.

   Keep the reference block unedited so a later 850gutters restyle can be
   diffed straight in; put every web-brand change below the two markers.
   ========================================================================== */

/* =============================================================================
   850 Gutters and Lighting — site styles.

   BRAND (2026-09-10, DESIGN.md v4 — superseding v3's American Gutter brand for
   THIS SITE ONLY, which itself superseded the plain-white v1/v2 for this site;
   see DESIGN.md for the full record): Chris retired American Gutters and Gulf
   Coast Lighting and combined them into 850 Gutters and Lighting. The palette
   below is sampled from the new logo — chrome/silver lettering with an
   electric-blue outline on black. svc-admin and web-gclighting are UNCHANGED —
   this is a scoped, deliberate exception for one business's own site, not a
   fleet-wide reversal.

   Contrast rule, carried into every button/CTA rule below: white body TEXT on
   --brand only reaches 4.0:1 (fails AA). --brand-btn (a shade darker) is what
   every FILL with white text on it must use instead — 4.95:1, passes.
   --brand itself stays reserved for outlines, rules, small accents (icons,
   the hero rule, the eave-highlight swatch) and the focus ring, never a
   white-text fill.

   The FUNCTIONAL tool (map, forms, roof-picker, lead capture) stays on a white
   card for readability, exactly as v3 (and v2 before it) left it — the chrome
   around it (nav, hero, footer, CTAs) carries the identity, the tool itself
   doesn't have to fight legibility to read as itself. `color-scheme: light`
   stays: there is still no dark MODE, just a dark brand panel by design.
   ========================================================================== */

/* Montserrat, self-hosted (app/static/fonts/, see its README.txt) — NOT a
   Google Fonts @import. This repo's test_assets.py forbids pulling type from
   a CDN: the map lives on the landing page of a lead-generation site, so a
   font-CDN outage or a corporate DNS block must not take the front door down
   with it, the same reasoning that vendored Leaflet. Both files are variable
   fonts (weight axis 100-900), so two files cover every weight/style this
   stylesheet calls for via the `font-weight: 400 800` ranges below. Montserrat
   800 stands in for the logo's own heavy squared-italic lettering (DESIGN.md
   v4) — do not add a second webfont. */
@font-face {
  font-family: "Montserrat";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("/static/fonts/montserrat-var.woff2") format("woff2");
}
@font-face {
  font-family: "Montserrat";
  font-style: italic;
  font-weight: 400 600;
  font-display: swap;
  src: url("/static/fonts/montserrat-italic-var.woff2") format("woff2");
}

:root {
  color-scheme: light;

  --bg: #ffffff;
  --bg-alt: #f4f5f7;
  --panel: #ebecf0;
  --card: #ffffff;
  --border: #dfe1e6;
  --text: #172b4d;
  --muted: #5e6c84;

  /* 850 Gutters and Lighting brand palette — DESIGN.md v4, sampled from the
     logo (850gutters.webp), 2026-09-10. Retires the American Gutter red/navy
     postcard palette for THIS SITE ONLY. */
  --brand: #0a7fea;        /* identity blue — outlines, rules, accents, focus ring */
  --brand-btn: #0b6fd4;    /* button FILLS with white text: 4.95:1, passes AA.
                              --brand only reaches 4.0:1 with white text — never
                              put white body text directly on --brand. */
  --brand-deep: #1858a7;   /* the logo's shading blue — hover/pressed, dark-panel rules */
  --chrome-light: #f3f3f3; /* letter highlight */
  --chrome: #c2c2c2;       /* letter body — muted text ON dark panels */
  --chrome-dark: #878787;  /* letter shadow — the quietest text on dark panels */
  --ink: #0b0e12;          /* header/footer ground — the logo's field, softened off pure black */
  --slate: #2a2e38;        /* secondary dark panel (hero band) */
  /* `--accent` stays the name every v1 selector already reads (nav-home, plain
     links) — it is the identity blue, and every place it is used sits on a
     WHITE background (see the contrast note above: --brand is for outlines,
     rules and text, never a white-text fill). */
  --accent: var(--brand);
  --accent-hover: var(--brand-deep);

  --ok: #216e4e;      --ok-bg: #dcfff1;
  --warn: #974f0c;    --warn-bg: #fff7d6;
  --danger: #ae2a19;  --danger-bg: #ffedeb;

  --font: "Montserrat", Helvetica, Arial, sans-serif;
  --radius: 0;            /* square corners everywhere — the brand's own rule */
  --radius-card: 0;
  --radius-tag: 0;
  --shadow-card: 0 1px 2px rgba(9, 30, 66, .08);
  --shadow-featured: 0 2px 4px rgba(0, 0, 0, .45);
  --shadow-text: 0 2px 3px rgba(0, 0, 0, .55);
  --ease: 150ms ease;
  --nav-h: 64px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: var(--font);
  font-size: 14px;
  line-height: 1.5;
  background: var(--bg);
  color: var(--text);
  -webkit-text-size-adjust: 100%;
}

h1, h2, h3 { font-weight: 800; line-height: 1.15; color: var(--ink); margin: 0; }
h1 { font-size: clamp(24px, 4vw, 32px); text-transform: uppercase; }
h2 { font-size: 20px; text-transform: uppercase; }
h3 { font-size: 16px; font-weight: 700; }
p { margin: 0 0 .6rem; }

a { color: var(--accent); transition: color var(--ease); }
a:hover { color: var(--accent-hover); }
img { max-width: 100%; height: auto; display: block; }
strong { font-weight: 700; }

/* Line icons: 20px, single colour, inherit from the parent. */
.icon {
  width: 20px;
  height: 20px;
  flex: 0 0 auto;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
  vertical-align: middle;
}

/* =========================================================================
   Top bar — stays WHITE (link legibility; see the file header). Signature
   device: a 3px identity-blue bottom border. Business name is the wordmark in
   TYPE, not the raster logo — the logo doesn't fit a 64px bar at any size that
   still reads. The full logo gets its due size on the hero band below and in
   the footer, both dark panels.
   ========================================================================= */
.nav {
  position: sticky;
  top: 0;
  /* Leaflet's .leaflet-top / .leaflet-bottom control containers are z-index 1000
     (vendor/leaflet/leaflet.css). An equal z-index is resolved by DOM order and
     <main> comes after <nav>, so the map's +/- control would paint OVER the bar.
     Anything here must stay strictly above 1000. */
  z-index: 1001;
  background: var(--bg);
  border-bottom: 3px solid var(--brand);
}
.nav-inner {
  max-width: 1200px;
  margin: 0 auto;
  min-height: var(--nav-h);
  padding: 0 1.25rem;
  display: flex;
  align-items: center;
  gap: 1rem;
}
.nav-brand {
  color: var(--ink);
  text-decoration: none;
  font-size: 17px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: .01em;
  line-height: 1.15;
  flex: 1 1 auto;
  min-width: 0;
}
.nav-brand:hover { color: var(--ink); }
.nav-right { margin-left: auto; display: flex; align-items: center; gap: 1.25rem; flex: 0 0 auto; }
.nav-link {
  white-space: nowrap;
  font-weight: 700;
  text-transform: uppercase;
  font-size: 13px;
  text-decoration: none;
}
.nav-link:hover { text-decoration: underline; }
.nav-home {
  color: var(--accent);
  text-decoration: underline;
  white-space: nowrap;
  font-weight: 700;
  text-transform: uppercase;
  font-size: 13px;
}
.nav-home:hover, .nav-home:focus-visible { color: var(--accent-hover); }
.nav-call {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  color: var(--ink);
  text-decoration: none;
  font-weight: 800;
  white-space: nowrap;
}
.nav-call .icon { color: var(--brand); }
.nav-call:hover { color: var(--brand); }

@media (max-width: 480px) {
  .nav-link { display: none; }
  .nav-inner { gap: .6rem; padding: 0 1rem; }
}

/* =========================================================================
   Hero band — the dark panel under the nav bar. This is where the logo and
   the drop-shadowed uppercase headline live (both correctly on a dark panel,
   never on white or light grey). Everything below it stays on white.
   ========================================================================= */
.hero-band {
  background: var(--slate);
  padding: 2.25rem 0;
}
.hero-band-inner {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 1.25rem;
  display: flex;
  align-items: center;
  gap: 2rem;
  flex-wrap: wrap;
}
.hero-logo {
  height: 110px;
  width: auto;
  flex: 0 0 auto;
}
.hero-copy { flex: 1 1 320px; min-width: 0; }
.hero-band .page-title {
  color: #fff;
  text-shadow: var(--shadow-text);
  margin: 0 0 .4rem;
}
.hero-rule { width: 55px; height: 2px; background: var(--brand); margin: 0 0 .6rem; }
.hero-band .lede {
  color: var(--chrome);
  font-style: italic;
  max-width: 62ch;
}
@media (max-width: 600px) {
  .hero-band { padding: 1.5rem 0; }
  .hero-logo { height: 80px; }
}

/* =========================================================================
   Buttons — the identity-blue square CTA (fill on --brand-btn, border on
   --brand, never white text directly on --brand — see the contrast note at
   the top of this file), hard shadow.
   ========================================================================= */
button, .btn {
  font-family: var(--font);
  font-size: 14px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: .02em;
  border: 1px solid var(--brand);
  border-radius: var(--radius);
  padding: .55rem 1rem;
  background: var(--brand-btn);
  color: #fff;
  cursor: pointer;
  text-decoration: none;
  display: inline-block;
  white-space: nowrap;
  line-height: 1.4;
  box-shadow: var(--shadow-featured);
  transition: background-color var(--ease), color var(--ease), border-color var(--ease), box-shadow var(--ease);
}
button:hover, .btn:hover { background: var(--brand-deep); color: #fff; }
.btn-primary { background: var(--brand-btn); color: #fff; }
.btn-primary:hover { background: var(--brand-deep); }
button.secondary, .btn-secondary {
  background: var(--card);
  color: var(--ink);
  border-color: var(--border);
  box-shadow: var(--shadow-card);
  text-transform: none;
  font-weight: 600;
  letter-spacing: 0;
}
button.secondary:hover, .btn-secondary:hover { background: var(--bg-alt); color: var(--ink); }
:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* =========================================================================
   Funnel band — DESIGN.md v4's licensed marketing funnel (2026-09-10): three
   live-preview tiles with a real OR element between each pair (a grid child,
   never a ::before/::after trick, so it survives the single-column wrap on
   a phone), then the two page-level CTAs. Every tile's motion is a plain
   `transition` fired by app/static/funnel.js toggling one class on a timer —
   no CSS-driven animation timeline anywhere in this file, matching the rest
   of the site.
   ========================================================================= */
.funnel { margin: 1.75rem 0 0; }
.funnel-tiles {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: .75rem;
  align-items: stretch;
}
@media (min-width: 700px) {
  .funnel-tiles { grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr) auto minmax(0, 1fr); }
}
.funnel-tile {
  display: flex;
  flex-direction: column;
  gap: .5rem;
  padding: 1rem;
  background: var(--card);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-card);
  color: var(--text);
  text-decoration: none;
  transition: border-color var(--ease);
}
.funnel-tile:hover, .funnel-tile:focus-visible { border-color: var(--brand); color: var(--text); }
.tile-visual {
  display: block;
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: var(--bg-alt);
  border: 1px solid var(--border);
}
.tile-visual svg { width: 100%; height: 100%; display: block; }
.tile-label { font-weight: 800; font-size: 14px; color: var(--ink); }
.tile-sub { font-size: 13px; color: var(--muted); }

/* Tile 1: a slow satellite zoom onto a roof. The whole scene scales up
   around the roof's own centre point; the outer <svg> clips it, so the
   effect reads as the camera moving closer, not the picture stretching. */
.sat-ground { fill: var(--panel); }
.sat-block { fill: var(--border); }
.sat-roof { fill: none; stroke: var(--brand); stroke-width: 2.5; }
.sat-scene { transform-origin: 77px 56px; transition: transform 3.5s ease-in-out; }
.tile-sat.is-zoomed .sat-scene { transform: scale(1.7); }

/* Tile 2: a short looping draw animation echoing tracer.js's own tap-and-
   connect interaction. A faint ghost of the roofline is always visible (the
   first frame); the bright line draws itself over it and the vertex dots pop
   in behind it, same yellow/brown as the real map's draggable corner markers
   (.vertex-dot above) on purpose -- it is the same interaction, in miniature. */
.draw-ghost { fill: none; stroke: var(--border); stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; }
.draw-line {
  fill: none; stroke: var(--brand); stroke-width: 3;
  stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 206; stroke-dashoffset: 206;
  transition: stroke-dashoffset 2.2s linear;
}
.tile-draw.is-drawn .draw-line { stroke-dashoffset: 0; }
.draw-dot { fill: #ffd54a; stroke: #6b5200; stroke-width: 1.5; opacity: 0; transition: opacity .2s ease; }
.tile-draw.is-drawn .draw-dot { opacity: 1; }
.tile-draw.is-drawn .draw-dot:nth-of-type(1) { transition-delay: 0s; }
.tile-draw.is-drawn .draw-dot:nth-of-type(2) { transition-delay: .5s; }
.tile-draw.is-drawn .draw-dot:nth-of-type(3) { transition-delay: 1s; }
.tile-draw.is-drawn .draw-dot:nth-of-type(4) { transition-delay: 1.4s; }
.tile-draw.is-drawn .draw-dot:nth-of-type(5) { transition-delay: 1.7s; }
.tile-draw.is-drawn .draw-dot:nth-of-type(6) { transition-delay: 2.1s; }

/* Tile 3: a scripted two-line exchange, same plain tone as the describe box
   (_describe_box.html) -- never a price. First frame = the homeowner's line
   already sent; the reply fades in on the loop, then both reset together. */
.tile-chat { display: flex; flex-direction: column; justify-content: center; gap: .4rem; padding: .5rem; }
.chat-bubble {
  max-width: 88%;
  padding: .35rem .55rem;
  font-size: 12px;
  line-height: 1.3;
  border: 1px solid var(--border);
  background: var(--card);
}
.chat-user { align-self: flex-end; background: var(--bg-alt); }
.chat-ai {
  align-self: flex-start;
  border-color: var(--brand);
  opacity: 0;
  transform: translateY(6px);
  transition: opacity .3s ease, transform .3s ease;
}
.tile-chat.is-shown .chat-ai { opacity: 1; transform: none; transition-delay: .6s; }

.funnel-or {
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 800;
  font-size: 12px;
  letter-spacing: .04em;
  color: var(--muted);
}

.funnel-cta { display: block; width: 100%; text-align: center; margin-top: 1.15rem; padding: .7rem 1rem; }
.funnel-cta-secondary { display: block; width: fit-content; margin: .7rem auto 0; }

/* =========================================================================
   How-it-works demonstration — /quote only. The looping MP4 is a committed
   asset; howitworks.js pauses it when prefers-reduced-motion matches.
   ========================================================================= */
.how-it-works { margin: 1.5rem 0 0; }
.how-title { font-size: 20px; margin: 0 0 .25rem; }
.how-sub { color: var(--muted); font-style: italic; margin: 0 0 .75rem; }
.how-frame {
  border: 1px solid var(--border);
  border-top: 3px solid var(--brand);
  background: var(--ink);
  box-shadow: var(--shadow-card);
}
.how-frame video {
  width: 100%;
  height: auto;
  display: block;
  background: var(--ink);
}

/* =========================================================================
   Layout — unchanged rhythm from the functional design; only the chrome
   around it carries the brand.
   ========================================================================= */
.wrap { max-width: 720px; margin: 0 auto; padding: 1.5rem 1rem 0; }

.cols { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1rem; }
@media (min-width: 960px) {
  .wrap-wide { max-width: 1200px; }
  .wrap { padding: 2.5rem 1.25rem 0; }
  .cols-two { grid-template-columns: minmax(0, 58fr) minmax(0, 42fr); gap: 1.25rem; align-items: start; }
  .col-gallery { position: sticky; top: calc(var(--nav-h) + 3px + 1rem); }
  .stage { padding: 1.25rem; }
  .featured { padding: 24px; }
  .trust { gap: 1rem 2.5rem; }
}

/* The one page title and its lead paragraph — on the WHITE page (quote.html,
   privacy.html) this renders in ink, no drop shadow (the brand's own rule:
   the shadow is for photos and slate, never white). On the landing page the
   title instead lives inside .hero-band (see above) and is never duplicated. */
.page-title { margin: 0 0 .3rem; color: var(--ink); }
.page-rule { width: 55px; height: 2px; background: var(--brand); margin: 0 0 .6rem; }
.lede { margin: 0; font-size: 16px; font-style: italic; line-height: 1.5; color: var(--muted); max-width: 62ch; }

.stage {
  background: var(--bg-alt);
  border-radius: var(--radius-card);
  padding: .75rem;
  margin: 1.5rem 0 0;
}

/* THE featured box: the 3px brand-red top border, square corners, the harder
   brand shadow. The instant-quote tool lives inside it, on both / and /quote. */
.featured {
  background: var(--card);
  border: 1px solid var(--border);
  border-top: 3px solid var(--brand);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-featured);
  padding: 24px;
}
.featured-title { font-size: 22px; margin: 0 0 .3rem; }
.featured-sub { color: var(--muted); margin: 0 0 1rem; font-style: italic; }

.steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: .75rem;
}
.step { display: flex; align-items: flex-start; gap: .5rem; font-size: 13px; color: var(--muted); line-height: 1.4; }
.step .icon { color: var(--ink); margin-top: .05rem; }
.step strong { display: block; color: var(--text); font-size: 14px; }

.featured > .tool-step,
.featured > .next-step,
.featured > .lead-card,
.featured > .estimate-card,
.featured > .describe-card,
.featured > .service-selector,
.featured > #lead-section-wrap { border-top: 1px solid var(--border); padding-top: 1rem; margin-top: 1rem; }
.tool-step h3, .next-step h3, .lead-card h3, .estimate-card h3, .describe-card h3,
.service-selector h3 { margin: 0 0 .35rem; }

/* =========================================================================
   Service selector (Phase 3) — app/services.py owns the catalog, this just
   mirrors the roof-shape picker's own look-and-feel so the page reads as one
   system. Suppressed entirely server-side when there is only one service.
   ========================================================================= */
.service-options { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: .5rem; margin-top: .5rem; }
.service-opt {
  flex-direction: column;
  align-items: flex-start;
  text-align: left;
  gap: .15rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: .6rem .7rem;
  cursor: pointer;
  background: var(--card);
  color: var(--text);
  transition: border-color var(--ease), background-color var(--ease);
}
.service-opt:hover { background: var(--bg-alt); }
.service-opt input[type="radio"] { width: auto; margin: 0 0 .2rem; accent-color: var(--brand); }
.service-opt:has(input:checked), .service-opt.is-checked { border-color: var(--brand); background: var(--bg-alt); }
.service-name { font-weight: 700; font-size: 14px; }
.service-opt small { color: var(--muted); font-size: 12px; line-height: 1.35; }

/* The unpriced-service fallback (app/services.py: priced=False still SELLS —
   it captures the lead and says a human will price it). Same visual register
   as .banner, never an invented number. */
#unpriced-note { margin-top: .5rem; }

.consent-check { margin-top: .6rem; font-size: 13px; }
.consent-check span { line-height: 1.4; }

.describe-card textarea { margin-top: .35rem; }
.describe-out {
  margin-top: .75rem;
  padding-top: .75rem;
  border-top: 1px solid var(--border);
}
.describe-out p { margin: 0 0 .35rem; }
.describe-out ul { margin: .1rem 0 .6rem 1.1rem; padding: 0; }
.describe-out .next-step-line { font-weight: 700; color: var(--text); margin-top: .1rem; }

.card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  padding: 1rem;
  margin-bottom: 1rem;
}
.card h2 { margin: 0 0 .35rem; }
.muted { color: var(--muted); font-size: 13px; }
.tiny { font-size: 12px; line-height: 1.45; }
.prose h2 { margin-top: 1rem; }
#your-home { scroll-margin-top: calc(var(--nav-h) + 3px + 8px); }

.trust {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: .75rem 1.5rem;
  margin: 2rem 0 0;
  padding: 1.5rem 0 0;
  border-top: 1px solid var(--border);
  color: var(--muted);
}
.trust li { display: inline-flex; align-items: center; gap: .5rem; }
.trust .icon { color: var(--ink); }

.demo-note {
  margin: 1rem 0 0;
  padding: .5rem .75rem;
  border: 1px solid var(--border);
  border-left: 3px solid var(--warn);
  border-radius: var(--radius);
  background: var(--bg-alt);
  color: var(--text);
}

.next-step .btn { margin-top: .25rem; }

@media (max-width: 600px) {
  .wrap { padding-top: 1.25rem; }
  .stage { padding: .5rem; margin-top: 1.25rem; }
  .featured { padding: 1rem; }
  .steps { grid-template-columns: minmax(0, 1fr); gap: .5rem; }
  .trust { margin-top: 1.5rem; padding-top: 1.25rem; }
}

/* =========================================================================
   Form controls
   ========================================================================= */
.controls, .row { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; margin-top: .5rem; }
.pickers { align-items: flex-end; }
.product-line { margin: 0 .5rem 0 0; font-size: 14px; color: var(--text); }
label { display: flex; flex-direction: column; font-size: 13px; color: var(--muted); gap: .2rem; }
.field { display: flex; flex-direction: column; gap: .25rem; min-width: 0; }
.field-label { font-size: 12px; font-weight: 700; color: var(--muted); }
.field-label .opt { font-weight: 400; }
.field-label .req { color: var(--brand-deep); }
.field-wide { grid-column: 1 / -1; }

input, select, textarea {
  font-family: var(--font);
  font-size: 14px;
  padding: .6rem .7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--text);
  width: 100%;
  transition: border-color var(--ease), box-shadow var(--ease);
}
textarea { resize: vertical; }
input:focus, select:focus, textarea:focus { border-color: var(--brand); outline: 2px solid var(--brand); outline-offset: 0; }
input::placeholder, textarea::placeholder { color: var(--muted); }
.address-row { display: flex; gap: .5rem; margin-top: .5rem; }
.address-row input { flex: 1; }
.perimeter { margin-left: auto; white-space: nowrap; color: var(--muted); }

.fallback-toggle-row { margin: .35rem 0 0; }
.address-fallback {
  margin-top: .5rem;
  padding-top: .75rem;
  border-top: 1px solid var(--border);
}
.address-fallback .lead-form textarea,
.address-fallback .lead-form .check { grid-column: 1 / -1; }

/* =========================================================================
   The one-form page (ops/ONEFORM-NOTES.md): a single form, then a preview
   panel populated by app/static/oneform.js from POST /api/quote's response.
   ========================================================================= */
.one-form textarea,
.one-form .field-wide,
.one-form .check { grid-column: 1 / -1; }
.featured > .one-form,
.featured > .map-preview,
.featured > .quote-preview { border-top: 1px solid var(--border); padding-top: 1rem; margin-top: 1rem; }
.quote-preview { margin-top: 1rem; }
.map-preview { margin-top: 1rem; }
#map-preview-label { margin: 0 0 .4rem; }
.result-cols {
  display: grid;
  gap: .75rem;
  grid-template-columns: minmax(0, 1fr);
  margin-top: .75rem;
}
@media (min-width: 640px) {
  .result-cols { grid-template-columns: 1fr 1fr; }
}
.result-card { margin-bottom: 0; }
.result-card h3 { margin: 0 0 .35rem; font-size: 15px; }

/* =========================================================================
   Map — the satellite imagery inside is content; the frame is plain. It is
   OPEN on load: there is no toggle and nothing hides it.
   ========================================================================= */
.map-frame { position: relative; margin: .5rem 0 0; }
#map {
  height: clamp(320px, 46vh, 440px);
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--panel);
}
/* The locator map on the quote form (below "What's going on?"). Same frame as
   the result map, its own height: it opens on the whole United States, which
   needs less vertical room than a roof outline does. */
.locator { margin-top: .35rem; }
.locator-frame { margin-top: .4rem; }
#locator-map {
  height: clamp(240px, 34vh, 340px);
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--panel);
}
.locator .hint { margin-top: .4rem; }

.demo-chip {
  position: absolute;
  top: .5rem; right: .5rem;
  z-index: 500;
  margin: 0;
  padding: .15rem .5rem;
  border-radius: var(--radius-tag);
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--text);
  font-size: 12px;
  font-weight: 700;
}
.demo-chip[hidden] { display: none; }

/* =========================================================================
   Provenance banner / candidate picker
   ========================================================================= */
.banner {
  margin: .5rem 0 0;
  padding: .5rem .75rem;
  border: 1px solid var(--border);
  border-left: 3px solid var(--muted);
  border-radius: var(--radius);
  background: var(--bg-alt);
  color: var(--text);
}
.banner-ask  { border-left-color: var(--ink); }
.banner-ok   { border-left-color: var(--ok); }
.banner-warn { border-left-color: var(--warn); }

.picker {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: .75rem;
  background: var(--bg-alt);
  margin: .5rem 0;
}
.picker-q { margin: 0 0 .5rem; }
.candidate-list { display: grid; gap: .5rem; }
.candidate {
  display: block;
  width: 100%;
  text-align: left;
  background: var(--card);
  color: var(--text);
  border: 1px solid var(--border);
  padding: .5rem .75rem;
  white-space: normal;
  font-weight: 400;
}
.candidate:hover { background: var(--bg-alt); color: var(--text); }
.candidate-primary { border-color: var(--ink); }
.cand-label { display: block; font-weight: 600; }
.cand-meta { display: block; font-size: 12px; color: var(--muted); margin-top: .1rem; }
.linkish {
  background: none;
  border: 0;
  color: var(--accent);
  padding: .4rem 0 0;
  font-weight: 400;
  text-transform: none;
  text-decoration: underline;
  box-shadow: none;
}
.linkish:hover { background: none; color: var(--accent-hover); }

/* Draggable corner markers on the map. They sit on satellite imagery next to
   tracer.js's yellow polyline, so they match it; this is content inside the map. */
.vertex-dot {
  background: #ffd54a;
  border: 2px solid #6b5200;
  border-radius: 50%;
  cursor: grab;
}
.vertex-dot:active { cursor: grabbing; }

.extra-building { margin: .5rem 0 0; }
.check { flex-direction: row; align-items: flex-start; gap: .5rem; color: var(--text); font-size: 14px; }
.check input[type="checkbox"] { width: auto; margin-top: .2rem; flex: 0 0 auto; }

/* Lighting draw tool (Phase 4) — reuses .vertex-dot's own styling above for
   its markers (no new brand colour introduced in this file); each run's
   POLYLINE colour is set inline in tracer.js, distinct from the roofline
   ring's, which is JS map content rather than a UI brand token. */
.light-run-list { display: grid; gap: .4rem; margin-top: .5rem; }
.run-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .4rem .75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: .4rem .6rem;
  background: var(--card);
}
.run-row.is-active { border-color: var(--brand); }
.run-name { font-weight: 700; font-size: 13px; }
.run-ft { color: var(--muted); font-size: 13px; }
.run-row select { width: auto; padding: .3rem .4rem; font-size: 13px; }
.run-row label.check { font-size: 12px; margin: 0; }

/* =========================================================================
   Roof-shape question — the eave highlight is the identity blue, matching the
   swatch and the legend text below (both say "blue = eave" together, on purpose).
   ========================================================================= */
.why { margin-bottom: .75rem; }
.roof-forms { display: grid; grid-template-columns: repeat(auto-fit, minmax(148px, 1fr)); gap: .5rem; }
.roof-opt {
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: .25rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: .5rem .5rem .6rem;
  cursor: pointer;
  background: var(--card);
  color: var(--text);
  font-size: 13px;
  transition: border-color var(--ease), background-color var(--ease), box-shadow var(--ease);
}
.roof-opt:hover { background: var(--bg-alt); }
.roof-opt input[type="radio"] { width: auto; margin: 0; accent-color: var(--brand); }
.roof-opt:has(input:checked), .roof-opt.is-checked {
  border-color: var(--brand);
  background: var(--bg-alt);
}
.roof-art { display: block; width: 100%; }
.roof-art svg { width: 100%; height: auto; max-height: 100px; }
.roof-name { font-weight: 700; font-size: 14px; text-transform: uppercase; }
.roof-opt small { color: var(--muted); font-size: 12px; line-height: 1.35; }
.legend { margin-top: .5rem; }
.swatch { display: inline-block; width: 14px; height: 4px; border-radius: 2px; vertical-align: middle; margin-right: .2rem; }
.swatch-eave { background: var(--brand); }
.swatch-rake { background: var(--muted); }

/* =========================================================================
   The band — its WIDTH is the message, so the bar must stay legible.
   Every figure in here is LINEAR FEET. No money is rendered on this element.
   ========================================================================= */
.gutter-band {
  margin: 0 0 .75rem;
  padding: .75rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-alt);
}
.band-label { margin: 0; font-size: 13px; color: var(--muted); }
.band-figure { margin: 0 0 .4rem; font-size: 20px; font-weight: 800; line-height: 1.3; }
.band-bar { height: 6px; border-radius: 3px; background: var(--border); overflow: hidden; }
.band-fill { height: 100%; border-radius: 3px; transition: width .25s ease; }
.band-tight { background: var(--ok); }
.band-mid   { background: var(--warn); }
.band-wide  { background: var(--danger); }
.band-spread { margin: .3rem 0 .4rem; }
.band-source { margin: .3rem 0 .1rem; font-weight: 700; }
.band-basis { margin: .1rem 0 .2rem; }
.band-assumption { margin: 0; }

/* =========================================================================
   Estimate (money lives here and ONLY here — see app/static/app.js, which does
   the single cents -> dollars conversion at the render boundary)
   ========================================================================= */
#estimate-box { margin-top: .75rem; }
.range { font-size: 20px; font-weight: 800; line-height: 1.3; }
.disclaimer { color: var(--muted); font-size: 13px; margin: .25rem 0 0; }

/* =========================================================================
   Lead form — the last step inside the featured box.
   ========================================================================= */
.lead-form { display: grid; gap: .65rem; margin-top: .5rem; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
.lead-form button { grid-column: 1 / -1; justify-self: start; }
.lead-result { margin-top: .5rem; font-weight: 700; }

/* =========================================================================
   Gallery — renders ONLY when app/static/photos/MANIFEST.json has real entries.
   ========================================================================= */
.gallery-card { margin-bottom: 0; position: relative; }
.gallery-badge {
  position: absolute;
  top: -10px;
  right: 1rem;
  padding: .25rem .5rem;
  background: var(--brand-btn);
  color: #fff;
  border: 1px solid var(--brand);
  font-size: 11px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: .02em;
}
.slides { margin-top: .5rem; border-radius: var(--radius); }
.slide-list { list-style: none; margin: 0; padding: 0; display: grid; }
.slide { grid-area: 1 / 1; display: none; opacity: 0; transition: opacity .4s ease; }
.slide.is-shown { display: block; }
.slide.is-active { display: block; opacity: 1; }
.shot { margin: 0; }
.shot img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; border-radius: var(--radius); background: var(--panel); }
.shot figcaption { padding: .5rem 0 0; font-size: 13px; color: var(--muted); min-height: 2.3em; }
.slide-controls { display: flex; align-items: center; gap: .5rem; margin-top: .5rem; }
.slide-counter { margin: 0 auto; }
.slide-progress { height: 3px; margin-top: .75rem; border-radius: 2px; background: var(--border); overflow: hidden; }
.slide-progress-fill { height: 100%; border-radius: 2px; background: var(--brand); transition: width .4s ease; }
@media (prefers-reduced-motion: reduce) {
  .slide, .slide-progress-fill, .band-fill, a, button, .btn, input, select, .roof-opt, .how-frame { transition: none; }
}

/* =========================================================================
   Footer — the second (and last) place the logo appears, on the same dark ink
   the logo's own field uses. Links are white/underlined here, NOT the identity
   blue — --brand only clears the AA floor on --ink with no headroom to spare,
   and it fails outright against --slate (the hero band's own dark panel), so
   the footer overrides `a`'s default blue for this dark panel, same reasoning
   that keeps the nav bar white in the first place.
   ========================================================================= */
.foot { margin-top: 3rem; background: var(--ink); color: var(--chrome); border-top: 3px solid var(--brand); }
.foot-inner { max-width: 720px; margin: 0 auto; padding: 2.5rem 1rem 2.25rem; font-size: 13px; }
.foot-logo { height: 96px; width: auto; margin: 0 0 1.15rem; }
.foot p { margin: 0 0 .35rem; }
.foot-name { color: #fff; font-weight: 800; text-transform: uppercase; font-size: 15px; }
.foot a { color: #fff; text-decoration: underline; }
.foot a:hover { color: var(--chrome); }
.foot-links { margin-top: .5rem; }
.attrib { margin-top: .75rem; color: var(--chrome-dark); }

/* A secondary button promoted to "the next thing to press" after a rough geocode
   (tracer.js). Same brand-btn fill as primary, no extra colour. */
button.next-step, .btn.next-step { background: var(--brand-btn, #0b6fd4); color: #fff; border-color: var(--brand, #0a7fea); }

/* =========================================================================
   SERVICES (2026-09-12) — the four-service rebuild.

   Every colour below is an existing token; no new hex enters the stylesheet
   (tests/test_brand.py pins the palette). Square corners, flat fills, no
   halo effects and no CSS motion timelines — DESIGN.md v4's bans still apply
   here, and tests/test_brand.py greps this file for every one of them. The
   waiting state on /quote moves because it is a committed GIF, which is the
   only reason it is a GIF.
   ========================================================================= */

/* ---- The landing page's service grid ---------------------------------- */
.section-title { margin: 2rem 0 .3rem; }
.svc-grid-wrap { margin: 2rem 0 0; }
.svc-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: .85rem;
  margin-top: 1rem;
}
.svc-card {
  display: flex;
  flex-direction: column;
  gap: .3rem;
  padding: 1rem;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  text-decoration: none;
  color: var(--text);
  transition: border-color var(--ease), box-shadow var(--ease);
}
.svc-card:hover { border-color: var(--brand); }
.svc-icon { color: var(--brand); }
.svc-name { font-weight: 800; text-transform: uppercase; letter-spacing: .02em; font-size: 14px; }
.svc-blurb { color: var(--muted); font-size: 13px; line-height: 1.45; }
.svc-card .linkish { margin-top: .2rem; font-size: 13px; }
.svc-grid-note { margin: 1rem 0 1.15rem; max-width: 62ch; }

/* ---- The quote page's picker ------------------------------------------ */
/* .lead-form is a grid of auto-fit columns; without this the fieldset lands in
   ONE column and the four boxes stack in a ~220px strip beside the name field. */
.svc-pick { border: 0; padding: 0; margin: 0 0 1.25rem; grid-column: 1 / -1; }
.svc-pick legend { padding: 0; margin-bottom: .5rem; }
.svc-pick-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: .7rem;
}
.svc-pick-cell { display: flex; flex-direction: column; }
/* The box IS the label, so the whole card is the checkbox's hit area. The
   "want to know more" link sits outside it — an <a> inside a <label> would
   steal the click that should tick the box. */
.svc-pick-box {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-areas: "check icon" "name name" "blurb blurb";
  align-items: center;
  gap: .35rem .5rem;
  padding: .85rem;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  cursor: pointer;
  flex: 1;
  transition: border-color var(--ease), background-color var(--ease);
}
.svc-pick-box input { grid-area: check; width: 20px; height: 20px; margin: 0; accent-color: var(--brand-btn); }
.svc-pick-icon { grid-area: icon; color: var(--brand); justify-self: start; }
.svc-pick-name { grid-area: name; font-weight: 800; text-transform: uppercase; letter-spacing: .02em; font-size: 14px; }
.svc-pick-blurb { grid-area: blurb; color: var(--muted); font-size: 12px; line-height: 1.4; }
.svc-pick-box:hover { border-color: var(--brand); }
.svc-pick-box:has(input:checked) { border-color: var(--brand); background: var(--bg-alt); }
.svc-pick-box:focus-within { outline: 2px solid var(--brand); outline-offset: 2px; }
.svc-pick-more { display: inline-block; margin: .35rem 0 0 .1rem; font-size: 12px; color: var(--brand-deep); }

/* ---- The waiting state ------------------------------------------------ */
.measuring { display: flex; flex-direction: column; align-items: center; text-align: center; gap: .2rem; padding: 1.5rem 0; }
.measuring-icon { width: 72px; height: 72px; }
.measuring-line { font-weight: 800; text-transform: uppercase; letter-spacing: .02em; font-size: 15px; margin: .4rem 0 0; }

/* ---- The itemized result ---------------------------------------------- */
.result-title { margin: 0 0 .6rem; }
.items { list-style: none; margin: 0; padding: 0; display: grid; gap: .6rem; }
.item {
  padding: .85rem;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
}
.item-unpriced { background: var(--bg-alt); }
.item-head { display: flex; align-items: baseline; justify-content: space-between; gap: .75rem; flex-wrap: wrap; }
.item-name { font-weight: 800; text-transform: uppercase; letter-spacing: .02em; font-size: 14px; }
.item-figure { font-weight: 800; font-size: 18px; color: var(--ink); white-space: nowrap; }
.item-unpriced .item-figure { color: var(--muted); font-size: 15px; }
.item-ft, .item-note { margin: .25rem 0 0; }
.item-more { display: inline-block; margin-top: .4rem; font-size: 12px; }
.items-total {
  margin: .9rem 0 0;
  padding-top: .75rem;
  border-top: 1px solid var(--border);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: .02em;
  font-size: 16px;
}

/* ---- The result map + draw control ------------------------------------ */
.map-head { display: flex; align-items: center; justify-content: space-between; gap: .75rem; flex-wrap: wrap; }
.map-head p { margin: 0; }
.map-pen { display: inline-flex; align-items: center; gap: .4rem; }
.draw-actions { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: .6rem; }

/* ---- Service pages ----------------------------------------------------- */
.svc-eyebrow { display: flex; align-items: center; gap: .4rem; color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: .04em; margin: 0 0 .35rem; }
.svc-eyebrow .icon { color: var(--brand); }
.svc-cta-card { border-color: var(--brand); }
.amp-credit { margin: .6rem 0 0; }

/* Two galleries side by side on wide screens; stacked below that. */
@media (min-width: 960px) {
  .cols-two-even { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 1.25rem; align-items: start; }
}

/* THE `hidden` ATTRIBUTE BEATS NOTHING BY DEFAULT.

   `[hidden]` is only `display: none` at user-agent priority, so ANY class rule
   that sets `display` wins over it. `.measuring { display: flex }` and
   `.draw-actions { display: flex }` therefore rendered permanently, and the
   "Instant satellite quote available in a moment" panel sat under the form from
   first paint — with a server-rendered `hidden` attribute that tested clean.
   Caught by looking at the page, not by the suite.

   One global rule instead of a per-class guard, because the next element toggled
   with `hidden` will have this problem too and will not come with a reminder. */
[hidden] { display: none !important; }

/* Service icons run larger than the 20px inline step icons: they are the thing
   that identifies a card at a glance, and the first pass at 20px was unreadable
   — the guard mesh became a blob and the AMP fixture a squiggle. The glyphs
   were simplified too (app/templates/_icons.html); this is the other half. */
.svc-icon .icon, .svc-pick-icon .icon { width: 30px; height: 30px; }
.svc-eyebrow .icon { width: 18px; height: 18px; }

/* Service-area hub, grouped by county (2026-09-13). Fifteen cities in one
   ungrouped column read as an endless list; two columns under a county heading
   fit the same content in half the scroll and say where the business works. */
.area-group { margin-top: 1.75rem; }
.area-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: .7rem; margin-top: .9rem; }
.area-card {
  display: flex; flex-direction: column; gap: .25rem; padding: .85rem;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius-card); box-shadow: var(--shadow-card);
  text-decoration: none; color: var(--text);
  transition: border-color var(--ease);
}
.area-card:hover { border-color: var(--brand); }
.area-city { font-weight: 800; text-transform: uppercase; letter-spacing: .02em; font-size: 14px; color: var(--brand-deep); }
.area-lede { color: var(--muted); font-size: 13px; line-height: 1.45; }

/* "Where we work" on the landing page (2026-09-13). The city pages were
   reachable only from a footer row, which is both hard to find and shallow for
   a crawler — a plain two-column list of links fixes both without another
   fifteen cards of chrome on the page. */
.area-cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1rem 2rem; margin: .75rem 0 1rem; }
.area-county { margin: 0 0 .4rem; font-size: 13px; color: var(--muted); }
.area-links { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: .2rem .75rem; }
.area-links a { font-size: 14px; }


/* =============================================================================
   ===== web-brand block 1: colour schemes (SPEC D5) ===========================
   The reference's :root above IS the `blue` scheme. Each set below overrides
   the six brand tokens only; every other token (borders, text, muted, the
   status colours) is shared. --accent / --accent-hover are var(--brand) /
   var(--brand-deep) in :root, so they follow automatically.

   --brand-btn is the ONLY fill white text sits on (the reference's contrast
   rule at the top of the file). Contrast against #fff, checked with the WCAG
   formula in tests/test_scheme_picker.py: blue 4.95, navy 10.2, red 6.3,
   green 5.8, orange 5.3, charcoal 10.0.

   --brand-on-dark (DECISIONS.md #80) is the wordmark's colour on the two dark
   panels it sits on — the hero band (--slate) and the footer (--ink) — and
   the ONLY brand-coloured thing on a dark ground. --brand itself is tuned
   for the white page and is dark-on-dark in the navy and charcoal sets
   (1.8:1 / 2.0:1 on the hero band: the logo box vanished into the panel,
   gate B). Each set names a colour that clears 3:1 (WCAG large text)
   against BOTH its --slate and its --ink, pinned by the same test: blue,
   red, green and orange reuse --brand (3.25–4.73 on --slate); navy and
   charcoal lift to a tint of it (navy 5.8 / 7.8, charcoal 8.2 / 10.4). The
   :root default below is the blue value — it lives here, not in the
   reference's :root, so the reference block above stays unedited.
   ========================================================================== */
:root { --brand-on-dark: #0a7fea; }

/* --accent is what every plain `a`, `.linkish` and `.nav-gbp` is painted in
   (the reference's :root sets it to --brand). On a six-scheme site --brand is
   NOT safe for 12-13px link text on white: orange reaches only 3.1:1, blue
   4.01 and green 4.38 — all under WCAG AA's 4.5:1 floor for body text (gate C,
   2026-09-15). --brand-btn is the token the reference already keeps above 4.5
   for white-on-brand fills, and reading it the other way round (brand on
   white) every scheme clears it too: blue 4.95, navy 10.17, red 6.32, green
   5.81, orange 5.33, charcoal 10.02 — pinned by tests/test_scheme_picker.py
   beside the button check. Hover already darkens to --brand-deep. Declared
   here, not in the reference's :root, so the reference block stays unedited;
   --brand keeps its own job (outlines, rules, icons, the focus ring). */
:root { --accent: var(--brand-btn); }

html[data-scheme="blue"] {
  --brand: #0a7fea; --brand-btn: #0b6fd4; --brand-deep: #1858a7; --ink: #0b0e12; --slate: #2a2e38; --brand-on-dark: #0a7fea;
}
html[data-scheme="navy"] {
  --brand: #1f4e9c; --brand-btn: #1b3f7f; --brand-deep: #122d5c; --ink: #0a0f1c; --slate: #1c2a44; --brand-on-dark: #7fa6e8;
}
html[data-scheme="red"] {
  --brand: #d9342b; --brand-btn: #b7271f; --brand-deep: #8f1c16; --ink: #140b0a; --slate: #33211f; --brand-on-dark: #d9342b;
}
html[data-scheme="green"] {
  --brand: #1f8a4c; --brand-btn: #1a7440; --brand-deep: #14582f; --ink: #0a120d; --slate: #1f2e25; --brand-on-dark: #1f8a4c;
}
html[data-scheme="orange"] {
  --brand: #e0761a; --brand-btn: #a85410; --brand-deep: #7a3c0a; --ink: #14100b; --slate: #332619; --brand-on-dark: #e0761a;
}
html[data-scheme="charcoal"] {
  --brand: #4a5563; --brand-btn: #3a434f; --brand-deep: #262c34; --ink: #0f1114; --slate: #23272d; --brand-on-dark: #b8c1cc;
}

/* The picker strip itself: directly under the nav, a label and six round
   swatches. Each swatch shows ITS scheme's button colour (a fixed value, not
   the live token — the row must not turn monochrome when one is picked). The
   reference's `button` rule (uppercase, brand fill, shadow, padding) is
   overridden here so a swatch is a colour dot, not a text button. */
.scheme-picker {
  max-width: 1200px;
  margin: 0 auto;
  padding: .55rem 1.25rem;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .5rem .6rem;
}
.scheme-label { font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); margin-right: .25rem; }
.scheme-swatch {
  width: 26px;
  height: 26px;
  padding: 0;
  border-radius: 50%;
  border: 2px solid #fff;
  box-shadow: 0 0 0 1px var(--border);
  background: var(--border);
  cursor: pointer;
  text-transform: none;
  letter-spacing: 0;
  line-height: 1;
}
.scheme-swatch:hover { box-shadow: 0 0 0 2px var(--muted); }
.scheme-swatch[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--ink); }
.scheme-swatch[data-scheme="blue"] { background: #0b6fd4; }
.scheme-swatch[data-scheme="navy"] { background: #1b3f7f; }
.scheme-swatch[data-scheme="red"] { background: #b7271f; }
.scheme-swatch[data-scheme="green"] { background: #1a7440; }
.scheme-swatch[data-scheme="orange"] { background: #a85410; }
.scheme-swatch[data-scheme="charcoal"] { background: #3a434f; }
.scheme-swatch:hover, .scheme-swatch[aria-pressed="true"]:hover { color: inherit; }
.scheme-swatch[data-scheme="blue"]:hover { background: #0b6fd4; }
.scheme-swatch[data-scheme="navy"]:hover { background: #1b3f7f; }
.scheme-swatch[data-scheme="red"]:hover { background: #b7271f; }
.scheme-swatch[data-scheme="green"]:hover { background: #1a7440; }
.scheme-swatch[data-scheme="orange"]:hover { background: #a85410; }
.scheme-swatch[data-scheme="charcoal"]:hover { background: #3a434f; }

/* =============================================================================
   ===== web-brand block 2: the brand-site pieces the reference has no rule for
   ========================================================================== */

/* The inline SVG wordmark (_logo.html) sits exactly where the reference's
   raster logo sat: the hero band and the footer, both dark panels — its only
   two placements. Its text and border are the brand colour (SPEC D1) as the
   scheme renders it on a dark ground: --brand-on-dark (block 1, DECISIONS.md
   #80), never --brand, which is dark-on-dark in two of the six schemes. */
.wordmark { color: var(--brand-on-dark); display: block; }
.hero-logo.wordmark { height: 110px; width: 320px; }
.foot-logo.wordmark { height: 96px; width: 279px; margin: 0 0 1.15rem; }
@media (max-width: 600px) {
  .hero-logo.wordmark { height: 80px; width: 233px; }
}

/* /services/<slug>: the reference's .lede is `margin: 0` and .card carries
   margin-BOTTOM only, so the lede's last line sat directly on the priced-by
   card's top border ("lot.", "replacement." touching the rule — gate B). One
   sibling rule opens the gap (DECISIONS.md #81). The wizard's done page, the
   only other lede-then-card pairing, already has 12px under its lede
   (`.section .lede`) and the two margins collapse, so it does not move;
   /service-areas' lede is followed by an .area-group, not a card. */
.lede + .card { margin-top: .75rem; }

/* The hero band's rule under the h1 sits on --slate, the same dark panel the
   wordmark does — so it takes the same token for the same reason (DECISIONS
   #80): --brand is 1.8:1 on navy's band and 1.98:1 on charcoal's and the rule
   simply disappeared there. --brand-on-dark clears 3:1 in every scheme. */
.hero-rule { background: var(--brand-on-dark); }

/* Funnel tile 3. `.tile-visual` is `aspect-ratio: 4/3; overflow: hidden`, and
   a box whose overflow makes it a SCROLL container gets no content-based
   minimum size — so between 700 and 959px, where the tile is ~166px wide and
   the ratio gives it 123px of height, the two chat bubbles (140-146px) were
   cut off mid-word. `clip` clips without being a scroll container, so the
   content-based minimum applies again and the tile grows to fit the bubbles
   instead of guillotining them; nothing changes at 400 or 1280, where the
   ratio already leaves room. (`min-height: max-content` does NOT fix it — the
   scroll-container rule zeroes the automatic minimum either way.) */
.tile-chat { overflow: clip; }
/* `clip` alone stops the cut but lets tile 3 grow past the other two, which
   pushed its label 35px below theirs across the whole 3-across band. The
   bubbles are decoration (aria-hidden), so they compact to fit the 4/3 box
   instead: at 768-959 all three visuals are 124.5px again in all three
   verticals; at exactly 700px, where the tile is at its narrowest, roofing's
   longer reply still runs 4.5px over — and now it grows by 4.5px instead of
   losing its last line. */
@media (min-width: 700px) and (max-width: 959px) {
  .tile-chat { gap: .3rem; padding: .4rem; }
  .tile-chat .chat-bubble { font-size: 11px; line-height: 1.25; padding: .28rem .45rem; max-width: 98%; }
}

/* What-we-do: four services (gutters) laid out by `auto-fit, minmax(210px)`
   made three columns plus one orphan on the second row for the whole
   600-959px band. Four cards want an even split — 2x2 while the row is
   narrow, 1x4 once four fit. Scoped with :has() so the three-service
   verticals (roofing, lawn) keep the plain auto-fit behaviour. */
@media (min-width: 600px) and (max-width: 959px) {
  .svc-grid:has(> .svc-card:nth-child(4)) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 960px) {
  .svc-grid:has(> .svc-card:nth-child(4)) { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* "Everything else we do" on a service page is the same .svc-grid but inside
   the 720px .wrap and a .card's padding — about 650px, which 210px tracks
   split into two columns, orphaning the third card at 768 AND at 1280. A
   smaller track minimum fits all three on one row there without touching the
   landing page's own grid. */
.card .svc-grid { grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }

/* The quote picker, same shape of problem: a vertical with exactly THREE
   services (roofing, lawn) put its third box alone at half width from ~700px
   up, because 220px tracks only fit two in the 650px form. Only the
   three-box case is re-tracked; gutters' four boxes stay a 2x2. */
.svc-pick-grid:has(> :nth-child(3):last-child) {
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}

/* "Want to know more?" was an 18px-tall tap target (WCAG 2.2 AA asks 24px).
   Padding, not a font bump: the link must stay quieter than the box above it. */
.svc-pick-more { padding: .3rem .1rem; margin-left: 0; }

/* A 2px ring around the whole box on a MOUSE click (:focus-within fires for
   any focus) read as a second selection state on top of the checkbox's own.
   Keyboard focus still gets the ring; a click just ticks the box. */
.svc-pick-box:focus-within { outline: none; }
.svc-pick-box:has(input:focus-visible) { outline: 2px solid var(--brand); outline-offset: 2px; }

/* The nav (SPEC D3): five items on the right instead of the reference's two,
   so the row may wrap — and on a phone NOTHING is hidden (the reference hides
   .nav-link at 480px; overridden here). The brand name stays on its own line
   when the right side wraps under it. */
.nav-inner { flex-wrap: wrap; padding-top: .5rem; padding-bottom: .5rem; }
/* The reference's .nav-right is `flex: 0 0 auto` — it never shrinks, so its
   own flex-wrap could never engage and at 400px the row simply ran off the
   right edge (seen in the first render pass). It must be allowed to shrink
   to the bar's width so its items wrap inside it. */
.nav-right { flex: 0 1 auto; min-width: 0; max-width: 100%; flex-wrap: wrap; gap: .5rem .85rem; }
/* The brand cell flexes from a ZERO basis (the reference's is `auto`): flex
   wraps a line before it shrinks anything, so with an `auto` basis the
   longest placeholder name ("[Insert Name Here] Landscaping") pushed the
   whole right-hand group onto a second row at 1280 (second render pass). From
   a zero basis the group is placed first and the name takes what is left,
   wrapping inside its own cell if it must. */
.nav-brand { flex: 1 1 0; min-width: 12rem; font-size: 16px; }
/* TABLETS ONLY, and the band is measured, not guessed (gate C, 2026-09-15).
   Letting the right-hand group flex from a ZERO basis too (the brand cell
   already does) makes the five items share the leftover width instead of the
   group being sized to its own content and then wrapping inside itself. That
   is a row cheaper from 680 to ~1055px (768: 115.6 -> 108.7; 960: 86.6 ->
   81.2) and a row DEARER either side of it: below 680 the two zero-basis
   cells split the row evenly and the name wraps (115.6 -> 136.2), and from
   ~1056px the auto basis already fits the whole bar on ONE line (67px) while
   an even split would not (81.2). Capped at 1039px for margin. */
@media (min-width: 680px) and (max-width: 1039px) { .nav-right { flex: 1 1 0; } }

.nav-cta { padding: .45rem .7rem; font-size: 12px; }
.nav-gbp { color: var(--accent); }
/* PHONES: the bar carries five right-hand items and hides none of them (SPEC
   D3), so it cannot also stay pinned. At 400px it wrapped to four rows and
   held 131px — 20-24% of the screen — on every scroll position (gate C,
   2026-09-15). Two changes: the type and gaps compact (105px / three rows at
   400), and the bar scrolls away with the page instead of sticking. Nothing
   is hidden; the bar is one tap of "scroll to top" away, and the page CTAs
   (not the nav) are what a phone visitor is meant to press. */
@media (max-width: 600px) {
  .nav { position: static; }
  .nav-inner { gap: .5rem .75rem; }
  .nav-brand { flex-basis: 100%; min-width: 0; }
  .nav-right { flex-basis: 100%; margin-left: 0; gap: .4rem .7rem; }
  .nav-link, .nav-call { font-size: 11px; }
}
@media (max-width: 480px) {
  .nav-link { display: inline-block; }
}

/* The "Our work" anchor (the nav's own `/#our-work`). Without a scroll margin
   the browser puts the section's top at viewport top, which is UNDER the
   sticky bar — the h2 and its sub line landed behind it at every width (gate
   C). The offset has to clear the bar's REAL height, which is not --nav-h at
   every width: five right-hand items wrap the bar to two rows below ~1120px
   (86.6px) and to three below ~880px (115.6px). Measured, not assumed. It
   also has to clear the .gallery-badge, which hangs 10px ABOVE the card.
     >= 1120px  one row      67px + 18 = 85
     601-1119   two or three rows, 115.6 worst case -> 140
     <= 600     the bar is not sticky (above); just the badge's 10px + air. */
#our-work { scroll-margin-top: calc(var(--nav-h) + 3px + 18px); }
@media (max-width: 1119px) { #our-work { scroll-margin-top: 140px; } }
@media (max-width: 600px) { #our-work { scroll-margin-top: 1.75rem; } }

/* The "Our work" placeholder frames (SPEC D4 §6, DECISIONS.md #64): the same
   size a photo would be — the reference's .shot img is 4/3 at the card's
   width; these are 16/10 with a ~360px floor at 1280 — light grey, a 1px
   border, the text centred. A frame must never collapse to zero height. */
.shot-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 360px;
  aspect-ratio: 16 / 10;
  background: var(--bg-alt);
  border: 1px solid var(--border);
  color: var(--muted);
  font-weight: 700;
  font-size: 15px;
  text-align: center;
  padding: 1rem;
}
@media (max-width: 600px) {
  .shot-placeholder { min-height: 200px; }
}
/* On a wide screen the 16/10 ratio made the frame 1102x689px — a grey box
   most of a laptop screen tall, which reads as a broken image rather than a
   photo slot (gate C, 2026-09-15). A real job photograph in this card is
   landscape and about this tall; cap the height and let the width run. */
@media (min-width: 960px) {
  .shot-placeholder { aspect-ratio: auto; height: 440px; min-height: 0; }
}

/* The account strip: the demo site's own call to action, after the trust
   row. Same register as .featured (white card, brand top rule), centred. */
.account-strip {
  margin: 2rem 0 0;
  padding: 1.5rem 1.25rem;
  background: var(--card);
  border: 1px solid var(--border);
  border-top: 3px solid var(--brand);
  box-shadow: var(--shadow-card);
  text-align: center;
}
.account-strip p { margin: 0 0 .9rem; font-size: 15px; }

/* The one-form page's example line (SPEC D6's .example-badge, used by the
   temporary /quote too): a small pastel status mark on its own line. */
.example-badge {
  display: block;
  width: fit-content;
  max-width: 100%;
  font-size: 11px;
  font-weight: 700;
  color: var(--ok);
  background: var(--ok-bg);
  border: 1px solid var(--ok);
  padding: 2px 6px;
  /* The top margin is not decoration: before the form this line sat flush
     against the three-step row (0px gap, gate C 2026-09-15) and read as a
     caption of the last step rather than the page's own EXAMPLE warning. */
  margin: 1rem 0 .75rem;
}

/* =============================================================================
   ===== web-brand block 3: the wizard, legal, refusal and notice pages ========
   These are web-brand's own rules, carried over so those pages render with
   the same class names they always had (SPEC D11). Where the reference's
   element rules would have changed a control's shape (`label` is a column
   flexbox there; `.btn` is the primary fill there) the rule below restores
   the wizard's own shape for that class.
   ========================================================================== */
.section { padding-block: 32px; }
.narrow { max-width: 640px; }
.wizard { max-width: 720px; }
.wrap.section { padding-inline: 1rem; }
@media (min-width: 960px) { .wrap.section { padding-inline: 1.25rem; } }

/* Inside a web-brand page section, a plain .btn is the SECONDARY control
   ("Back", "Skip for now", "Try again"); only .btn-primary is the brand fill.
   Same look as the reference's own .btn-secondary. */
.section .btn:not(.btn-primary) {
  background: var(--card);
  color: var(--ink);
  border-color: var(--border);
  box-shadow: var(--shadow-card);
  text-transform: none;
  font-weight: 600;
  letter-spacing: 0;
}
.section .btn:not(.btn-primary):hover { background: var(--bg-alt); color: var(--ink); }
.btn-small { padding: 6px 10px; font-size: 13px; }

.section h1 { margin: 0 0 12px; }
.section h2 { margin: 0 0 8px; }
.section .lede { margin: 0 0 12px; }

.price-sentence { font-size: 16px; margin: 0 0 10px; font-style: normal; color: var(--text); }

.callout {
  background: var(--bg-alt);
  border: 1px solid var(--border);
  border-left: 3px solid var(--brand);
  padding: 14px 16px;
  margin-block: 16px;
}

/* SCOPED TO .wizard ON PURPOSE (gate C, 2026-09-15). These four rules are the
   wizard's own field shape; every `.field` they were written for lives inside
   `section.wizard` (app/templates/wizard/_layout.html). Unscoped they also hit
   /quote's form, which is the REFERENCE's form (SPEC D6) — `margin-block: 14px`
   on top of `.lead-form`'s own grid gap made that form ~86px taller than
   850gutters.com's (30/10/10/20px gaps became 44/38/38/34), and `font: inherit`
   + 8px padding made the phone input and the textarea 13px where the name and
   address inputs beside them were 14px. */
.wizard .field { margin-block: 14px; }
.wizard .field label { display: block; flex-direction: row; font-weight: 600; font-size: 14px; color: var(--text); margin-bottom: 4px; }
.wizard .field input[type=text], .wizard .field input[type=email], .wizard .field input[type=tel], .wizard .field textarea, .wizard .field input[type=file] {
  width: 100%;
  padding: 8px;
  border: 1px solid var(--border);
  font: inherit;
  background: var(--card);
  color: var(--text);
}
.hint { color: var(--muted); font-size: 12px; margin: 4px 0 0; }
.field-error, .error-banner {
  color: var(--danger);
  background: var(--danger-bg);
  padding: 6px 8px;
  margin: 6px 0 0;
  font-size: 13px;
  list-style: none;
}
ul.field-error { padding-left: 8px; }
#logo-check-result.ok { color: var(--ok); font-size: 13px; margin-top: 4px; }
#logo-check-result.bad { color: var(--danger); font-size: 13px; margin-top: 4px; }
/* The saved logo on step 2, with its own "Remove logo" form beside it. The img
   is boxed at 96px because a logo passes upload at 600x600 and would otherwise
   fill the column. `.wizard .field` is column-flex; this one row is not. */
.current-logo { flex-direction: row; align-items: flex-start; gap: 12px; }
.current-logo img { flex: none; width: 96px; height: 96px; object-fit: contain; border: 1px solid var(--border); background: var(--card); }
.current-logo .inline-form { margin: 0; }
#logo-clear { align-self: flex-start; margin-top: 4px; }

.checkbox-label { display: flex; flex-direction: row; align-items: flex-start; gap: 8px; font-weight: 400; font-size: 14px; color: var(--text); cursor: pointer; }
.checkbox-label input[type=checkbox] { margin-top: 3px; flex: none; width: auto; }

.legal-body h1, .legal-body h2, .legal-body h3 { margin-top: 20px; }
.legal-body h1:first-child, .legal-body h2:first-child, .legal-body h3:first-child { margin-top: 0; }
.legal-body ul { padding-left: 18px; }
.legal-body p { margin-block: 8px; }

/* Ethan asked for a NOTE at the foot of /how-it-works, not a legal footnote:
   at `.muted.tiny` it was 12px and read as boilerplate nobody is meant to
   finish (gate C, 2026-09-15). Body size, italic, still muted. */
/* The demo quote form's honesty line (DECISIONS.md #95). It sits WITH the
   fields, not in the banner above them: a visitor reads the field they are
   about to fill in, not the page header. */
.demo-form-note {
  grid-column: 1 / -1;
  margin: 0 0 .25rem;
  padding: .55rem .7rem;
  border-left: 3px solid var(--brand-btn);
  background: var(--bg-alt, #f4f5f7);
  color: var(--ink-soft, #44506b);
  font-size: 13px;
  line-height: 1.45;
}

.production-note { margin-top: 1.25rem; font-size: 14px; font-style: italic; }

/* ---- the account wizard (YP31, contracts/account.md §5) ---------------- */
.wizard-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.inline-form { display: flex; align-items: center; gap: 8px; margin: 0 0 12px; font-size: 13px; }

.rail { display: flex; flex-wrap: wrap; gap: 5px; list-style: none; padding: 0; margin: 0 0 20px; }
.rail-step { border: 1px solid var(--border); background: var(--bg-alt); font-size: 13px; }
.rail-step > a, .rail-step > span { display: inline-flex; align-items: center; gap: 6px; padding: 5px 9px; color: var(--text); text-decoration: none; }
.rail-step.current { border-color: var(--brand); background: var(--card); }
.rail-step.done { background: var(--ok-bg); border-color: var(--ok-bg); }
.rail-step.disabled { color: var(--muted); }
.rail-step.disabled > span { color: var(--muted); }
.rail-num {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; font-size: 11px; font-weight: 700;
  background: var(--card); border: 1px solid var(--border); color: var(--text);
}
.rail-step.current .rail-num { background: var(--brand-btn); border-color: var(--brand-btn); color: #fff; }
.rail-tag { font-size: 11px; color: var(--muted); background: var(--card); border: 1px solid var(--border); padding: 0 5px; }
.rail-tag.ok { color: var(--ok); border-color: var(--ok-bg); }

.actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-top: 18px; }
.steps-list { padding-left: 20px; margin: 0 0 12px; }
.steps-list li { margin-block: 6px; }
/* Step 2's nested list (Ethan's own sub-items on /how-it-works). */
.steps-sublist { padding-left: 18px; margin: 4px 0 0; }
.steps-sublist li { margin-block: 2px; color: var(--muted); }

.choice { display: flex; flex-direction: row; gap: 10px; align-items: flex-start; border: 1px solid var(--border); padding: 12px; margin-block: 10px; font-weight: 400; font-size: 14px; color: var(--text); cursor: pointer; }
.choice input[type=radio] { margin-top: 3px; flex: none; width: auto; }
.choice > span { display: block; flex: 1; min-width: 0; }
.choice .hint { display: block; }
.choice-input { display: block; width: 100%; margin-top: 8px; padding: 8px; border: 1px solid var(--border); font: inherit; background: var(--card); color: var(--text); }
.choice .field-error { display: block; }
.cname-block { display: block; margin-top: 8px; padding: 8px 10px; background: var(--bg-alt); border: 1px solid var(--border); font-size: 13px; }
.cname-block code { display: block; margin-block: 4px; overflow-x: auto; }
code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 13px; background: var(--bg-alt); border: 1px solid var(--border); padding: 1px 4px; overflow-wrap: anywhere; }

.notice { padding: 8px 10px; margin: 0 0 12px; font-size: 13px; }
.notice.ok { color: var(--ok); background: var(--ok-bg); }
.notice.warn { color: var(--warn); background: var(--warn-bg); }

.rate-row { border: 1px solid var(--border); padding: 10px 12px; margin-block: 10px; }
.rate-row legend { font-weight: 700; padding-inline: 4px; }
.rate-inputs { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.rate-inputs label { font-weight: 400; margin: 0; flex-direction: row; }
.rate-inputs input { width: 96px; padding: 8px; border: 1px solid var(--border); font: inherit; background: var(--card); color: var(--text); }
.rate-inputs .hint { flex-basis: 100%; margin: 0; }

/* The Rates step's worked example (app/typical_property.py): what a typical
   property is, what the trade charges, and what that comes to — so nobody has
   to multiply "dollars per 1,000 sq ft" in their head. Context under the
   inputs, not a callout — but at 13px/--muted with a grey rule it was the
   quietest text in the fieldset, i.e. the block that answers "what do I
   charge" was styled as the page's fine print. So: 14px and the wizard's own
   blue explainer rule (.demo-form-note, .callout), still quieter than the
   legend and the boxes. */
.worked-example {
  margin: 10px 0 0;
  padding: 8px 10px;
  background: var(--bg-alt);
  border-left: 3px solid var(--brand-btn);
  font-size: 14px;
  line-height: 1.45;
  color: var(--muted);
}
.worked-example .wx-line { display: block; margin: 0; }
.worked-example .wx-line + .wx-line { margin-top: 4px; }
.worked-example strong { color: var(--text); font-weight: 700; }
/* A quantity never breaks across lines: at 400px "2,550 sq ft" was rendering
   as "2,550 sq" / "ft", with the unit orphaned from the number. */
.worked-example .wx-qty { white-space: nowrap; }
/* Their own numbers, marked as theirs: full-strength text and the brand rule,
   so it reads as an answer rather than as more example copy.

   The space is RESERVED, always. This <p> is an aria-live region that is empty
   until there is something to say, so it can neither be `hidden` (a live
   region that starts out of the tree is announced unreliably) nor be allowed
   to appear from nothing (filling it used to push the next rate row ~30px down
   the page mid-keystroke). Empty, it holds one line's height and shows no
   rule. */
.worked-example .wx-yours {
  margin-top: 6px;
  padding-top: 6px;
  /* content-box ON PURPOSE against the global `* { box-sizing: border-box }`:
     the reserve has to be one LINE plus this rule and its padding, not one
     line total. Under border-box the empty box measured 20.3px against a
     filled 27.3px and the page still moved 7px on the keystroke that
     completed the pair. */
  box-sizing: content-box;
  min-height: 1.45em;
  border-top: 1px solid var(--border);
  color: var(--text);
  font-weight: 700;
}
.worked-example .wx-yours:empty { border-top-color: transparent; }

.summary p { margin-block: 6px; }
.wizard .field input[disabled] { background: var(--bg-alt); color: var(--muted); }

/* `hidden` must beat every class that sets `display` (the reference's own
   note above) — restated last so nothing appended later can outrank it. */
[hidden] { display: none !important; }

/* =============================================================================
   ===== web-brand block 4: the homeowner question, and the pen's four modes ===
   The 2026-09-15 verticals expansion. Every rule here is driven by a PACK
   record (app/packs/*.py), never by a hostname: the same markup renders the
   steepness radio on roofing, the storey radio on paint and nothing at all on
   lawn, because the template iterates `pack.homeowner_choices`.
   Plain white, no dark AI look — these use the reference's own tokens
   (--bg-alt, --border, --brand) and add no new colour.
   ========================================================================== */

/* THE HOMEOWNER RADIO. A real <input type="radio"> inside a <label>, styled —
   the same discipline the service picker above already keeps, so it is
   keyboard-reachable and announced as a radio group. */
/* `.lead-form` is a grid of auto-fit 180px columns. WITHOUT `grid-column: 1/-1`
   this fieldset lands in ONE of those columns, and the render says so
   immediately: every option stacked in a 195px-wide stripe with the radio
   floating above its own label. `.svc-pick` two blocks up carries the same
   line with the same comment, for the same reason — found again here only by
   screenshotting the page, which no assertion in the suite could have done. */
.svc-answer { border: 0; padding: 0; margin: 0 0 1.1rem; grid-column: 1 / -1; }
.svc-answer .answer-row {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin-top: .5rem;
}
.answer-opt {
  display: inline-flex;
  align-items: center;
  /* `flex-direction: row` IS LOAD-BEARING. The reference stylesheet has a bare
     `label { display: flex; flex-direction: column }` at element specificity,
     so setting `display: inline-flex` here inherits COLUMN and puts the radio
     dot centred ABOVE its own label — it reads as a heading, not a control.
     `.check` above carries the identical override for the identical reason.
     Found by screenshotting the page; no assertion in the suite sees it, and
     it is the second member of this family on this fleet after the blanket
     `input { width: 100% }` that detached three radios from their labels on
     2026-09-14. */
  flex-direction: row;
  flex-wrap: nowrap;
  gap: .45rem;
  padding: .5rem .8rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: #fff;
  cursor: pointer;
  /* A radio label is a target, not a paragraph: it must not stretch to the
     form's full width the way `.field input { width: 100% }` makes everything
     else do. Shrink-wrapped, and it wraps onto a second row on a phone. */
  flex: 0 1 auto;
  max-width: 100%;
}
.answer-opt input {
  /* The blanket `input { width: 100% }` two blocks up detaches a radio from
     its own label — the same defect the 2026-09-14 admin render pass found on
     three stacks. Pinned back to its intrinsic size here. */
  width: auto;
  min-width: 0;
  margin: 0;
  accent-color: var(--brand);
}
.answer-opt:hover { background: var(--bg-alt); }
.answer-opt:has(input:checked) {
  border-color: var(--brand);
  box-shadow: inset 0 0 0 1px var(--brand);
  font-weight: 600;
}
.answer-opt:focus-within { outline: 2px solid var(--brand); outline-offset: 2px; }
.svc-answer .hint { margin-top: .45rem; }

/* THE PIN-DROP SIZE PICKER (pack.draw_mode == "pins"). Sits under the map
   head, above the map, so the size you are about to drop is visible while you
   are looking at the tree you are about to drop it on. */
.pin-sizes { margin: .6rem 0 .4rem; }
.pin-sizes .field { margin-bottom: .35rem; }
.pin-sizes select { max-width: 24rem; }
#pin-tally { margin: 0; font-variant-numeric: tabular-nums; }

/* The drag handle on a pin's canopy circle. White fill, brand ring — the same
   treatment the outline tracer's vertex dots already use, so a handle reads as
   "grab me" on both. */
.leaflet-container .pin-handle { cursor: ew-resize; }

/* /portal (2026-09-16): the brand site frames svc-admin's public portal demo.
   The frame is a bordered card like the "Our work" gallery; the two buttons
   sit in one row and wrap on a phone. */
.portal-offer { padding-left: 20px; margin: 1rem 0; }
.portal-offer li { margin-block: 8px; line-height: 1.5; }
.portal-frame-wrap {
  border: 1px solid var(--border);
  background: var(--card);
  box-shadow: var(--shadow-card);
  margin: 1rem 0;
}
.portal-frame { display: block; width: 100%; height: 760px; border: 0; }
.portal-actions { display: flex; flex-wrap: wrap; gap: .6rem; margin: 1rem 0 .5rem; }
.account-strip-portal { margin: .9rem 0 0; }
@media (max-width: 600px) { .portal-frame { height: 600px; } }
