/* =========================================================================
 THE HARDWOOD GUYS — COMPONENT LIBRARY
 Depends on tokens.css (load it first). Every color/spacing/type value
 below is a var(--semantic-token) reference — no raw hex, no raw px
 color literal, ever. That is what makes a retone a one-file change.
 ========================================================================= */

/* ---------------------------------------------------------------------
 0. RESET / BASE
 --------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html {
 -webkit-text-size-adjust: 100%;
 scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
 html { scroll-behavior: auto; }
}

body {
 margin: 0;
 font-family: var(--font-body);
 font-size: var(--text-base);
 line-height: var(--leading-normal);
 color: var(--color-text-body);
 background: var(--color-bg-page);
 font-variant-numeric: normal;
}

img, svg { max-width: 100%; display: block; }
button, input, select, textarea { font: inherit; color: inherit; }
ul, ol { padding: 0; margin: 0; list-style: none; }
h1, h2, h3, h4, p, figure, blockquote { margin: 0; }
a { color: inherit; text-decoration: none; }

h1, h2, h3, h4 {
 font-family: var(--font-heading);
 color: var(--color-text-heading);
 line-height: var(--leading-tight);
 font-weight: var(--weight-semibold);
}

.tabular-nums { font-variant-numeric: tabular-nums; }

/* Universal focus-visible treatment — solid outline, never a glow-only
 box-shadow, so it survives on every background in the system. */
:focus-visible {
 outline: 3px solid var(--color-focus-ring);
 outline-offset: 2px;
 border-radius: var(--radius-sm);
}
.site-header :focus-visible,
.site-header__mobile-nav :focus-visible,
.mobile-cta-bar :focus-visible,
.site-footer :focus-visible,
.hero :focus-visible {
 outline-color: var(--color-focus-ring-inverse);
}

/* ---------------------------------------------------------------------
 1. LAYOUT UTILITIES
 --------------------------------------------------------------------- */
.container {
 max-width: var(--container-max);
 margin-inline: auto;
 padding-inline: var(--container-pad);
}

.section {
 padding-block: var(--space-16);
}
.section--alt { background: var(--color-bg-page-alt); }
.section--dark { background: var(--color-bg-header); color: var(--color-text-inverse); }

.section-head {
 max-width: 640px;
 margin-bottom: var(--space-8);
}
.section-head p { color: var(--color-text-muted); margin-top: var(--space-3); }

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

/* Skip link — visible on keyboard focus, WCAG 2.4.1 */
.skip-link {
 position: absolute;
 top: -60px;
 left: var(--space-4);
 z-index: var(--z-skip-link);
 background: var(--color-cta-bg);
 color: var(--color-cta-text);
 padding: var(--space-3) var(--space-5);
 border-radius: 0 0 var(--radius-base) var(--radius-base);
 font-weight: var(--weight-semibold);
 transition: top var(--motion-base) var(--motion-ease);
}
.skip-link:focus {
 top: 0;
}

/* ---------------------------------------------------------------------
 2. BUTTONS
 --------------------------------------------------------------------- */
.btn {
 display: inline-flex;
 align-items: center;
 justify-content: center;
 gap: var(--space-2);
 min-height: var(--touch-target-min);
 padding: var(--space-3) var(--space-6);
 border-radius: var(--radius-base);
 font-family: var(--font-body);
 font-weight: var(--weight-semibold);
 font-size: var(--text-base);
 border: 2px solid transparent;
 cursor: pointer;
 transition: background-color var(--motion-base) var(--motion-ease),
 color var(--motion-base) var(--motion-ease),
 box-shadow var(--motion-base) var(--motion-ease),
 border-color var(--motion-base) var(--motion-ease);
 text-align: center;
}
.btn--lg { padding: var(--space-4) var(--space-8); font-size: var(--text-md); }
.btn--sm { padding: var(--space-2) var(--space-4); font-size: var(--text-sm); min-height: 36px; }
.btn--block { width: 100%; }

.btn--primary {
 background: var(--color-cta-bg);
 color: var(--color-cta-text);
}
.btn--primary:hover { background: var(--color-cta-bg-hover); box-shadow: var(--shadow-md); }
.btn--primary:disabled {
 background: var(--color-cta-bg-disabled);
 color: var(--color-cta-text-disabled);
 cursor: not-allowed;
}

.btn--secondary {
 background: var(--color-btn-secondary-bg);
 color: var(--color-btn-secondary-text);
 border-color: var(--color-btn-secondary-border);
}
.btn--secondary:hover {
 background: var(--color-btn-secondary-hover-bg);
 color: var(--color-btn-secondary-hover-text);
}

/* Outline button — used on top of the hero photo scrim (dark). Hover
 fills solid rather than swapping to red, because red-on-dark is one
 of the documented contrast traps. */
.btn--outline {
 background: transparent;
 color: var(--color-btn-outline-text);
 border-color: var(--color-btn-outline-border);
}
.btn--outline:hover {
 background: var(--color-btn-outline-hover-bg);
 color: var(--color-btn-outline-hover-text);
}

.btn svg { width: 1.15em; height: 1.15em; flex: none; }

/* ---------------------------------------------------------------------
 3. HEADER
 --------------------------------------------------------------------- */
.site-header {
 position: sticky;
 top: 0;
 z-index: var(--z-header);
 background: var(--color-bg-header);
 color: var(--color-text-inverse);
 box-shadow: var(--shadow-header);
}

.site-header__bar {
 display: flex;
 align-items: center;
 gap: var(--space-6);
 min-height: 72px;
 padding-block: var(--space-2);
}

.site-header__logo {
 display: flex;
 align-items: center;
 gap: var(--space-2);
 flex: none;
 max-width: 260px;
 color: var(--color-text-inverse);
}
/* The mark is a near-square badge (1391x1175), not a wide wordmark — it needs real
   height to read. David asked for it prominently displayed. */
.site-header__logo-mark { height: 92px; width: auto; display: block; flex: none; }
@media (max-width: 900px) {
  .site-header__logo-mark { height: 76px; }
}
@media (max-width: 480px) {
  .site-header__logo-mark { height: 58px; }
}

/* The bar carries logo + 6 nav items + phone + hours + CTA. Below 1400px the
   decorative tagline is the first thing to go — the crest already says who we
   are, and the footer NAP block repeats the location. */
@media (max-width: 1400px) {
  .site-header__logo-tag { display: none; }
}
/* Keep a real gutter between the last nav item and the phone block. */
.site-header__bar { column-gap: var(--space-8); }

.site-header__logo-word {
 font-family: var(--font-heading);
 font-weight: var(--weight-bold);
 font-size: 1.35rem;
 line-height: 1.1;
 letter-spacing: var(--tracking-tight);
}
.site-header__logo-tag {
 display: block;
 white-space: nowrap;
 font-family: var(--font-body);
 font-size: var(--text-xs);
 font-weight: var(--weight-medium);
 letter-spacing: var(--tracking-wide);
 text-transform: uppercase;
 color: var(--color-text-inverse);
 opacity: 0.85;
}

.site-nav { flex: 1; display: none; min-width: 0; }
.site-nav ul { display: flex; gap: var(--space-5); flex-wrap: nowrap; white-space: nowrap; }
@media (max-width: 1100px) {
  .site-nav ul { gap: var(--space-4); }
}
.site-nav a {
 display: inline-block;
 padding: var(--space-2) 0;
 font-weight: var(--weight-medium);
 color: var(--color-nav-link);
 border-bottom: 2px solid transparent;
}
.site-nav a:hover,
.site-nav a:focus-visible {
 text-decoration: underline;
 text-underline-offset: 4px;
}

.site-header__meta {
 display: none;
 flex-direction: column;
 align-items: flex-end;
 line-height: 1.3;
 flex: none;
}
.site-header__phone {
 font-weight: var(--weight-bold);
 font-size: var(--text-md);
 color: var(--color-text-inverse);
}
.site-header__phone:hover,
.site-header__phone:focus-visible { text-decoration: underline; }
.site-header__hours {
 font-size: var(--text-xs);
 color: var(--color-text-inverse);
 opacity: 0.85;
}
.site-header__cta { display: none; flex: none; }

.hamburger {
 flex: none;
 margin-left: auto;
 display: inline-flex;
 align-items: center;
 justify-content: center;
 width: var(--touch-target-min);
 height: var(--touch-target-min);
 background: transparent;
 border: 2px solid transparent;
 border-radius: var(--radius-base);
 color: var(--color-text-inverse);
 cursor: pointer;
}
.hamburger svg { width: 26px; height: 26px; }
.hamburger .icon-close { display: none; }
.hamburger[aria-expanded="true"] .icon-menu { display: none; }
.hamburger[aria-expanded="true"] .icon-close { display: block; }

