/* Old Red Heating & Air
 *
 * Deliberately plain. System fonts, square corners, hard offset shadows with no
 * blur, no gradient washes, no easing-heavy motion. A polished page reads as
 * expensive, and this audience is a homeowner whose AC just died on a design
 * day — anything that looks like it costs money pushes them to the next result.
 *
 * Two things arrived after that rule was written and both were let in on terms.
 * Old Red — the dog — is the mark, in the header and the footer and nowhere
 * else. The field reel carries photographs of actual jobs, which is the one
 * kind of image this site can show without looking like a template: it is
 * evidence, not decoration, and it is the only place motion is allowed. Stock
 * photography of smiling technicians is still banned, and not on taste
 * grounds — see the note above fieldReel() in src/render.ts.
 *
 * Mobile-first at 390px. Most emergency HVAC traffic is a phone.
 * No webfonts. System stack only, so nothing blocks render.
 */

/* Cream ground is the brand default now — a dark basalt page reads as
   tech/SaaS rather than a home-service site somebody's grandmother should
   trust enough to call. OS dark-mode preference still gets a dark ground
   below; nobody who didn't ask for dark sees it. */
:root {
  --bg:         #fffdf8;  /* page */
  --surface:    #ffffff;  /* form card, cards */
  --surface2:   #f4f1ea;  /* assurance band, 3-step strip, secondary callbar */
  --line:       #d8d3c6;  /* hairlines between sections */
  --line-soft:  #1a1a17;  /* input borders */
  --ink:        #1a1a17;  /* primary text */
  --ink-dim:    #4a473f;  /* body text */
  --ink-faint:  #6b675c;  /* labels, legal, captions */
  --accent:     #a01f27;  /* CTA fill */
  --accent-hi:  #8a1a21;  /* links, CTA hover */
  --sand:       #8a5a28;  /* eyebrow, disclosure rule */
  --shadow:     #1a1a17;  /* hard offset shadow */
  --ftbg:       #f1eee4;  /* footer */
  --sky:        #3d5560;  /* secondary headings on long-form pages */
  --crest:      #a01f27;  /* shield stroke + crest lettering */

  --ff: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
        "Helvetica Neue", Arial, sans-serif;

  --wrap: 70rem;    /* full-bleed chrome: header, footer */
  --read: 46rem;    /* long-form reading column on desktop */
  --conv: 560px;    /* conversion stack, capped on desktop */
  --gut: 1.25rem;
  --bar: 64px;      /* mobile call bar height */
}

/* The two accents are NOT the same red by accident. #a01f27 is the brand value
   and holds white text on light grounds; on basalt it goes muddy, so dark mode
   lifts to #c9343c. Both carry #fff above 4.5:1. Do not collapse them.
   Dark mode also swaps --line-soft from near-black ink to grey: an ink border
   on basalt reads as an edge that isn't there, where grey reads as the same
   hard boundary the light treatment gets from ink.

   Dark applies two ways: automatically from OS preference below, or forced by
   the header toggle, which stamps data-theme on <html> (see the inline script
   in layout() and app.js). :not([data-theme="light"]) lets someone whose OS is
   dark still force cream; the second, unconditional block lets someone whose
   OS is light still force basalt. Both copies of the variable list have to
   stay in sync by hand — there is no third place to define them once. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:         #131310;
    --surface:    #1b1b16;
    --surface2:   #24241d;
    --line:       #3d3d33;
    --line-soft:  #55554a;
    --ink:        #f1eee4;
    --ink-dim:    #b3b0a4;
    --ink-faint:  #999682;
    --accent:     #c9343c;
    --accent-hi:  #e04a52;
    --sand:       #c98a52;
    --shadow:     #000;
    --ftbg:       #0d0d0b;
    --sky:        #b6c5cc;
    /* The crest inverts with the ground, same reasoning as the two reds: a red
       shield on cream is the brand mark, but on basalt a thin #c9343c hairline
       disappears at 2px, so dark mode draws it in ink. */
    --crest:      #f1eee4;
  }
}
:root[data-theme="dark"] {
  --bg:         #131310;
  --surface:    #1b1b16;
  --surface2:   #24241d;
  --line:       #3d3d33;
  --line-soft:  #55554a;
  --ink:        #f1eee4;
  --ink-dim:    #b3b0a4;
  --ink-faint:  #999682;
  --accent:     #c9343c;
  --accent-hi:  #e04a52;
  --sand:       #c98a52;
  --shadow:     #000;
  --ftbg:       #0d0d0b;
  --sky:        #b6c5cc;
  --crest:      #f1eee4;
}

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}

body {
  margin: 0;
  font-family: var(--ff);
  font-size: 1.0625rem;
  line-height: 1.6;
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}
body.has-call { padding-bottom: var(--bar); }
@media (min-width: 48em) { body.has-call { padding-bottom: 0; } }

