/* ==========================================================================
   MM Appliances Repair Inc. — site stylesheet
   Palette, type scale and spacing come from tokens.css (= brand/tokens.css).

   Premium here is spacing, hierarchy, restraint and alignment — not ornament.
   The font stack is the system stack by deliberate decision (plan §12.3): a
   web font costs 30–80KB and a render delay on exactly the mobile connections
   these customers use.

   Two rules this file must never break:
     · Signal orange is for primary calls to action and nothing else.
     · Body copy is never orange.
   Enforced numerically by `python3 brand/verify-contrast.py`.
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

html {
  /* ── THE WHOLE SITE, ONE STEP SMALLER ────────────────────────────────────
     Every size in this file and in tokens.css is in rem, so this one number
     is the site's zoom. 90% takes the type, the buttons and the rem spacing
     down a tenth together — after the token scale had already stepped down
     once, the owner still read the phone pages as oversized, and the sizes
     that made them so were the dozens written in rem outside the tokens.
     Scaling the root catches all of them and keeps their proportions.

     A percentage, not a px value: a visitor's browser font preference and
     page zoom still scale through it. The vw halves of the clamp() tokens
     are scaled by the same tenth in tokens.css, so a fluid heading comes
     down with the rest instead of lagging it.

     What does NOT shrink with it is written in px: tap targets (44px and
     up), the form inputs' 16px (iOS zooms the page on focus below that),
     icons, logos and the gutters. */
  font-size: 90%;
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
  }
}

body {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-body);          /* 16px */
  line-height: var(--lh-body);
  color: var(--text-body);
  background: var(--surface-page);
  /* Room for the sticky action bar so it never covers the footer disclosure. */
  padding-bottom: var(--stickybar-h, 0px);
}

/* The compliance rewriter toggles `hidden` on marked nodes and cannot reason
   about CSS classes. This must stay, and must stay `!important`. */
[hidden] { display: none !important; }

img, svg { max-width: 100%; }
img { height: auto; }

h1 {
  margin: 0;
  color: var(--text-strong);
  font-weight: 600;
  line-height: var(--lh-heading);
  letter-spacing: var(--ls-caps);
  text-wrap: balance;
}
h2, h3, h4 {
  margin: 0;
  color: var(--text-strong);
  font-weight: var(--fw-heading);
  line-height: var(--lh-heading);
  letter-spacing: var(--ls-heading);
  text-wrap: balance;
}
p { margin: 0; }
ul, ol { margin: 0; padding: 0; }

a { color: var(--text-link); text-underline-offset: .18em; }
a:hover { color: var(--text-link-hover); }

:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring);
  outline-offset: var(--focus-ring-offset);
  border-radius: 3px;
}

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--c-navy); color: #fff;
  padding: .8rem 1.1rem; font-weight: 700;
}
.skip:focus { left: 0; color: #fff; }

/* ── Layout primitives ───────────────────────────────────────────────────── */

.wrap {
  width: 100%;
  margin-inline: auto;
  padding-inline: var(--gutter, 20px);
}
/* The wide column's one number. The hero art and the partner carousel align
   to it too (see --content-left and .trusted), so it is a token rather than a
   literal repeated three times. 1320 rather than 1240: on a desktop the page
   had a third of the screen empty either side while four-up tiles wrapped
   their own names. */
:root { --wrap-wide: 1320px; }
.wrap--wide  { max-width: var(--wrap-wide); }
.wrap--prose { max-width: 760px; }
.wrap--full  { max-width: none; }

@media (min-width: 700px)  { :root { --gutter: clamp(28px, 4vw, 56px); } }
@media (min-width: 1040px) { :root { --gutter: clamp(40px, 5vw, 72px); } }

.section { padding-block: clamp(2.75rem, 1.6rem + 4.6vw, 5.25rem); }
.section--paper { background: var(--surface-page); }
.section--tint  { background: var(--surface-raised); }
.section--navy  { background: var(--c-navy); color: #fff; }
.section--navy h2, .section--navy h3 { color: #fff; }

/* ── SECTION HEADINGS: ONE TREATMENT ─────────────────────────────────────
   Size, weight, tracking and leading come from here for every section
   signpost on the site, so a new section cannot quietly arrive at its own
   size. The three that had drifted are listed against --fs-section in
   tokens.css.

   `.section__title--legacy` is gone with this: it set --fw-heading and
   --ls-heading, which the base `h2` rule already gives every h2 on the page,
   so it had been a no-op class carried on three headings. */
.section__head { margin-bottom: clamp(1.5rem, 1rem + 1.6vw, 2.5rem); }
.section__title,
.whyus__title,
.ctaband__title,
.reviews__title {
  font-size: var(--fs-section);
  font-weight: var(--fw-heading);
  line-height: var(--lh-heading);
  letter-spacing: var(--ls-display);
  text-wrap: balance;
}
.section__lead {
  margin-top: .6rem;
  font-size: var(--fs-lead);
  color: var(--text-muted);
  max-width: 54ch;
}
.section--navy .section__lead { color: var(--c-slate-on-dark); }
.section__note { margin-top: 1.75rem; color: var(--text-muted); max-width: 64ch; }

/* ── Buttons ─────────────────────────────────────────────────────────────────
   The one orange element. White on #D9480F measures 4.30:1, which clears AA
   only as LARGE text (>=18.66px bold) — so the 19px/700 below is an
   accessibility constraint, not a style preference. Hover and active darken so
   the button clears full body contrast (5.18:1, 6.16:1) during interaction.
   Shrink the label and the button stops being accessible.                   */

/* A solid, quiet button. Depth comes from one soft, offset shadow; the hover
   state darkens the fill and the active state presses it, nothing else moves.
   (The previous version was a copied "soft-UI" button with two animated wave
   layers and a 6px hard-offset shadow — ornament on the one element whose only
   job is to be obviously, calmly pressable.) */
.cta {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  gap: .5rem;
  min-height: var(--cta-min-height);        /* 48px */
  padding: .8rem 1.6rem;
  background: var(--cta-bg);
  color: var(--cta-fg);
  font-size: var(--cta-font-size);          /* 19px — do not reduce */
  font-weight: var(--cta-font-weight);      /* 700 — do not reduce */
  line-height: 1.2;
  text-decoration: none;
  border: 0;
  border-radius: var(--cta-radius);
  box-shadow: 0 1px 2px rgb(11 37 69 / .12), 0 8px 18px -10px rgb(165 0 52 / .55);
  cursor: pointer;
  transition: background-color .15s ease, box-shadow .15s ease, transform .15s ease;
}
.cta:hover {
  color: var(--cta-fg);
  background: var(--cta-bg-hover);
  box-shadow: 0 1px 2px rgb(11 37 69 / .12), 0 10px 22px -10px rgb(165 0 52 / .6);
}
.cta:active {
  color: var(--cta-fg);
  background: var(--cta-bg-active);
  transform: translateY(1px);
  box-shadow: 0 1px 2px rgb(11 37 69 / .12);
}
.cta--sm { min-height: 44px; padding: .55rem 1.05rem; font-size: 1.0625rem; }

/* The terminal action — the button that actually submits the job. Bigger
   because it is the last thing the customer does and because, on a phone, the
   thumb reaches the bottom of the screen with the least precision. */
.cta--lg {
  min-height: 3.5rem;                       /* 56px */
  padding: 1.05rem 1.9rem;
  font-size: 1.25rem;                       /* 20px — still large text, 4.30:1 */
}

/* Full-width. The default for anything in the funnel: a one-handed tap aimed
   at a 160px-wide target in the middle of the screen is how a step gets
   missed, and a full-width button cannot be missed. */
.cta--block { display: flex; width: 100%; }

/* ── Submitting, and disabled ───────────────────────────────────────────────
   Submitting DARKENS (#AE3A0B, 6.16:1 with white) rather than fading. A faded
   button reads as "broken" or "not for you" at the exact moment the customer
   most needs to believe the thing is working — and a fade also drops contrast
   on the one button whose label must stay readable. Only a genuinely
   unavailable control fades, and WCAG 1.4.3 exempts those.                  */
.cta--busy,
.cta--busy:hover,
.cta--busy[disabled] {
  background: var(--cta-bg-active);
  color: var(--cta-fg);
  cursor: progress;
  opacity: 1;
  transform: none;
  box-shadow: 0 1px 2px rgb(11 37 69 / .12);
}
.cta-secondary.cta--busy,
.cta-secondary.cta--busy[disabled] {
  background: transparent; color: var(--c-navy); border-color: var(--c-navy);
}
.cta[disabled], .cta-secondary[disabled] {
  opacity: .55; cursor: not-allowed; transform: none; box-shadow: none;
}

.cta__label { display: inline-block; }
/* Present in the markup on every button and shown only while submitting, so
   the guard script toggles a class instead of building SVG in the browser. */
.cta__spin { display: none; flex: 0 0 auto; }
.cta--busy .cta__spin { display: inline-block; animation: cta-spin .75s linear infinite; }
@keyframes cta-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  /* Still moving — it is the only signal that the request is in flight — but
     slowly enough not to trigger anyone. */
  .cta--busy .cta__spin { animation-duration: 3s; }
}

.cta-secondary {
  display: inline-flex; align-items: center; justify-content: center;
  gap: .5rem;
  min-height: var(--cta-min-height);
  padding: .8rem 1.4rem;
  background: transparent;
  color: var(--c-navy);
  font-size: var(--fs-body);
  font-weight: var(--fw-semibold);
  text-decoration: none;
  border: 2px solid var(--c-navy);
  border-radius: var(--cta-radius);
  transition: background-color .15s ease, color .15s ease;
}
.cta-secondary:hover { background: var(--c-navy); color: #fff; }
.cta-secondary--onnavy { color: #fff; border-color: rgb(255 255 255 / .55); }
.cta-secondary--onnavy:hover { background: #fff; color: var(--c-navy); border-color: #fff; }

/* ── Header ──────────────────────────────────────────────────────────────── */

.hdr {
  position: sticky; top: 0; z-index: 60;
  background: var(--c-paper);
  border-bottom: 1px solid var(--c-rule);
  box-shadow: 0 1px 0 rgb(11 37 69 / .04), 0 8px 24px -18px rgb(11 37 69 / .35);
}
.hdr__inner {
  display: flex; align-items: center; gap: clamp(.7rem, 1.6vw, 1.5rem);
  min-height: 72px;
  padding-block: .7rem;
}
/* §9.3: tap targets >=44px. A logo is still a link. */
.hdr__brand { display: flex; align-items: center; flex: 0 0 auto; min-height: 44px; text-decoration: none; }
.hdr__brand:focus-visible { outline-offset: 4px; }
.hdr__logo-wide { display: none; }
.hdr__logo-mark { display: block; }
/* The LG mark and "Certified" tucked into the logo's bottom-right corner. */
.hdr__lg {
  position: absolute; right: .25rem; bottom: .15rem;
  display: flex; flex-direction: row; align-items: center; gap: .25rem;
}
.hdr__lgimg { height: 30px; width: auto; }
.hdr__lgtext { font-size: .55rem; font-weight: 600; line-height: 1; color: #6b7280; }
.hdr__lg--req { right: .15rem; bottom: .1rem; gap: .2rem; }
.hdr__lg--req .hdr__lgimg { height: 22px; }
.hdr__lg--req .hdr__lgtext { font-size: .45rem; }
@media (min-width: 560px) {
  .hdr__logo-wide { display: block; }
  .hdr__logo-mark { display: none; }
}

.hdr__nav { display: none; flex: 1 1 0; min-width: 0; justify-content: center; }
@media (min-width: 1100px) { .hdr__nav { display: flex; } }

/* The links sit directly on the header. Only the current item gets a pill,
   drawn by the shared slider once the script runs. */
.navpill {
  position: relative;
  display: inline-flex;
}
.navpill ul { display: flex; gap: 2px; list-style: none; }
.navpill a {
  position: relative; z-index: 1;
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 40px; padding: 0 clamp(.58rem, .8vw, .95rem);
  color: var(--c-navy-mid); font-size: clamp(.8rem, .2vw + .72rem, .875rem); font-weight: 600;
  text-transform: uppercase; letter-spacing: var(--ls-caps);
  text-decoration: none; border-radius: 999px; white-space: nowrap;
  transition: color .3s cubic-bezier(.4, 0, .2, 1);
}
.navpill a:hover, .navpill a:focus-visible { color: var(--c-navy); background: var(--c-paper-tint); }
.navpill a[aria-current="page"] { color: #fff; }

/* Keep the nav comfortably separated from the actions while preserving every
   link at intermediate desktop widths. */
@media (min-width: 980px) and (max-width: 1120px) {
  .hdr__inner { gap: .65rem; }
  .hdr__actions { gap: .3rem; }
}


/* Weight matches the Trusted Partners eyebrow (600) on every item, including the
   current one. A different weight on the current item would be WIDER, and the
   slider measures live widths — so the indicator would mis-size the moment it
   moved onto it. Colour and fill carry the emphasis instead. */

/* The glow tab: a short bar above the current item's pill, bloomed amber.
   Amber is already the site's "you are here" signal (it was the underline this
   replaces), so the meaning carries over rather than being reinvented. */
.navpill__slider::before,
.navpill a[aria-current="page"]::before {
  content: "";
  position: absolute; left: 50%; top: -8px;
  width: 30px; height: 4px; margin-left: -15px;
  border-radius: 999px;
  background: #A50034;
  opacity: .78;
  box-shadow: 0 0 7px 0 rgb(165 0 52 / .4), 0 0 18px 3px rgb(165 0 52 / .22);
}

/* ZERO-JS STATE: the current link draws its own fill. This is the baseline and
   it is always correct — the script only takes the drawing over. */
.navpill a[aria-current="page"] { background: var(--c-navy-mid); }

/* The shared indicator. Hidden until the script measures it, so it can never
   flash at the wrong position on first paint. */
.navpill__slider {
  position: absolute; z-index: 0; top: 0; bottom: 0; left: 0;
  width: var(--w, 0);
  transform: translateX(var(--x, 0));
  border-radius: 999px;
  background: var(--c-navy-mid);
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .08), 0 6px 16px -8px rgb(0 0 0 / .75);
  opacity: 0; pointer-events: none;
}
/* Script is live: hand the fill and the tab to the slider. The link currently
   owning the slider gets the inverse label; every other link returns to navy. */
.navpill[data-slide] .navpill__slider { opacity: 1; }
.navpill[data-slide] a[aria-current="page"] { color: var(--c-navy-mid); background: transparent; }
.navpill[data-slide] a[aria-current="page"]::before { content: none; }
.navpill[data-slide] a.is-slider-active {
  color: #fff;
  background: transparent;
}
/* `is-ready` is set one frame after the first measurement, so the opening
   position is placed instantly and only subsequent moves animate. */
.navpill[data-slide].is-ready .navpill__slider {
  transition:
    transform .44s cubic-bezier(.34, 1.26, .48, 1),
    width .44s cubic-bezier(.34, 1.26, .48, 1);
}

.hdr__actions {
  display: flex; align-items: center; flex: 0 0 auto;
  gap: clamp(.45rem, 1vw, .85rem); margin-left: auto;
}
/* Below the sticky bar's breakpoint the header CTA is redundant. */
.hdr__cta { display: none; flex: 0 0 auto; text-transform: uppercase; letter-spacing: var(--ls-caps); font-size: .875rem; padding: .45rem .9rem; }
@media (min-width: 700px) { .hdr__cta { display: inline-flex; } }

/* ── Phone and tablet menu ───────────────────────────────────────────────────
   Below the link row's breakpoint the primary nav used to vanish: a phone had
   the logo, a WhatsApp icon and no way to Services, Areas, About or Contact
   except the footer, four screens down. This is a <details>, so it opens and
   closes with no script and no state; public/nav-menu.js adds Escape, the
   outside tap, the scroll lock and focus. */
.mnav { position: static; }
@media (min-width: 1100px) { .mnav { display: none; } }
/* The button is the icon alone: a 44px square holding three bars, with no
   frame and no word. "Menu" stays in the markup as the accessible name and
   is shown to nobody — on a phone the three bars ARE the word, and the frame
   plus label spent ~60px of a 375px header saying it twice. */
.mnav__btn {
  list-style: none; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; padding: 0;
  border: 0; border-radius: 10px;
  background: transparent; color: var(--c-navy);
  -webkit-tap-highlight-color: transparent;
  transition: background-color .15s ease;
}
.mnav__btn::-webkit-details-marker { display: none; }
.mnav__btn::marker { content: none; }
.mnav__btn:active { background: var(--surface-raised); }
@media (hover: hover) { .mnav__btn:hover { background: var(--surface-raised); } }
.mnav__btn:focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring); outline-offset: 2px; }
.mnav__label {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
/* Three bars that fold into a cross. */
.mnav__icon { position: relative; display: block; width: 22px; height: 16px; }
.mnav__icon span {
  position: absolute; left: 0; right: 0; height: 2px; border-radius: 2px;
  background: currentColor;
  transition: transform .2s ease, opacity .2s ease;
}
.mnav__icon span:nth-child(1) { top: 0; }
.mnav__icon span:nth-child(2) { top: 7px; }
.mnav__icon span:nth-child(3) { top: 14px; }
.mnav[open] .mnav__icon span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.mnav[open] .mnav__icon span:nth-child(2) { opacity: 0; }
.mnav[open] .mnav__icon span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* The sheet hangs from the header's bottom edge and scrolls inside itself if
   the screen is short. `100%` inside the calc is the header's own height, so
   the sheet never reaches past the bottom of the viewport. */
.mnav__panel {
  position: absolute; left: 0; right: 0; top: 100%; z-index: 2;
  padding: .25rem var(--gutter, 20px) calc(1.25rem + env(safe-area-inset-bottom, 0px));
  background: var(--c-paper);
  border-top: 1px solid var(--c-rule);
  box-shadow: 0 28px 40px -28px rgb(11 37 69 / .55);
  max-height: calc(100dvh - 100%); overflow-y: auto; overscroll-behavior: contain;
}
/* The page behind, dimmed from the header down. On the details element rather
   than the panel so the panel's own overflow cannot clip it. */
.mnav[open]::after {
  content: ""; position: absolute; left: 0; right: 0; top: 100%; height: 100dvh; z-index: 1;
  background: rgb(11 37 69 / .45);
}
.mnav__list { list-style: none; margin: 0; padding: 0; }
.mnav__list a {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  min-height: 56px; padding: 0 .15rem;
  border-bottom: 1px solid var(--c-rule);
  color: var(--c-navy); font-size: 1.0625rem; font-weight: 700; text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}
.mnav__list a svg { flex: 0 0 auto; color: var(--c-rule-strong); }
.mnav__list a:active { background: var(--surface-raised); }
.mnav__list a[aria-current="page"] { color: var(--c-lg-crimson, #A50034); }
.mnav__list a[aria-current="page"] svg { color: currentColor; }
.mnav__actions { display: grid; grid-template-columns: 1fr 1fr; gap: .6rem; padding-top: 1rem; }
.mnav__cta { grid-column: 1 / -1; width: 100%; }
.mnav__call, .mnav__wa {
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  min-height: 48px; border-radius: var(--cta-radius);
  border: 1.5px solid var(--c-navy); color: var(--c-navy);
  font-size: 1.0625rem; font-weight: 700; text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}
.mnav__call:active, .mnav__wa:active { background: var(--surface-raised); }
.mnav__wa svg { color: #25D366; }
.mnav__hours { margin: 1rem 0 0; text-align: center; font-size: .9375rem; line-height: 1.5; color: var(--text-muted); }
.mnav__hours strong { color: var(--c-navy); }
/* Set by nav-menu.js while the sheet is open. */
html.menu-open { overflow: hidden; }
@media (prefers-reduced-motion: reduce) { .mnav__btn, .mnav__icon span { transition: none; } }

/* ── Breadcrumbs ─────────────────────────────────────────────────────────── */

.crumbs { background: var(--surface-raised); border-bottom: 1px solid var(--c-rule); }
.crumbs ol {
  display: flex; flex-wrap: wrap; align-items: center; gap: .4rem;
  list-style: none; padding-block: .7rem;
  font-size: var(--fs-micro);
}
.crumbs li { display: flex; align-items: center; gap: .4rem; }
.crumbs a { color: var(--text-muted); text-decoration: none; }
.crumbs a:hover { color: var(--c-navy); text-decoration: underline; }
.crumbs [aria-current="page"] { color: var(--c-navy); font-weight: 600; }
.crumbs__sep { color: var(--c-rule); }

/* ── Hero ────────────────────────────────────────────────────────────────── */

.hero {
  position: relative; isolation: isolate; overflow: hidden;
  background: var(--c-navy-mid);
  color: #fff;
  padding-block: clamp(3rem, 1.8rem + 5.5vw, 6.5rem);

  /* ONE NUMBER OWNS THE SPLIT. The text column's width and the width the
     drawing is told to keep clear used to be two independent values — a
     `15ch` cap on the headline and a hard-coded 620px reserve in the media
     box — and they disagreed: at the display size's 5.5rem ceiling, 15ch is
     ~845px, so past ~1800px the headline grew straight into the fridge.
     Both now read the same token, so they cannot drift apart again.
     600px is measured, not guessed: the ACTIONS row is the widest thing in
     the column — the LG badge holds it at 588px from the wrap's CONTENT edge
     at every viewport, independent of the headline — and 600 rounds that up
     to a round number with a pixel or two to spare. */
  --hero-text-col: 600px;
  /* Clear air between the column and the drawing. A gap, not a safety
     margin: at the widths where the art is worth showing it should read as
     staged beside the text, not butted against it. Kept modest on purpose —
     the column is a fixed width, so every pixel of gap comes straight out of
     the drawing, and the drawing has a legibility floor (its readout type is
     ~15 drawing units, which stops being readable below roughly 0.6 scale). */
  --hero-art-gap: clamp(1.5rem, 2vw, 3rem);
}
/* A navy that is a material rather than a flat fill: a soft off-centre lift
   toward navy-light, which reads as depth without becoming a gradient effect. */
.hero::before {
  content: ""; position: absolute; inset: 0; z-index: -2;
  background:
    radial-gradient(120% 90% at 78% 8%, rgba(165, 0, 52, 0.25) 0%, transparent 58%),
    linear-gradient(180deg, var(--c-navy-light) 0%, var(--c-navy-mid) 62%);
}
.hero__media {
  position: absolute; inset: 0; z-index: -1;
  display: flex; align-items: center; justify-content: flex-end;
  overflow: hidden;
  pointer-events: none;
}
/* The deliberate empty state for photography (§12.4 — no stock imagery, and
   the owners' shoot has not happened): a drawn appliance run with diagnostic
   callouts, held in the right half of the content column. When a real photo
   lands it takes this slot with no layout change.

   NO EXTRA OPACITY HERE, DELIBERATELY. The artwork is already graded inside
   the file — carcass strokes at 0.1–0.24 white, crimson leaders and tag chips
   at full strength so the diagnostic detail stays the one thing that reads.
   Fading the whole layer would flatten that grading back into one grey wash.

   ITS LEFT EDGE IS SET BY THE TEXT, ITS RIGHT EDGE BY THE VIEWPORT. Pinning
   the art to the 1240px content column aligned it with the header CTA but
   capped it at ~500px — on a wide monitor that reads as a small inset rather
   than a hero. The width has to come from somewhere, and the only slack
   available is the margin OUTSIDE the content column, so the art bleeds to
   the viewport edge. That is a deliberate trade: the oven's right edge no
   longer lines up with the header CTA, and in exchange the drawing fills the
   hero's height instead of floating in it.

   `--content-left` reproduces where `.wrap` actually starts, and
   `--hero-text-col` (declared on `.hero`, and the same token the headline is
   capped to) is the text column's real width. Measuring from the text rather
   than from a percentage is what keeps the badge off the fridge at every
   width; reading the SAME token the text is sized by is what stops the two
   drifting apart as the display size scales.

   SIZED BY THE SPACE IT IS GIVEN, NEVER BY THE VIEWPORT WIDTH. Two earlier
   attempts failed on exactly that and both failed at the top edge: `cover`
   magnified the art 1.5x until its caption ran through the headline, and
   `width: 100%` tied the art's HEIGHT to the viewport's WIDTH — on a wide,
   short hero that renders it taller than the hero, clipping the whole
   diagnostic tag row away and leaving the red leaders running off the top
   edge pointing at nothing. `contain` inside a bounded box cannot do that:
   the box is the guarantee, and the art grows to fill whichever axis binds. */
.hero:not(.hero--photo) .hero__media {
  --content-left: max(var(--gutter, 20px), calc((100% - var(--wrap-wide, 1240px)) / 2 + var(--gutter, 20px)));
  inset: clamp(1.5rem, 2.5vw, 2.75rem) clamp(1rem, 2.5vw, 3rem)
         clamp(7.5rem, 5.5rem + 4vw, 11rem)
         calc(var(--content-left) + var(--hero-text-col) + var(--hero-art-gap));
  display: block;
}
.hero__diagram {
  display: block;
  width: 100%; height: 100%;
}

/* ── Hero diagnostic readouts ─────────────────────────────────────────────
   The drawing is an inline <svg> (see HeroDiagram.astro), so it is styled
   from here rather than from inside the asset — which is what lets the
   readouts use the site's own type and palette instead of a second set of
   colours hardcoded into an image.

   NONE OF THESE COLOURS ARE NEW. The asset arrived with #C8243C / #8FD3A8 /
   #F0C27E, none of which are in the palette. `--c-amber` is the token
   licensed for highlights on navy and carries the live probe; `--c-orange`
   is NOT used here, because the palette reserves it for the primary CTA and
   a blinking marker competing with Request Service is exactly the rule's
   point. Out-of-spec readings take `--c-warning-on-dark`, which exists
   because the light-surface warning fails against navy. */
.hero__diagram .dx__rule  { stroke: rgba(255,255,255,.18); stroke-width: 1; }
.hero__diagram .dx__trunk { stroke: rgba(255,255,255,.09); stroke-width: 1; }

.hero__diagram .dx__label {
  font-family: var(--font-sans);
  font-size: 15px; font-weight: 600; letter-spacing: .11em;
  fill: var(--c-slate-on-dark);
}
/* Mono on the VALUE only. The reading is a measurement, which is what
   monospace is for; setting the component name in it too would be the
   costume version of the same idea, and it is what made the old chips read
   as decoration. Tabular figures stop the digits jittering on every swap. */
.hero__diagram .dx__value {
  font-family: var(--font-mono);
  font-size: 15px; letter-spacing: .01em;
  font-variant-numeric: tabular-nums;
  fill: var(--c-mist);
}
.hero__diagram .dx__value.is-flag { fill: var(--c-warning-on-dark); }

/* The register rolls: the old reading clears upward behind the rule's clip
   and the new one rises into its place, so the swap reads as a mechanical
   change of state rather than a crossfade between two labels. */
.hero__diagram .dx__label,
.hero__diagram .dx__value {
  transition: opacity .28s ease, transform .28s cubic-bezier(.16, 1, .3, 1);
}
.hero__diagram .is-out { opacity: 0; transform: translateY(-9px); transition-duration: .16s; }
.hero__diagram .is-in  { opacity: 0; transform: translateY(9px);  transition: none; }

/* The probe. `--dx-travel` is mirrored in hero-diagnostics.js, which times
   the arrival pulse against it. */
.hero__diagram .dx__probe {
  --dx-travel: 620ms;
  transition: transform var(--dx-travel) cubic-bezier(.16, 1, .3, 1);
}
.hero__diagram .dx__dot   { fill: var(--c-amber); }
.hero__diagram .dx__ring  { fill: none; stroke: var(--c-amber); stroke-width: 1; opacity: .5; }
.hero__diagram .dx__pulse { fill: none; stroke: var(--c-amber); stroke-width: 1.5; opacity: 0; }
.hero__diagram .dx__pulse.is-pulsing { animation: dx-pulse .9s cubic-bezier(.16, 1, .3, 1); }
@keyframes dx-pulse {
  from { opacity: .85; transform: scale(.5); }
  to   { opacity: 0;   transform: scale(2.6); }
}

/* Reduced motion keeps the readings changing — that is the information —
   and drops the travel, the roll and the pulse, which are the movement.
   The script also lengthens the dwell so the remaining change is calmer. */
@media (prefers-reduced-motion: reduce) {
  .hero__diagram .dx__probe { transition: none; }
  .hero__diagram .dx__label,
  .hero__diagram .dx__value { transition: opacity .2s ease; }
  .hero__diagram .is-out { transform: none; }
  .hero__diagram .is-in  { transform: none; }
  .hero__diagram .dx__pulse.is-pulsing { animation: none; }
}
/* The text column is a fixed ~620px at every width, so everything the drawing
   gets is what the viewport has over and above it — which means the art
   shrinks fast as the window narrows. Measured, it fills 100% of the hero's
   height at 2560, 93% at 1920, 67% at 1440, 61% at 1280, and under half by
   1150, where it stops reading as a drawing at all. 1200px is where that
   curve crosses "worth showing"; below it, drop the layer rather than crush
   it — the ::before gradient still carries the panel on its own. */
@media (max-width: 1199px) { .hero__diagram { display: none; } }

/* The hero is far wider than the photograph is tall (3.4:1 against the image's 1.79:1
   at desktop), so `cover` keeps only a horizontal band. Centring that band vertically
   lands it on blank wall; 58% drops it onto the worktop and the appliance run, which is
   the half of the room that says what the business does. The horizontal 58% pushes the
   same detail clear of the headline, which occupies the left. */
.hero--photo .hero__media img {
  width: 100%; height: 100%;
  object-fit: cover; object-position: 58% 58%;
  opacity: .64;
}
/* Legibility is bought with the scrim, not by fading the photograph out. The gradient is
   opaque navy under the text column and thins across the right, so the headline keeps its
   contrast while the image stays a photograph rather than a grey wash. */
.hero--photo::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background:
    linear-gradient(100deg, rgb(11 37 69 / .75) 0%, rgb(11 37 69 / .45) 40%, rgb(11 37 69 / .3) 65%, rgb(11 37 69 / .6) 100%),
    linear-gradient(0deg, rgb(11 37 69 / .4) 0%, transparent 38%);
}
/* Narrow viewports crop ~2.2x into the middle of the frame and the text sits over all of
   it, so the photograph both loses its subject and costs contrast. Framing right catches
   the washer-dryer run instead of blank worktop, and the lower opacity buys back the
   contrast the full-width text needs. */
@media (max-width: 760px) {
  .hero--photo .hero__media img { object-position: 72% 62%; opacity: .52; }
  .hero--photo::after {
    background:
      linear-gradient(170deg, rgb(11 37 69 / .6) 0%, rgb(11 37 69 / .4) 40%, rgb(11 37 69 / .4) 60%, rgb(11 37 69 / .6) 100%),
      linear-gradient(0deg, rgb(11 37 69 / .4) 0%, transparent 42%);
  }
}
.hero__inner { position: relative; }

.hero__title {
  max-width: 19ch;
  font-size: var(--fs-display);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
  color: #fff;
}
/* The oversized homepage headline. Was an inline `font-size:6em` — a fixed
   multiple of the root size with no view of the viewport it lands in, so the
   headline could only answer a narrow window by overflowing it. The clamp
   keeps the same presence at full width and scales down on its own. */
.hero__title--display {
  font-size: clamp(2.5rem, 1.5rem + 3.6vw, 5rem);
  line-height: 1.02;
}
/* Tighter than the 19ch default, because this headline no longer has the hero
   to itself. 15ch turns a strained single line into two — the better setting
   for a display size anyway — but 15ch is a multiple of the FONT SIZE, and at
   the clamp's 5.5rem ceiling that is ~845px, well past the gutter the drawing
   was told to keep clear. `min()` lets the ch measure govern while it is the
   smaller of the two and hands the limit to the shared column token once it
   is not. Matched to the drawing's own breakpoint: below 1200px it is hidden
   and the headline is welcome to the full measure. */
@media (min-width: 1200px) {
  .hero__title--display { max-width: min(15ch, var(--hero-text-col)); }
}
.hero__lead {
  margin-top: 1.15rem; max-width: 46ch;
  font-size: clamp(1.1rem, 1.02rem + .4vw, 1.3125rem);
  line-height: 1.5;
  color: var(--text-inverse-soft);
}
.hero__lead a { color: #fff; }
.hero__actions { display: flex; flex-wrap: wrap; align-items: center; gap: .75rem; margin-top: 2rem; }
/* The LG direct-service card beside the two buttons. It is artwork with its own
   white ground, so it is sized as a card, not as an icon. */
.hero__badge { height: 80px; width: auto; margin-left: .75rem; }
.hero__credit { margin-top: 1.5rem; font-size: var(--fs-micro); color: var(--c-slate-on-dark); }
.hero--compact { padding-block: clamp(2.25rem, 1.5rem + 3vw, 3.5rem); }
.hero--compact .hero__title { font-size: var(--fs-h1); }

/* ── Why choose us ───────────────────────────────────────────────────────────
   Seven claims as cards, on the page tint, directly under the hero.

   This block has been a badge row, a card inside the hero and a strip
   overhanging it. Each of those was a label-sized container, and these claims
   are promises that need a sentence — so the card is the shape that fits the
   content rather than the content being cut to fit the shape.

   Flex rather than grid, for one reason: SEVEN items. Four columns leaves
   three on the second row, and a grid would push them into the left-hand
   columns with a hole on the right, which reads as a mistake. Flex with
   `justify-content: center` and a fixed basis gives identical columns on the
   full row and centres the remainder, so the odd count looks deliberate —
   which it is, because padding it to eight would mean publishing a claim
   that is not yet true.                                                     */
.whyus {
  --whyus-gap: clamp(.75rem, .55rem + .6vw, 1.1rem);
  /* `--surface-raised`, not a `--surface-page-tint` fallback. That token does
     not exist — this was resolving to the fallback and landing on the right
     colour by luck, which is the kind of thing that stays correct until
     someone changes the palette. */
  background: var(--surface-raised);
  padding-block: clamp(2.5rem, 1.8rem + 3vw, 4.5rem);
}

/* ── WHERE TWO TINTED SECTIONS MEET ──────────────────────────────────────
   `.whyus` and the symptom picker are both `--surface-raised`, so there was
   no boundary between them at all: the page ran on as one block and the
   "What's wrong?" heading read as a third heading belonging to the cards
   above it rather than as the start of something new.

   A hairline rather than a change of ground, because neither section can
   give its tint up — the cards above are white and the pills below are
   white, and both need something to sit on. Written as an adjacency rule so
   the next tinted section added next to one is covered too. */
.whyus + .section--tint,
.section--tint + .section--tint,
.section--paper + .section--paper { border-top: 1px solid var(--c-rule); }

.whyus__head {
  display: flex; flex-direction: column; align-items: center; gap: .7rem;
  margin: 0 0 clamp(1.75rem, 1.2rem + 2vw, 2.75rem);
  text-align: center;
}
/* --fs-micro is for legal lines and table meta; at that size the eyebrow read
   as a caption that had floated above the heading rather than as part of it. */
.whyus__eyebrow {
  font-size: 1rem; font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--c-navy-light);
}
.whyus__title {
  max-width: 22ch;
  color: var(--c-navy);
}

.whyus__grid {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: var(--whyus-gap);
  margin: 0; padding: 0; list-style: none;
}
/* `flex-grow: 0` is what centres the last row. With grow:1 the lone seventh
   card would stretch to the full width and stop being a card. */
.whyus__cell { flex: 0 1 100%; display: flex; }
@media (min-width: 640px) {
  .whyus__cell { flex-basis: calc((100% - var(--whyus-gap)) / 2); }
}
@media (min-width: 980px) {
  .whyus__cell { flex-basis: calc((100% - 2 * var(--whyus-gap)) / 3); }
}
@media (min-width: 1180px) {
  .whyus__cell { flex-basis: calc((100% - 3 * var(--whyus-gap)) / 4); }
}

.whycard {
  display: flex; flex-direction: column;
  width: 100%;
  padding: clamp(.9rem, .8rem + .35vw, 1.15rem);
  background: var(--surface-page);
  border: 1px solid var(--c-rule);
  border-radius: 16px;
  color: var(--c-navy);
  text-decoration: none;
  box-shadow: 0 1px 2px rgb(11 37 69 / .04);
  transition: border-color .18s ease, box-shadow .18s ease, transform .18s ease;
}
/* Only the two warranty cards are links. A hover ground and a lift, not an
   underline: one underlined card in a grid of seven reads as damage. */
a.whycard:hover,
a.whycard:focus-visible {
  border-color: var(--c-navy-light);
  box-shadow: 0 2px 4px rgb(11 37 69 / .07), 0 12px 26px rgb(11 37 69 / .1);
  transform: translateY(-2px);
}
a.whycard:hover .whycard__title,
a.whycard:focus-visible .whycard__title {
  text-decoration: underline; text-underline-offset: .18em;
}

.whycard__mark {
  display: grid; place-items: center;
  width: 48px; height: 48px;
  margin-bottom: .65rem;
  border-radius: 13px;
  background: var(--c-paper-tint);
  color: var(--c-navy-light);
}
.whycard__mark svg { width: 26px; height: 26px; }
/* The LG mark is a colour logo and needs white under it. */
.whycard__mark--logo { background: var(--surface-page); box-shadow: inset 0 0 0 1px var(--c-rule); }
.whycard__lglogo { width: 27px; height: 27px; object-fit: contain; }

.whycard__title {
  display: block;
  font-size: 1.0625rem; font-weight: 700; line-height: 1.3;
  letter-spacing: -.01em;
  text-wrap: balance;
}
/* Smaller than the title by a clear step, not a hair. In a four-up row the
   description is support, and at the title's own size it competed with it —
   seven cards of near-equal type is a wall rather than a scan. */
.whycard__sub {
  display: block; margin-top: .25rem;
  font-size: .8125rem; line-height: 1.4;
  color: var(--c-slate);
}

/* ── Trusted partners ────────────────────────────────────────────────────────
   A dark band that sits directly under the hero, so the marquee reads as a
   continuation of the hero rather than a separate strip. Logos are wordmarks
   only — no tiles, no borders — and they fade at both edges through a mask so
   the movement reads as continuous rather than starting and stopping.       */

.partners {
  /* When nested in the hero this is transparent; standalone keeps the dark band. */
  background: linear-gradient(180deg, var(--c-navy) 0%, var(--c-navy-mid) 100%);
  border-bottom: 1px solid var(--c-rule-on-dark);
  padding-block: clamp(1.5rem, 1.1rem + 1.6vw, 2.25rem);
  overflow: hidden;
}
.partners__inner {
  width: 100%; max-width: var(--wrap-wide, 1240px);
  margin-inline: auto;
  padding-inline: var(--gutter, 20px);
  display: flex; flex-direction: column; gap: 1.6rem;
  align-items: center;
}
.partners__eyebrow {
  margin: 0;
  text-transform: uppercase; letter-spacing: var(--ls-caps);
  font-size: var(--fs-micro); font-weight: 600;
  color: var(--c-slate-on-dark);
}
.partners__viewport {
  width: 100%;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 10%, #000 90%, transparent 100%);
  mask-image: linear-gradient(to right, transparent 0, #000 10%, #000 90%, transparent 100%);
}
.partners__track {
  display: flex; align-items: center;
  gap: 0;
  width: max-content;
  animation: partners-scroll 38s linear infinite;
  will-change: transform;
}
.partners__viewport:hover .partners__track { animation-play-state: paused; }
.partners__logo {
  flex: 0 0 auto;
  /* Per brand, written by TrustedPartners.astro as a custom property rather
     than an inline `height`, so a media query can still scale the row — an
     inline height beats every stylesheet rule and the phone's smaller row
     was silently not happening. */
  height: var(--logo-h, 40px);
  display: inline-flex; align-items: center;
  margin-inline-end: clamp(3rem, 6vw, 5rem);
}
.partners__logo img {
  height: 100%; width: auto;
  filter: brightness(0) invert(1);
  opacity: .82;
  transition: opacity .2s ease;
}
.partners__logo:hover img { opacity: 1; }
.partners__logo--color img { filter: none; opacity: .95; }
.partners__logo:not(:has(img)) {
  font-size: clamp(1.05rem, .95rem + .3vw, 1.25rem);
  font-weight: 700; letter-spacing: .02em;
  color: rgb(255 255 255 / .72);
  white-space: nowrap;
}
@keyframes partners-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
@media (prefers-reduced-motion: reduce) {
  .partners__track { animation: none; }
  .partners__viewport { overflow-x: auto; }
}

/* Hero-integrated variant: the carousel sits over the photo at the foot. */
.hero__foot {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 1;
  padding-block: 2.25rem 1.35rem;
  background: linear-gradient(0deg, rgb(11 37 69 / .85) 0%, rgb(11 37 69 / .45) 58%, transparent 100%);
}
/* Reserve room at the hero's foot so the carousel never covers the CTAs. */
.hero:has(.hero__foot) { padding-bottom: clamp(10rem, 8rem + 4vw, 13rem); }
.hero .partners {
  background: transparent;
  border-bottom: 0;
  padding-block: 0;
}
.hero .partners__eyebrow { color: var(--c-slate-on-dark); }

/* ── Symptom picker ──────────────────────────────────────────────────────────
   The highest-conversion element on the site. Tiles are navy-on-paper because
   they are the PATH to the call to action, not the call to action; the single
   orange tile is the genuine unfiltered /request link.                      */

/* The one section on a crimson ground. It sits directly under "Why choose
   us", which is on the blue tint, and the two were previously the same
   colour with nothing between them. The hairline from the adjacency rule
   stays: at this strength the hue alone is a difference you notice only once
   you are looking for it, which is the point of it. */
#symptoms.section--tint { background: var(--surface-crimson); }

/* ── THE SYMPTOM PICKER ───────────────────────────────────────────────────
   Pills, in aligned columns.

   Two earlier shapes, and why this is the third. A grid of fixed-height
   rounded BOXES stretched every tile to the tallest label in its row, so a
   two-word fault got the same 128px block as "There's an error code on the
   display". A free wrap of pills fixed the emptiness but gave up the column:
   each row broke wherever its labels happened to end, so the second pill of
   every row started at a different x and the block read as rubble.

   A grid of PILLS keeps both halves. Columns line up down the page, and the
   pill is a pill — one line of type, a 44px disc, a stadium outline — rather
   than a card with air in it. Three columns, not four: at container width
   four would be ~285px and the longest label wraps, which drags every pill in
   that row to two lines because grid rows are as tall as their tallest cell.
   Thirteen items never divide evenly into any column count, and that is fine
   here — the leftover cell is the orange catch-all, which is the one item
   that benefits from standing on its own.                                  */
.symptoms {
  display: grid; gap: .6rem;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  list-style: none; padding: 0; margin: 0;
}
@media (min-width: 620px) {
  .symptoms { gap: .7rem; grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.symptom {
  display: flex; align-items: center; gap: .65rem;
  width: 100%; height: 100%;
  min-height: 56px;
  padding: .45rem 1.1rem .45rem .45rem;
  background: var(--surface-page);
  color: var(--c-navy);
  font-size: 1rem; font-weight: 600; line-height: 1.25;
  text-decoration: none;
  border: 1.5px solid var(--c-rule);
  border-radius: 999px;
  transition: border-color .18s ease, background-color .18s ease,
              color .18s ease, transform .18s ease, box-shadow .18s ease;
}
.symptom:hover,
.symptom:focus-visible {
  background: var(--c-navy); color: #fff;
  border-color: var(--c-navy);
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
}

/* A disc, not a rounded square: at pill scale a squared chip inside a fully
   round outline is two competing radii in 56px. */
.symptom__icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; flex: 0 0 auto;
  background: var(--surface-raised);
  color: var(--c-navy-light);
  border-radius: 50%;
  transition: background-color .18s ease, color .18s ease,
              transform .25s cubic-bezier(.34, 1.45, .64, 1);
}
.symptom:hover .symptom__icon,
.symptom:focus-visible .symptom__icon {
  background: var(--c-navy-mid); color: var(--c-amber);
  transform: scale(1.1) rotate(-6deg);
}

/* The label leans towards the arrow as the arrow arrives, so the pill reads
   as one movement rather than three things animating next to each other. */
.symptom__label {
  display: block;
  transition: transform .18s ease;
}
.symptom:hover .symptom__label,
.symptom:focus-visible .symptom__label { transform: translateX(3px); }

/* The arrow holds its slot from the start and only slides and fades, so
   nothing on the line moves when a pill is hovered. */
.symptom__go {
  flex: 0 0 auto; margin-left: auto; padding-left: .5rem;
  opacity: 0; transform: translateX(-6px);
  transition: opacity .18s ease, transform .18s ease;
}
.symptom:hover .symptom__go,
.symptom:focus-visible .symptom__go { opacity: 1; transform: none; }

/* Hover is a flourish, and a flourish is the first thing to go. */
@media (prefers-reduced-motion: reduce) {
  .symptom,
  .symptom__icon,
  .symptom__label,
  .symptom__go { transition-duration: .01ms; }
  .symptom:hover,
  .symptom:focus-visible { transform: none; }
  .symptom:hover .symptom__icon,
  .symptom:focus-visible .symptom__icon { transform: none; }
  .symptom:hover .symptom__label,
  .symptom:focus-visible .symptom__label { transform: none; }
}

/* Sidebar width on a service page: the same pill, one size down, one column. */
.symptoms--compact { grid-template-columns: minmax(0, 1fr); gap: .45rem; }
.symptoms--compact .symptom {
  min-height: 46px; padding: .35rem .9rem .35rem .35rem;
  font-size: .9375rem; gap: .55rem;
}
.symptoms--compact .symptom__icon { width: 36px; height: 36px; }

/* Phones: ONE pill per row. Two across put "Fridge warm, freezer fine" and
   "There's an error code on the display" onto three and four lines inside
   160px pills, and because a grid row is as tall as its tallest cell, every
   pill beside one of those stretched to match — the block read as a wall of
   broken lines. One across, each symptom is one line in a 46px pill, the
   stadium outline is a stadium again, and the thirteen scan top to bottom
   the way the eye already moves on a phone. No hover affordance either,
   since there is no hover. */
@media (max-width: 619px) {
  .symptoms { grid-template-columns: minmax(0, 1fr); gap: .4rem; }
  .symptoms .symptom {
    min-height: 46px;
    font-size: .9375rem;
    padding: .3rem .85rem .3rem .3rem;
    gap: .55rem;
  }
  .symptoms .symptom__icon { width: 34px; height: 34px; }
  .symptoms .symptom__go { display: none; }
}

/* The one orange pill: an actual primary CTA (unfiltered /request). */
.symptom--cta {
  background: var(--cta-bg); color: var(--cta-fg); border-color: var(--cta-bg);
}
.symptom--cta .symptom__icon { background: rgb(255 255 255 / .18); color: #fff; }
.symptom--cta:hover,
.symptom--cta:focus-visible {
  background: var(--cta-bg-hover); border-color: var(--cta-bg-hover); color: #fff;
}
.symptom--cta:hover .symptom__icon,
.symptom--cta:focus-visible .symptom__icon { background: rgb(255 255 255 / .22); color: #fff; }

/* ── Steps ───────────────────────────────────────────────────────────────── */

.steps {
  display: grid; gap: 1.75rem; list-style: none;
  grid-template-columns: 1fr;
}
.step {
  position: relative; display: flex; gap: 1rem; align-items: flex-start;
}
/* The track: a faint line joining each badge to the next, with an amber fill
   that grows in after the badge lands. */
.step:not(:last-child)::before,
.step:not(:last-child)::after {
  content: ""; position: absolute; border-radius: 2px;
  left: calc(1.875rem - 1px); top: 4.1rem; bottom: -1.75rem; width: 2px;
}
.step:not(:last-child)::before { background: var(--c-rule); }
.step:not(:last-child)::after {
  background: var(--c-amber); transform: scaleY(0); transform-origin: top;
}
.steps.is-in .step:not(:last-child)::after {
  animation: step-grow-y .7s ease-out calc(var(--d) + .5s) both;
}

.step__badge {
  position: relative; flex: 0 0 auto;
  display: grid; place-items: center;
  width: 3.75rem; height: 3.75rem; border-radius: 50%;
  background: var(--c-navy); color: #fff;
  box-shadow: 0 0 0 6px var(--surface-page);
}
.step__icon { width: 2rem; height: 2rem; overflow: visible; }
.step__n {
  position: absolute; top: -.4rem; right: -.4rem;
  display: grid; place-items: center;
  width: 1.5rem; height: 1.5rem; border-radius: 50%;
  background: var(--c-amber); color: var(--c-navy);
  font-size: .8rem; font-weight: 800; font-variant-numeric: tabular-nums;
  box-shadow: 0 0 0 3px var(--surface-page);
}

/* Progressive: these hidden states only apply once the script has run. */
.steps.is-ready .step__badge { opacity: 0; transform: scale(.4); }
.steps.is-ready .draw { stroke-dasharray: 1; stroke-dashoffset: 1; }
.steps.is-ready .step__body { opacity: 0; }

.steps.is-in .step__badge {
  animation: step-badge .6s var(--d) cubic-bezier(.2, .9, .3, 1.25) both;
}
.steps.is-in .step__body {
  animation: step-fade .6s calc(var(--d) + .25s) ease-out both;
}
.steps.is-in .draw {
  stroke-dasharray: 1;
  animation: step-draw .8s ease-out both;
  animation-delay: calc(var(--d) + .25s);
}
.steps.is-in .step .d2 { animation-delay: calc(var(--d) + .4s); }
.steps.is-in .step .d3 { animation-delay: calc(var(--d) + .55s); }
.steps.is-in .step .d4 { animation-delay: calc(var(--d) + .7s); }
.steps.is-in .ping {
  transform-box: fill-box; transform-origin: center;
  animation: step-ping 1.8s ease-out 1.4s infinite;
}

.step__title { font-size: 1.125rem; margin-bottom: .35rem; font-weight: var(--fw-heading); letter-spacing: var(--ls-heading); }
.step__text { color: var(--text-muted); font-size: 1rem; line-height: 1.55; }
.step__text strong { color: var(--c-navy); }
.steps__tail { margin-top: 2.25rem; }

@media (min-width: 960px) {
  .steps { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 2rem; }
  .step { flex-direction: column; gap: 1rem; }
  .step:not(:last-child)::before,
  .step:not(:last-child)::after {
    left: 4.75rem; right: -1.25rem; top: calc(1.875rem - 1px); bottom: auto;
    width: auto; height: 2px;
  }
  .step:not(:last-child)::after { transform-origin: left center; transform: scaleX(0); }
  .steps.is-in .step:not(:last-child)::after {
    animation: step-grow-x .7s ease-out calc(var(--d) + .5s) both;
  }
}

@keyframes step-draw { to { stroke-dashoffset: 0; } }
@keyframes step-badge {
  from { opacity: 0; transform: scale(.4); }
  to { opacity: 1; transform: scale(1); }
}
@keyframes step-fade {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: none; }
}
@keyframes step-grow-x { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes step-grow-y { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@keyframes step-ping {
  0% { transform: scale(1); opacity: .9; }
  70%, 100% { transform: scale(2.2); opacity: 0; }
}

/* ── Panel ───────────────────────────────────────────────────────────────── */

.panel { border-radius: 14px; padding: clamp(1.25rem, 1rem + 1vw, 2rem); }
.panel--outline { border: 1.5px solid var(--c-rule); background: var(--surface-page); }
.panel--tint { background: var(--surface-raised); border: 1px solid var(--c-rule); }
.panel--navy { background: var(--c-navy); color: #fff; }
.panel__title { font-size: 1.1875rem; margin-bottom: .75rem; }
.panel--navy .panel__title { color: #fff; }

/* ── City cards ──────────────────────────────────────────────────────────── */

.city-grid {
  display: grid; gap: clamp(.75rem, .5rem + 1vw, 1rem);
  grid-template-columns: repeat(auto-fill, minmax(12.5rem, 1fr));
  list-style: none;
}
.city-card {
  position: relative; isolation: isolate;
  display: flex; flex-direction: column; justify-content: flex-end; gap: .25rem;
  min-height: 10rem; padding: 1rem 1.125rem;
  border-radius: 14px; overflow: hidden;
  color: #fff; text-decoration: none;
  background: linear-gradient(135deg, var(--c-navy-light) 0%, var(--c-navy) 100%);
  box-shadow: 0 1px 2px rgb(11 37 69 / .12);
  transition: transform .2s ease, box-shadow .2s ease;
}
.city-card::before,
.city-card::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  transition: opacity .6s ease, filter .6s ease;
}
.city-card::before {
  background: var(--city-img) center / cover no-repeat;
  opacity: .28; filter: grayscale(.9) brightness(1.15);
}
.city-card::after {
  background: linear-gradient(180deg, rgb(11 37 69 / 0) 0%, rgb(11 37 69 / .35) 40%, rgb(11 37 69 / .9) 100%);
}
.city-card:hover::before,
.city-card:focus-visible::before { opacity: 1; filter: none; }
.city-card__tag {
  position: absolute; top: .75rem; left: .75rem;
  padding: .25rem .6rem;
  background: var(--c-amber); color: var(--c-navy);
  font-size: .75rem; font-weight: 700; line-height: 1.2;
  border-radius: var(--radius-pill);
}
.city-card__name {
  font-size: 1.25rem; font-weight: var(--fw-heading);
  letter-spacing: var(--ls-heading); line-height: 1.15;
  text-shadow: 0 1px 6px rgb(11 37 69 / .7);
}
.city-card__meta { font-size: .8125rem; font-weight: 600; color: var(--text-inverse-soft); text-shadow: 0 1px 4px rgb(11 37 69 / .7); }

.city-card, .city-card:hover, .city-card:focus-visible { color: #fff; }
.city-card:hover { transform: translateY(-3px); box-shadow: 0 14px 28px -14px rgb(11 37 69 / .6); }
.city-card:focus-visible { outline: 3px solid var(--c-amber); outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) {
  .city-card, .city-card::before, .city-card::after { transition: none; }
  .city-card:hover { transform: none; }
}

/* ── CTA band ────────────────────────────────────────────────────────────── */

.ctaband {
  background: var(--c-navy); color: #fff;
  padding-block: clamp(2.5rem, 1.8rem + 3vw, 4rem);
}
.ctaband__inner {
  display: flex; flex-wrap: wrap; gap: 1.75rem 2.5rem;
  align-items: center; justify-content: space-between;
}
.ctaband__title { color: #fff; max-width: 18ch; }
.ctaband__body { margin-top: .75rem; max-width: 50ch; color: var(--text-inverse-soft); }
.ctaband__actions { display: flex; flex-wrap: wrap; gap: .75rem; }

/* ── Service page ────────────────────────────────────────────────────────── */

.svc { padding-block: clamp(2rem, 1.4rem + 2.5vw, 3.5rem); }
.svc__grid { display: grid; gap: 2.5rem; }
.svc__title {
  font-size: var(--fs-h1); line-height: var(--lh-display);
  letter-spacing: var(--ls-display); margin-bottom: 1.25rem;
}
/* Aside first on a phone: symptom shortcuts are the fastest route into
   /request, and burying them under 1,200 words wastes the page. */
.svc__aside { order: -1; }
.svc__card {
  padding: 1.35rem;
  background: var(--surface-raised);
  border: 1.5px solid var(--c-rule);
  border-radius: 14px;
}
.svc__cardtitle { font-size: 1.1875rem; }
.svc__cardlead { margin: .4rem 0 1rem; font-size: 1rem; color: var(--text-muted); }
.svc__cardcta { display: flex; flex-direction: column; gap: .6rem; margin-top: 1.25rem; }
.svc__cardcta .cta, .svc__cardcta .cta-secondary { width: 100%; }
.svc__cardnote {
  margin-top: .35rem; font-size: var(--fs-micro);
  color: var(--text-muted); line-height: 1.5;
}
@media (min-width: 960px) {
  .svc__grid { grid-template-columns: minmax(0, 1fr) 330px; gap: 3.5rem; align-items: start; }
  .svc__aside { order: 0; position: sticky; top: 84px; }
}

/* ── Index, legal, default ───────────────────────────────────────────────── */

.idx, .page, .legal { padding-block: clamp(2rem, 1.4rem + 2.5vw, 3.5rem); }
/* The article body shares the page header's wide wrap, so copy starts on the same
   left edge as the H1 and runs the full column instead of floating in the middle. */
.page .prose, .legal .prose, .page .prose p, .page .prose li,
.legal .prose p, .legal .prose li { max-width: none; }
.phead__lead { max-width: none; }
/* `.idx__title`, `.page__title` and `.legal__title` were removed: no template
   emits any of them. Every page title now goes through `.phead__title`, which
   is the point — the legal pages were rendering an unstyled <h1> straight into
   .prose precisely because this rule looked like it was covering them. */

/* ── Prose ───────────────────────────────────────────────────────────────────
   Rendered markdown. Rhythm is set with margin-top on siblings so the first
   and last child never add stray space.                                     */

.prose { color: var(--text-body); }
.prose > * + * { margin-top: 1.15rem; }
.prose p, .prose li { max-width: 68ch; }
.prose p { line-height: var(--lh-body); }

.prose h2 {
  font-size: var(--fs-h2); margin-top: 2.5rem;
  padding-top: 1.75rem; border-top: 1px solid var(--c-rule);
}
.prose h3 { font-size: var(--fs-h3); margin-top: 2rem; }
.prose h4 { font-size: var(--fs-h4); margin-top: 1.5rem; }
.prose > h2:first-child, .prose > h3:first-child { margin-top: 0; padding-top: 0; border-top: 0; }

.prose ul, .prose ol { padding-left: 1.3rem; }
.prose li + li { margin-top: .5rem; }
.prose li::marker { color: var(--c-navy-light); }
.prose strong { color: var(--c-navy); font-weight: 700; }
.prose a { color: var(--text-link); font-weight: 500; }
/* In-page targets would otherwise land under the sticky header. */
.prose a[id] { scroll-margin-top: 5.5rem; }

.prose blockquote {
  margin-inline: 0; padding: 1rem 1.25rem;
  background: var(--surface-raised);
  border-left: 4px solid var(--c-navy);
  border-radius: 0 8px 8px 0;
}
.prose blockquote > * + * { margin-top: .75rem; }

.prose code {
  font-family: var(--font-mono); font-size: .88em;
  background: var(--surface-raised); padding: .12em .4em;
  border: 1px solid var(--c-rule); border-radius: 4px;
}
.prose pre {
  overflow-x: auto; padding: 1rem;
  background: var(--c-navy); color: #fff; border-radius: 10px;
}
.prose pre code { background: none; border: 0; color: inherit; }

.prose hr { border: 0; border-top: 1px solid var(--c-rule); margin-block: 2.5rem; }

/* Tables scroll inside their own container — the page body never scrolls
   sideways (§9.3: no horizontal scroll). */
.prose table {
  display: block; width: 100%; overflow-x: auto;
  border-collapse: collapse; font-size: 1rem;
}
.prose thead th {
  text-align: left; background: var(--surface-raised); color: var(--c-navy);
  font-weight: 700; white-space: nowrap;
}
.prose th, .prose td {
  padding: .7rem .85rem; border-bottom: 1px solid var(--c-rule);
  vertical-align: top;
}

.prose--invert, .prose--invert p, .prose--invert li { color: var(--text-inverse-soft); }
.prose--invert h2, .prose--invert h3, .prose--invert h4, .prose--invert strong { color: #fff; }
.prose--invert h2 { border-top-color: var(--c-rule-on-dark); }
.prose--invert h3 { font-weight: var(--fw-heading); letter-spacing: var(--ls-heading); }
.prose--invert a { color: #fff; }
.prose--invert li::marker { color: var(--c-amber); }
.prose--invert blockquote { background: var(--c-navy-mid); border-left-color: var(--c-amber); }
.prose--invert code { background: var(--c-navy-mid); border-color: var(--c-rule-on-dark); color: #fff; }

.prose--lead > p:first-of-type { font-size: var(--fs-lead); }
.prose--article { font-size: var(--fs-body); }
.prose--legal { font-size: var(--fs-body); line-height: 1.72; }
.prose--legal h2 { font-size: var(--fs-h3); }
.prose--legal p, .prose--legal li { max-width: 72ch; }

/* ── Legal: contents and numbered sections ───────────────────────────────────
   A warranty is cited, not just read: "section 4 says". The numbers make that
   possible out loud and the ids make it possible in a link. */

.legal__toc {
  margin-bottom: 2.75rem; padding: 1.15rem 1.35rem 1.3rem;
  background: var(--surface-raised);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-lg);
}
.legal__toctitle {
  font-size: var(--fs-micro); font-weight: 700;
  text-transform: uppercase; letter-spacing: var(--ls-caps);
  color: var(--text-muted);
}
.legal__toclist {
  list-style: none; margin: .85rem 0 0; padding: 0;
  display: grid; gap: .1rem;
}
@media (min-width: 720px) { .legal__toclist { grid-template-columns: 1fr 1fr; column-gap: 2rem; } }
.legal__toclist a {
  display: flex; gap: .7rem; align-items: baseline;
  padding: .3rem .4rem .3rem 0; border-radius: 6px;
  color: var(--text-body); text-decoration: none;
}
.legal__toclist a:hover { color: var(--text-link-hover); text-decoration: underline; }
.legal__tocnum {
  flex: 0 0 1.4rem; text-align: right;
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-micro); font-weight: 700; color: var(--c-lg-crimson);
}

/* The number sits in the margin rather than in the sentence, so a heading read
   aloud is still just the heading — hence `aria-hidden` on the numeral in the
   template and the text in its own span here. */
.prose--legal .legal__h2 {
  display: flex; gap: .85rem; align-items: baseline;
  scroll-margin-top: 1.5rem;
}
.legal__num {
  flex: 0 0 auto; min-width: 1.4rem;
  font-size: var(--fs-micro); font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--c-lg-crimson);
  /* Pulls the numeral up onto the heading's cap line: baseline alignment on a
     display size drops a micro numeral to the bottom of the glyph box. */
  position: relative; top: -0.15em;
}
.legal__htext { flex: 1 1 auto; }

@media (min-width: 1040px) {
  /* Out into the margin, where it reads as a reference mark rather than as part
     of the title. Only once there is room for it — below this it would collide
     with the text column. */
  .prose--legal .legal__h2 { margin-left: -2.25rem; }
  .legal__num { min-width: 1.4rem; }
}

/* An index body is a list of destinations, not reading matter: top-level
   lists become a card grid with real tap targets. */
.prose--index ul.idx__list {
  display: grid; gap: .75rem; padding-left: 0; list-style: none;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
}
/* Padding belongs to the CARD, not to the link inside it: the markdown is
   `<li><a>Title</a> — description</li>`, so padding on the anchor left the
   description hard against the card's bottom and right edges. */
.prose--index ul.idx__list > li {
  max-width: none; margin-top: 0;
  padding: 1rem 1.1rem;
  background: var(--surface-page);
  color: var(--text-muted); font-size: 1rem; line-height: 1.5;
  border: 1.5px solid var(--c-rule); border-radius: 12px;
  transition: border-color .15s ease, transform .15s ease, box-shadow .15s ease;
}
.prose--index ul.idx__list > li:hover {
  border-color: var(--c-navy); transform: translateY(-2px); box-shadow: var(--shadow-md);
}
/* Each card is a column: icon, title, then the description on its own line. The
   description is a separate block because the markdown breaks it onto a new line. */
.prose--index ul.idx__list > li { display: flex; flex-direction: column; }
.prose--index ul.idx__list > li > a:first-child {
  display: block; margin-bottom: .4rem;
  font-size: 1.0625rem; font-weight: 700; line-height: 1.3; color: var(--c-navy); text-decoration: none;
}
.idx__icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.75rem; height: 2.75rem; margin-bottom: .85rem;
  border-radius: 10px;
  background: var(--c-paper-tint); color: var(--c-navy);
  transition: background-color .2s ease, color .2s ease, transform .2s ease;
}
.idx__icon svg { width: 26px; height: 26px; }
.prose--index ul.idx__list > li:hover .idx__icon { background: var(--c-navy); color: #fff; transform: scale(1.06); }
@media (prefers-reduced-motion: reduce) {
  .idx__icon { transition: none; }
  .prose--index ul.idx__list > li:hover .idx__icon { transform: none; }
}
.prose--index ul.idx__list > li:hover > a:first-child { text-decoration: underline; }
.prose--index ul.idx__list > li::marker { content: none; }

/* ── Footer ──────────────────────────────────────────────────────────────── */

.ftr {
  background: var(--c-paper-tint); color: var(--c-slate);
  padding-top: clamp(2.5rem, 1.8rem + 3vw, 4rem);
  padding-bottom: 2rem;
  border-top: 1px solid var(--c-rule);
}
.ftr__top { display: grid; gap: 2.5rem; }
@media (min-width: 860px) { .ftr__top { grid-template-columns: 360px 1fr; gap: 3.5rem; } }

.ftr__tag { margin-top: 1.1rem; color: var(--c-ink); font-size: 1.0625rem; line-height: 1.5; }
.ftr__contact { display: flex; flex-direction: column; gap: .35rem; margin: 1.1rem 0 1.35rem; }
.ftr__tel {
  display: inline-flex; align-items: center; min-height: 44px;
  color: var(--c-navy); font-size: 1.25rem; font-weight: 800; text-decoration: none;
}
.ftr__tel:hover { color: var(--c-orange); }
.ftr__hours { font-size: 1rem; color: var(--c-slate); white-space: nowrap; }
.ftr__address { font-size: 1rem; color: var(--c-slate); }
.ftr__since { font-size: 1rem; font-style: italic; font-weight: 400; font-family: Georgia, 'Times New Roman', serif; color: var(--c-orange); letter-spacing: 0.02em; }

.ftr__cols { display: grid; gap: 1.75rem; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.ftr__coltitle {
  font-size: var(--fs-micro); color: var(--c-navy);
  text-transform: uppercase; letter-spacing: var(--ls-caps);
  font-weight: 600;
  margin-bottom: .75rem;
}
.ftr__col ul { list-style: none; display: flex; flex-direction: column; gap: .1rem; }
.ftr__col a {
  /* 44px minimum tap target (Apple HIG / Material). These were 39px, which on a
     phone is the difference between hitting Privacy and hitting Terms. */
  display: flex; align-items: center; min-height: 44px; padding: .4rem 0;
  color: var(--c-slate); font-size: 1rem; text-decoration: none;
}
.ftr__col a:hover { color: var(--c-navy); text-decoration: underline; }

.ftr__legal {
  display: flex; flex-wrap: wrap; gap: .5rem 1.5rem;
  margin-top: 2.5rem; padding-top: 1.5rem;
  border-top: 1px solid var(--c-rule);
  font-size: var(--fs-micro);
}
.ftr__entity { color: var(--c-slate); }

/* THE STATUTORY DISCLOSURE.
   Real, visible, selectable text at normal footer size, AA contrast
   (#AFC0D2 on #0B2545 = 8.27:1). Never hidden, shrunk, clipped or dimmed —
   ARS §32-1121(A)(14)(c) removes the exemption from anyone who advertises
   without these words. The ONLY permitted hiding mechanism here is the
   `hidden` attribute, which the edge rewriter owns. */
.compliance-disclosure { color: var(--c-slate); }

/* ── Sticky action bar ───────────────────────────────────────────────────── */

.stickybar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 70;
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: .5rem;
  padding: .55rem var(--gutter, 20px);
  padding-bottom: calc(.55rem + env(safe-area-inset-bottom, 0px));
  background: var(--c-navy);
  border-top: 1px solid var(--c-rule-on-dark);
}
/* Both labels are one line, always. "Request Service" broke onto two lines
   beside the Call button on a 390px phone — the one place a wrapped label is
   a wrapped BUTTON, twice the height of its neighbour. The size tracks the
   viewport from 16px at 320 up to the CTA token from ~480, and the CTA column
   is `minmax(0, 1fr)` so the grid can actually hand it the room. */
.stickybar__call, .stickybar__cta {
  display: inline-flex; align-items: center; justify-content: center; gap: .45rem;
  min-height: 48px; border-radius: var(--cta-radius);
  font-weight: 700; text-decoration: none; white-space: nowrap;
}
.stickybar__call {
  padding: 0 1rem; color: #fff;
  border: 2px solid rgb(255 255 255 / .55); font-size: 1.0625rem;
}
.stickybar__call:hover { background: #fff; color: var(--c-navy); border-color: #fff; }
.stickybar__cta {
  padding: 0 1rem;
  background: var(--cta-bg); color: var(--cta-fg);
  font-size: clamp(1rem, .55rem + 2.2vw, var(--cta-font-size)); font-weight: var(--cta-font-weight);
}
.stickybar__cta:hover { background: var(--cta-bg-hover); color: #fff; }

/* Conversion page: Call alone, full width. See components/sticky-bar.ts for
   why the Request Service action is dropped there rather than repeated. */
.stickybar--call { grid-template-columns: 1fr; }
.stickybar--call .stickybar__call {
  width: 100%; font-size: var(--cta-font-size); font-weight: var(--cta-font-weight);
}

/* The bar is a mobile pattern; above the header's own CTA breakpoint it goes
   away and the body padding with it. */
:root { --stickybar-h: 68px; }
/* No bar means no reserved strip. Set on <body> by Page.astro. */
body.nobar { --stickybar-h: 0px; }
@media (min-width: 700px) {
  .stickybar { display: none; }
  :root { --stickybar-h: 0px; }
}

/* ── Load-bearing columns ───────────────────────────────────────────────────
   A flex or grid item defaults to `min-width: auto`, which means it refuses to
   shrink below its widest unbreakable child. One long word, one wide table in
   the prose, one Request ID in tabular figures, and the whole two-column
   layout overflows sideways instead of wrapping — on a phone, where there is
   no room to absorb it. These four are the columns that carry content of
   unknown width, so each is told it may shrink. */
.svc__main,
.reqform__main,
.ftr__brand,
.step__body { min-width: 0; }

/* Footer lockup: artwork beside type, rather than type baked into artwork.
   See the note in SiteFooter.astro for why the raster wordmark was dropped. */
.ftr__lockup {
  display: flex; align-items: center; gap: .7rem;
  margin-bottom: .9rem;
}
.ftr__lgcert { height: 112.5px; width: auto; margin-left: .7rem; }
/* And the step body takes the space the numeral does not. */
.step__body { flex: 1 1 auto; }

.reqform__main > form { width: 100%; min-width: 0; }

/* ==========================================================================
   THE REQUEST FORM
   The single conversion point for the entire business (§9.1). Designed to be
   completed one-handed, on a phone, by someone whose fridge is defrosting onto
   the kitchen floor.

   Rules that drive everything below:
     · Inputs are >=16px or iOS zooms the page on focus, which loses the
       user's place mid-form. 17px here.
     · Every control is >=48px tall. A 16px native radio dot is not a tap
       target on a wet hand.
     · Single column, always. Side-by-side fields on a phone are a thumb
       reaching across the screen.
     · Errors are never colour alone — icon, border weight and text as well.
   ========================================================================== */

.reqform { padding-block: clamp(1.25rem, .9rem + 1.6vw, 2.25rem) clamp(2rem, 1.4rem + 2.6vw, 3.5rem); }

/* One column, a little narrower than the page. Four picture tiles across at a
   size the eye can take in at once, and no line of copy longer than a reader
   wants. The 320px aside this replaces is gone: its four steps are the strip
   below the heading, its three commitments sit beside Continue (StepFoot), and
   its phone number is the card under the form. */
.reqform__col { margin-inline: auto; }
.reqform form { display: flex; flex-direction: column; gap: 1.25rem; }

/* ── Head: title, one-line promise, two chips ─────────────────────────────
   Desktop: the two questions a form has to answer before anyone starts it —
   how long, and what it costs — as quiet chips opposite the title. Phone: a
   compact bar, back · title · "1 of 5". A 40px heading and a paragraph are the
   first screenful of a phone, and the customer read them on the page they
   came from. */
.reqhead {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 1.5rem;
  margin-bottom: 1.1rem;
}
.reqhead__text { min-width: 0; }
.reqhead__title {
  font-size: clamp(1.75rem, 1.3rem + 1.9vw, 2.5rem);
  font-weight: var(--fw-heading); letter-spacing: var(--ls-display); line-height: var(--lh-display);
  color: var(--c-navy);
}
.reqhead__lead { margin-top: .5rem; font-size: 1.0625rem; line-height: 1.5; color: var(--text-muted); max-width: 52ch; }
.reqhead__back, .reqhead__count { display: none; }
.reqhead__chips {
  list-style: none; margin: 0; padding: .4rem 0 0;
  display: flex; flex-wrap: wrap; justify-content: flex-end; gap: .45rem; flex: 0 0 auto;
}
.chip {
  display: inline-flex; align-items: center; gap: .35rem;
  min-height: 30px; padding: 0 .75rem 0 .6rem;
  border-radius: 999px; background: var(--surface-raised);
  color: var(--c-navy); font-size: .8125rem; font-weight: 700; white-space: nowrap;
}
.chip svg { color: var(--c-navy-light); }

/* ── What happens after: one line ─────────────────────────────────────────
   Four facts, six words each, in a tinted strip. The first is marked as the
   one happening now. */
.howit {
  list-style: none; margin: 0 0 1.35rem; padding: .7rem 1rem;
  /* Content-sized columns spread across the row, so the four facts share one
     line at the column's full width; narrower, a track shrinks to its words
     and wraps rather than the strip overflowing. */
  display: grid; grid-template-columns: auto auto auto auto; justify-content: space-between;
  gap: .5rem 1.25rem; padding: .8rem 1.15rem;
  background: var(--surface-raised); border-radius: 12px;
}
.howit__item {
  display: flex; align-items: center; gap: .6rem; min-width: 0;
  font-size: .9375rem; line-height: 1.3; color: var(--text-muted);
}
.howit__num {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--c-navy); color: #fff;
  font-size: .8125rem; font-weight: 800; font-variant-numeric: tabular-nums;
}
.howit__item.is-now .howit__num { background: var(--cta-bg); }
.howit__text { min-width: 0; }
.howit__text strong { color: var(--c-navy); font-weight: 700; }
.howit__sep { margin: 0 .35rem; color: var(--c-rule-strong); }
/* The phone's closed disclosure of the same four facts. Desktop never shows it. */
.howit-m { display: none; }

@media (max-width: 699px) {
  .reqhead {
    display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: .35rem;
    margin: -.25rem 0 .85rem;
  }
  .reqhead__back {
    display: inline-flex; align-items: center; justify-content: center;
    width: 40px; height: 40px; margin-left: -.6rem; border-radius: 10px;
    color: var(--c-navy);
  }
  .reqhead__back:hover { background: var(--surface-raised); }
  .reqhead__title { font-size: 1.0625rem; font-weight: 700; letter-spacing: -.01em; line-height: 1.2; }
  .reqhead__lead, .reqhead__chips, .howit { display: none; }
  .reqhead__count {
    display: block; font-size: .875rem; font-weight: 600; color: var(--text-muted);
    font-variant-numeric: tabular-nums;
  }

  .howit-m { display: block; margin-top: 1.25rem; background: var(--surface-raised); border-radius: 12px; }
  .howit-m__summary {
    display: flex; align-items: center; gap: .5rem; list-style: none; cursor: pointer;
    min-height: 48px; padding: .6rem .95rem;
    font-size: .9375rem; font-weight: 700; color: var(--c-navy);
  }
  .howit-m__summary::-webkit-details-marker { display: none; }
  .howit-m__chev { flex: 0 0 auto; transition: transform .15s ease; }
  .howit-m[open] .howit-m__chev { transform: rotate(90deg); }
  .howit-m__list { list-style: none; margin: 0; padding: 0 .95rem .95rem; display: grid; gap: .7rem; }
  .howit-m__item { display: flex; gap: .65rem; align-items: flex-start; }
  .howit-m__num {
    flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
    width: 22px; height: 22px; margin-top: .1rem; border-radius: 50%;
    background: var(--c-navy); color: #fff; font-size: .75rem; font-weight: 800;
  }
  .howit-m__item.is-now .howit-m__num { background: var(--cta-bg); }
  .howit-m__body { min-width: 0; }
  .howit-m__body strong { display: block; font-size: .9375rem; color: var(--c-navy); line-height: 1.3; }
  .howit-m__body p { margin: .1rem 0 0; font-size: .875rem; line-height: 1.4; color: var(--text-body); }
}

/* ── Sections ────────────────────────────────────────────────────────────── */

.fset {
  border: 0; margin: 0; padding: 0;
  /* min-width:0 stops a long <legend> forcing the fieldset wider than the
     viewport — a long-standing fieldset quirk, and the cause of most
     "why does this page scroll sideways on a phone" bugs. */
  min-width: 0;
}
.fset + .fset { margin-top: .5rem; padding-top: 2rem; border-top: 1px solid var(--c-rule); }
.fset__legend {
  display: flex; align-items: center; gap: .7rem;
  padding: 0; margin-bottom: .35rem; float: left; max-width: 100%;
}
/* Anything that shares the heading's line — the appliance search — floats
   opposite it; the hint and the body clear both. */
.fset__tools { float: right; margin: -.15rem 0 .35rem .75rem; }
.fset__step {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.85rem; height: 1.85rem; flex: 0 0 auto;
  background: var(--c-navy); color: #fff;
  font-size: .9375rem; font-weight: 800; font-variant-numeric: tabular-nums;
  border-radius: 50%;
}
.fset__title {
  font-size: clamp(1.25rem, 1.1rem + .8vw, 1.625rem); font-weight: var(--fw-heading);
  letter-spacing: var(--ls-heading); color: var(--c-navy);
}
.fset__hint {
  clear: both; margin-bottom: 1.1rem; padding-top: .5rem;
  color: var(--text-muted); font-size: 1rem; max-width: 60ch;
}
.fset__body { clear: both; display: flex; flex-direction: column; gap: 1rem; padding-top: .65rem; }

/* ── Fields ──────────────────────────────────────────────────────────────── */

.field { display: flex; flex-direction: column; }
.field__label {
  display: flex; align-items: baseline; gap: .45rem; flex-wrap: wrap;
  font-size: 1.0625rem; font-weight: 700; color: var(--c-navy);
  margin-bottom: .4rem;
}
.field__req { color: var(--c-warning); font-weight: 800; }
.field__opt {
  font-size: var(--fs-micro); font-weight: 500; color: var(--text-muted);
  letter-spacing: .01em;
}
.field__hint {
  margin-bottom: .55rem; font-size: 1rem; line-height: 1.5;
  color: var(--text-muted); max-width: 60ch;
}

.field__input {
  width: 100%; min-height: 52px;
  padding: .75rem .9rem;
  font-family: inherit;
  font-size: 16px;               /* px, not rem: iOS zooms the page on focus
                                    below 16px, and the root is scaled to 90% */
  line-height: 1.4;
  color: var(--text-body);
  background: var(--surface-page);
  border: 1.5px solid var(--border-control);
  border-radius: 10px;
  transition: border-color .12s ease, box-shadow .12s ease;
  -webkit-appearance: none; appearance: none;
}
.field__input::placeholder { color: var(--text-placeholder); }
.field__input:hover { border-color: var(--c-navy-light); }
.field__input:focus {
  border-color: var(--c-navy);
  box-shadow: 0 0 0 3px rgb(27 64 121 / .18);
  outline: none;
}
.field__input:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring);
  outline-offset: 1px;
}
.field__input--area { min-height: 118px; resize: vertical; line-height: 1.55; }

.field__selectwrap { position: relative; display: block; }
.field__select { padding-right: 2.75rem; cursor: pointer; background: var(--surface-page); }
.field__chev {
  position: absolute; right: .95rem; top: 50%; transform: translateY(-50%);
  color: var(--c-navy); pointer-events: none;
}

/* Error state: border weight + colour + HALO + icon + text. Never colour alone.

   THE HALO IS THE POINT. A 2px red border on a 1.5px grey one is a half-pixel
   of difference at arm's length, and on a form this long the customer is
   looking for the bad field from several inches away, not inspecting it. The
   soft red ring reads as "this one" from across the page the way a border
   weight never does — and it is a `box-shadow`, so it costs no layout and
   cannot push the control's neighbours around.

   The control's own text goes red with it, because a field whose VALUE is the
   problem (a ZIP outside the area, a number that is not a phone number) should
   look wrong where the wrong thing actually is. */
.field--error .field__input {
  border-color: var(--c-warning);
  border-width: 2px;
  border-radius: 12px;
  color: var(--c-warning);
  box-shadow: 0 0 0 4px rgb(166 27 27 / .13);
}
.field--error .field__input::placeholder { color: rgb(166 27 27 / .55); }
/* Focus deepens the same ring rather than swapping in the navy one: the field
   is still in error while you are typing in it, and losing the halo on focus
   would read as the problem having been fixed by the click. */
.field--error .field__input:focus {
  border-color: var(--c-warning);
  box-shadow: 0 0 0 4px rgb(166 27 27 / .22);
}
.field--error .field__chev { color: var(--c-warning); }
/* The halo needs somewhere to go. Without this the 4px ring of the field above
   runs into the label of the field below, which is the one thing a glow must
   never do on a dense form. */
.field--error { padding-bottom: .25rem; }
.field__error {
  display: flex; align-items: flex-start; gap: .4rem;
  margin-top: .55rem;
  color: var(--c-warning); font-size: 1rem; font-weight: 500; line-height: 1.45;
}
.field__error svg { flex: 0 0 auto; margin-top: .14rem; }

/* The way back out of a step.
   It was a 38x20 inline text link under a full-width Continue, which is below the
   44px minimum and easy to miss. A multi-step flow needs a visible escape route
   (Apple HIG), so it is a real secondary control: quiet next to the primary CTA,
   but the same size to the thumb. */
.stepback {
  /* `fit-content` + `align-self`, because the step body is a flex column and an
     inline-flex child still stretches to the cross axis there. A full-width Back
     reads as a second primary button next to Continue, which is the opposite of
     what a secondary action should look like. */
  display: inline-flex; align-self: flex-start; width: fit-content;
  align-items: center; gap: .3rem;
  min-height: 44px; margin-top: .55rem; padding: 0 .85rem 0 .55rem;
  border-radius: 9px;
  color: var(--c-navy); font-size: 1rem; font-weight: 600;
  text-decoration: none;
}
.stepback:hover { background: var(--c-paper-tint); text-decoration: underline; }
.stepback svg { flex: 0 0 auto; }

/* ── Step progress ───────────────────────────────────────────────────────── */

.prog { margin: 0; }
.prog__track {
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  gap: .3rem; list-style: none; margin: 0; padding: 0;
}
.prog__seg {
  height: 5px; border-radius: 999px;
  background: var(--c-rule, #dde3ea);
  /* Colour alone must not carry the state, so the current step is also the only
     segment that is taller. */
  transition: background-color 180ms ease;
}
.prog__seg.is-done { background: var(--c-navy); }
.prog__seg.is-now  { background: var(--cta-bg); height: 7px; margin-top: -1px; }
.prog__label {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .25rem .6rem;
  margin: .5rem 0 0;
  font-size: .8125rem; font-weight: 600; color: var(--text-muted);
}
/* A phone says "1 of 5" in its header bar instead. The sentence stays in the
   accessibility tree — it is the only place the step is NAMED. */
@media (max-width: 699px) {
  .prog__label {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  }
}
.prog__count { color: var(--text-muted); }
.prog__name { color: var(--c-navy); }
.prog__name::before { content: "·"; margin-right: .6rem; color: var(--c-rule-strong); }
.prog__next { margin-left: auto; font-weight: 500; }
@media (prefers-reduced-motion: reduce) { .prog__seg { transition: none; } }

/* ── Choice cards ────────────────────────────────────────────────────────── */

.choices { border: 0; margin: 0; padding: 0; min-width: 0; }
.choices__grid {
  display: grid; gap: .5rem; margin-top: .25rem;
  /* Two across on a phone, three from tablet, four for picture tiles on a
     desktop. Fixed counts rather than auto-fit so a row never ends in one
     stretched tile. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (min-width: 640px) { .choices__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 900px) { .choices__grid--pictures { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.choices__grid--one { grid-template-columns: 1fr; }

/* Heading above each run of options, when the options carry a group.
   Twenty-three appliances in one column is a wall of text on a phone; four short
   runs under headings is scannable. Styled as a quiet label rather than a section
   title, because the question in the section heading is still the thing being
   asked. The optional glyph is the group's picture, not the tile's. */
.choices__grouphead {
  display: flex; align-items: center; gap: .5rem;
  margin: 1.4rem 0 .35rem;
  font-size: .875rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--c-navy-mid);
}
.choices__grouphead svg { flex: 0 0 auto; color: var(--c-navy-light); }
.choices__grouphead:first-of-type { margin-top: .5rem; }

/* One row: the words, then the radio at the far end. Under 60px tall so a list
   of fifteen is read, not scrolled. The 1px control border is --border-control
   (3.6:1) — WCAG 1.4.11 treats a card's edge as the control's boundary. */
.choice {
  position: relative;
  display: flex; align-items: center; gap: .65rem;
  min-height: 52px; padding: .65rem .85rem;
  background: var(--surface-page);
  border: 1px solid var(--border-control);
  border-radius: 12px;
  cursor: pointer;
  transition: border-color .12s ease, background-color .12s ease, box-shadow .15s ease;
}
.choice:hover {
  border-color: var(--c-navy-light); background: var(--surface-raised);
  box-shadow: 0 6px 16px -14px rgb(11 37 69 / .6);
}
/* The real radio stays in the DOM and in the a11y tree; it is only moved out
   of sight, never `display:none` — a hidden input is not focusable. */
.choice input {
  position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0;
}
.choice__box {
  flex: 0 0 auto; margin-left: auto;
  width: 20px; height: 20px; border-radius: 50%;
  border: 1.5px solid var(--c-rule-strong); background: var(--surface-page);
  transition: border-color .12s ease, box-shadow .12s ease;
}
.choice input:checked ~ .choice__box {
  border-color: var(--c-navy);
  box-shadow: inset 0 0 0 4px var(--surface-page), inset 0 0 0 20px var(--c-navy);
}
/* Chosen: a navy edge drawn twice (border + inset ring) so it reads as 2px
   without the tile growing, plus the raised ground. */
.choice:has(input:checked) {
  border-color: var(--c-navy); background: var(--surface-raised);
  box-shadow: inset 0 0 0 1px var(--c-navy);
}
.choice:has(input:focus-visible) {
  outline: var(--focus-ring-width) solid var(--focus-ring);
  outline-offset: 2px;
}
.choice__text { display: flex; flex-direction: column; gap: .1rem; min-width: 0; }
.choice__label { font-size: .9375rem; font-weight: 600; color: var(--c-navy); line-height: 1.25; }
.choice__note { font-size: .8125rem; color: var(--text-muted); line-height: 1.3; }
.choice--disabled { opacity: .5; cursor: not-allowed; }
.choice--disabled:hover { border-color: var(--border-control); background: var(--surface-page); box-shadow: none; }
.choices--error .choice { border-color: var(--c-warning); }
@media (prefers-reduced-motion: reduce) { .choice, .choice__box { transition: none; } }

/* ── Checkboxes ──────────────────────────────────────────────────────────── */

.checkrow { display: flex; flex-direction: column; }
.check {
  position: relative;
  display: flex; align-items: flex-start; gap: .75rem;
  min-height: 48px; padding: .7rem .85rem;
  background: var(--surface-raised);
  border: 1.5px solid transparent;
  border-radius: 11px;
  cursor: pointer;
}
.check:hover { background: #E9F0F6; }
.check input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; }
.check__box {
  flex: 0 0 auto; margin-top: .08rem;
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px;
  background: var(--surface-page);
  border: 2px solid var(--c-navy-light);
  border-radius: 6px;
  color: transparent;
  transition: background-color .12s ease, border-color .12s ease, color .12s ease;
}
.check__box svg { width: 15px; height: 15px; }
.check input:checked ~ .check__box {
  background: var(--c-navy); border-color: var(--c-navy); color: #fff;
}
.check:has(input:focus-visible) {
  outline: var(--focus-ring-width) solid var(--focus-ring);
  outline-offset: 2px;
}
.check__text { display: flex; flex-direction: column; gap: .2rem; }
.check__label { font-size: 1.0625rem; font-weight: 600; color: var(--c-navy); line-height: 1.35; }
.check__note { font-size: var(--fs-micro); color: var(--text-muted); line-height: 1.45; }
.check__note a { color: var(--text-link); }
.checkrow--emph .check { background: #FFF4EC; border-color: #F3C9AE; }
.checkrow--emph .check:hover { background: #FFEADC; }
.checkrow--error .check { border-color: var(--c-warning); }

/* ── File upload ─────────────────────────────────────────────────────────── */

.filefield__input {
  position: absolute; opacity: 0; width: 1px; height: 1px;
}
.filefield__drop {
  display: flex; align-items: center; gap: .9rem;
  min-height: 72px; padding: 1rem;
  background: var(--surface-raised);
  color: var(--c-navy);
  border: 2px dashed var(--border-control);
  border-radius: 12px;
  cursor: pointer;
  transition: border-color .12s ease, background-color .12s ease;
}
.filefield__drop:hover { border-color: var(--c-navy-light); background: #E9F0F6; }
.filefield__input:focus-visible + .filefield__drop,
.filefield:has(.filefield__input:focus-visible) .filefield__drop {
  outline: var(--focus-ring-width) solid var(--focus-ring); outline-offset: 2px;
}
.filefield__text { display: flex; flex-direction: column; gap: .2rem; }
.filefield__text strong { font-size: 1.0625rem; }
.filefield__text span { font-size: var(--fs-micro); color: var(--text-muted); line-height: 1.45; }

/* The prompt stops inviting taps once three photos are on — the panel stays
   focusable so the count can still be read out, it just stops looking like a
   button for something that cannot happen. */
.filefield--full .filefield__drop {
  border-style: solid; cursor: default;
}
.filefield--full .filefield__drop:hover {
  border-color: var(--border-control); background: var(--surface-raised);
}

/* ── Attached photos ──────────────────────────────────────────────────────
   Drawn by file-preview.client.ts. Hidden and empty without JavaScript, where
   the field falls back to a plain file input that still posts.             */
.filefield__list {
  list-style: none; margin: .75rem 0 0; padding: 0;
  display: flex; flex-direction: column; gap: .5rem;
}
.filefield__item {
  display: flex; align-items: center; gap: .75rem;
  padding: .5rem .625rem;
  background: var(--surface-raised);
  border: 1px solid var(--c-rule);
  border-radius: 10px;
}
.filefield__thumb {
  width: 52px; height: 52px; flex: 0 0 52px;
  object-fit: cover; border-radius: 7px;
  background: var(--c-rule);
}
.filefield__meta {
  display: flex; flex-direction: column; gap: .1rem;
  min-width: 0; flex: 1 1 auto;
}
/* The filename is the one piece of text here that can be arbitrarily long and
   is not worth wrapping — a two-line name pushes Remove out of reach on a
   phone, and the first half of the name is the half that identifies it. */
.filefield__name {
  font-size: .9375rem; font-weight: 600; color: var(--c-navy);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.filefield__size { font-size: var(--fs-micro); color: var(--text-muted); }
.filefield__remove {
  flex: 0 0 auto;
  min-height: 44px; padding: .5rem .75rem;
  font: inherit; font-size: var(--fs-micro); font-weight: 600;
  color: var(--c-warning);
  background: none; border: 1px solid transparent; border-radius: 8px;
  cursor: pointer;
}
.filefield__remove:hover { background: #FBECEC; border-color: #EBC9C9; }
.filefield__remove:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring); outline-offset: 2px;
}
.filefield__status {
  margin: .5rem 0 0;
  font-size: var(--fs-micro); line-height: 1.45; color: var(--text-muted);
}
.filefield__status--error { color: var(--c-warning); font-weight: 600; }

/* ── Notices ─────────────────────────────────────────────────────────────────
   `help` is the C7 phone-consult outcome. It is NAVY, not red: the customer
   has done nothing wrong, the work is simply gas or hardwired and the law
   wants a conversation first. Red there reads as rejection and loses a job we
   could have done.                                                          */

/* A notice that follows anything gets air above it. Without this it butts
   straight up against the paragraph or the card before it — on the booking
   confirmation the "Something changed?" banner touched the slip above it. */
* + .notice { margin-top: 1.5rem; }
.notice {
  display: flex; gap: .85rem;
  padding: 1.1rem 1.15rem;
  border-radius: 12px;
  border: 1.5px solid;
  font-size: 1.0625rem; line-height: 1.55;
}
.notice__icon { flex: 0 0 auto; margin-top: .15rem; }
.notice__body { display: flex; flex-direction: column; gap: .4rem; min-width: 0; }
.notice__title { font-weight: 800; font-size: 1.125rem; line-height: 1.3; }
.notice__text > * + * { margin-top: .6rem; }
.notice__text a { color: inherit; font-weight: 600; }

.notice--info { background: var(--surface-raised); border-color: var(--c-rule); color: var(--text-body); }
.notice--info .notice__icon, .notice--info .notice__title { color: var(--c-navy); }

.notice--help { background: var(--c-navy); border-color: var(--c-navy); color: var(--text-inverse-soft); }
.notice--help .notice__icon, .notice--help .notice__title { color: #fff; }
.notice--help .notice__text a { color: #fff; }

.notice--ok { background: #F1F8F4; border-color: #BFE0CC; color: var(--text-body); }
.notice--ok .notice__icon, .notice--ok .notice__title { color: var(--c-success); }

.notice--warn { background: #FFF4EC; border-color: #F3C9AE; color: var(--text-body); }
.notice--warn .notice__icon, .notice--warn .notice__title { color: #9A3412; }

.notice--stop { background: #FDF3F3; border-color: #E8BDBD; color: var(--text-body); }
.notice--stop .notice__icon, .notice--stop .notice__title { color: var(--c-warning); }

/* The phone action inside a notice. On `help` this is the whole point of the
   panel, so it is a full button; orange stays reserved for the form's own
   primary submit, and this one is white-on-navy. */
.notice__cta {
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  align-self: flex-start;
  min-height: 48px; margin-top: .45rem; padding: 0 1.2rem;
  font-size: 1.0625rem; font-weight: 700; text-decoration: none;
  border-radius: var(--cta-radius);
}
.notice--help .notice__cta { background: #fff; color: var(--c-navy); }
.notice--help .notice__cta:hover { background: var(--c-amber); color: var(--c-navy); }
.notice--warn .notice__cta, .notice--stop .notice__cta, .notice--info .notice__cta {
  background: var(--c-navy); color: #fff;
}
.notice--warn .notice__cta:hover, .notice--stop .notice__cta:hover,
.notice--info .notice__cta:hover { background: var(--c-navy-mid); color: #fff; }

/* ── Error summary ───────────────────────────────────────────────────────── */

/* ── Error summary ───────────────────────────────────────────────────────────
   A PLAIN WHITE CARD. Two versions ago this was a pink rectangle with a 2px
   crimson border — the browser's own error styling with the palette swapped
   in. One version ago it was a card with a crimson rail and a tinted header
   band. Both spent colour on the CONTAINER, which is the one part of an alert
   that carries no information.

   The weight now goes where the meaning is: a red warning triangle and a red
   title say "this failed", and everything underneath is ordinary body copy on
   an ordinary white card, because the messages are things to read rather than
   things to be alarmed by. The card itself is the site's standard surface —
   white, hairline, soft shadow — so an error looks like part of the page
   rather than an interruption pasted onto it.

   ONE RED, AND IT IS THE PALETTE'S. `--c-warning` (#A61B1B) rather than the
   brighter red of a stock alert component: red text on white has to clear 4.5:1
   to be readable by the people most likely to be reading it, and the bright
   reds sit around 3.3:1. Same shape, same weight, legible. */
.errsum {
  position: relative;
  /* Room for the dismiss button, which is pulled into the top-right corner. */
  padding: 1.4rem 3.1rem 1.45rem 1.5rem;
  background: var(--c-paper);
  border: 1px solid var(--c-rule);
  border-radius: 16px;
  box-shadow: 0 1px 2px rgb(11 37 69 / .05), 0 10px 28px rgb(11 37 69 / .07);
  /* Was 0, and the panel sat flush against the step-progress bar directly
     under it — the bars read as part of the panel's bottom edge. */
  margin-bottom: 1.75rem;
}
/* NO FOCUS RING ON THE PANEL ITSELF. It carries `tabindex="-1"` so that
   Foundation can move the caret here after a failed POST — it is a landing
   spot for an announcement, not a control, and nothing can tab to it. The
   orange ring that focus drew was therefore pure noise: it fired on every
   error load, framed the whole card in a colour that is not even in the alert,
   and told the customer nothing they could act on. The tabbable things inside
   it (the rows, the chip, the dismiss) keep their own rings. */
.errsum:focus { outline: none; }

/* Icon in its own column, everything else in a second one, so the title, the
   messages and the action all share a left edge clear of the triangle. */
.errsum__inner { display: flex; align-items: flex-start; gap: .7rem; }
.errsum__icon { flex: none; display: block; margin-top: .05rem; color: var(--c-warning); }
.errsum__content { min-width: 0; flex: 1; }

.errsum__title {
  margin: 0;
  font-size: 1.125rem; font-weight: 700; line-height: 1.3;
  color: var(--c-warning);
}
.errsum__sub {
  margin: .4rem 0 0;
  font-size: 1rem; line-height: 1.5;
  color: var(--text-body);
}

.errsum__list { margin: .85rem 0 0; padding: 0; list-style: none; }
.errsum__item + .errsum__item { margin-top: .2rem; }

/* A row reads as body copy and behaves as a control: no borders, no dividers,
   no filled chevron disc — the hover tint is the whole affordance until you
   are on it. The negative inline margin lets that tint bleed slightly past the
   text without the row looking inset from the paragraph above it. */
.errsum__row {
  display: flex; align-items: center; gap: .9rem;
  min-height: 44px;
  margin-inline: -.55rem;
  padding: .45rem .55rem;
  border-radius: 10px;
  text-decoration: none;
  color: inherit;
}
.errsum__text { display: block; min-width: 0; }
.errsum__field {
  display: block;
  font-size: 1rem; font-weight: 700; line-height: 1.4;
  color: var(--text-strong);
}
.errsum__msg {
  display: block;
  font-size: 1rem; line-height: 1.5;
  color: var(--text-muted);
}
.errsum__go {
  flex: none; margin-left: auto;
  display: grid; place-items: center;
  color: var(--c-warning);
  transition: transform .18s ease;
}
a.errsum__row:hover { background: var(--c-paper-tint); }
a.errsum__row:hover .errsum__go { transform: translateX(3px); }
a.errsum__row:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring);
  outline-offset: -2px;
}
.errsum__row--static { min-height: 0; margin-inline: 0; padding-inline: 0; }

/* "on the When step" — a row that NAVIGATES has to say so before it is
   clicked, or the chevron means both "scroll down a bit" and "leave this
   page", and the second one loses the customer their place. */
.errsum__where {
  margin-left: .45rem;
  font-size: .8125rem; font-weight: 600;
  color: var(--c-warning);
  background: color-mix(in srgb, var(--c-warning) 8%, var(--c-paper));
  border-radius: var(--radius-pill);
  padding: .1rem .45rem;
  white-space: nowrap;
}

/* The action, as a quiet chip on the page tint rather than a second CTA — the
   primary action on this page is still Send my request. */
.errsum__chip {
  display: inline-flex; align-items: center; gap: .5rem;
  min-height: 44px;
  margin-top: .9rem;
  padding: .55rem 1rem;
  border-radius: 10px;
  background: var(--c-paper-tint);
  color: var(--text-muted);
  font-size: 1rem; font-weight: 600;
  text-decoration: none;
}
.errsum__chip:hover { background: color-mix(in srgb, var(--c-navy) 8%, var(--c-paper)); }
.errsum__chipicon { flex: none; color: var(--text-muted); }

.errsum__close {
  position: absolute; top: .55rem; right: .55rem;
  display: grid; place-items: center;
  width: 44px; height: 44px;          /* AA target; the glyph inside is 19px */
  padding: 0; border: 0; border-radius: 10px;
  background: none; color: var(--c-warning);
  cursor: pointer;
}
.errsum__close:hover { background: color-mix(in srgb, var(--c-warning) 7%, var(--c-paper)); }

@media (max-width: 480px) {
  .errsum { padding: 1.15rem 2.9rem 1.2rem 1.15rem; }
  .errsum__chip { width: 100%; justify-content: center; }
}
@media (prefers-reduced-motion: reduce) {
  .errsum__go { transition: none; }
  a.errsum__row:hover .errsum__go { transform: none; }
}

/* ── Jumped-to field ──────────────────────────────────────────────────────
   The flash that answers "where did it just send me?". Deliberately short and
   deliberately NOT a persistent state: `.field--error` already carries the red
   border and the message, and a highlight left switched on would still be lit
   after the customer has fixed the field. error-jump.client.ts removes the
   class on `animationend`.

   A ring that expands and fades, not a colour swap — the outgoing ring draws
   the eye to the edge of the block, which is how you find a field rather than
   read one. `outline` rather than `box-shadow` so it cannot be clipped by an
   ancestor and costs no layout. */
@keyframes field-flash {
  0%   { outline-color: rgb(166 27 27 / .55); outline-offset: 2px;  background-color: rgb(166 27 27 / .07); }
  70%  { outline-color: rgb(166 27 27 / .22); outline-offset: 9px;  background-color: rgb(166 27 27 / .03); }
  100% { outline-color: rgb(166 27 27 / 0);   outline-offset: 12px; background-color: rgb(166 27 27 / 0); }
}
.field--flash {
  border-radius: var(--radius-md);
  outline: 3px solid transparent;
  animation: field-flash .85s cubic-bezier(.16, 1, .3, 1) forwards;
}
/* Reduced motion keeps the ANSWER — a brief mark saying "this one" — and drops
   the expansion, which is the movement. */
@media (prefers-reduced-motion: reduce) {
  @keyframes field-flash {
    0%, 70% { outline-color: rgb(166 27 27 / .5); background-color: rgb(166 27 27 / .06); }
    100%    { outline-color: rgb(166 27 27 / 0);  background-color: rgb(166 27 27 / 0); }
  }
  .field--flash { animation-duration: 1.1s; outline-offset: 3px; }
}

/* ── Submit ──────────────────────────────────────────────────────────────── */

.reqform__submit {
  display: flex; flex-direction: column; gap: .9rem;
  padding-top: 1.5rem; border-top: 1px solid var(--c-rule);
}
.reqform__submit .cta { width: 100%; min-height: 56px; }
.reqform__note {
  font-size: var(--fs-micro); color: var(--text-muted); line-height: 1.55;
  max-width: 62ch;
}

/*
 * The purpose statement and privacy link above the submit buttons. This replaced a
 * required "Yes, contact me about this repair" tick box: submitting the form is the
 * consent, so the box was friction, but the DISCLOSURE it carried still has to be on
 * the page. Sized as a note, not as fine print — it is legible, not hidden.
 */
.reqform__consent {
  font-size: var(--fs-micro); color: var(--text-muted); line-height: 1.55;
  max-width: 62ch; margin: 1.5rem 0 0;
}
.reqform__consent a { color: inherit; text-decoration: underline; }
@media (min-width: 620px) {
  .reqform__submit { flex-direction: row; align-items: center; gap: 1.25rem; }
  .reqform__submit .cta { width: auto; min-width: 230px; }
  .reqform__note { margin: 0; }
}

/* ── Step foot: Back, the three seals, Continue ────────────────────────────
   Desktop: one row under a hairline. Back on the left from step two on, the
   three commitments as small sealed icons in the middle, Continue on the
   right — the promise sits exactly where the eye goes before it presses.
   Phone: the same element becomes a bar at the foot of the screen; see the
   media block below. */
.reqfoot {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center;
  grid-template-areas: "seals seals seals" "back nudge cta";
  gap: .9rem 1.25rem;
  margin-top: .5rem;
}
.reqfoot--noback { grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "seals seals" "nudge cta"; }
.reqfoot__back { grid-area: back; margin: 0; }
/* The seals share one line above the hairline; the actions sit under it. The
   compressor seal is long because it is honest ("limited", "on refrigerators"),
   and three honest seals do not fit beside a button at this width. */
.reqfoot__seals {
  grid-area: seals; list-style: none; margin: 0; min-width: 0;
  padding: 0 0 .9rem; border-bottom: 1px solid var(--c-rule);
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1.75rem;
}
.seal {
  display: inline-flex; align-items: center; gap: .6rem;
  font-size: .9375rem; line-height: 1.25; color: var(--text-muted);
}
.seal__icon {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: 50%;
  background: var(--surface-raised); color: var(--c-navy-light);
  box-shadow: inset 0 0 0 1px var(--c-rule);
}
.seal__text { min-width: 0; }
.seal strong { color: var(--c-navy); font-weight: 700; }
.seal a { color: inherit; text-decoration: none; }
.seal a:hover { text-decoration: underline; text-underline-offset: 3px; }
.reqfoot__cta { grid-area: cta; justify-self: end; min-width: 230px; }
.reqfoot__hint, .reqfoot__call { display: none; }
.reqfoot__nudge {
  grid-area: nudge; margin: 0; text-align: right;
  font-size: .9375rem; font-weight: 600; color: var(--c-warning);
}

/* A Continue with nothing to continue with yet. Quiet, not disabled: pressing
   it says what is missing (step-foot.client.ts). Hover and active are named so
   the muted look holds under the pointer. */
.cta--waiting, .cta--waiting:hover, .cta--waiting:active {
  background: var(--surface-raised); color: var(--text-muted);
  box-shadow: inset 0 0 0 1px var(--c-rule-strong); transform: none;
}
.reqfoot .cta:not(.cta--busy):not(.cta--waiting) .cta__label::after { content: "\00a0\2192"; }

@media (max-width: 699px) {
  .reqfoot {
    position: sticky; bottom: 0; z-index: 30;
    grid-template-columns: auto minmax(0, 1fr); grid-template-areas: "hint hint" "call cta";
    gap: .5rem .6rem;
    /* Full bleed: the bar is edge to edge, inside a wrap that has gutters. */
    margin: 1rem calc(-1 * var(--gutter, 20px)) 0;
    padding: .6rem var(--gutter, 20px) calc(.7rem + env(safe-area-inset-bottom, 0px));
    background: var(--surface-page);
    border-top: 1px solid var(--c-rule);
    box-shadow: 0 -10px 24px -18px rgb(11 37 69 / .45);
  }
  /* The final step: the button belongs under the consent line it commits. */
  .reqfoot--static { position: static; box-shadow: none; }
  .reqfoot__back, .reqfoot__seals { display: none; }
  .reqfoot__hint { grid-area: hint; }
  .reqfoot__hint {
    display: block; grid-area: hint; margin: 0; text-align: center;
    font-size: .8125rem; color: var(--text-muted);
  }
  .reqfoot__call {
    display: inline-flex; grid-area: call; align-items: center; justify-content: center;
    width: 48px; height: 48px; border-radius: var(--cta-radius);
    border: 1.5px solid var(--c-navy); color: var(--c-navy); background: var(--surface-page);
  }
  .reqfoot__cta { grid-area: cta; justify-self: stretch; min-width: 0; }
  .reqfoot__cta .cta { padding-inline: 1rem; }
  .reqfoot__nudge { grid-area: auto; grid-column: 1 / -1; text-align: center; font-size: .875rem; }
}

/* ── Rather talk it through? ───────────────────────────────────────────────
   The escape hatch, directly under the step's own actions: visible on every
   step, never competing with Continue. Desktop reads as one line; a phone
   stacks it and centres it. */
.callcard {
  display: flex; align-items: center; flex-wrap: wrap; gap: .6rem 1.1rem;
  margin-top: 1.5rem; padding: .9rem 1.1rem;
  border: 1px solid var(--c-rule); border-radius: 14px; background: var(--surface-page);
}
.callcard__lead { margin: 0; font-size: 1rem; font-weight: 700; color: var(--text-muted); }
.callcard__btn {
  display: inline-flex; align-items: center; gap: .5rem;
  min-height: 44px; padding: 0 1rem;
  border-radius: 10px; background: var(--c-navy); color: #fff;
  font-size: 1.0625rem; font-weight: 800; text-decoration: none; white-space: nowrap;
  transition: background-color .15s ease;
}
.callcard__btn:hover { background: var(--c-navy-light); color: #fff; }
.callcard__hours { font-size: .875rem; color: var(--text-muted); }
@media (max-width: 699px) {
  .callcard {
    flex-direction: column; align-items: center; text-align: center;
    gap: .55rem; margin-top: 1.25rem; padding: 1.25rem 1rem;
  }
  .callcard__lead { font-size: 1.0625rem; }
  .callcard__btn { font-size: 1.25rem; padding: 0 1.4rem; min-height: 48px; }
}

/* ── Find an appliance ─────────────────────────────────────────────────────
   Desktop and script-on only (appliance-search.client.ts removes `hidden`).
   Under 700px the stylesheet keeps it off: a phone keyboard would cover the
   tiles it was filtering. */
.reqsearch { position: relative; display: none; }
@media (min-width: 700px) { .reqsearch { display: block; } }
.reqsearch__icon {
  position: absolute; left: .7rem; top: 50%; transform: translateY(-50%);
  color: var(--text-muted); pointer-events: none;
}
.reqsearch__input {
  width: min(280px, 38vw); min-height: 40px; padding: 0 .75rem 0 2.15rem;
  border: 1px solid var(--border-control); border-radius: 9px;
  background: var(--surface-page); color: var(--text-body);
  font: inherit; font-size: .9375rem;
}
.reqsearch__input::placeholder { color: var(--text-placeholder); }
.reqsearch__input:hover { border-color: var(--c-navy-light); }
.reqsearch__input:focus { border-color: var(--c-navy); outline: none; box-shadow: 0 0 0 3px rgb(11 37 69 / .12); }
.reqsearch__input:focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring); outline-offset: 2px; box-shadow: none; }
.reqsearch__empty { margin: .6rem 0 0; font-size: .9375rem; color: var(--text-muted); }
.reqsearch__empty strong { color: var(--c-navy); }

/* ── Scroll reveal ───────────────────────────────────────────────────────────
   Content rises into place as it enters the viewport. This is CSS-only, on
   purpose: marketing pages ship no script for effects. Browsers without
   scroll-driven animations, and anyone who asked for reduced motion, get the
   content at rest, fully visible, with no animation at all. */
@keyframes reveal-up {
  from { opacity: 0; transform: translateY(22px); }
  to   { opacity: 1; transform: none; }
}
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .panel,
    .symptoms > li,
    .trustbar__item,
    .city-grid > li {
      animation: reveal-up linear backwards;
      animation-timeline: view();
      animation-range: entry 0% entry 35%;
    }
    /* The pills no longer sit in known columns, so the stagger is a simple
       cascade along the wrap order rather than a per-column offset. */
    .symptoms > li:nth-child(3n + 2) { animation-range: entry 3% entry 38%; }
    .symptoms > li:nth-child(3n) { animation-range: entry 6% entry 41%; }
  }
}

/* Icons in the places that were text only. A small badge keeps the icon's weight
   matched to the surrounding type rather than floating beside it. */
.panel__title { display: flex; align-items: center; gap: .6rem; }
.panel__icon {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.2rem; height: 2.2rem; padding: .45rem;
  border-radius: 10px;
  background: var(--c-paper-tint); color: var(--c-navy);
}
.panel { transition: transform .2s ease, box-shadow .2s ease; }
@media (hover: hover) {
  .panel:hover { transform: translateY(-2px); box-shadow: 0 12px 24px -18px rgb(11 37 69 / .5); }
}
@media (prefers-reduced-motion: reduce) {
  .panel { transition: none; }
  .panel:hover { transform: none; }
}

/* The "Something else" box. Hidden until that card is chosen, then it eases in. Without
   :has() support it simply stays visible. */
.otherfield { display: block; }
@supports selector(:has(*)) {
  .otherfield { display: none; }
  .fset__body:has(input[name="appliance"][value="other"]:checked) > .otherfield {
    display: flex; flex-direction: column;
  }
  @media (prefers-reduced-motion: no-preference) {
    .fset__body:has(input[name="appliance"][value="other"]:checked) > .otherfield {
      animation: reveal-up .25s ease-out both;
    }
  }
}

/* Picture tiles: icon, name, radio — one row. The icon sits in a small tinted
   square that goes navy when the tile is chosen, so the state is visible
   without relying on the border colour alone. */
.choice--icon { padding: .5rem .65rem .5rem .5rem; min-height: 58px; gap: .55rem; }
.choice--icon .choice__box { width: 18px; height: 18px; }
.choice--icon input:checked ~ .choice__box { box-shadow: inset 0 0 0 3.5px var(--surface-page), inset 0 0 0 18px var(--c-navy); }
.choice__icon {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.25rem; height: 2.25rem; border-radius: 9px;
  background: var(--c-paper-tint); color: var(--c-navy);
  transition: background-color .15s ease, color .15s ease, transform .15s ease;
}
.choice:hover .choice__icon { transform: scale(1.05); }
.choice:has(input:checked) .choice__icon { background: var(--c-navy); color: #fff; }
@media (prefers-reduced-motion: no-preference) {
  @keyframes tile-pop { 0% { transform: scale(.9); } 60% { transform: scale(1.07); } 100% { transform: none; } }
  .choice:has(input:checked) .choice__icon { animation: tile-pop .24s ease-out; }
}
/* A phone lists the picture tiles ONE ACROSS. Two across put "Water
   dispenser", "Washer-dryer combo" and "Dryer vent cleaning" onto two and
   three lines inside tiles 160px wide, so the grid was ragged and the eye
   read line breaks instead of names. One across, every name is one line of
   one size in a 46px row, and the list is a list. It is a third longer than
   the grid was; a clean list that scrolls beats a short grid that wraps.
   The name alone, no radio: the chosen state is the edge, the ground and the
   inverted icon. The clarifier comes back only where two tiles share a name
   ("Dryer · Electric", "Dryer · Gas"), inline. */
@media (max-width: 639px) {
  .choices__grid--pictures { grid-template-columns: 1fr; gap: .4rem; }
  .choices__grouphead { margin: 1.1rem 0 .3rem; font-size: .8125rem; }
  .choice--icon { min-height: 46px; padding: .3rem .7rem .3rem .4rem; gap: .6rem; border-radius: 10px; }
  .choice--icon .choice__icon { width: 2rem; height: 2rem; border-radius: 8px; }
  .choice--icon .choice__label { white-space: nowrap; }
  .choice--icon .choice__box, .choice--icon .choice__note { display: none; }
  .choice--icon.choice--dup .choice__text { display: block; }
  .choice--icon.choice--dup .choice__note { display: inline; }
  .choice--icon.choice--dup .choice__note::before { content: " · "; }
}
@media (prefers-reduced-motion: reduce) {
  .choice__icon { transition: none; animation: none; }
  .choice:hover .choice__icon { transform: none; }
}

/* ── Page header ─────────────────────────────────────────────────────────────
   Every non-legal page's opening. Without it a page starts cold: an H1 on
   white above a column of markdown, which was the single thing that most made
   the site read as unfinished. */

.phead {
  position: relative; isolation: isolate; overflow: hidden;
  padding-block: clamp(2rem, 1.4rem + 2.6vw, 3.5rem);

  /* The motif, as numbers the two layers share. --phead-px/py are written by
     page-header.client.ts and default to the middle of the band, which is
     where the highlight rests before the pointer has ever been over it. */
  --phead-ink: rgb(110 137 166 / .3);
  --phead-ink-live: rgb(165 0 52 / .55);
  --phead-cell: 20px;
  --phead-px: 50%;
  --phead-py: 50%;
}
.phead__inner { position: relative; z-index: 1; }
.phead--tint { background: var(--surface-raised); border-bottom: 1px solid var(--c-rule); }
/* The default. Navy, not the old pale tint: the tint was the same near-white
   as the nav above and the prose below, so the band did not read as a band at
   all. On navy the motif has to be drawn in white rather than slate, and the
   pointer highlight in amber rather than crimson — crimson on navy is nearly
   invisible, and amber is the one accent the palette reserves for dark
   surfaces. */
.phead--navy {
  background: var(--c-navy);
  color: var(--text-inverse);
  --phead-ink: rgb(255 255 255 / .15);
  --phead-ink-live: rgb(240 140 0 / .55);
}

/* ── THE MOTIF ───────────────────────────────────────────────────────────
   Two copies of one drawing: the resting layer, and a crimson one revealed
   in a circle around the pointer. Both are `aria-hidden` and neither is an
   image — see the header of PageHeader.astro.                             */
/* The fade lives on the wrapper so it applies to the resting drawing and the
   pointer highlight alike. It clears the text column entirely before the
   motif reaches even a fifth of its strength: a drawing a reader has to look
   past is worse than no drawing. */
.phead__artwrap {
  position: absolute; inset: 0; z-index: 0;
  pointer-events: none;
  -webkit-mask-image: linear-gradient(90deg, transparent 0 38%, rgb(0 0 0 / .18) 54%, #000 86%);
  mask-image: linear-gradient(90deg, transparent 0 38%, rgb(0 0 0 / .18) 54%, #000 86%);
}
.phead__art {
  position: absolute; inset: 0;
  color: var(--phead-ink);
}
.phead__art .motif {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  overflow: visible;
}
/* The one filled mark in the set: the summit of the contour map, and the
   source of the signal rings. */
.phead__art .motif__peak { fill: currentColor; }
/* The lit copy is drawn a little heavier as well as in colour. At a 1.25
   hairline a change of hue alone is almost invisible against the resting
   layer underneath it, which is the whole effect. */
.phead__art--live .motif { stroke-width: 1.5; }

/* `background-position: 0 0` is not a default worth leaving to chance: the
   two layers must share a grid origin exactly, or the highlight draws a
   second set of dots offset from the first and the effect reads as a blur
   rather than as the same dots lighting up. */
.phead__art[data-motif="dots"] {
  background-image: radial-gradient(circle at center, currentColor 1px, transparent 1.2px);
  background-size: var(--phead-cell) var(--phead-cell);
  background-position: 0 0;
}
.phead__art[data-motif="grid"] {
  background-image:
    linear-gradient(90deg, currentColor 0 1px, transparent 1px 100%),
    linear-gradient(0deg,  currentColor 0 1px, transparent 1px 100%);
  background-size: calc(var(--phead-cell) * 2) calc(var(--phead-cell) * 2);
  background-position: 0 0;
  opacity: .55;
}

/* The live layer. Masked only by the circle around the pointer — the
   horizontal fade comes from the wrapper, so the highlight can never appear
   behind the heading however close the cursor gets to it. */
.phead__art--live {
  color: var(--phead-ink-live);
  -webkit-mask-image: radial-gradient(11rem 11rem at var(--phead-px) var(--phead-py), #000 0%, rgb(0 0 0 / .4) 48%, transparent 74%);
  mask-image: radial-gradient(11rem 11rem at var(--phead-px) var(--phead-py), #000 0%, rgb(0 0 0 / .4) 48%, transparent 74%);
  opacity: 0;
  transition: opacity .35s ease;
}
@media (hover: hover) {
  /* `.is-live` is added on the first pointermove — see the note in
     page-header.client.ts about why `:hover` alone lights the wrong place. */
  .phead.is-live:hover .phead__art--live { opacity: 1; }
}
/* The fade is the only moving part, and it is the first thing to go. */
@media (prefers-reduced-motion: reduce) {
  .phead__art--live { transition: none; }
}

/* The eyebrow carries the brand colour on its own. It used to open with a short
   crimson rule, which read as a stray dash in front of the words rather than as
   a mark. */
.phead__eyebrow {
  text-transform: uppercase; letter-spacing: var(--ls-caps);
  font-size: var(--fs-micro); font-weight: 700; color: var(--c-lg-crimson);
  margin-bottom: .6rem;
}
.phead--navy .phead__eyebrow { color: var(--c-amber); }
.phead__title {
  font-size: var(--fs-h1); line-height: var(--lh-display);
  letter-spacing: var(--ls-display); max-width: 36ch; text-wrap: balance;
}
@media (min-width: 900px) { .phead__title { text-wrap: nowrap; } }
.phead--navy .phead__title { color: var(--text-inverse); }
.phead__lead {
  margin-top: .9rem; max-width: 56ch;
  font-size: var(--fs-lead); line-height: 1.5; color: var(--text-muted);
}
.phead--navy .phead__lead { color: var(--text-inverse-soft); }
.phead__lead a { color: var(--text-link); font-weight: 500; }
/* On navy there is no second colour left to mark a link with — amber is spoken
   for by the eyebrow — so the link is the body colour and carries its weight in
   the underline instead. */
.phead--navy .phead__lead a {
  color: var(--text-inverse);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
}
.phead--navy .phead__lead a:hover { text-decoration-color: var(--c-amber); }

/* The consent disclosure on the request form. Small, muted, but still real text. */
.reqform__consent {
  margin: .25rem 0 0;
  font-size: .8125rem; line-height: 1.45;
  color: var(--text-muted);
}
.reqform__consent a { color: var(--text-link); font-weight: 500; }

/* ── Editorial article sections ──────────────────────────────────────────────
   Rendered markdown gets a section per h2, an icon per heading, and the card
   and checklist treatments applied by lib/content/article.ts. The rules here
   are presentation only: every one of them degrades to the plain prose above
   when the decorator finds nothing to transform. */

.prose > .artsec {
  margin-top: 2.5rem;
  padding-top: 1.75rem;
  border-top: 1px solid var(--c-rule);
}
.prose > .artsec:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
.artsec > * + * { margin-top: 1rem; }

.prose .artsec__title { display: flex; align-items: center; gap: .7rem; }
.artsec > .artsec__title { margin-top: 0; padding-top: 0; border-top: 0; }
.artsec__icon {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.35rem; height: 2.35rem;
  border-radius: 10px;
  background: var(--c-paper-tint); color: var(--c-navy);
}
.artsec__icon svg { width: 21px; height: 21px; }
.artsec__heading { min-width: 0; }

/* Bold-led paragraphs become cards: the icon names the idea, the bold line is
   the title, the rest is the explanation. */
.fcard {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  gap: .9rem; align-items: start;
  padding: 1rem 1.1rem;
  background: var(--surface-page);
  border: 1.5px solid var(--c-rule);
  border-radius: 12px;
}
.fcard + .fcard { margin-top: .75rem; }
/* On the wide index pages a card would otherwise run the full 1240px; cap it
   at reading width while the link-card grids above keep the full row. */
.prose--index .fcard { max-width: 78ch; }
.fcard__icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.4rem; height: 2.4rem;
  border-radius: 9px;
  background: var(--c-paper-tint); color: var(--c-navy);
}
.fcard__icon svg { width: 20px; height: 20px; }
.fcard__title {
  margin: 0;
  font-size: 1.0625rem; font-weight: 700; line-height: 1.35;
  color: var(--c-navy);
}
.fcard__body { margin-top: .2rem; font-size: 1rem; line-height: 1.55; }
.fcard__body p { margin: 0; }
.fcard__body p + p { margin-top: .5rem; }

.hours-list {
  margin: 0;
  padding: .35rem 0;
  border-top: 1px solid var(--c-rule);
  border-bottom: 1px solid var(--c-rule);
}
.hours-list__row {
  display: grid;
  grid-template-columns: minmax(9rem, 1fr) auto;
  align-items: baseline;
  gap: 1.5rem;
  padding: .75rem 0;
}
.hours-list__row + .hours-list__row { border-top: 1px solid var(--c-rule); }
.hours-list dt {
  font-weight: 700;
  color: var(--c-navy);
}
.hours-list dd {
  margin: 0;
  font-weight: 600;
  color: var(--text-body);
  text-align: right;
  white-space: nowrap;
}
@media (max-width: 480px) {
  .hours-list__row { grid-template-columns: 1fr auto; gap: 1rem; }
}

/* Contact page: the three published mailboxes, inside the "Email." card.
   The dl is pulled into the card body by `data-card-body`, so it carries no
   frame of its own — the card is the frame. Each row is one tap target: the
   icon lives inside the <a> so the glyph, the address and the 44px height are
   a single link rather than a decoration sitting next to one. The description
   is indented to the address, not to the icon, so the three lines of prose
   align with each other down the card. */
.contact-emails {
  margin: .7rem 0 0;
  padding: 0;
  border-top: 1px solid var(--c-rule);
}
.contact-emails__row { padding: .3rem 0 .55rem; }
.contact-emails__row + .contact-emails__row { border-top: 1px solid var(--c-rule); }
.contact-emails dt { font-weight: 700; }
.contact-emails dt a {
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  min-height: 44px;
  color: var(--c-navy);
  font-weight: 700;
  word-break: break-word;
}
.contact-emails__icon {
  display: inline-flex;
  flex: 0 0 auto;
  /* Navy, not the CTA colour: these are three equal routes, not a button,
     and the brand pack has an open conflict on the crimson override. */
  color: var(--c-navy);
  opacity: .62;
}
.contact-emails dd {
  margin: 0;
  /* 18px glyph + .55rem gap: the description hangs under the address. */
  padding-left: calc(18px + .55rem);
  font-size: .95rem;
  line-height: 1.5;
  color: var(--text-body);
}

.fixgrid { display: grid; gap: .75rem; grid-template-columns: 1fr; }
.fixgrid .fcard + .fcard { margin-top: 0; }
@media (min-width: 760px) {
  .fixgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* "What we don't do yet": a vertical list of tiles in the homepage symptom-tile
   frame — chip, border, radius — set as a row: icon left, title and one
   explanation right. Statements, not links, so no lift or fill on hover. */
.xgrid { display: grid; gap: .7rem; grid-template-columns: 1fr; max-width: 72ch; }
.xtile {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  column-gap: 1rem; row-gap: .25rem; align-items: start;
  padding: 1rem 1.1rem 1.05rem;
  background: var(--surface-page);
  border: 1.5px solid var(--c-rule);
  border-radius: 14px;
}
.xtile__icon {
  grid-row: 1 / 3;
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px;
  background: var(--surface-raised);
  color: var(--c-navy-light);
  border-radius: 11px;
}
.xtile__icon svg { width: 24px; height: 24px; }
.prose .xtile__title {
  grid-column: 2; align-self: center;
  margin: 0; max-width: none;
  font-size: 1.0625rem; font-weight: 700; line-height: 1.3;
  color: var(--c-navy); text-wrap: balance;
}
.prose .xtile__body {
  grid-column: 2;
  margin: 0; max-width: none;
  font-size: 1rem; line-height: 1.5; color: var(--text-body);
}
.xtile:not(:has(.xtile__body)) { align-items: center; }
.artsec--exclusions > .xgrid + p { margin-top: 1.5rem; max-width: 68ch; }

/* A bullet list with no titles is a checklist. The tick is absolutely
   positioned: a grid would make every text run and inline element its own
   column, which turns a sentence into one word per line. */
.checklist { list-style: none; padding-left: 0; display: grid; gap: .55rem; }
.checklist li {
  position: relative;
  padding-left: 2rem;
  max-width: none; margin: 0;
}
.checkmark {
  position: absolute; left: 0; top: .18rem;
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.4rem; height: 1.4rem;
  border-radius: 50%;
  background: var(--c-paper-tint); color: var(--c-navy);
}
.checkmark svg { width: 15px; height: 15px; }

/* A numbered list is a sequence, so the number gets a real badge. */
.stepcheck {
  list-style: none; padding-left: 0;
  counter-reset: stepcheck;
  display: grid; gap: .75rem;
}
.stepcheck > li {
  position: relative;
  counter-increment: stepcheck;
  max-width: none; margin: 0;
  padding: .9rem 1rem .9rem 3.4rem;
  background: var(--surface-page);
  border: 1.5px solid var(--c-rule);
  border-radius: 12px;
}
.stepcheck > li::before {
  content: counter(stepcheck);
  position: absolute; left: .9rem; top: .9rem;
  display: flex; align-items: center; justify-content: center;
  width: 2.1rem; height: 2.1rem;
  border-radius: 50%;
  background: var(--c-navy); color: #fff;
  font-size: .9375rem; font-weight: 700;
}

/* Place and brand names read as objects rather than as a run-on sentence. */
.place {
  display: inline-block;
  padding: .05em .45em;
  border-radius: 999px;
  background: var(--c-paper-tint); color: var(--c-navy);
  font-weight: 600; white-space: nowrap;
}
.brandchip {
  display: inline-block;
  margin: .1em .12em;
  padding: .1em .5em;
  border: 1px solid var(--c-rule); border-radius: 999px;
  background: var(--surface-page); color: var(--c-navy);
  font-weight: 600; white-space: nowrap;
}

/* The brand list on /about is a row of wordmarks, not a row of words — the
   same marks, in the same order, as the marquee under the hero. They are
   knocked back to one ink so a row of seven house styles does not turn into
   seven competing colours next to the copy.

   ── NO PILLS. THE MARKS FLOAT IN LINE. ───────────────────────────────────
   Each mark used to sit in a bordered, shadowed pill. Seven of them made a row
   of seven boxes of differing widths whose only shared edge was the one the
   browser gave them, and the pill's 1.1rem of inner padding pushed the first
   logo off the prose margin — so the row started a centimetre to the right of
   every paragraph above it, which is the single thing the eye notices.

   Dropping the box fixes the indent for free: with no padding the first mark's
   own left edge IS the text margin. What the box was doing, though, was
   separating marks that have no frame of their own, so the gap does that job
   now and has to be much larger than the .55rem that merely separated pills.

   `height` on the row item (not on the image) is what keeps the marks on one
   optical line: the files are wildly different proportions and each is sized
   individually by brandRowLogoHeight(), so a common BOX height centred on a
   common baseline is the only thing making a 14px Electrolux and a 44px
   Samsung read as members of the same row. */
/* `.prose ul` (0,1,1) beats a bare `.brandlogos` (0,1,0) and sets
   padding-left: 1.3rem for bullets. That 21px — not anything inside the SVG
   files, which carry no horizontal padding at all — is what held the first
   wordmark off the text margin. The `.prose` prefix here only matches that
   specificity; it is not a nesting requirement. */
.prose .brandlogos,
.brandlogos {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 1.1rem 2rem;
  /* These margins are tuned against the INK, not against the element box, and
     that is the whole difficulty. The row's height is set by Samsung's 85px
     square canvas while its visible mark is only ~13px tall, so ~36px of
     transparent canvas sits above and below the logos: margins that look
     correct in the box model leave the marks either floating in a void or
     jammed against the paragraph.
     Measured target is ~22px of clear air between the text above and the
     topmost ink, a little more than the 16px paragraph rhythm because a logo
     needs more room than a line of type. Re-measure if the tallest canvas in
     ROW_HEIGHT changes — the dead space changes with it. */
  margin: -0.25rem 0 -1.25rem;
  padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0;
  list-style: none;
}
/* NO fixed height. The canvases run from 13px (Electrolux, a tight crop) to
   85px (Samsung, a wordmark floating in a square), and every file centres its
   ink — so letting the items size themselves and centring them is what puts
   all seven marks on one optical line. Forcing a common box height instead
   would clip the tall canvases or shrink the ink inside them. */
.prose .brandlogos__item,
.brandlogos__item {
  display: inline-flex; align-items: center; justify-content: flex-start;
  margin: 0; padding: 0;
  background: none; border: 0; box-shadow: none;
  transition: opacity .2s ease;
}
/* `.prose li` sets a list marker and leading; neither belongs on a logo. */
.prose .brandlogos__item::marker { content: none; }
/* One ink for the row. The dark files go to a silhouette with brightness(0);
   the two drawn white for the marquee get there by inverting instead, which is
   the same black and not an invisible rectangle. */
.brandlogos__img {
  /* Per brand, from article.ts as `--logo-h` rather than an inline height,
     so the phone strip below can scale the marks. */
  height: var(--logo-h, 20px);
  width: auto; max-width: 100%;
  filter: brightness(0);
  opacity: .74;
  transition: opacity .2s ease;
}
.brandlogos__img--white { filter: invert(1); }
.brandlogos__item:hover .brandlogos__img { opacity: 1; }

/* The certification artwork, directly under the sentence that claims it.
   The -.4rem this used to carry put the image 6px INTO the line above it —
   measured, not guessed. The artwork carries its own whitespace, so these
   values are set from the rendered gap rather than from the file's box. */
.lgcert { margin: 1.15rem 0 1.3rem; }
.lgcert__img {
  display: block;
  width: auto; height: 84px;
  max-width: 100%;
}
@media (max-width: 619px) {
  /* The row itself no longer wraps here — below 700px it becomes the
     scrolling strip in the phone layer at the end of this file. */
  .lgcert__img { height: 72px; }
}

/* ── Service-area cards ─────────────────────────────────────────────────────
   The areas page's table becomes the homepage city cards — same photo, same
   navy, same hover — with the ZIPs the city covers on the card and the whole
   card linking to the local page. The grid resets the prose list rules. */
.prose--index ul.area-grid {
  padding-left: 0; list-style: none;
  grid-template-columns: repeat(auto-fill, minmax(13.5rem, 1fr));
}
.prose--index ul.area-grid > li { max-width: none; margin: 0; padding: 0; }
.prose--index ul.area-grid > li + li { margin-top: 0; }
.prose--index .area-grid .city-card,
.prose--index .area-grid .city-card:hover,
.prose--index .area-grid .city-card:focus-visible { color: #fff; text-decoration: none; font-weight: var(--fw-body); }
.city-card--zips { min-height: 12.5rem; gap: .45rem; }
.city-card__zips {
  display: flex; flex-wrap: wrap; gap: .3rem;
  max-width: 100%;
}
.city-card--zips .zipchip {
  padding: .12rem .42rem;
  border-radius: 5px;
  background: rgb(255 255 255 / .14);
  color: #fff;
  font-size: .8125rem; font-weight: 600; line-height: 1.45;
  font-variant-numeric: tabular-nums;
  text-shadow: none;
  backdrop-filter: blur(2px);
}
.city-card--zips:hover .zipchip,
.city-card--zips:focus-visible .zipchip { background: rgb(11 37 69 / .55); }
.city-card--zips .city-card__meta { margin-top: .1rem; }
.city-card--zips .city-card__name { font-size: 1.3125rem; }
span.city-card--zips:hover { transform: none; box-shadow: 0 1px 2px rgb(11 37 69 / .12); }
span.city-card--zips:hover::before { opacity: .28; filter: grayscale(.9) brightness(1.15); }

/* The service aside's promise strip: the three assurances that qualify the CTA. */
.svc__promises {
  list-style: none; margin: 0 0 .35rem; padding: .85rem .9rem;
  display: grid; gap: .55rem;
  background: var(--c-paper-tint); border-radius: 10px;
}
.svc__promises li {
  display: flex; align-items: center; gap: .55rem;
  font-size: .9rem; font-weight: 600; line-height: 1.3; color: var(--c-navy);
}
.svc__promises svg { flex: 0 0 auto; color: var(--c-navy-light); }

/* Footer column headings. */
.ftr__coltitle { display: block; }

@media (prefers-reduced-motion: reduce) {
  .prose--index .area-grid .city-card { transition: none; }
}

.reviews { display: grid; gap: 1.25rem; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); margin-top: 1.5rem; }
.review-card { background: var(--surface-raised, #fff); border: 1px solid var(--rule, #D5DCE5); border-radius: 12px; padding: 1.5rem; box-shadow: 0 8px 24px rgba(11, 37, 69, 0.06); transition: transform .2s ease, box-shadow .2s ease; }
.review-card:hover { transform: translateY(-3px); box-shadow: 0 12px 32px rgba(11, 37, 69, 0.12); }
.review-card__quote { font-size: 1.05rem; line-height: 1.6; color: var(--ink, #16191D); margin: 0 0 .75rem; }
.review-card__meta { font-size: .875rem; color: var(--c-slate); font-weight: 600; margin: 0; }

:root {
  /* The LG crimson. It was already here three times as a raw #A50034 — the
     CTA, the nav pill and the hero's radial wash — so this names the value
     rather than introducing one. */
  --c-lg-crimson: #A50034;
  --cta-bg: var(--c-lg-crimson);
  --cta-bg-hover: #8B002C;
  --cta-bg-active: #7A0026;

  /* A tinted ground with the crimson in it instead of the blue.

     Mixed into PAPER, not into --surface-raised. Mixing crimson into the
     blue tint only desaturates it: the two hues cancel and the section comes
     out a darker grey, which reads as a shadow rather than as a colour.
     Blended into white at the same lightness as the blue tint, the hue is
     what changes and the weight of the section does not.

     The literal fallback is for browsers without color-mix; it is the same
     value, computed. Keep the two in step. */
  --surface-crimson: #FBF4F7;
  --surface-crimson: color-mix(in srgb, var(--c-lg-crimson) 4%, var(--c-paper));
}

.trustbar--reveal .trustbar__item {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity .6s ease, transform .6s ease;
}
.trustbar--reveal.is-revealed .trustbar__item {
  opacity: 1;
  transform: none;
  transition: border-color .2s ease, box-shadow .2s ease, transform .2s ease;
}
.trustbar--reveal.is-revealed .trustbar__item:nth-child(1) { transition-delay: 0s; }
.trustbar--reveal.is-revealed .trustbar__item:nth-child(2) { transition-delay: .08s; }
.trustbar--reveal.is-revealed .trustbar__item:nth-child(3) { transition-delay: .16s; }
.trustbar--reveal.is-revealed .trustbar__item:nth-child(4) { transition-delay: .24s; }
.trustbar--reveal.is-revealed .trustbar__item:nth-child(5) { transition-delay: .32s; }
.trustbar--reveal.is-revealed .trustbar__item:nth-child(6) { transition-delay: .40s; }
.trustbar--reveal.is-revealed .trustbar__item:nth-child(7) { transition-delay: .48s; }
/* The stagger is for the entrance only. Without this the reveal's delay would
   still be on the item when it is hovered, and the lift would arrive late. */
.trustbar--reveal.is-revealed .trustbar__item:hover {
  transition-delay: 0s;
  transform: translateY(-2px);
}
.trustbar--reveal.is-revealed .trustbar__icon {
  animation: trustIconPop .7s cubic-bezier(.2, .8, .2, 1) both;
}
.trustbar--reveal.is-revealed .trustbar__item:nth-child(2) .trustbar__icon { animation-delay: .08s; }
.trustbar--reveal.is-revealed .trustbar__item:nth-child(3) .trustbar__icon { animation-delay: .16s; }
.trustbar--reveal.is-revealed .trustbar__item:nth-child(4) .trustbar__icon { animation-delay: .24s; }
.trustbar--reveal.is-revealed .trustbar__item:nth-child(5) .trustbar__icon { animation-delay: .32s; }
.trustbar--reveal.is-revealed .trustbar__item:nth-child(6) .trustbar__icon { animation-delay: .40s; }
.trustbar--reveal.is-revealed .trustbar__item:nth-child(7) .trustbar__icon { animation-delay: .48s; }
@keyframes trustIconPop {
  0% { transform: scale(.6); opacity: 0; }
  60% { transform: scale(1.12); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .trustbar--reveal .trustbar__item { opacity: 1; transform: none; transition: none; }
  .trustbar--reveal.is-revealed .trustbar__icon { animation: none; }
}

.hdr__wa {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px;
  color: #25D366;
  border: 2px solid #25D366;
  border-radius: 50%;
  transition: transform .2s ease, background .2s ease;
}
.hdr__wa:hover { transform: scale(1.08); background: rgba(37, 211, 102, .1); }

.ftr__wa {
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px;
  color: #25D366;
  border: none;
  border-radius: 50%;
  transition: transform .2s ease, background .2s ease;
}
.ftr__wa:hover { transform: scale(1.08); background: rgba(37, 211, 102, .1); }

.ftr__fb {
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px;
  color: #1877F2;
  border: none;
  border-radius: 50%;
  transition: transform .2s ease, background .2s ease;
}
.ftr__social { display: inline-flex; gap: .5rem; align-items: center; margin-right: .5rem; }
.ftr__fb:hover { transform: scale(1.08); background: rgba(24, 119, 242, .1); }

/* ==========================================================================
   THE PHONE LAYER
   Below 700px the page is a phone, held one-handed, often in a kitchen with
   the other hand busy. These rules do not shrink the desktop page; they set
   it again for a thumb: one column, full-width actions, 44px targets, cards
   laid as rows rather than boxes, a 64px header with a sheet menu, and the
   hours that fit one line. Each component's base rules stay in its own
   section above; this is the phone's reading of them, kept in one place so
   the phone can be reviewed as one design rather than hunted through the
   file. 700px is the site's existing seam — the sticky bar and the header
   CTA already turn over there.
   ========================================================================== */
@media (max-width: 699px) {
  /* ── Touch feel ─────────────────────────────────────────────────────────
     No grey flash on tap, no double-tap-to-zoom delay on controls, and a
     visible press on every card so a touch is answered before the page
     changes. */
  a, button, summary, label, [role="button"] { -webkit-tap-highlight-color: transparent; }
  a, button, summary, input, select, textarea, label { touch-action: manipulation; }
  .whycard:active, .city-card:active, .symptom:active, .choice:active,
  .prose--index ul.idx__list > li:active, .fcard:active, .review-card:active { transform: scale(.985); }
  .cta:active, .cta-secondary:active { transform: translateY(1px) scale(.99); }

  /* ── Header: a 64px bar ─────────────────────────────────────────────────
     The mark drops a size, the WhatsApp disc grows to a target, and the
     Menu button sits at the right where a thumb rests. Safe-area padding
     for a notched phone in standalone mode. */
  .hdr { padding-top: env(safe-area-inset-top, 0px); }
  .hdr__inner { min-height: 64px; padding-block: .45rem; gap: .5rem; }
  /* ── The LG certification beside the logo, not under it ──────────────
     On desktop the LG mark and "Certified" tuck into the open corner of the
     logo's frame. At 62px that frame has no corner to spare: the mark was
     15px, hanging below the frame's foot, and "Certified" had been dropped
     altogether — so the header said "LG" and nothing about what that meant.
     Here it is a second lockup beside the first, mark over word, parted
     from the logo by a hairline: the co-brand form a customer already reads
     as "authorised for". The logo gives up a little height to pay for it on
     the narrowest phones (48px at 320, the full 62 from ~415) and the whole
     brand still clears the two 44px actions at 320. */
  .hdr__brand { gap: .5rem; }
  .hdr__logo-mark { height: clamp(48px, 13vw + 8px, 62px); width: auto; }
  .hdr__lg, .hdr__lg--req {
    position: static; flex: 0 0 auto;
    flex-direction: column; align-items: center; gap: 0;
    padding-left: .4rem; border-left: 1px solid var(--c-rule);
  }
  /* lg.svg carries its ink in the middle 44% of a square canvas, so 34px of
     file is ~15px of mark with ~9px of blank above and below. The word is
     pulled up into that blank so it sits 4px under the ink, not 10. */
  .hdr__lgimg, .hdr__lg--req .hdr__lgimg { height: 34px; }
  .hdr__lgtext, .hdr__lg--req .hdr__lgtext {
    display: block; margin-top: -6px;
    font-size: .6875rem; font-weight: 600; letter-spacing: 0;
    color: var(--c-navy-mid);
  }
  .hdr__actions { gap: .5rem; }
  .hdr__wa { width: 44px; height: 44px; }

  /* ── Breadcrumbs: one line that scrolls, with rows a thumb can hit ─── */
  .crumbs ol {
    flex-wrap: nowrap; overflow-x: auto; white-space: nowrap; padding-block: 0;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
    font-size: .875rem;
  }
  .crumbs ol::-webkit-scrollbar { display: none; }
  .crumbs li { flex: 0 0 auto; }
  .crumbs a, .crumbs [aria-current="page"] { display: inline-flex; align-items: center; min-height: 44px; }

  /* ── Hero ───────────────────────────────────────────────────────────────
     The headline, the two actions, the brands — and nothing else. The
     standfirst is gone on a phone: four lines between the headline and the
     button pushed Request Service to the fold on an SE, and every word of
     it is said again under "Why choose us" one screen down. The LG seal is
     gone too: the header now says "LG Certified" beside the logo, two
     inches above, and the strip below opens with LG's wordmark — a third LG
     mark in the same screen was a tic, not reassurance. The two actions are
     the same height (the Call button used to be 10px taller than the
     primary, from its own padding). */
  .hero { padding-block: 2rem 0; }
  /* The foot strip measures ~118px at the phone's logo scale; 8.5rem
     reserves that plus air so its gradient starts below the buttons. */
  .hero:has(.hero__foot) { padding-bottom: 8.5rem; }
  .hero__title--display { font-size: clamp(1.875rem, 1.2rem + 4.3vw, 2.5rem); max-width: none; }
  .hero__lead { display: none; }
  .hero__actions { display: grid; grid-template-columns: 1fr; gap: .6rem; margin-top: 1.35rem; }
  .hero__actions .cta, .hero__actions .cta-secondary {
    width: 100%; min-height: 52px; padding-block: 0; white-space: nowrap;
  }
  /* The phone number is the long label. 17px from 320 to ~375, rising to the
     CTA token by 480 — one line at every width, and a size the primary above
     it is never visibly smaller than. */
  .hero__actions .cta-secondary { font-size: clamp(1.0625rem, .5rem + 2.4vw, var(--cta-font-size)); }
  .hero__badge { display: none; }
  .hero__foot { padding-block: 1.25rem 1rem; }
  .partners__inner { gap: .85rem; }
  .partners__logo { height: calc(var(--logo-h, 40px) * .7); margin-inline-end: 2rem; }

  /* ── Why choose us: rows, not boxes ─────────────────────────────────────
     Seven stacked cards with the icon above the title ran to a thousand
     pixels. As rows — icon left, title and line right — the seven fit in
     half that, and each is still a 60px target. */
  .whyus { padding-block: 2.25rem 2rem; }
  .whyus__head { gap: .35rem; margin-bottom: 1.15rem; }
  /* The label above the heading goes: "Why choose us" over "Fixed right, in
     your home" is the same thought twice, and a phone pays for every line. */
  .whyus__eyebrow { display: none; }
  .whyus__grid { gap: .5rem; }
  .whycard {
    display: grid; grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas: "mark title" "mark sub";
    column-gap: .85rem; row-gap: .1rem; align-items: center;
    padding: .7rem .85rem; border-radius: 12px;
  }
  .whycard__mark { grid-area: mark; width: 42px; height: 42px; margin: 0; border-radius: 11px; }
  .whycard__mark svg { width: 22px; height: 22px; }
  .whycard__lglogo { width: 24px; height: 24px; }
  .whycard__title { grid-area: title; font-size: 1rem; align-self: end; }
  .whycard__sub { grid-area: sub; margin-top: 0; font-size: .8125rem; align-self: start; }
  .whycard:not(:has(.whycard__sub)) .whycard__title { align-self: center; }

  /* ── Sections ──────────────────────────────────────────────────────── */
  .section { padding-block: 2.25rem; }
  .section__head { margin-bottom: 1.1rem; }
  .section__lead { font-size: 1rem; margin-top: .45rem; }
  .section__note { margin-top: 1.25rem; }

  /* ── How it works: the same timeline, a size down ─────────────────── */
  .steps { gap: 1.35rem; }
  .step { gap: .85rem; }
  .step__badge { width: 3.25rem; height: 3.25rem; }
  .step__icon { width: 1.7rem; height: 1.7rem; }
  .step:not(:last-child)::before,
  .step:not(:last-child)::after { left: calc(1.625rem - 1px); top: 3.55rem; bottom: -1.35rem; }
  .step__title { font-size: 1rem; margin-bottom: .25rem; }
  .step__text { font-size: .9375rem; line-height: 1.5; }
  .steps__tail { margin-top: 1.5rem; }

  /* ── Where we work: two across ──────────────────────────────────────
     Ten cards one-across was 1,700px of scrolling for a list of names. Two
     across at 7.25rem keeps the photograph and the name and halves the
     page. The areas page's cards carry ZIP chips and stay one across. */
  .city-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .6rem; }
  .city-card { min-height: 7.25rem; padding: .75rem .8rem; border-radius: 12px; gap: .15rem; }
  .city-card__name { font-size: 1rem; }
  .city-card__meta { font-size: .75rem; }
  .city-card__tag { top: .5rem; left: .5rem; padding: .18rem .5rem; font-size: .6875rem; }
  .prose--index ul.area-grid { grid-template-columns: 1fr; gap: .6rem; }
  .prose--index .area-grid .city-card--zips { min-height: 0; padding: 2.4rem .9rem .9rem; }

  /* ── CTA band: both actions full width ───────────────────────────── */
  .ctaband { padding-block: 2.25rem; }
  .ctaband__inner { gap: 1rem; }
  /* Heading and the two buttons. The sentence under the heading describes
     the form that is one tap away, to a reader who has just read the page. */
  .ctaband__body { display: none; }
  .ctaband__actions { display: grid; grid-template-columns: 1fr; gap: .6rem; width: 100%; }
  .ctaband__actions .cta, .ctaband__actions .cta-secondary { width: 100%; }

  /* ── Page header: the title alone ──────────────────────────────────
     The eyebrow and the standfirst are desktop furniture. On a phone the
     band is the H1 on navy and nothing else, so the page's own opening
     paragraph — which says the same thing at reading size — starts inside
     the first screen instead of under 80px of preamble. */
  .phead { padding-block: 1.35rem 1.45rem; }
  .phead__eyebrow, .phead__lead { display: none; }
  .phead__title { font-size: clamp(1.5rem, 1.1rem + 2.5vw, 1.75rem); }

  /* ── Index cards (services): rows ──────────────────────────────────
     Icon left, title and description right, instead of the icon on its
     own line above a title above a paragraph. Only cards WITH an icon
     take the grid; a plain link card keeps the stacked layout. */
  .idx, .page, .legal { padding-block: 1.5rem; }
  .prose--index ul.idx__list { gap: .6rem; }
  .prose--index ul.idx__list > li:has(.idx__icon) {
    display: grid; grid-template-columns: auto minmax(0, 1fr);
    column-gap: .85rem; row-gap: .1rem; align-items: start;
    padding: .8rem .9rem;
  }
  /* The icon sits with the TITLE, not centred on the card: centred, it
     floated beside the description and the title looked like a caption
     above a row it did not belong to. */
  .prose--index ul.idx__list > li:has(.idx__icon) .idx__icon {
    grid-row: 1 / 3; align-self: start; margin: .1rem 0 0;
    width: 2.25rem; height: 2.25rem;
  }
  .prose--index ul.idx__list > li:has(.idx__icon) > a:first-of-type { margin-bottom: 0; }
  .prose--index ul.idx__list > li { font-size: .9375rem; line-height: 1.45; }

  /* ── Legal contents: rows a thumb can hit ─────────────────────────── */
  .legal__toc { padding: 1rem 1.1rem 1.1rem; margin-bottom: 1.75rem; }
  .legal__toclist a { min-height: 44px; align-items: center; padding: .2rem .4rem .2rem 0; }

  /* ── Reading pages ─────────────────────────────────────────────────── */
  .prose h2 { font-size: 1.375rem; margin-top: 2rem; padding-top: 1.4rem; }
  .prose h3 { font-size: 1.1875rem; margin-top: 1.6rem; }
  .prose > .artsec { margin-top: 2rem; padding-top: 1.4rem; }
  .artsec__icon { width: 2.1rem; height: 2.1rem; border-radius: 9px; }
  .artsec__icon svg { width: 19px; height: 19px; }
  .fcard { padding: .85rem .95rem; gap: .75rem; }
  .fcard__icon { width: 2.2rem; height: 2.2rem; }
  .xtile { padding: .9rem .95rem; column-gap: .8rem; }
  .prose blockquote { padding: .85rem 1rem; }

  /* ── Brand row (/about): a strip that scrolls, not a grid that wraps ──
     Seven wordmarks of seven proportions wrapped into a ragged three, three
     and one, with Amana alone on the last row. On a phone the row is one
     line that scrolls under the thumb, faded at both edges so the half mark
     at the right says "more". The marks scale to 85% through --logo-h, and
     the desktop row's ink-tuned margins are re-tuned here for that scale:
     Samsung's 72px canvas carries ~31px of blank above and below its ink. */
  .prose .brandlogos,
  .brandlogos {
    flex-wrap: nowrap; gap: 0 1.75rem;
    margin: -.75rem calc(-1 * var(--gutter, 20px)) -1.25rem;
    padding-inline: var(--gutter, 20px);
    overflow-x: auto; overscroll-behavior-x: contain;
    scroll-snap-type: x proximity; scroll-padding-inline: var(--gutter, 20px);
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--gutter, 20px), #000 calc(100% - 2.5rem), transparent 100%);
    mask-image: linear-gradient(to right, transparent 0, #000 var(--gutter, 20px), #000 calc(100% - 2.5rem), transparent 100%);
  }
  .brandlogos::-webkit-scrollbar { display: none; }
  .prose .brandlogos__item,
  .brandlogos__item { flex: 0 0 auto; scroll-snap-align: start; }
  .brandlogos__img { height: calc(var(--logo-h, 20px) * .85); }

  /* ── Service page ──────────────────────────────────────────────────── */
  .svc { padding-block: 1.5rem; }
  .svc__grid { gap: 1.75rem; }
  .svc__card { padding: 1.1rem; }
  .svc__cardlead { margin-bottom: .8rem; }
  .svc__cardcta { margin-top: 1rem; }

  /* ── Notices ───────────────────────────────────────────────────────── */
  .notice { padding: .9rem 1rem; gap: .7rem; }
  .notice__title { font-size: 1rem; }

  /* ── Footer ────────────────────────────────────────────────────────── */
  .ftr { padding-top: 2.25rem; padding-bottom: calc(1.25rem + env(safe-area-inset-bottom, 0px)); }
  .ftr__top { gap: 1.75rem; }
  .ftr__lockup { gap: .5rem; margin-bottom: .75rem; }
  .ftr__lockup img:first-child { height: 92px; width: auto; }
  .ftr__lgcert { height: 84px; margin-left: .35rem; }
  .ftr__tag { margin-top: .75rem; font-size: 1rem; }
  .ftr__contact { gap: .3rem; margin: .85rem 0 1rem; }
  .ftr__contact .cta { width: 100%; }
  .ftr__tel { font-size: 1.25rem; }
  .ftr__hours { font-size: .9375rem; white-space: normal; }
  .ftr__social { display: flex; gap: .5rem; margin: .35rem 0 0; }
  .ftr__wa, .ftr__fb { width: 44px; height: 44px; }
  .ftr__cols { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.25rem 1rem; }
  .ftr__coltitle { margin-bottom: .4rem; }
  .ftr__col a { min-height: 44px; padding: .3rem 0; }
  .ftr__legal { margin-top: 1.75rem; padding-top: 1.1rem; }
}

/* ── Tablet: the menu shows, the link row does not ────────────────────────
   Between the phone and the desktop row the header keeps its CTA and gains the
   Menu button; nothing else changes shape. */
@media (min-width: 700px) and (max-width: 1099px) {
  .hdr__actions { gap: .6rem; }
}