.site-header__mobile-nav {
 position: fixed;
 inset: 72px 0 0 0;
 background: var(--color-bg-header);
 z-index: var(--z-mobile-nav);
 overflow-y: auto;
 padding: var(--space-6) var(--container-pad) var(--space-24);
}
.site-header__mobile-nav[hidden] { display: none; }

.site-header__mobile-nav ul { display: flex; flex-direction: column; }
.site-header__mobile-nav a {
 display: block;
 padding: var(--space-4) 0;
 font-family: var(--font-heading);
 font-size: var(--text-lg);
 font-weight: var(--weight-semibold);
 color: var(--color-text-inverse);
 border-bottom: 1px solid rgba(255, 255, 255, 0.18);
}
.site-header__mobile-nav a:hover,
.site-header__mobile-nav a:focus-visible { text-decoration: underline; }

.site-header__mobile-meta {
 margin-top: var(--space-8);
 padding-top: var(--space-8);
 border-top: 1px solid rgba(255, 255, 255, 0.25);
}
.site-header__mobile-meta .site-header__phone { font-size: var(--text-xl); }
.site-header__mobile-meta .site-header__hours { display: block; margin-top: var(--space-2); }

@media (min-width: 1024px) {
 .site-nav { display: block; }
 .site-header__meta { display: flex; }
 .site-header__cta { display: inline-flex; }
 .hamburger { display: none; }
}

/* ---------------------------------------------------------------------
 4. MOBILE STICKY BOTTOM CTA BAR
 The single most important conversion element on the site: it is the
 only UI guaranteed to be on-screen no matter how far a phone user has
 scrolled. Two 44px+ targets, safe-area aware, always visible.
 --------------------------------------------------------------------- */
/* The bar has THREE children on every page -- call, text, estimate -- and
   the column count here must always track that. This mismatched to a
   2-column grid once (call + estimate only, text forgotten) and the bar
   silently wrapped to two rows on every page below 1024px, covering page
   content. Call and text are equal-weight "reach us" actions; estimate
   stays widest since it's the red primary conversion action. */
.mobile-cta-bar {
 position: fixed;
 left: 0; right: 0; bottom: 0;
 z-index: var(--z-bottom-bar);
 display: grid;
 grid-template-columns: 1fr 1fr 1.3fr;
 gap: 1px;
 background: var(--color-border-default);
 box-shadow: var(--shadow-bottom-bar);
 padding-bottom: env(safe-area-inset-bottom, 0);
}
.mobile-cta-bar a {
 display: flex;
 align-items: center;
 justify-content: center;
 gap: var(--space-2);
 min-height: var(--touch-target-min);
 padding-block: var(--space-4);
 font-weight: var(--weight-bold);
 font-size: var(--text-base);
}
.mobile-cta-bar__call {
 background: var(--color-bg-header);
 color: var(--color-text-inverse);
}
.mobile-cta-bar__call:hover,
.mobile-cta-bar__call:focus-visible { text-decoration: underline; }
.mobile-cta-bar__text {
 background: var(--color-bg-header);
 color: var(--color-text-inverse);
}
.mobile-cta-bar__text:hover,
.mobile-cta-bar__text:focus-visible { text-decoration: underline; }
.mobile-cta-bar__estimate {
 background: var(--color-cta-bg);
 color: var(--color-cta-text);
}
.mobile-cta-bar__estimate:hover,
.mobile-cta-bar__estimate:focus-visible { background: var(--color-cta-bg-hover); }
.mobile-cta-bar svg { width: 20px; height: 20px; }

/* Reserve space so the fixed bar never covers the last content/footer.
   This must track the bar's actual rendered height (~62px at 1 row, once
   the column count above matches the child count) -- it was left at 64px
   from a previous 2-row/122px bar and happened to still clear a 1-row bar,
   but if the bar ever grows again this reserve has to grow with it. */
.has-mobile-cta-bar { padding-bottom: 64px; }

@media (min-width: 1024px) {
 .mobile-cta-bar { display: none; }
 .has-mobile-cta-bar { padding-bottom: 0; }
}

/* ---------------------------------------------------------------------
 5. PLACEHOLDER PHOTO BLOCK
 Every "photo" in this system is a placeholder — never a hotlinked
 live-site image, never an external request. The pattern + label make
 it obvious in review that real photography drops in later.
 --------------------------------------------------------------------- */
.photo-placeholder {
 position: relative;
 display: flex;
 align-items: flex-end;
 overflow: hidden;
 background:
 repeating-linear-gradient(135deg,
 rgba(var(--navy-rgb), 0.10) 0px, rgba(var(--navy-rgb), 0.10) 2px,
 transparent 2px, transparent 14px),
 var(--color-bg-page-alt);
 border: 1px solid var(--color-border-default);
 color: var(--color-text-muted);
 aspect-ratio: 4 / 3;
}
.photo-placeholder--wide { aspect-ratio: 16 / 9; }
.photo-placeholder--tall { aspect-ratio: 3 / 4; }
.photo-placeholder--square { aspect-ratio: 1 / 1; }
.photo-placeholder__label {
 display: flex;
 align-items: center;
 gap: var(--space-2);
 width: 100%;
 padding: var(--space-3);
 background: linear-gradient(to top, rgba(var(--near-black-rgb), 0.55), transparent);
 color: var(--white);
 font-size: var(--text-xs);
 font-weight: var(--weight-medium);
}
.photo-placeholder__label svg { width: 18px; height: 18px; flex: none; }

/* ---------------------------------------------------------------------
 6. HERO
 --------------------------------------------------------------------- */
.hero {
 position: relative;
 color: var(--color-text-inverse);
 /* .hero__media runs a Ken Burns scale/translate (see header-final.css's
    thg-kenburns keyframes) that, by the end of the 20s animation, grows
    .hero__media's own box to roughly 108% of .hero's width and shifts it
    left of 0 -- e.g. a 768px-wide hero measures its media at left:-43px
    width:829px once fully scaled. overflow:hidden is what keeps that
    contained; overflow:clip is added as a stricter guarantee (some engines
    have handled transformed-descendant overflow differently under plain
    `hidden`) that a scaled/translated child can never register as real
    page-level horizontal scroll. Keep both declarations.
    Verified live: with the animation forced to its end state,
    document.documentElement.scrollWidth still equals clientWidth. */
 overflow: hidden;
 overflow: clip;
}
.hero__media {
 position: absolute;
 inset: 0;
 background:
 linear-gradient(rgba(var(--near-black-rgb), 0.62), rgba(var(--near-black-rgb), 0.5)),
 repeating-linear-gradient(120deg,
 rgba(255, 255, 255, 0.06) 0px, rgba(255, 255, 255, 0.06) 2px,
 transparent 2px, transparent 18px),
 var(--color-bg-header);
 z-index: 0;
}
.hero__media-tag {
 position: absolute;
 bottom: var(--space-4);
 right: var(--space-4);
 z-index: 1;
 font-size: var(--text-xs);
 color: var(--white);
 opacity: 0.7;
 background: rgba(var(--near-black-rgb), 0.4);
 padding: var(--space-1) var(--space-3);
 border-radius: var(--radius-full);
}
.hero__content {
 position: relative;
 z-index: 1;
 padding-block: var(--space-20) var(--space-16);
 max-width: 760px;
}
.hero__eyebrow {
 font-size: var(--text-sm);
 font-weight: var(--weight-semibold);
 letter-spacing: var(--tracking-wide);
 text-transform: uppercase;
 color: var(--white);
 opacity: 0.9;
 margin-bottom: var(--space-4);
}
.hero__title {
 font-size: var(--text-4xl);
 color: var(--white);
 letter-spacing: var(--tracking-tight);
 margin-bottom: var(--space-5);
}
.hero__subtitle {
 font-size: var(--text-md);
 line-height: var(--leading-relaxed);
 color: var(--white);
 max-width: 56ch;
 margin-bottom: var(--space-8);
}

.proof-chips {
 display: flex;
 flex-wrap: wrap;
 gap: var(--space-3);
 margin-bottom: var(--space-8);
}
.proof-chip {
 display: inline-flex;
 align-items: center;
 gap: var(--space-2);
 padding: var(--space-2) var(--space-4);
 border-radius: var(--radius-full);
 background: rgba(255, 255, 255, 0.12);
 border: 1px solid rgba(255, 255, 255, 0.35);
 font-size: var(--text-sm);
 font-weight: var(--weight-medium);
 color: var(--white);
}
.proof-chip svg { width: 18px; height: 18px; flex: none; }
/* Gold, sparingly: only the review-star and TV icons, per brand
 direction. Icon-only so chip text stays white for readability against
 the photo scrim (gold text on white/red both fail contrast — see
 tokens.css known traps). */