img { max-width: 100%; height: auto; }

a { color: var(--accent-hi); text-underline-offset: 3px; }
a:hover { color: var(--accent); }

/* Visible keyboard focus everywhere, never removed. */
:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
  border-radius: 0;
}

.skip {
  position: absolute; left: -9999px;
  background: var(--accent); color: #fff;
  padding: .75rem 1rem; font-weight: 700; z-index: 100;
}
.skip:focus { left: .5rem; top: .5rem; }

/* ---------------------------------------------------------- header */

.hd {
  position: sticky; top: 0; z-index: 50;
  background: var(--bg);
  border-bottom: 1px solid var(--line);
}
/* The call button, not the mark, sets this header's height — it measures 56px
   against the crest's 53, so the shield costs the fold nothing. Trimming the
   padding from 10px to 8px on top of that leaves the sticky header 4px shorter
   than it was before Old Red got his shield. Raising the crest past 56px would
   start pushing the fold down on a phone, so don't. */
.hd__in {
  max-width: var(--wrap); margin: 0 auto;
  padding: 8px 14px;
  display: flex; align-items: center; gap: 12px;
}
.mark {
  text-decoration: none; color: var(--ink); line-height: 1;
  display: flex; align-items: center; gap: 9px;
}
.mark__t { display: block; }

/* ---------------------------------------------------------- crest

   Shield in SVG, Old Red raster on top of it. The box is fixed px and the dog
   is positioned inside it as a percentage, so he reserves his space before he
   decodes — the header is the first thing painted and CLS here is CLS on every
   page. Sizes are set on .crest alone; everything inside scales off it. */
.crest { position: relative; display: block; flex: none; }
.crest__sv { display: block; width: 100%; height: 100%; }
.crest__dog {
  position: absolute; left: 50%; transform: translateX(-50%);
  width: auto; object-fit: contain;
}
/* Hairlines in px rather than user units — one stroke rule for both sizes, and
   the shield stays as crisp at 46px as it is at 100px. */
.crest__edge {
  fill: none; stroke: var(--crest);
  stroke-width: 2; vector-effect: non-scaling-stroke;
  stroke-linejoin: round;
}
/* No fill: the shield is masked out behind the ribbon instead, so the crest
   carries no assumption about what colour it is standing on. */
.crest__ribbon {
  fill: none; stroke: var(--crest);
  stroke-width: 1.5; vector-effect: non-scaling-stroke;
  stroke-linejoin: round;
}
.crest__arc, .crest__band {
  fill: var(--crest); font-family: var(--ff); font-weight: 800;
  text-anchor: middle;
}
.crest__arc { font-size: 11px; letter-spacing: 1.2px; }
/* HEATING & AIR is 13 characters inside the 80-unit gap between the ribbon's
   notches. At 8px it filled that gap end to end and read as crowded, so the
   ribbon grew at both ends and the type gave back half a unit. Keep the word
   space either side above 4 units — under that the notches crowd the R. */
.crest__band { font-size: 7.4px; letter-spacing: .8px; }

/* Header: 38px of dog against the 40px he was before the shield went around
   him, so the mark grew by the shield and not by shrinking him. */
.crest--sm { width: 46px; height: 53px; }
.crest--sm .crest__dog { top: 15%; height: 72%; }
.crest--sm .crest__edge { stroke-width: 2.25; }

/* Footer: the full lockup. 1.2× its viewBox, which is the smallest scale that
   keeps the ribbon lettering above 9px — under that the arc turns to texture
   and the crest is better off without it. */
.crest--lg { width: 120px; height: 142px; }
.crest--lg .crest__dog { top: 29%; height: 39%; }
.mark__b {
  display: block;
  font-weight: 800; font-size: 17px;
  letter-spacing: .09em;
  color: var(--ink);
}
.mark__b::first-letter { color: var(--accent); }
.mark__s {
  display: block; font-size: 9.5px; font-weight: 400; letter-spacing: .17em;
  text-transform: uppercase; color: var(--ink-faint); margin-top: 3px;
}
/* The header's one line of copy — replaces what used to be a service-link
   nav. Those links only ever swapped the page for a near-identical one, which
   is not a decision worth header space; this says the one thing that argues
   for calling instead. Hidden below the width the call button needs to stay
   readable on its own; the button keeps its own margin-left: auto there so it
   still lands on the right edge with no quip to do the pushing. */
.hd__quip {
  display: none; margin: 0 0 0 auto;
  font-size: .78rem; font-weight: 700; line-height: 1.3;
  text-align: right; color: var(--ink-dim);
}
@media (min-width: 62em) { .hd__quip { display: block; } }
.hd .btn--call { margin-left: auto; }
@media (min-width: 62em) { .hd .btn--call { margin-left: .6rem; } }

