/* =============================================================================
   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;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.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: 1.75rem 0;
}
.hero-band-inner {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 1.25rem;
  display: flex;
  align-items: center;
  gap: 1.5rem;
  flex-wrap: wrap;
}
.hero-logo {
  height: 92px;
  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.25rem 0; }
  .hero-logo { height: 68px; }
}

/* =========================================================================
   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.5rem 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: .4rem;
  padding: .75rem;
  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: 700; font-size: 13px; color: var(--ink); }
.tile-sub { font-size: 12px; 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: 1rem; }
.funnel-cta-secondary { display: block; width: fit-content; margin: .6rem auto 0; }

/* =========================================================================
   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; }

  /* Phase 3 two-box layout (ops/850gutters-build-spec.md §5 item 2): "Describe
     what you want" and "Enter your address" (_tool.html's #your-home, its
     FIRST section) sit adjacent in source order on quote.html on purpose (see
     the comment in templates/quote.html) so plain grid auto-placement puts
     them side by side here, in the same tab order a screen reader already
     sees. Everything else in .featured (steps, selector, map, roof shape,
     estimate, capture) stays full width, single column, in the order it
     always rendered — below 960px this block is inert and the page is the
     one column it always was.

     Scoped to `.wrap:not(.wrap-wide) .featured` — quote.html's <main> is
     `class="wrap"`; the landing page's is `class="wrap wrap-wide"` (its OWN
     two-column split is `.cols-two` above, unrelated to this one). `.featured`
     is shared markup (index.html renders the same tool in the same box), so
     this must not turn the landing page's copy of it into a grid too — the
     describe box sits far from #your-home there and would leave a bare gap
     beside the address box on desktop. */
  .wrap:not(.wrap-wide) .featured { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 1.25rem; }
  .wrap:not(.wrap-wide) .featured > * { grid-column: 1 / -1; }
  .wrap:not(.wrap-wide) .featured > .describe-card,
  .wrap:not(.wrap-wide) .featured > #your-home {
    grid-column: span 1;
    border: 1px solid var(--border);
    background: var(--bg-alt);
    padding: 1rem;
  }
}

/* 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: 20px;
}
.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(3, minmax(0, 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; }
input, select, textarea {
  font-family: var(--font);
  font-size: 14px;
  padding: .5rem .6rem;
  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; }

/* =========================================================================
   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);
}
.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: .5rem; 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 { 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: 2rem 1rem 2rem; font-size: 13px; }
.foot-logo { height: 84px; width: auto; margin: 0 0 1rem; }
.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); }