.proof-chip__icon--gold { color: var(--color-rating-star); }

.hero__actions {
 display: flex;
 flex-wrap: wrap;
 gap: var(--space-4);
}

/* Mobile-only hero photo caption (site/index.html). Job name + stain/finish,
   captioned the same way as the stain & finish carousel (section 20 below:
   .sgal__cap/.sgal__what/.sgal__spec) so the two read as one system --
   same heading font for the "what", same small-caps gold treatment for the
   spec. Hidden here by default: on desktop the hero copy already sits over
   the photo (kept as-is, David's call), so this caption only ever needs to
   show up under the 767px breakpoint. That breakpoint switch, and the
   positioning against the mobile banner's height, live in site/index.html's
   own mobile hero rules, since --hero-banner-h is defined there. */
.hero__photo-cap {
 display: none;
 position: absolute;
 z-index: 1;             /* .hero__scrim is also absolute and comes LATER in the DOM; without this it paints over the caption */
 margin: 0;
 flex-wrap: wrap;
 align-items: baseline;
 gap: 0 var(--space-2);
 color: var(--color-text-inverse);
 /* A shadow, not a panel. David's standing rule is no dark box behind hero
    text -- brighten the photo instead. A shadow buys legibility where the
    caption crosses a sunlit patch of floor without laying a slab over the
    photograph. */
 text-shadow: 0 1px 3px rgba(var(--near-black-rgb), 0.75);
}
/* Small on purpose. This answers "what floor am I looking at?" for the one
   visitor in twenty who wonders; it is a photo credit, not a headline, and it
   must not eat the picture or the page. */
.hero__photo-cap-what { font-family: var(--font-heading); font-size: var(--text-sm); font-weight: var(--weight-semibold); }
.hero__photo-cap-spec {
 font-size: var(--text-xs);
 font-weight: var(--weight-semibold);
 letter-spacing: var(--tracking-wide);
 text-transform: uppercase;
 color: var(--gold);            /* gold on navy -- the verified pairing, see tokens.css section 1 */
}

/* ---------------------------------------------------------------------
 7. REVIEW BAND
 --------------------------------------------------------------------- */
.reviews__head {
 display: flex;
 flex-wrap: wrap;
 align-items: center;
 justify-content: space-between;
 gap: var(--space-4);
 margin-bottom: var(--space-8);
}
.review-badge {
 display: inline-flex;
 align-items: center;
 gap: var(--space-3);
 padding: var(--space-3) var(--space-5);
 background: var(--color-bg-card);
 border: 1px solid var(--color-border-default);
 border-radius: var(--radius-base);
 box-shadow: var(--shadow-sm);
}
.review-badge__stars { display: flex; gap: 2px; color: var(--color-rating-star); }
.review-badge__stars svg { width: 18px; height: 18px; }
.review-badge__text { font-size: var(--text-sm); color: var(--color-text-body); font-weight: var(--weight-medium); }
.review-badge__text strong { display: block; font-size: var(--text-base); color: var(--color-text-heading); }

.reviews__list {
 display: grid;
 gap: var(--space-6);
 grid-template-columns: 1fr;
}
.review-card {
 background: var(--color-bg-card);
 border: 1px solid var(--color-border-default);
 border-radius: var(--radius-lg);
 padding: var(--space-6);
 box-shadow: var(--shadow-sm);
 display: flex;
 flex-direction: column;
 gap: var(--space-4);
}
.review-card__stars { display: flex; gap: 2px; color: var(--color-rating-star); }
.review-card__stars svg { width: 16px; height: 16px; }
.review-card blockquote {
 font-size: var(--text-base);
 line-height: var(--leading-relaxed);
 color: var(--color-text-body);
}
.review-card__author {
 font-weight: var(--weight-semibold);
 color: var(--color-text-heading);
 font-size: var(--text-sm);
}
.review-card__location { color: var(--color-text-muted); font-weight: var(--weight-regular); }

/* Platform mark on the author line, so a reader can see at a glance whether a
 review came from Google or Nextdoor. Nominative use -- it identifies where
 the review actually lives, which is exactly what these marks are for.

 Sized by HEIGHT with width:auto: the Google G is square (48x48) but the
 Nextdoor house is 110x89, so a shared square box would have squashed the
 house. Both sit on the same 18px cap line instead. */
.review-card__src {
 display: inline-block;
 height: 18px;
 width: auto;
 vertical-align: -3px;
 margin-right: var(--space-2);
 object-fit: contain;
}

.reviews__stat {
 margin-top: var(--space-8);
 text-align: center;
 color: var(--color-text-muted);
 font-size: var(--text-sm);
}
.reviews__stat strong { color: var(--color-text-heading); font-family: var(--font-heading); font-size: var(--text-lg); }

@media (min-width: 768px) {
 .reviews__list { grid-template-columns: repeat(3, 1fr); }
}

/* ---------------------------------------------------------------------
 8. QUOTE FORM
 --------------------------------------------------------------------- */
.quote-form-wrap {
 background: var(--color-bg-card);
 border: 1px solid var(--color-border-default);
 border-radius: var(--radius-lg);
 box-shadow: var(--shadow-md);
 padding: var(--space-8);
 max-width: 720px;
 margin-inline: auto;
}

.form-error-summary {
 display: none;
 background: var(--color-bg-error);
 border: 1px solid var(--color-border-input-error);
 border-left: 4px solid var(--color-text-error);
 border-radius: var(--radius-base);
 padding: var(--space-4) var(--space-5);
 margin-bottom: var(--space-6);
 color: var(--color-text-error);
 font-weight: var(--weight-semibold);
 gap: var(--space-2);
 align-items: flex-start;
}
.form-error-summary.is-visible { display: flex; }
.form-error-summary svg { width: 22px; height: 22px; flex: none; margin-top: 2px; }
.form-error-summary a { text-decoration: underline; color: var(--color-text-error); }

.form-grid {
 display: grid;
 gap: var(--space-5);
 grid-template-columns: 1fr;
}
.form-field { display: flex; flex-direction: column; gap: var(--space-2); }
.form-field--span2 { grid-column: 1 / -1; }

.form-field label {
 font-weight: var(--weight-semibold);
 font-size: var(--text-sm);
 color: var(--color-text-heading);
}
.form-field .optional-tag {
 font-weight: var(--weight-regular);
 color: var(--color-text-muted);
 text-transform: none;
}
.form-field input[type="text"],
.form-field input[type="tel"],
.form-field input[type="email"],
.form-field input[type="number"],
.form-field select,
.form-field textarea {
 min-height: var(--touch-target-min);
 padding: var(--space-3) var(--space-4);
 border: 2px solid var(--color-border-input);
 border-radius: var(--radius-base);
 background: var(--color-bg-input);
 font-size: var(--text-base);
 color: var(--color-text-body);
}
.form-field textarea { min-height: 120px; resize: vertical; }
.form-field select {
 appearance: none;
 background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236B6B6B' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
 background-repeat: no-repeat;
 background-position: right var(--space-4) center;
 background-size: 18px;
 padding-right: var(--space-10);
}
.form-field input:focus-visible,
.form-field select:focus-visible,
.form-field textarea:focus-visible {
 outline: 3px solid var(--color-focus-ring);
 outline-offset: 1px;
 border-color: var(--color-focus-ring);
}
.form-field input[aria-invalid="true"],
.form-field select[aria-invalid="true"],
.form-field textarea[aria-invalid="true"] {
 border-color: var(--color-border-input-error);
}
.field-error {
 display: none;
 align-items: center;
 gap: var(--space-1);
 font-size: var(--text-sm);
 color: var(--color-text-error);
 font-weight: var(--weight-medium);
}
.field-error.is-visible { display: flex; }
.field-error svg { width: 16px; height: 16px; flex: none; }
.field-hint { font-size: var(--text-sm); color: var(--color-text-muted); }

.form-checkbox {
 display: flex;
 align-items: flex-start;
 gap: var(--space-3);
}
.form-checkbox input[type="checkbox"] {
 width: 24px; height: 24px;
 flex: none;
 margin-top: 2px;
 accent-color: var(--color-cta-bg);
}
.form-checkbox label { font-weight: var(--weight-regular); font-size: var(--text-sm); color: var(--color-text-body); }

.form-actions { margin-top: var(--space-2); }
.form-fine-print {
 font-size: var(--text-xs);
 color: var(--color-text-muted);
 margin-top: var(--space-3);
}