/* Theme toggle: light/dark, defaulting to OS preference. Only the icon that
   matches the *other* theme is shown — sun on a dark ground offers light,
   moon on a light ground offers dark. Same cascade as the variables above:
   OS-dark-without-override, then the explicit data-theme override, each
   block swapping which icon is visible. */
.theme-tgl {
  flex: none; width: 30px; height: 30px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line); background: var(--surface); color: var(--ink);
  padding: 0; cursor: pointer;
}
.theme-tgl:hover { border-color: var(--ink-faint); }
.theme-tgl svg { width: 15px; height: 15px; stroke: currentColor; stroke-width: 1.4;
  stroke-linecap: round; stroke-linejoin: round; fill: none; }
.theme-tgl .i-sun { display: none; }
.theme-tgl .i-moon { display: block; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-tgl .i-sun { display: block; }
  :root:not([data-theme="light"]) .theme-tgl .i-moon { display: none; }
}
:root[data-theme="dark"] .theme-tgl .i-sun { display: block; }
:root[data-theme="dark"] .theme-tgl .i-moon { display: none; }

/* ---------------------------------------------------------- breadcrumbs

   Thin strip directly under the sticky header, above whatever hero the page
   opens with. Absent on the homepage and on error pages — see
   renderBreadcrumbs in src/render.ts, which renders nothing below two
   crumbs. */
.crumbs { background: var(--bg); border-bottom: 1px solid var(--line); }
.crumbs__ol {
  max-width: var(--wrap); margin: 0 auto; padding: 9px 14px;
  display: flex; flex-wrap: wrap; list-style: none;
  font-size: 12.5px; color: var(--ink-faint);
}
.crumbs__i a { color: var(--ink-faint); text-decoration: none; }
.crumbs__i a:hover { color: var(--accent-hi); text-decoration: underline; }
.crumbs__i[aria-current="page"] { color: var(--ink); font-weight: 700; }
.crumbs__i:not(:last-child)::after { content: "/"; margin: 0 .5em; color: var(--ink-faint); }

/* ---------------------------------------------------------- conversion band */

/* Pitch on the left, form on the right, from 62em up. Below that the two
   columns collapse into source order, which is the mobile design: headline,
   call, objections, form. */
.conv { padding: 26px 18px 30px; background: var(--bg); }
.conv__in {
  max-width: var(--conv); margin: 0 auto;
  display: grid; gap: 24px;
}
.conv__eyebrow {
  margin: 0 0 10px; font-size: 10.5px; font-weight: 700;
  letter-spacing: .2em; text-transform: uppercase; color: var(--sand);
}
.conv__h1 {
  margin: 0 0 10px;
  font-size: clamp(38px, 9vw, 46px); line-height: 1.02; font-weight: 800;
  letter-spacing: -.03em; color: var(--ink); text-wrap: balance;
}
.conv__sub {
  margin: 0 0 20px; max-width: 34ch;
  font-size: 16px; line-height: 1.45; color: var(--ink-dim);
}
.conv__form .form-wrap { padding: 0; }

/* ---------------------------------------------------------- availability */

/* The urgency band, directly above the call button because that is the pairing
   that converts: "someone is on call right now" and then the thing to press.

   Three states, and the default matters most. Server-side this renders the
   published schedule with a neutral pip — a statement that is true whenever
   the page is served, which is what lets the HTML stay shared-cached. app.js
   sets data-s="open" or "closed" once it has resolved the actual minute. With
   JavaScript off, or a time zone the browser cannot resolve, the neutral state
   is what stands, and it claims nothing about right now.

   The pip only pulses in the open state. A blinking dot next to "desk opens at
   7am" would be motion advertising the absence of the thing it points at. */
.avail {
  display: flex; align-items: center; gap: 13px;
  margin: 0; padding: 11px 14px 11px 13px;
  background: var(--surface2);
  border: 1px solid var(--line);
  border-left: 3px solid var(--ink-faint);
  box-shadow: 4px 4px 0 var(--shadow);
}
.avail[data-s="open"]   { border-left-color: var(--accent); }
.avail[data-s="closed"] { border-left-color: var(--sand); }

/* Square, like everything else here, and ringed rather than glowing — the ring
   is a second square offset by box-shadow spread, not a blur. */
.avail__pip {
  flex: none; width: 8px; height: 8px; margin: 3px;
  background: var(--ink-faint);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--ink-faint) 20%, transparent);
}
.avail[data-s="open"] .avail__pip {
  background: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 26%, transparent);
  animation: pip 2.4s ease-in-out infinite;
}
.avail[data-s="closed"] .avail__pip {
  background: var(--sand);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--sand) 22%, transparent);
}
@keyframes pip { 50% { opacity: .32; } }

/* Directly beneath the call button, reading as its caption. The 10px gap
   matches what .cta__cap used to sit at, and the band takes over that
   caption's job — see phoneCtaHero(). The button's 6px hard shadow falls into
   this gap, so anything tighter has the band eating its own drop shadow. */