.form-success {
 display: none;
 text-align: center;
 padding: var(--space-10) var(--space-4);
}
.form-success.is-visible { display: block; }
.form-success__icon {
 width: 56px; height: 56px;
 margin-inline: auto var(--space-4);
 color: var(--color-text-success);
 background: var(--color-bg-success);
 border: 2px solid var(--color-text-heading);
 border-radius: var(--radius-full);
 display: flex; align-items: center; justify-content: center;
}
.form-success h3 { margin-bottom: var(--space-2); }
.form-success p { color: var(--color-text-muted); max-width: 46ch; margin-inline: auto; }
.quote-form.is-submitted .form-grid,
.quote-form.is-submitted .form-checkbox,
.quote-form.is-submitted .form-actions,
.quote-form.is-submitted .form-fine-print { display: none; }

@media (min-width: 640px) {
 .form-grid { grid-template-columns: 1fr 1fr; }
}

/* ---------------------------------------------------------------------
 9. VIDEO STRIP (YouTube facade — real iframe only injected on click)
 --------------------------------------------------------------------- */
.video-grid {
 display: grid;
 gap: var(--space-6);
 grid-template-columns: 1fr;
}
.video-card { display: flex; flex-direction: column; gap: var(--space-3); }
.video-thumb {
 position: relative;
 width: 100%;
 aspect-ratio: 16 / 9;
 border-radius: var(--radius-base);
 overflow: hidden;
 border: 0;
 padding: 0;
 cursor: pointer;
 background:
 linear-gradient(rgba(var(--near-black-rgb), 0.35), rgba(var(--near-black-rgb), 0.55)),
 repeating-linear-gradient(135deg,
 rgba(255, 255, 255, 0.08) 0px, rgba(255, 255, 255, 0.08) 2px,
 transparent 2px, transparent 16px),
 var(--color-bg-header);
}
.video-thumb__play {
 position: absolute;
 inset: 0;
 display: flex;
 align-items: center;
 justify-content: center;
}
.video-thumb__play svg {
 width: 64px; height: 64px;
 color: var(--white);
 filter: drop-shadow(0 2px 6px rgba(var(--near-black-rgb), 0.4));
}
.video-thumb:hover .video-thumb__play svg,
.video-thumb:focus-visible .video-thumb__play svg { transform: scale(1.08); }
.video-thumb__play svg { transition: transform var(--motion-base) var(--motion-ease); }
.video-thumb__duration {
 position: absolute;
 bottom: var(--space-2);
 right: var(--space-2);
 background: rgba(var(--near-black-rgb), 0.75);
 color: var(--white);
 font-size: var(--text-xs);
 font-weight: var(--weight-semibold);
 padding: 2px var(--space-2);
 border-radius: var(--radius-sm);
}
.video-card__title {
 font-size: var(--text-sm);
 font-weight: var(--weight-medium);
 color: var(--color-text-body);
 line-height: var(--leading-snug);
}
.video-embed-frame {
 width: 100%;
 aspect-ratio: 16 / 9;
 border: 0;
 border-radius: var(--radius-base);
}

@media (min-width: 768px) {
 .video-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1024px) {
 .video-grid { grid-template-columns: repeat(4, 1fr); }
}

/* ---------------------------------------------------------------------
 10. PROCESS STRIP
 --------------------------------------------------------------------- */
.process-grid {
 display: grid;
 gap: var(--space-8);
 grid-template-columns: 1fr;
 counter-reset: process;
}
.process-step {
 position: relative;
 padding-top: var(--space-4);
 /* wood accent, not red — red is reserved exclusively for clickable CTAs
 so it never loses signal strength through decorative reuse */
 border-top: 3px solid var(--color-accent-wood);
}
.process-step__icon {
 width: 48px; height: 48px;
 color: var(--color-text-heading);
 margin-bottom: var(--space-4);
}
.process-step__number {
 font-family: var(--font-heading);
 font-size: var(--text-sm);
 font-weight: var(--weight-bold);
 color: var(--color-text-muted);
 letter-spacing: var(--tracking-wide);
 margin-bottom: var(--space-2);
}
.process-step h3 { font-size: var(--text-lg); margin-bottom: var(--space-2); }
.process-step p { color: var(--color-text-muted); }

@media (min-width: 768px) {
 .process-grid { grid-template-columns: repeat(3, 1fr); }
}

/* ---------------------------------------------------------------------
 11. FINANCING STRIP
 --------------------------------------------------------------------- */
.financing-strip {
 background: var(--color-strip-bg);
 color: var(--color-strip-text);
 border-block: 1px solid var(--color-border-default);
 padding-block: var(--space-6);
}
.financing-strip__row {
 display: flex;
 flex-wrap: wrap;
 align-items: center;
 justify-content: center;
 gap: var(--space-3);
 text-align: center;
 font-size: var(--text-base);
 font-weight: var(--weight-medium);
}
.financing-strip__row svg { width: 28px; height: 28px; color: var(--color-strip-accent); flex: none; }
.financing-strip strong { color: var(--color-text-heading); }
.financing-strip small { display: block; color: var(--color-text-muted); font-size: var(--text-xs); margin-top: var(--space-1); }

/* ---------------------------------------------------------------------
 12. SERVICE CARDS
 --------------------------------------------------------------------- */
.service-grid {
 display: grid;
 gap: var(--space-6);
 grid-template-columns: 1fr;
}
.service-card {
 display: flex;
 flex-direction: column;
 background: var(--color-bg-card);
 border: 1px solid var(--color-border-default);
 border-radius: var(--radius-lg);
 overflow: hidden;
 box-shadow: var(--shadow-sm);
 transition: box-shadow var(--motion-base) var(--motion-ease), transform var(--motion-base) var(--motion-ease);
}
.service-card:hover { box-shadow: var(--shadow-lg); transform: translateY(-2px); }
.service-card__body { padding: var(--space-6); display: flex; flex-direction: column; gap: var(--space-3); flex: 1; }
.service-card h3 { font-size: var(--text-lg); }
.service-card p { color: var(--color-text-muted); flex: 1; }
.service-card__links {
 display: flex;
 flex-wrap: wrap;
 gap: var(--space-4);
 margin-top: var(--space-2);
}
.service-card__links a {
 font-weight: var(--weight-semibold);
 font-size: var(--text-sm);
 color: var(--color-text-link);
 display: inline-flex;
 align-items: center;
 gap: var(--space-1);
 min-height: var(--touch-target-min);
}
.service-card__links a:hover,
.service-card__links a:focus-visible { color: var(--color-text-link-hover); text-decoration: underline; }
.service-card__links a.is-primary { color: var(--color-cta-bg); }
.service-card__links a.is-primary:hover,
.service-card__links a.is-primary:focus-visible { color: var(--color-cta-bg-hover); }
.service-card__links svg { width: 16px; height: 16px; }
/* An inline SVG with no size rule expands to its intrinsic/replaced-element
   default (~300x300 for a square viewBox with no width/height attribute) --
   that is what shipped here: a 298x298px phone icon swallowing the card.
   Sized to match .service-card__links svg above so both icons in a card
   agree. */
.service-card__note svg { width: 16px; height: 16px; flex: none; }

@media (min-width: 640px) {
 .service-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1024px) {
 .service-grid { grid-template-columns: repeat(4, 1fr); }
}

/* ---------------------------------------------------------------------
 13. BEFORE / AFTER SLIDER
 Vanilla JS drag + keyboard operable (Arrow Left/Right, Home, End).
 Respects prefers-reduced-motion (no transition on drag/keys, only an
 instant position update) — see components.html script.
 --------------------------------------------------------------------- */
.ba-slider {
 position: relative;
 width: 100%;
 aspect-ratio: 4 / 3;
 border-radius: var(--radius-lg);
 overflow: hidden;
 border: 1px solid var(--color-border-default);
 box-shadow: var(--shadow-sm);
 touch-action: pan-y;
 user-select: none;
}
.ba-slider__after,
.ba-slider__before {
 position: absolute;
 inset: 0;
 aspect-ratio: auto;
 border: 0;
 border-radius: 0;
}
.ba-slider__before {
 /* clipped by JS setting inline width % — starts at 50 */
 width: 50%;
 overflow: hidden;
}
.ba-slider__before .photo-placeholder,
.ba-slider__after .photo-placeholder {
 position: absolute;
 inset: 0;
 width: var(--ba-fixed-width, 100%);
 height: 100%;
 border-radius: 0;
 border: 0;
}
.ba-slider__before .photo-placeholder { width: 100vw; max-width: none; }
.ba-slider__handle {
 position: absolute;
 top: 0;
 bottom: 0;
 left: 50%;
 width: 4px;
 background: var(--white);
 transform: translateX(-50%);
 box-shadow: 0 0 0 1px rgba(var(--near-black-rgb), 0.25);
}
.ba-slider__handle-grip {
 position: absolute;
 top: 50%;
 left: 50%;
 transform: translate(-50%, -50%);
 width: var(--touch-target-min);
 height: var(--touch-target-min);
 border-radius: var(--radius-full);
 background: var(--white);
 color: var(--color-text-heading);
 display: flex;
 align-items: center;
 justify-content: center;
 box-shadow: var(--shadow-md);
 cursor: ew-resize;
 border: 3px solid var(--color-cta-bg);
}
.ba-slider__handle-grip:focus-visible {
 outline: 3px solid var(--color-focus-ring);
 outline-offset: 3px;
}
.ba-slider__handle-grip svg { width: 22px; height: 22px; }
.ba-slider__tag {
 position: absolute;
 top: var(--space-3);
 z-index: 1;
 padding: var(--space-1) var(--space-3);
 border-radius: var(--radius-full);
 font-size: var(--text-xs);
 font-weight: var(--weight-bold);
 letter-spacing: var(--tracking-wide);
 text-transform: uppercase;
 background: rgba(var(--near-black-rgb), 0.65);
 color: var(--white);
}
.ba-slider__tag--before { left: var(--space-3); }
.ba-slider__tag--after { right: var(--space-3); }
.ba-caption {
 margin-top: var(--space-3);
 font-size: var(--text-sm);
 color: var(--color-text-muted);
 text-align: center;
}

/* ---------------------------------------------------------------------
 14. FOOTER — the only place disclaimers live (Canonical Decision #4)
 --------------------------------------------------------------------- */
.site-footer {
 background: var(--color-bg-footer);
 color: var(--color-text-inverse);
 padding-block: var(--space-16) var(--space-8);
}
.footer-grid {
 display: grid;
 gap: var(--space-10);
 grid-template-columns: 1fr;
 padding-bottom: var(--space-10);
 border-bottom: 1px solid rgba(255, 255, 255, 0.2);
}
/* Intentionally a styled <p>, not a heading element: footer nav groups are
 already labeled via aria-label on each <nav>, so no heading tag is
 introduced here. Keeps the document's heading outline (H1 → H2 → H3)
 uninterrupted by the footer, which sits after the last real heading. */
.footer-col-title {
 color: var(--color-text-inverse);
 font-size: var(--text-sm);
 font-weight: var(--weight-bold);
 letter-spacing: var(--tracking-wide);
 text-transform: uppercase;
 margin-bottom: var(--space-4);
}
.footer-col ul { display: flex; flex-direction: column; gap: var(--space-3); }
.footer-col a { color: var(--color-nav-link); opacity: 0.9; }
.footer-col a:hover, .footer-col a:focus-visible { text-decoration: underline; opacity: 1; }
.footer-brand p { color: var(--color-text-inverse); opacity: 0.85; max-width: 40ch; margin-top: var(--space-3); }
.footer-brand .site-header__phone { font-size: var(--text-lg); display: inline-block; margin-top: var(--space-4); }
.footer-social { display: flex; gap: var(--space-3); margin-top: var(--space-5); }
.footer-social a {
 width: 40px; height: 40px;
 display: flex; align-items: center; justify-content: center;
 border: 1px solid rgba(255, 255, 255, 0.4);
 border-radius: var(--radius-full);
}
.footer-social svg { width: 18px; height: 18px; }

.footer-bottom {
 margin-top: var(--space-8);
 display: flex;
 flex-direction: column;
 gap: var(--space-4);
 font-size: var(--text-xs);
 color: var(--color-text-inverse);
 opacity: 0.75;
}
.footer-disclaimer { max-width: 80ch; line-height: var(--leading-relaxed); }
.footer-legal { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-5); }
.footer-legal a { text-decoration: underline; }

@media (min-width: 640px) {
 .footer-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1024px) {
 .footer-grid { grid-template-columns: 1.4fr repeat(5, 1fr); }
 .footer-bottom { flex-direction: row; align-items: center; justify-content: space-between; }
}

/* ---------------------------------------------------------------------
 15. MISC
 --------------------------------------------------------------------- */
.badge-row {
 display: flex;
 flex-wrap: wrap;
 gap: var(--space-3);
 align-items: center;
}

.eyebrow {
 display: inline-block;
 font-size: var(--text-sm);
 font-weight: var(--weight-semibold);
 letter-spacing: var(--tracking-wide);
 text-transform: uppercase;
 color: var(--color-cta-bg);
 margin-bottom: var(--space-3);
}

.showcase-block {
 border: 1px dashed var(--color-border-default);
 border-radius: var(--radius-lg);
 padding: var(--space-6);
 margin-bottom: var(--space-16);
}
.showcase-index {
 padding-block: var(--space-4);
 border-bottom: 1px solid var(--color-border-default);
 background: var(--color-bg-page-alt);
}
.showcase-index p {
 font-size: var(--text-xs);
 font-weight: var(--weight-bold);
 letter-spacing: var(--tracking-wide);
 text-transform: uppercase;
 color: var(--color-text-muted);
 margin-bottom: var(--space-2);
}
.showcase-index ul { display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-5); }
.showcase-index a {
 display: inline-flex;
 align-items: center;
 min-height: 32px;
 font-size: var(--text-sm);
 font-weight: var(--weight-semibold);
 color: var(--color-text-link);
}
.showcase-index a:hover, .showcase-index a:focus-visible { color: var(--color-text-link-hover); text-decoration: underline; }

.showcase-block__label {
 display: inline-block;
 font-family: var(--font-body);
 font-size: var(--text-xs);
 font-weight: var(--weight-bold);
 letter-spacing: var(--tracking-wide);
 text-transform: uppercase;
 color: var(--color-text-muted);
 background: var(--color-bg-page-alt);
 padding: var(--space-1) var(--space-3);
 border-radius: var(--radius-sm);
 margin-bottom: var(--space-6);
}

/* Belt-and-suspenders global reduced-motion guard, in addition to the
 token-level override in tokens.css (covers any literal duration a
 future component author forgets to tokenize). */
@media (prefers-reduced-motion: reduce) {
 *, *::before, *::after {
 animation-duration: 0.01ms !important;
 animation-iteration-count: 1 !important;
 transition-duration: 0.01ms !important;
 }
}

/* ---------------------------------------------------------------------
 16. CREDO / "WHY HOMEOWNERS PICK US" SECTION (homepage)
 Mobile-first. Breakpoints match the finish-hero/vfeat sections already
 inline on site/index.html (760px / 1040px) rather than this file's
 768/1024 pair, so the homepage keeps one consistent responsive rhythm
 across the credo, video-feature and reviews blocks that sit next to it.
 --------------------------------------------------------------------- */
.credo .section-head { margin-bottom: var(--space-10); }

/* -- stat cards: 1 col mobile / 2 col tablet / 4 col desktop -- */
.credo-stats {
 display: grid;
 grid-template-columns: 1fr;
 gap: var(--space-5);
}
.credo-stat {
 display: flex;
 flex-direction: column;
 gap: var(--space-2);
 background: var(--color-bg-card);
 border: 1px solid var(--color-border-default);
 border-radius: var(--radius-lg);
 padding: var(--space-6);
 box-shadow: var(--shadow-sm);
 text-align: center;
}
.credo-stat__figure {
 font-family: var(--font-heading);
 font-size: var(--text-2xl);
 font-weight: var(--weight-bold);
 color: var(--color-text-heading);
 line-height: var(--leading-tight);
}
.credo-stat__label {
 font-size: var(--text-sm);
 color: var(--color-text-muted);
 line-height: var(--leading-snug);
}

/* -- recurring h3 divider, used 3x through the section -- */
.credo-subhead {
 margin: var(--space-12) 0 var(--space-6);
 font-size: var(--text-xl);
}

/* -- recognition / awards cards: 1 / 2 / 4 col -- */
.credo-recognition {
 display: grid;
 grid-template-columns: 1fr;
 gap: var(--space-5);
}
.credo-rec {
 background: var(--color-bg-card);
 border: 1px solid var(--color-border-default);
 border-radius: var(--radius-lg);
 padding: var(--space-6);
 box-shadow: var(--shadow-sm);
}
/* Gold fails contrast on a white card (1.77:1 — see the known contrast
 traps documented in tokens.css), so the icon uses navy here. Gold is
 reserved for the dark .credo-kit band below, where navy-on-gold is the
 one verified-safe (7.67:1 AAA) pairing. */
/* One shared icon slot for BOTH the line icons and the real award badge.
   They were 30px and 56px with different bottom margins, so the badge
   dwarfed its neighbours AND pushed its heading down out of line with the
   other three. Matching height and margin is what aligns the headings --
   there is no separate alignment rule, the icons themselves are the ruler.
   Sized by HEIGHT with width:auto because the artwork is not all square. */