.avail--under { margin-top: 10px; }

.avail__tx { min-width: 0; }
.avail__hd {
  display: block;
  font-size: 14.5px; font-weight: 800; letter-spacing: -.005em;
  line-height: 1.25; color: var(--ink);
}
.avail__sub {
  display: block; margin-top: 2px;
  font-size: 12px; line-height: 1.35; color: var(--ink-dim);
}

/* ---------------------------------------------------------- assurances */

/* The four objections a homeowner raises between reading the headline and
   handing over a phone number, answered in the gap where they surface. Every
   line restates a commitment the page already makes further down — this band
   introduces no new promise, which is what keeps it defensible on a site that
   will not claim an arrival time. No icons: there is no illustration anywhere
   else here and four little glyphs would be the only decoration on the page. */
.trust {
  list-style: none; margin: 22px 0 0; padding: 15px 18px;
  background: var(--surface2);
  border: 1px solid var(--line);
  /* Two up from the narrowest supported width. Stacked, four of these are
     200px of scroll between the headline and the form, which is the opposite
     of the point. Copy is cut to fit the column rather than the column
     stretched to fit the copy — this band is scanned, not read. */
  display: grid; gap: 13px 16px; grid-template-columns: 1fr 1fr;
}
.trust__i { padding-left: 10px; border-left: 3px solid var(--sand); }
.trust__k {
  display: block; margin-bottom: 2px;
  font-size: 11px; font-weight: 800; letter-spacing: .05em;
  text-transform: uppercase; color: var(--ink);
}
.trust__v { display: block; font-size: 12.5px; line-height: 1.35; color: var(--ink-dim); }

/* ---------------------------------------------------------- buttons */