.credo-rec__icon,
.credo-rec__badge {
 display: block;
 width: auto;
 height: 40px;
 margin-bottom: var(--space-4);
}
.credo-rec__icon {
 color: var(--color-text-heading);
}

/* Per-card colour. Navy line art next to the full-colour Nextdoor badge read
 as unfinished, so each icon now carries its own hue.

 CONTRAST: every stroke colour below clears 3:1 on white (WCAG 1.4.11 for
 meaningful graphics) -- red 5.01:1, blue 5.29:1, navy 13.60:1. Gold is the
 exception at 1.77:1 and is therefore used ONLY as a FILL behind a navy
 stroke, never as the stroke itself: the navy outline carries the contrast
 and the gold just makes the medal look like a medal. Same rule the hero
 star chips follow. */
.credo-rec__icon--award { color: var(--color-text-heading); }
.credo-rec__icon--award .medal  { fill: var(--gold); }
.credo-rec__icon--award .ribbon { stroke: var(--red); }

.credo-rec__icon--tv { color: var(--red); }
.credo-rec__icon--tv .screen { fill: rgba(var(--near-black-rgb), 0.06); }

.credo-rec__icon--ai { color: var(--blue); }
.credo-rec__icon--ai .spark { fill: var(--gold); stroke: var(--gold); }
/* Real award artwork, used in place of .credo-rec__icon on the cards where a
 genuine badge exists. Sized to the same optical weight as the line icons so
 a mixed row still reads as one row -- David asked for these "small". The
 supplied PNG is transparent, so no card, no plate, no border. */
/* Sizing now comes from the shared .credo-rec__icon/.credo-rec__badge rule
   above so the two can never drift apart again. */
.credo-rec h4 {
 font-size: var(--text-md);
 margin-bottom: var(--space-2);
}
.credo-rec p {
 color: var(--color-text-muted);
 font-size: var(--text-sm);
 line-height: var(--leading-normal);
}
/* Call to action on a recognition card. Styled as a link now so that swapping
 the <p> for an <a> when the Best of 2026 ballot URL lands is a one-element
 change with no CSS to chase. --pending is the pre-ballot state: same slot,
 same rhythm, but muted and not pretending to be clickable. */
.credo-rec__cta {
 display: inline-block;
 margin-top: var(--space-3);
 font-size: var(--text-sm);
 font-weight: var(--weight-semibold);
 color: var(--color-text-link);
 text-decoration: underline;
 text-underline-offset: 2px;
}
.credo-rec__cta:hover,
.credo-rec__cta:focus-visible { color: var(--color-text-link-hover); }
.credo-rec__cta--pending {
 color: var(--color-text-muted);
 font-weight: var(--weight-regular);
 text-decoration: none;
}

/* -- equipment "kit" band: dark navy, full width, the visual anchor -- */
.credo-kit {
 margin-top: var(--space-12);
 background: var(--color-bg-header);
 color: var(--color-text-inverse);
 border-radius: var(--radius-lg);
 padding: var(--space-8) var(--space-6);
}
.credo-kit h3,
.credo-kit h4 {
 color: var(--color-text-inverse);
}
.credo-kit__eyebrow {
 font-size: var(--text-sm);
 font-weight: var(--weight-semibold);
 letter-spacing: var(--tracking-wide);
 text-transform: uppercase;
 color: var(--gold);
 margin-bottom: var(--space-2);
}
.credo-kit__lede {
 color: var(--color-text-inverse);
 font-size: var(--text-md);
 line-height: var(--leading-relaxed);
 max-width: 62ch;
 margin-top: var(--space-3);
}
.credo-kit__items {
 display: grid;
 grid-template-columns: 1fr;
 gap: var(--space-6);
 margin-top: var(--space-8);
}
/* Stacked until there is genuinely room for two columns INSIDE the item.
 Side-by-side was tried first and fails everywhere below ~1040px: at 360px
 the 110px machine left a ~190px text column, so "Hypergrinder I-4 Wood
 planetary sander" wrapped onto four lines and the body copy ran at about
 four words per line. Two columns of a two-column grid is not enough width
 for an image AND prose — the row only earns its keep on desktop. */
.credo-kit__item {
 display: flex;
 flex-direction: column;
 align-items: flex-start;
 gap: var(--space-4);
}
/* Transparent PNGs shot on a dark background — no card, no light
 background, no border, so the navy band shows straight through.

 Sized by HEIGHT, not width. The two machines have very different aspect
 ratios (the sander is 334x693, the vacuum 290x453), so a shared max-width
 rendered the sander 290px tall next to a 219px vacuum — they read as one
 big machine and one small one rather than two pieces of the same kit.
 Pinning the height and letting width follow puts them on the same scale. */
.credo-kit__item img {
 flex: none;
 width: auto;
 height: 220px;
 max-width: 100%;
 object-fit: contain;
 background: transparent;
 border: 0;
}
.credo-kit__item > div { min-width: 0; }
.credo-kit__item h4 {
 font-size: var(--text-md);
 margin-bottom: var(--space-2);
}
.credo-kit__item p {
 color: var(--color-text-inverse);
 font-size: var(--text-sm);
 line-height: var(--leading-normal);
 opacity: 0.9;
}
.credo-kit__note {
 color: var(--color-text-inverse);
 margin-top: var(--space-8);
 padding-top: var(--space-6);
 border-top: 1px solid rgba(255, 255, 255, 0.2);
 font-size: var(--text-sm);
 line-height: var(--leading-normal);
 opacity: 0.85;
}

/* -- numbered steps: 1 / 2 / 4 col -- */
.credo-steps {
 display: grid;
 grid-template-columns: 1fr;
 gap: var(--space-6);
}
.credo-step {
 display: flex;
 flex-direction: column;
 gap: var(--space-2);
}
.credo-step__num {
 display: inline-flex;
 align-items: center;
 justify-content: center;
 width: 40px;
 height: 40px;
 border-radius: var(--radius-full);
 background: var(--gold);
 color: var(--color-text-heading); /* navy-on-gold = 7.67:1 AAA, tokens.css */
 font-family: var(--font-heading);
 font-weight: var(--weight-bold);
 font-size: var(--text-lg);
 margin-bottom: var(--space-2);
}
.credo-step h4 { font-size: var(--text-lg); }
.credo-step p { color: var(--color-text-muted); }
.credo-step p a {
 color: var(--color-text-link);
 font-weight: var(--weight-semibold);
 text-decoration: underline;
 text-underline-offset: 2px;
}
.credo-step p a:hover,
.credo-step p a:focus-visible {
 color: var(--color-text-link-hover);
}

/* -- recent-floors photo gallery: 1 / 2 / 3 col --
 CURRENTLY UNUSED — no .credo-gallery markup ships today. The homepage
 gallery this was written for was cut on 2026-08-12: David wanted it to show
 dark / medium brown / light floors, and no genuinely light floor photo
 exists yet. Measured floor luminance across every candidate: darks 62-79,
 mediums 104-127, and the LIGHTEST available 100-102 — a grey-washed medium,
 not a natural blonde, so the "light" panel would not have read as lighter
 than the "medium" one. Kept rather than deleted because the markup comes
 straight back the day that photo arrives. See
 assets/incoming/gallery-candidates/README.txt. */
.credo-gallery {
 display: grid;
 grid-template-columns: 1fr;
 gap: var(--space-4);
}
.credo-gallery img {
 width: 100%;
 height: auto;
 display: block;
 /* --radius-md is not defined in tokens.css (only sm/base/lg/full exist);
 --radius-lg is the closest defined step and matches the review-card /
 service-card / ba-slider radius used elsewhere in this file. */
 border-radius: var(--radius-lg);
 aspect-ratio: 4 / 3;
 object-fit: cover;
 border: 1px solid var(--color-border-default);
 box-shadow: var(--shadow-sm);
}

/* -- closing CTA row -- */
.credo-cta {
 display: flex;
 flex-wrap: wrap;
 justify-content: center;
 align-items: center;
 gap: var(--space-4);
 margin-top: var(--space-12);
}
/* .btn--outline is authored for use on top of the dark hero photo scrim
 (white text/border on transparent — see section 2 above). The credo CTA
 sits on this section's light background, where that would render an
 invisible button, so it's scoped here to look/behave like .btn--secondary
 instead of touching the shared .btn--outline rule. */
.credo-cta .btn--outline {
 color: var(--color-btn-secondary-text);
 border-color: var(--color-btn-secondary-border);
}
.credo-cta .btn--outline:hover,
.credo-cta .btn--outline:focus-visible {
 background: var(--color-btn-secondary-hover-bg);
 color: var(--color-btn-secondary-hover-text);
}