.btn {
  display: inline-flex; align-items: center; gap: .5rem;
  padding: .85rem 1.35rem;
  font-weight: 800; font-size: 1rem; letter-spacing: .01em;
  text-decoration: none; border: 2px solid transparent;
  border-radius: 0;                     /* hard edges throughout */
  cursor: pointer; font-family: inherit;
  /* No duration. The snap is the point. */
}
.btn--call {
  background: var(--accent); color: #fff;
  box-shadow: 3px 3px 0 var(--shadow);
  flex-direction: column; align-items: flex-start; gap: 0;
  padding: 7px 12px;
}
.btn--call:hover { color: #fff; transform: translate(-1px,-1px); box-shadow: 4px 4px 0 var(--shadow); }
.btn--call:active { transform: translate(2px,2px); box-shadow: 2px 2px 0 var(--shadow); }
.btn__k { font-size: 9px; letter-spacing: .18em; text-transform: uppercase; opacity: .85; }
.btn__n { font-size: 15px; font-weight: 800; letter-spacing: .01em; }
.btn--sm .btn__n { font-size: 15px; }

.btn--ghost {
  background: transparent; color: var(--ink);
  border-color: var(--line-soft);
}
.btn--ghost:hover { color: var(--ink); border-color: var(--ink); background: var(--surface2); }

.btn--go {
  background: var(--accent); color: #fff; width: 100%;
  justify-content: center; box-shadow: 5px 5px 0 var(--shadow);
  padding: 17px 16px; font-size: 17px; font-weight: 800;
}
.btn--go:hover { transform: translate(-1px,-1px); box-shadow: 6px 6px 0 var(--shadow); }
.btn--go:active { transform: translate(2px,2px); box-shadow: 3px 3px 0 var(--shadow); }
.btn--go[disabled] { opacity: .7; cursor: default; transform: none; }

/* ---------------------------------------------------------- call cta */

.cta {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  width: 100%; padding: 18px 16px; text-decoration: none;
  background: var(--accent); color: #fff;
  box-shadow: 6px 6px 0 var(--shadow); border-radius: 0;
}
.cta:hover { color: #fff; transform: translate(-1px,-1px); box-shadow: 7px 7px 0 var(--shadow); }
.cta:active { transform: translate(2px,2px); box-shadow: 3px 3px 0 var(--shadow); }
.cta__k {
  font-size: 11px; font-weight: 800; letter-spacing: .2em;
  text-transform: uppercase; opacity: .9;
}
.cta__n { font-size: 30px; font-weight: 800; letter-spacing: -.01em; margin-top: 3px; }
.cta__cap {
  margin: 10px 0 0; font-size: 12.5px; color: var(--ink-faint); text-align: center;
}

/* ---------------------------------------------------------- three-step */

/* Lives inside the conversion band's trailing slot, so it is a block in a
   column rather than a full-bleed strip across the page. Three separate
   cards, not three rows in one shared card: on the desktop conv band — where
   the slot is stretched to whatever the form leaves behind, see .conv__aside
   — that leftover height is usually well past what three tight rows need,
   and one card just grows a big gap in the middle of itself. Spacing three
   complete cards apart instead uses the same leftover height without any one
   of them reading as mostly empty. `gap` is the floor for narrower slots;
   space-between only adds to it, never below it. */
.steps {
  display: flex; flex-direction: column; gap: 13px; justify-content: space-between;
  height: 100%;
}
.steps__i {
  background: var(--surface2);
  border: 1px solid var(--line);
  padding: 14px 16px;
}
.steps__r { display: flex; gap: 12px; align-items: flex-start; }
.steps__n {
  min-width: 26px; height: 26px; display: grid; place-items: center;
  background: var(--accent); color: #fff;
  font-size: 14px; font-weight: 800;
}
.steps__t { margin: 0; font-size: 14.5px; line-height: 1.4; color: var(--ink); }
/* Indented to sit under the headline text rather than the number badge —
   26px number + 12px gap. */
.steps__d {
  margin: 6px 0 0; padding-left: 38px;
  font-size: 12.5px; line-height: 1.4; color: var(--ink-faint);
}

/* ---------------------------------------------------------- our services

   One tile per SERVICES entry (src/content.ts): icon over label, the whole
   tile a link. Same card language as .cards on the root hub — surface,
   hairline border, accent border on hover — so this reads as a sibling of
   the territory picker rather than a new pattern. Icons carry no colour of
   their own; they inherit ink so they invert for free in dark mode. */
.svcgrid { padding: 0 18px 30px; }
.svcgrid__n {
  margin: -6px 0 16px; max-width: 62ch;
  font-size: 13.5px; line-height: 1.4; color: var(--ink-faint);
}
.svcgrid__ls {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: .6rem;
  grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 30em) { .svcgrid__ls { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 56em) { .svcgrid__ls { grid-template-columns: repeat(4, 1fr); } }
.svcgrid__a {
  display: flex; flex-direction: column; align-items: flex-start; gap: .55rem;
  height: 100%; padding: 1rem .9rem;
  background: var(--surface); border: 1px solid var(--line);
  color: var(--ink); text-decoration: none;
}
.svcgrid__a:hover { border-color: var(--accent); background: var(--surface2); color: var(--ink); }
.svcgrid__i {
  width: 26px; height: 26px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
}
.svcgrid__t { font-size: 13.5px; font-weight: 700; line-height: 1.25; }

/* ---------------------------------------------------------- photo hero
 *
 * A full-bleed photograph with a caption card pinned over its bottom-left
 * corner. The section's own height (not the image) sets the box, so
 * nothing shifts while the photo decodes, and the card is an opaque,
 * hard-edged panel rather than a scrim — legibility comes from the same
 * cream-on-photo contrast the rest of the page reads with, not a gradient
 * wash (see the no-gradient rule at the top of this file).
 */
.photo { position: relative; width: 100%; height: clamp(300px, 58vw, 460px);
  overflow: hidden; background: var(--surface2); }
.photo__img { display: block; width: 100%; height: 100%; object-fit: cover;
  object-position: 62% 0%; }
.photo__frame {
  position: absolute; inset: 0;
  max-width: var(--wrap); margin: 0 auto; padding: 0 var(--gut);
  pointer-events: none;
}
/* Below 62em the box only runs 300–460px tall and object-position keeps the
   whole photo in frame (nothing is cropped at that aspect), which packs the
   head, the filter boxes and this panel into a short strip with no gap big
   enough for the panel to clear both. Bottom is the least-bad seam there — it
   trades the filters for keeping the head, which is the one thing that has to
   stay visible (see .photo__img above). From 62em up, cropping does the
   opposite job — it drops enough of the bottom that top-left clears both. */
.photo__panel {
  position: absolute; left: 0; right: 0; bottom: 16px; max-width: 26rem;
  background: var(--bg); border: 1px solid var(--line); border-left: 5px solid var(--accent);
  box-shadow: 6px 6px 0 var(--shadow);
  padding: 1rem 1.15rem 1.15rem;
  pointer-events: auto;
}
.photo__h {
  margin: .3rem 0 .5rem;
  font-size: clamp(1.35rem, 4.6vw, 1.9rem);
  line-height: 1.08; font-weight: 800; letter-spacing: -.02em;
  color: var(--ink); text-wrap: balance;
}
.photo__note { margin: 0; font-size: 12px; line-height: 1.4; color: var(--ink-faint); }
@media (min-width: 62em) {
  .photo { height: clamp(420px, 42vw, 720px); }
  .photo__panel { top: 32px; bottom: auto; }
}

/* ---------------------------------------------------------- geo suggestion */

.nearby {
  border: 1px solid var(--line); border-left: 4px solid var(--sand);
  background: var(--surface); padding: .9rem 1rem; margin: 0 0 1rem;
  font-size: 14.5px; color: var(--ink-dim);
}
.nearby a { font-weight: 700; }

/* ---------------------------------------------------------- long-form hero */

.hero { background: var(--bg); border-bottom: 1px solid var(--line); }
.hero__in { max-width: var(--wrap); margin: 0 auto; padding: 2.4rem var(--gut) 2.6rem; }
.eyebrow {
  margin: 0 0 .9rem; font-size: .7rem; font-weight: 700;
  letter-spacing: .2em; text-transform: uppercase; color: var(--sand);
}
.hero__h1 {
  margin: 0 0 .9rem;
  font-size: clamp(2.05rem, 7.5vw, 3.4rem);
  line-height: 1.02; font-weight: 800;
  letter-spacing: -.028em; color: var(--ink);
  text-wrap: balance;
}
.hero__sub {
  margin: 0 0 1.6rem; max-width: 44ch;
  font-size: clamp(1.02rem, 2.4vw, 1.22rem);
  color: var(--ink-dim); line-height: 1.5;
}
.hero--root .hero__in { padding-block: 3.5rem 3.2rem; }

/* ---------------------------------------------------------- blocks */

.blk, .how, .sib, .servicer {
  max-width: var(--wrap); margin: 0 auto; padding: 2.4rem var(--gut);
}
.blk + .blk { padding-top: 0; }

.h2 {
  margin: 0 0 1rem;
  font-size: clamp(1.4rem, 3.6vw, 1.95rem);
  line-height: 1.14; font-weight: 800; letter-spacing: -.02em;
  color: var(--ink); text-wrap: balance;
}
.h3 {
  margin: 1.9rem 0 .6rem; font-size: 1.06rem; font-weight: 800;
  letter-spacing: .01em; color: var(--sky);
}
.blk p, .how p { margin: 0 0 1rem; max-width: 68ch; color: var(--ink-dim); }
.blk p b, .how p b, .qs b { color: var(--ink); }

.note {
  border-left: 3px solid var(--line-soft);
  padding: .55rem 0 .55rem .9rem;
  font-size: .88rem !important; color: var(--ink-faint) !important;
}
.warn {
  border-left: 4px solid var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  padding: .9rem 1rem; color: var(--ink) !important;
}

/* the one big number — verified design temperature */
.fact {
  display: grid; gap: 1rem; margin: 0 0 1.5rem;
  border: 1px solid var(--line); border-left: 5px solid var(--accent);
  background: var(--surface); padding: 1.4rem 1.3rem;
}
.fact__n {
  font-size: clamp(2.9rem, 11vw, 4.6rem); line-height: .9;
  font-weight: 800; letter-spacing: -.04em; color: var(--accent);
  font-variant-numeric: tabular-nums;
}
.fact__b p { margin: 0 0 .8rem; }
.fact__b p:last-child { margin-bottom: 0; }
/* Sized against the 46rem reading column, not the viewport — a 15rem number
   column inside it left the text too narrow and the number stranded in white. */
@media (min-width: 46em) {
  .fact { grid-template-columns: minmax(7rem, 11rem) 1fr; gap: 1.5rem; align-items: start; }
  /* The base size is a vw clamp, which is right while the number owns the full
     mobile width and wrong once it has a fixed 11rem column to live in — at
     1280px the clamp resolved wider than the column and ran into the text. */
  .fact__n { font-size: 3.4rem; }
}

/* ---------------------------------------------------------- lists */

.how__ol { margin: 0; padding: 0; list-style: none; counter-reset: s; display: grid; gap: 1rem; }
.how__ol li {
  counter-increment: s; position: relative; padding-left: 3rem;
  color: var(--ink-dim); max-width: 68ch;
}
.how__ol li::before {
  content: counter(s); position: absolute; left: 0; top: .05em;
  width: 2rem; height: 2rem; display: grid; place-items: center;
  background: var(--accent); color: #fff; font-weight: 800; font-size: .95rem;
}

.qs { margin: 0; padding: 0; list-style: none; display: grid; gap: .85rem; }
.qs li {
  position: relative; padding-left: 1.4rem; color: var(--ink-dim); max-width: 68ch;
}
.qs li::before {
  content: ""; position: absolute; left: 0; top: .62em;
  width: .5rem; height: .5rem; background: var(--sand);
}

.cards { list-style: none; margin: 0; padding: 0; display: grid; gap: .75rem; }
@media (min-width: 34em) { .cards { grid-template-columns: repeat(2,1fr); } }
@media (min-width: 56em) { .cards { grid-template-columns: repeat(3,1fr); } }
.card a {
  display: block; height: 100%; text-decoration: none;
  background: var(--surface); border: 1px solid var(--line);
  padding: 1.15rem 1.1rem;
}
.card a:hover { border-color: var(--accent); background: var(--surface2); }
.card h3 { margin: 0 0 .2rem; font-size: 1.02rem; font-weight: 800; color: var(--ink); }
.card p { margin: 0; font-size: .82rem; color: var(--ink-faint); }

.sib__h { margin: 0 0 1rem; font-size: 1.1rem; font-weight: 800; color: var(--sky); }
.sib__ul { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }
@media (min-width: 40em) { .sib__ul { grid-template-columns: repeat(2,1fr); } }
@media (min-width: 60em) { .sib__ul { grid-template-columns: repeat(3,1fr); } }
.sib__ul a {
  display: flex; flex-direction: column; text-decoration: none;
  border: 1px solid var(--line); padding: .8rem .9rem; color: var(--ink);
}
.sib__ul a:hover { border-color: var(--accent); background: var(--surface); }
.sib__ul span { font-weight: 700; font-size: .95rem; }
.sib__ul em { font-style: normal; font-size: .76rem; color: var(--ink-faint); }

code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .86em; background: var(--surface2); padding: .1rem .38rem;
  color: var(--ink); border: 1px solid var(--line);
}

/* ---------------------------------------------------------- servicer */

.servicer { padding-block: 1.6rem; }
.servicer__h { margin: 0 0 .7rem; font-size: .74rem; letter-spacing: .16em;
  text-transform: uppercase; color: var(--ink-faint); font-weight: 700; }
.servicer__dl {
  margin: 0; display: grid; gap: .3rem .9rem;
  border: 1px solid var(--line); border-left: 4px solid var(--sky);
  background: var(--surface); padding: 1rem 1.1rem;
}
.servicer__dl dt { font-size: .72rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-faint); }
.servicer__dl dd { margin: 0 0 .5rem; font-weight: 700; color: var(--ink); }
.servicer__dl dd:last-child { margin-bottom: 0; }

/* ---------------------------------------------------------- form */

.form-wrap { padding: 24px 18px; }
.form {
  background: var(--surface);
  border: 1px solid var(--line);
  border-top: 5px solid var(--accent);
  padding: 18px 16px;
}
.form__h { margin: 0 0 4px; font-size: 22px; font-weight: 800;
  letter-spacing: -.015em; color: var(--ink); }
.form__sub { margin: 0 0 16px; font-size: 13.5px; color: var(--ink-dim); }

.f { margin: 0 0 16px; }
.f__l { display: block; font-size: 11px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink-dim); margin-bottom: 6px; }
.f__opt { text-transform: none; letter-spacing: 0; font-weight: 400;
  color: var(--ink-faint); font-size: 11px; }
.f__i {
  width: 100%; font-family: inherit;
  font-size: 16px;                       /* >=16px so iOS does not zoom on focus */
  padding: 12px;
  color: var(--ink); background: var(--bg);
  border: 2px solid var(--line-soft); border-radius: 0;
}
.f__i:focus { border-color: var(--accent); outline: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent); }
.f__i::placeholder { color: var(--ink-faint); }
.f__i--zip {
  font-size: 26px; font-weight: 800; letter-spacing: .22em;
  padding: 10px 12px; max-width: 9.5ch;
  font-variant-numeric: tabular-nums;
}
.f__i--ta { resize: vertical; line-height: 1.5; }
.f__help { margin: 6px 0 0; font-size: 12px; color: var(--ink-faint); }
.f__msg { margin: 8px 0 0; font-size: 13px; font-weight: 700; min-height: 1.2em; }
.f__msg[data-s="err"] { color: var(--accent-hi); }
.f__msg[data-s="ok"]  { color: var(--sky); }

.form__fine { margin: 12px 0 0; font-size: 12px; color: var(--ink-faint); line-height: 1.5; }

/* SMS consent. The A2P 10DLC campaign is registered against this exact control:
   Twilio's web-form opt-in rules require a checkbox the visitor actively ticks,
   never pre-selected, with the disclosure readable beside it. Ticking it is the
   consent, so it must not be styled as optional decoration — and booking works
   whether or not it is ticked, which is why nothing here is `required`. */