@media (min-width: 760px) {
 .credo-stats { grid-template-columns: repeat(2, 1fr); }
 .credo-recognition { grid-template-columns: repeat(2, 1fr); }
 .credo-kit { padding: var(--space-10) var(--space-8); }
 .credo-kit__items { grid-template-columns: repeat(2, 1fr); }
 .credo-steps { grid-template-columns: repeat(2, 1fr); }
 .credo-gallery { grid-template-columns: repeat(2, 1fr); }
}

@media (min-width: 1040px) {
 .credo-stats { grid-template-columns: repeat(4, 1fr); }
 .credo-recognition { grid-template-columns: repeat(4, 1fr); }
 .credo-kit { padding: var(--space-12) var(--space-10); }
 /* Only here is each item wide enough (~520px) to put the machine beside
    the copy and still leave a readable measure. See .credo-kit__item. */
 .credo-kit__item { flex-direction: row; }
 .credo-kit__item img { height: 290px; }
 .credo-steps { grid-template-columns: repeat(4, 1fr); }
 .credo-gallery { grid-template-columns: repeat(3, 1fr); }
}

/* =========================================================================
   17. THANK-YOU PAGE  (/thank-you)

   Reached only by a successful form post, and the only page that fires the
   Google Ads conversion. Deliberately plain: the visitor has already
   converted, so this page's job is to reassure and to offer the phone --
   not to sell again.
   ========================================================================= */
.thanks { max-width: 46rem; margin-inline: auto; text-align: center; }
/* The global reset sets `img, svg { display: block }`, and a block-level SVG
   ignores the parent's text-align -- so this sat hard against the left edge
   while every other element on the page was centred. margin-inline:auto is
   what actually centres it. */
.thanks__tick {
 width: 56px; height: 56px;
 color: var(--color-text-heading);
 margin: 0 auto var(--space-4);
}
.thanks__lede {
 font-size: var(--text-lg);
 line-height: var(--leading-relaxed);
 color: var(--color-text-body);
 max-width: 40ch; margin-inline: auto;
}
.thanks__now {
 margin-top: var(--space-8);
 padding: var(--space-6);
 background: var(--color-bg-page-alt);
 border-radius: var(--radius-lg);
}
.thanks__now-label {
 font-weight: var(--weight-semibold);
 color: var(--color-text-heading);
 margin-bottom: var(--space-3);
}
.thanks__hours { margin-top: var(--space-3); font-size: var(--text-sm); color: var(--color-text-muted); }
.thanks__next-head { margin-top: var(--space-12); }
.thanks__steps {
 list-style: none; padding: 0;
 margin-top: var(--space-6);
 display: grid; gap: var(--space-6);
 text-align: left;
}
.thanks__steps > li { display: flex; gap: var(--space-4); align-items: flex-start; }
.thanks__num {
 flex: none;
 display: inline-flex; align-items: center; justify-content: center;
 width: 36px; height: 36px;
 border-radius: var(--radius-full);
 background: var(--gold);
 color: var(--color-text-heading);   /* navy on gold = 7.67:1 AAA */
 font-family: var(--font-heading);
 font-weight: var(--weight-bold);
}
.thanks__steps h3 { font-size: var(--text-md); margin-bottom: var(--space-1); }
.thanks__steps p { color: var(--color-text-muted); font-size: var(--text-sm); line-height: var(--leading-normal); }
.thanks__meanwhile { margin-top: var(--space-10); color: var(--color-text-muted); font-size: var(--text-sm); }
.thanks__meanwhile a { color: var(--color-text-link); text-decoration: underline; text-underline-offset: 2px; }
@media (min-width: 760px) {
 .thanks__steps { grid-template-columns: repeat(3, 1fr); }
 .thanks__steps > li { flex-direction: column; }
}

/* =========================================================================
   18. FINISH PAGE IMAGERY  (/water-vs-oil-polyurethane and siblings)

   These comparison pages were pure text -- no images at all, placeholder
   hero, black type on white for the whole scroll. Colour here comes from
   real product photography rather than decorative tint.
   ========================================================================= */

/* Product cutout floated beside the prose. The PNG/WebP is transparent, so
   no card, no plate, no border -- it sits on the page background. */
.finish-figure--product {
 margin: var(--space-6) 0;
 text-align: center;
}
.finish-figure--product img {
 width: auto;
 max-width: 180px;
 height: auto;
 margin-inline: auto;
}
.finish-figure--product figcaption {
 margin-top: var(--space-3);
 font-size: var(--text-sm);
 color: var(--color-text-muted);
}
@media (min-width: 760px) {
 /* Float it out of the text flow so the comparison copy stays one column
    and the jug reads as an aside, not an interruption. */
 .finish-figure--product {
  float: right;
  width: 200px;
  margin: 0 0 var(--space-6) var(--space-8);
 }
}

/* The two THG-branded reference sheets. They are tall portraits (1195x1400)
   full of small type, so they get real width rather than thumbnail width --
   shrunk to a card they would be unreadable and pointless. */
.finish-sheets {
 display: grid;
 grid-template-columns: 1fr;
 gap: var(--space-8);
 margin-top: var(--space-6);
}
.finish-sheets img {
 width: 100%;
 height: auto;
 border-radius: var(--radius-lg);
 border: 1px solid var(--color-border-default);
 background: var(--white);
}
.finish-sheets figcaption {
 margin-top: var(--space-3);
 font-size: var(--text-sm);
 color: var(--color-text-muted);
}
@media (min-width: 760px) {
 .finish-sheets { grid-template-columns: repeat(2, 1fr); }
}

/* Revision stamp on the privacy policy. The policy text promises to keep a
   "Last Updated" date current, and for months the page showed no date at all
   -- a policy that says it is dated and is not undermines the one thing a
   privacy page has to be, which is trustworthy. */
.policy-updated {
 margin-top: var(--space-4);
 padding: var(--space-4);
 background: var(--color-bg-page-alt);
 border-radius: var(--radius-base);
 font-size: var(--text-sm);
 color: var(--color-text-body);
 line-height: var(--leading-normal);
}

/* =========================================================================
   19. TIMED LEAD POPUP  (site/lead-popup.js)

   Bottom sheet on mobile, centred dialog on desktop. The mobile treatment is
   not a style choice: Google demotes pages that show intrusive interstitials
   to mobile search traffic, and a third of this site's visits are people
   arriving on blog posts from search. A panel in the lower part of the
   screen with a visible close control is outside what that targets; a
   full-screen takeover would not be.
   ========================================================================= */