.f--consent { margin-bottom: 14px; }
.consent { display: flex; gap: 10px; align-items: flex-start; cursor: pointer; }
.consent__box { flex: none; width: 18px; height: 18px; margin: 1px 0 0; accent-color: var(--sky); }
.consent__txt { font-size: 12px; color: var(--ink-faint); line-height: 1.55; }
.consent__txt a { color: inherit; text-decoration: underline; }

/* honeypot — off-screen, never display:none (bots skip hidden fields) */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.form__done {
  background: var(--surface);
  border: 1px solid var(--line); border-top: 5px solid var(--sky);
  padding: 18px 16px;
}
.form__done h2 { margin: 0 0 8px; font-size: 22px; font-weight: 800; color: var(--ink); }
.form__done p { margin: 0; font-size: 14.5px; line-height: 1.5; color: var(--ink-dim); }

/* ---------------------------------------------------------- footer */

.ft { background: var(--ftbg); border-top: 1px solid var(--line); margin-top: 2rem; }
/* The 96px bottom padding is load-bearing: it clears the fixed call bar so the
   legal line can never be covered by it. */
.ft__in {
  max-width: var(--wrap); margin: 0 auto; padding: 22px 18px 96px;
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: 12px 28px;
}
.ft__brand { display: flex; align-items: center; gap: 13px; }
.ft__brand .mark__b { font-size: 15px; }
.ft__brand .mark__s { font-size: 9px; }
.ft__disc {
  margin: 0; max-width: 72ch;
  font-size: 12.5px; line-height: 1.6; color: var(--ink-faint);
  border-left: 3px solid var(--sand); padding-left: 12px;
}
/* Sits to the right of the crest on anything wide enough, and drops onto its
   own line below it when the two no longer fit. */