.thg-popup {
 position: fixed;
 inset: 0;
 z-index: 9998;
 display: flex;
 align-items: flex-end;              /* bottom sheet on mobile */
 justify-content: center;
 background: rgba(var(--near-black-rgb), 0.45);
}
.thg-popup__panel {
 position: relative;
 width: 100%;
 max-width: 26rem;
 max-height: 78vh;
 overflow-y: auto;
 background: var(--color-bg-card);
 border-radius: var(--radius-lg) var(--radius-lg) 0 0;
 padding: var(--space-5);
 box-shadow: 0 -8px 40px rgba(var(--near-black-rgb), 0.28);
 animation: thg-popup-in var(--motion-base) var(--motion-ease);
}
@keyframes thg-popup-in { from { transform: translateY(14px); opacity: 0; } to { transform: none; opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .thg-popup__panel { animation: none; } }

.thg-popup__close {
 position: absolute;
 top: var(--space-1);
 right: var(--space-1);
 width: 44px; height: 44px;         /* full touch target — it must be easy to dismiss */
 display: flex; align-items: center; justify-content: center;
 background: transparent; border: 0; cursor: pointer;
 font-size: 28px; line-height: 1;
 color: var(--color-text-muted);
 border-radius: var(--radius-full);
}
.thg-popup__close:hover { color: var(--color-text-heading); background: var(--color-bg-page-alt); }

.thg-popup__eyebrow {
 font-size: var(--text-xs);
 font-weight: var(--weight-semibold);
 letter-spacing: var(--tracking-wide);
 text-transform: uppercase;
 color: var(--color-text-muted);
}
.thg-popup__title { font-size: var(--text-lg); margin: var(--space-1) 0 var(--space-2); }
.thg-popup__lede { color: var(--color-text-body); font-size: var(--text-sm); line-height: var(--leading-normal); }
.thg-popup__lede a { color: var(--color-text-link); text-decoration: underline; }

.thg-popup__form { margin-top: var(--space-4); display: grid; gap: var(--space-1); }
.thg-popup__form label { font-size: var(--text-sm); font-weight: var(--weight-semibold); color: var(--color-text-heading); }
.thg-popup__form input {
 width: 100%;
 /* Stays at the 44px touch minimum -- shrinking the tap target to save
    height would trade a real usability floor for a cosmetic one. */
 min-height: var(--touch-target-min);
 padding: var(--space-2) var(--space-3);
 border: 1px solid var(--color-border-input);
 border-radius: var(--radius-base);
 background: var(--color-bg-input);
 margin-bottom: var(--space-1);
}
.thg-popup__form input:focus-visible { border-color: var(--color-border-input-focus); }
.thg-popup__form button { margin-top: var(--space-3); }
.thg-popup__err:empty { display: none; }
.thg-popup__err {
 color: var(--color-text-error);
 font-size: var(--text-sm);
 font-weight: var(--weight-semibold);
}
.thg-popup__alt { margin-top: var(--space-3); text-align: center; font-size: var(--text-sm); color: var(--color-text-muted); }
.thg-popup__alt a { color: var(--color-text-link); font-weight: var(--weight-semibold); text-decoration: underline; }

@media (min-width: 640px) {
 .thg-popup { align-items: center; }
 .thg-popup__panel { border-radius: var(--radius-lg); }
}

/* =========================================================================
   20. STAIN & FINISH CAROUSEL  (site/gallery.js, /api/gallery)

   Ten of David's best floors, each captioned with its stain colour and
   finish. Content is edited at /admin/gallery, so nothing here may assume a
   fixed number of slides.
   ========================================================================= */
.sgal { --sgal-radius: var(--radius-lg); }
.sgal__frame {
 position: relative;
 overflow: hidden;
 border-radius: var(--sgal-radius);
 /* The space is reserved BEFORE the photos arrive. Every uploaded image is
    cropped to exactly 1600x900 by the admin page, so 16/9 is a promise the
    data actually keeps and the page never jumps as slides load. */
 aspect-ratio: 16 / 9;
 background: var(--color-bg-header);
 border: 1px solid var(--color-border-default);
 box-shadow: var(--shadow-md);
}
.sgal__track {
 display: flex;
 height: 100%;
 transition: transform var(--motion-slow, 420ms) var(--motion-ease);
}
@media (prefers-reduced-motion: reduce) { .sgal__track { transition: none; } }

.sgal__slide { position: relative; flex: 0 0 100%; margin: 0; height: 100%; }
.sgal__slide img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Caption sits on a gradient rather than a solid bar: the photo is the point,
   and a solid strip crops it. Dark enough at the base for white text. */
/* Bottom left, and deliberately restrained. The photo is the point of this
   carousel -- the caption only has to answer "which floor is this and what is
   on it". An earlier version ran 20px/15px type over a scrim that started
   two-thirds of the way up the frame, and on a phone the job name wrapped to
   two lines and buried the floor. Type is now the same 15px/13px as the hero
   photo credit, and the scrim is shorter and starts lower, so far more of the
   photograph survives. */
.sgal__cap {
 position: absolute;
 inset: auto 0 0 0;
 padding: var(--space-6) var(--space-5) var(--space-3);
 /* Shorter and tighter to the bottom edge than a full-height wash: opaque
    enough under the words to keep them legible over a sunlit floor, but
    clear of the picture well before the halfway mark. */
 background: linear-gradient(to top,
   rgba(var(--near-black-rgb), 0.78) 0%,
   rgba(var(--near-black-rgb), 0.55) 45%,
   transparent 100%);
 color: var(--white);
 display: flex;
 flex-wrap: wrap;
 align-items: baseline;
 gap: 0 var(--space-3);
 text-shadow: 0 1px 3px rgba(var(--near-black-rgb), 0.6);
}
.sgal__what { font-family: var(--font-heading); font-size: var(--text-sm); font-weight: var(--weight-semibold); }
.sgal__spec {
 font-size: var(--text-xs);
 font-weight: var(--weight-semibold);
 letter-spacing: var(--tracking-wide);
 text-transform: uppercase;
 color: var(--gold);            /* gold on a dark scrim -- the verified pairing */
}

.sgal__prev, .sgal__next {
 position: absolute; top: 50%; transform: translateY(-50%);
 width: 44px; height: 44px;     /* full touch target */
 display: flex; align-items: center; justify-content: center;
 border: 0; border-radius: var(--radius-full);
 background: rgba(255, 255, 255, 0.9);
 color: var(--color-text-heading);
 font-size: 20px; line-height: 1; cursor: pointer;
}
.sgal__prev { left: var(--space-3); } .sgal__next { right: var(--space-3); }
.sgal__prev:hover, .sgal__next:hover { background: var(--white); }

.sgal__dots { display: flex; justify-content: center; gap: var(--space-2); margin-top: var(--space-4); }
.sgal__dot {
 width: 10px; height: 10px; padding: 0;
 border: 0; border-radius: var(--radius-full);
 background: var(--border); cursor: pointer;
}
.sgal__dot.is-on { background: var(--color-text-heading); }
/* The visible dot is small, but the thing you tap is not. */
.sgal__dot::after { content: ""; position: absolute; }
.sgal__dots { position: relative; }
.sgal__dot { position: relative; margin: 12px 6px; }
.sgal__dot::before { content: ""; position: absolute; inset: -12px -6px; }

@media (max-width: 480px) {
 /* A phone frame is only ~190px tall, so every pixel the caption takes is a
    pixel of floor the visitor does not get to see.

    This block used to read `font-size: var(--text-base)`, stepping the title
    DOWN from a 20px base. Once the base became 15px that same rule silently
    scaled it back UP to 17px on exactly the screens that needed it smallest,
    and at 17px a name like "Harmony On The Lakes Home in Canton, Ga" wrapped
    to two lines and the caption swallowed 51% of the picture. Overrides that
    are written as an absolute value rather than a direction do this: they
    stop tracking the thing they were meant to follow.

    13px keeps the longer job names to a single line at 375px, and the tighter
    line-height and padding take the caption to roughly a third of the frame.
    The spec line shares the size -- uppercase, gold and letterspaced is
    already enough to separate the two. */
 .sgal__cap {
  padding: var(--space-4) var(--space-4) var(--space-3);
  line-height: 1.3;
 }
 .sgal__what { font-size: var(--text-xs); }
 .sgal__prev, .sgal__next { display: none; }   /* swipe instead */
}

/* Watermark on the carousel. Top-left, above the photo, clear of the caption
   and of the prev arrow (which is vertically centred). Semi-transparent with
   a soft shadow so it reads on a pale floor and a dark one alike -- the logo
   is red, white and gold, and some of these photos are pale oak in full sun.
   Small on purpose: it is a signature, not a billboard. */
.sgal__mark {
 position: absolute;
 top: var(--space-3);
 left: var(--space-3);
 z-index: 2;
 pointer-events: none;          /* never intercepts a swipe */
 display: block;
 width: clamp(64px, 8vw, 96px);
 opacity: 0.88;
 filter: drop-shadow(0 2px 6px rgba(var(--near-black-rgb), 0.55));
}
.sgal__mark img { width: 100%; height: auto; display: block; }

/* The carousel sat flush against the recognition cards above it, so the two
   read as one run of boxes. It is its own thing and needs room to be that. */
.credo .sgal { margin-top: var(--space-10); }

/* ---------------------------------------------------------------------
   21. CREW VAN BREAKER
   A single photograph dropped into the middle of a long text page. Several
   of these pages -- /costguide worst of all at ~11,000 characters with no
   image whatsoever -- were an unbroken wall of prose, which is where readers
   give up. This is not decoration: a marked van in a real driveway is the
   proof that an actual local crew turns up, placed at the point in the page
   where the reader is deciding whether to trust that.
   --------------------------------------------------------------------- */
.crew-van {
  margin: var(--space-12) auto;
  /* This figure sits as a direct child of <main>, OUTSIDE the .container that
     wraps every other block on these pages, so it inherits no gutter. Left as
     just `max-width`, it rendered edge to edge on a phone -- measured 0..390px
     on a 390px screen -- while the text around it stayed inset, which reads as
     a broken image rather than a deliberate full-bleed. Subtracting the
     container gutter from the width restores the alignment on narrow screens
     and still caps at 760px on wide ones. */
  width: calc(100% - (var(--container-pad) * 2));
  max-width: 760px;
}
.crew-van img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius-base);
  border: 1px solid var(--color-border-default);
}
/* No figcaption rule here on purpose. This photo carries no caption: David no
   longer owns this van, so any line describing it as the crew's current
   vehicle would be a claim that is no longer true. The picture stays because
   it is a good picture; it must not be captioned back into a promise. */