.ft__legal { margin: 0; font-size: 11.5px; color: var(--ink-faint); }
/* The call bar is display:none from here up, so the clearance it needed stops
   being clearance and starts being a hole under a one-line footer. */
@media (min-width: 48em) { .ft__in { padding-bottom: 30px; } }

/* ---------------------------------------------------------- call bar */

.callbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
  display: grid; grid-template-columns: 1.4fr 1fr;
  border-top: 1px solid var(--shadow);
  padding-bottom: env(safe-area-inset-bottom);
}
.callbar--one { grid-template-columns: 1fr; }
.callbar a {
  display: grid; place-items: center; min-height: var(--bar);
  text-decoration: none; font-weight: 800;
  padding: .5rem .75rem; text-align: center;
}
.callbar__a { background: var(--accent); color: #fff; font-size: 17px; }
.callbar__b { background: var(--surface2); color: var(--ink); font-size: 16px; }
.callbar__a:hover { color: #fff; }
.callbar__b:hover { background: var(--line); color: var(--ink); }
@media (min-width: 48em) { .callbar { display: none; } }

/* ---------------------------------------------------------- desktop */

/* Tablet: still one column, capped and centred at the conversion width, with
   everything below it on the same axis. Long-form blocks sit in a box narrowed
   to the reading measure rather than a 70rem box holding 68ch of prose — that
   left-aligns text inside a much wider container and reads as two unrelated
   layouts stacked on each other. */
@media (min-width: 48em) {
  .blk, .how, .sib, .servicer, .hero__in, .svcgrid {
    max-width: var(--read);
    margin-left: auto; margin-right: auto;
  }
}

/* Desktop: the band goes two-up.
 *
 * The old rule here capped the whole page at a 560px column and centred it,
 * which is a phone layout with margins rather than a desktop one. It also left
 * the form a full screen below a button that existed only to scroll down to
 * it. Above 62em there is room to put the form beside the pitch instead, so
 * the call and the form are both on screen at once and the button is gone.
 *
 * 62em matches the header-nav breakpoint: the same width at which the page
 * stops behaving like a phone in every other respect. */
@media (min-width: 62em) {
  .conv { padding: 46px 24px 52px; }
  .conv__in {
    max-width: var(--wrap);
    /* The form column is fixed-ish and the pitch takes the slack: a form that
       grows with the viewport just stretches its inputs. minmax(0,1fr) rather
       than 1fr so long headline words cannot push the grid wider than --wrap. */
    grid-template-columns: minmax(0, 1fr) minmax(23rem, 26rem);
    grid-template-rows: auto 1fr;
    gap: 2rem 3.5rem; align-items: start;
  }
  /* Which column runs longer — pitch+aside or the form — depends on the page
     and is not worth predicting, so both are stretched to the taller one's
     height instead of picking a side. Explicit placement puts the trailing
     block into column 1's row 2 instead of leaving half the band empty —
     while source order keeps it after the form, which is where a phone
     wants it. */
  .conv__pitch { grid-column: 1; grid-row: 1; }
  .conv__form  { grid-column: 2; grid-row: 1 / span 2; align-self: stretch; }
  /* Stretched to the full height of row 2 rather than left pinned to its top —
     .steps spreads its three rows across that height (see three-step above)
     instead of leaving dead air under a short card. */
  .conv__aside { grid-column: 1; grid-row: 2; align-self: stretch; }
  /* The form card mirrors that: stretched to fill whatever column 2 got
     given, with its own content centred rather than pinned to the top, so a
     short form (four fields) in a tall card reads as a deliberate choice
     instead of a card that ran out of content early. */
  .conv__form .form-wrap { display: flex; flex-direction: column; height: 100%; }
  .conv__form .form { flex: 1; display: flex; flex-direction: column; justify-content: center; }
  .conv__h1 { font-size: clamp(46px, 4.6vw, 60px); }
  /* No ch cap here — the pitch column (minmax(0,1fr)) is already bounded by
     --wrap, so a fixed line-length cap just strands whitespace between the
     sub copy and the form column instead of controlling readability. */
  .conv__sub { max-width: none; font-size: 17.5px; }
}
