/* Artisan Windows & Doors — shared design system.
   Liquid-glass on deep indigo. Every page links this; page-specific styles
   stay inline in that page. Brand purple (from the logo): #582e85. */

:root {
  color-scheme: dark;
  --ink: #0d0a1c;            /* page base — deep indigo */
  --text: #f4f6f8;           /* body text — near-white, always */
  --text-soft: #cfc9dd;
  --accent: #b79bff;         /* brand purple glow (lightened from #582e85) */
  --amber: #ffb45c;          /* warm window-light accent */
  --glass: rgba(255, 255, 255, 0.06);
  --glass-strong: rgba(255, 255, 255, 0.09);
  --glass-edge: rgba(255, 255, 255, 0.16);
  --radius: 18px;
  /* Sticky header height, measured in Chrome after the utility strip landed
     (1440px: 109 · 1000px: 154, nav wraps to its own row · 390px: 132).
     Anything that pins below the header offsets from this instead of carrying
     its own magic number — three of them drifted out of sync the moment the
     header changed height. */
  --header-h: 109px;
}
@media (max-width: 1080px) { :root { --header-h: 154px; } }
@media (max-width: 640px)  { :root { --header-h: 132px; } }

* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body {
  font: 15px/1.65 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  color: var(--text);
  background: var(--ink);
  min-height: 100vh;
  overflow-x: hidden;
}

/* ---------- Backdrop + atmospheres -----------------------------------------
   Bill's Grok hero art is a fixed sky behind the glass on every page. Which
   sky depends on the page's atmosphere — set <body data-atmosphere="…"> and
   the site shifts weather and time of day as you move through it:
     storm (default) — purple storm twilight; index
     heavy           — the storm at its worst; impact windows + hurricane
     dawn            — sunrise; window school + lanai
     day             — bright sunshine; entry doors
     sunset          — golden hour; patio doors
     gloomy          — overcast sunshower; FAQ page (all seven skies in use)
   To add one: drop the image in assets/, add a
   body[data-atmosphere="…"] block below, and pick a scrim heavy enough that
   near-white text stays legible over the brightest part of the sky. All art
   shares the same framing (house right, purple sky left) so page-to-page
   fades read as time passing over one neighborhood. */
body {
  --sky: url("hero.webp");
  --scrim-top: rgba(13, 10, 28, 0.2);
  --scrim-bottom: rgba(13, 10, 28, 0.6);
}
body[data-atmosphere="dawn"] {
  --sky: url("hero-morning.webp");
  /* hazier art than the storm — light scrim, let the sunrise glow */
  --scrim-top: rgba(13, 10, 28, 0.22);
  --scrim-bottom: rgba(13, 10, 28, 0.6);
}
body[data-atmosphere="sunset"] {
  --sky: url("hero-sunset.webp");   /* golden hour — patio doors */
  --scrim-top: rgba(13, 10, 28, 0.32);
  --scrim-bottom: rgba(13, 10, 28, 0.68);
}
body[data-atmosphere="gloomy"] {
  --sky: url("hero-gloomy.webp");   /* overcast sunshower — FAQ */
  --scrim-top: rgba(13, 10, 28, 0.3);
  --scrim-bottom: rgba(13, 10, 28, 0.66);
}
body[data-atmosphere="heavy"] {
  --sky: url("hero-heavy.webp");    /* the storm at its worst — impact + hurricane */
  --scrim-top: rgba(13, 10, 28, 0.24);
  --scrim-bottom: rgba(13, 10, 28, 0.62);
}
body[data-atmosphere="day"] {
  /* bright sunny daytime — entry doors (the wife's call: door shoppers
     want sunny and happy). Brightest sky in the set = heaviest scrim. */
  --sky: url("hero-day.webp");
  --scrim-top: rgba(13, 10, 28, 0.22);
  --scrim-bottom: rgba(13, 10, 28, 0.58);
  /* THE GLASS FLIPS DARK ON THIS SKY. Everywhere else --glass is a white veil
     (rgba(255,255,255,.06)), which works because the sky behind it is dark —
     the veil lifts the card just enough to separate it. Over the bright day
     art the same white veil makes the card BRIGHTER than its surroundings, and
     the lavender kicker (--accent, deliberately light) drops to unreadable on
     it. Tinting the glass dark instead of light restores the contrast the rest
     of the set gets for free, and keeps one card treatment across the site.
     (Bill's catch on entry doors, 2026-08-03.) */
  --glass: rgba(9, 7, 20, 0.32);
  --glass-strong: rgba(9, 7, 20, 0.44);
  --glass-edge: rgba(255, 255, 255, 0.20);
}
/* Section headers sit straight on the sky with no card behind them. On every
   other atmosphere that's dark enough to carry them; on the day art the lower
   half is sunlit grass, and a lavender kicker on bright green is the same
   legibility problem again. Same halo the .bare blocks use. */
body[data-atmosphere="day"] section > .kicker,
body[data-atmosphere="day"] section > h2,
body[data-atmosphere="day"] section > .section-lede {
  text-shadow: 0 1px 14px rgba(9, 7, 20, 0.75), 0 1px 3px rgba(9, 7, 20, 0.6);
}
.backdrop {
  position: fixed;
  inset: 0;
  z-index: -2;
  overflow: hidden;
  background:
    linear-gradient(180deg, var(--scrim-top) 0%, var(--scrim-bottom) 100%),
    var(--sky) center / cover no-repeat,
    linear-gradient(180deg, #171030 0%, #0d0a1c 100%);
}
@media (max-width: 640px) {
  /* keep the glowing lanai in frame on portrait screens */
  .backdrop { background-position: center, 68% center, center; }
}

/* ---------- Install gallery (filmstrip carousel) ---------------------------
   Reusable: a glass card holding a horizontal scroll-snap strip of square
   photo tiles. Native touch swipe on phones (no library); arrow buttons page
   it on desktop; images lazy-load so only what's scrolled to downloads. End
   the strip with a .car-cta tile — browsing should land on the phone number. */
.gallery-card { padding: clamp(1.5rem, 3vw, 2.25rem); }
.gallery-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
/* controls only work with JS, so they only exist with JS (.js added by the
   page script); no-JS visitors still swipe the strip natively */
.car-controls { display: none; gap: 0.55rem; align-items: center; }
.js .car-controls { display: flex; }
/* Controls have to read on EVERY sky. A white-tinted pill (--glass-strong)
   disappears against the bright day/dawn art — it only ever looked right
   because the first galleries lived on storm pages. Dark fill + a brighter
   edge holds on both ends of the set; the count gets the same halo the .bare
   text uses. (Caught on the gallery page, 2026-08-03.) */
.car-count {
  font-size: 0.85rem;
  color: var(--text);
  min-width: 3.4em;
  text-align: center;
  text-shadow: 0 1px 10px rgba(9, 7, 20, 0.75);
}
.car-btn {
  width: 42px;
  height: 42px;
  border-radius: 50%;
  background: rgba(13, 10, 28, 0.45);
  border: 1px solid rgba(255, 255, 255, 0.32);
  color: var(--text);
  font-size: 1.15rem;
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: background 0.2s, border-color 0.2s, color 0.2s;
}
.car-btn:hover, .car-btn:focus-visible { background: rgba(255, 180, 92, 0.16); border-color: var(--amber); color: var(--amber); }
.car-track {
  margin-top: 1.2rem;
  display: flex;
  gap: 0.9rem;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding-bottom: 0.6rem;
  scrollbar-width: thin;
  scrollbar-color: var(--glass-edge) transparent;
}
.car-item {
  flex: 0 0 min(320px, 76vw);
  aspect-ratio: 1;
  scroll-snap-align: start;
  border-radius: 14px;
  overflow: hidden;
  border: 1px solid var(--glass-edge);
  margin: 0;
}
.car-item img { width: 100%; height: 100%; object-fit: cover; display: block; }
.car-cta { display: grid; place-items: center; text-align: center; background: var(--glass-strong); padding: 1.25rem; }
.car-cta .kicker { margin-bottom: 0.9rem; }
/* the end tile can carry both doors (call + quote); stack them */
.car-cta .call-btn, .car-cta .ghost-btn { display: block; }
.car-cta .ghost-btn { margin-top: 0.6rem; }
.gallery-card .section-lede { margin-top: 0.8rem; }
/* "see all our work →" / "explore the styles →" under a strip */
.gallery-more { margin-top: 1.1rem; font-size: 0.92rem; }
.gallery-more a { color: var(--accent); font-weight: 600; text-decoration: none; }
.gallery-more a:hover, .gallery-more a:focus-visible { text-decoration: underline; }
@media (prefers-reduced-motion: no-preference) { .car-track { scroll-behavior: smooth; } }

/* ---------- Storm weather: live rain + heat lightning ----------------------
   Storm pages only — index today, impact pages tomorrow, free with the
   atmosphere. Pure CSS, no images, no JS: the streaks are tiny gradient
   ellipses tiled and slid along their slant (two sheets, near/far, falling
   between the drops baked into the hero art), and the lightning is two glow
   layers up in the clouds pulsing on long mismatched loops (19s / 31s) so
   the flashes drift instead of ticking like a metronome. Keep it subtle —
   "hey, that's cool," not a screensaver. (Known cost, accepted: these are
   four persistent composited layers on storm pages; don't add more sheets.) */
body:is([data-atmosphere="storm"], [data-atmosphere="heavy"]) .backdrop::before,
body:is([data-atmosphere="storm"], [data-atmosphere="heavy"]) .backdrop::after {
  content: "";
  position: absolute;
  inset: -22%;
  pointer-events: none;
}
body:is([data-atmosphere="storm"], [data-atmosphere="heavy"]) .backdrop::before {
  /* near rain: brighter, faster — wide prime-number spacings keep the
     columns from ever lining up into a visible grid */
  background-image:
    radial-gradient(1.6px 26px at 23px 44px, rgba(210, 200, 245, 0.18), transparent 70%),
    radial-gradient(1.3px 20px at 71px 104px, rgba(210, 200, 245, 0.12), transparent 70%);
  background-size: 113px 132px, 157px 132px;
  /* Negative rotate = lean like / (falling toward the right), matching hero art
     and assets/rain.js. Was +11deg (\) then -11deg; Jeremiah QA 2026-07-21
     asked for ~2× the slant → -22deg. */
  transform: rotate(-22deg);
  animation: rain-near 0.9s linear infinite;
}
body:is([data-atmosphere="storm"], [data-atmosphere="heavy"]) .backdrop::after {
  /* far rain: fainter, slower */
  background-image:
    radial-gradient(1px 14px at 17px 30px, rgba(205, 195, 240, 0.10), transparent 70%),
    radial-gradient(1px 11px at 89px 86px, rgba(205, 195, 240, 0.07), transparent 70%);
  background-size: 127px 118px, 181px 118px;
  transform: rotate(-22deg);
  animation: rain-far 1.6s linear infinite;
}
/* each sheet shifts by exactly one tile height along its slant = seamless loop */
@keyframes rain-near { to { transform: rotate(-22deg) translateY(132px); } }
@keyframes rain-far  { to { transform: rotate(-22deg) translateY(118px); } }

body:is([data-atmosphere="storm"], [data-atmosphere="heavy"])::before,
body:is([data-atmosphere="storm"], [data-atmosphere="heavy"])::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: 0;
}
body:is([data-atmosphere="storm"], [data-atmosphere="heavy"])::before {
  /* cool white-violet flash high in the clouds */
  background: radial-gradient(46% 32% at 63% 14%, rgba(238, 224, 255, 0.6), rgba(214, 178, 255, 0.16) 55%, transparent 75%);
  animation: heat-lightning 19s linear infinite;
}
body:is([data-atmosphere="storm"], [data-atmosphere="heavy"])::after {
  /* warmer answer nearer the lanai glow, offset so they rarely coincide */
  background: radial-gradient(38% 28% at 80% 26%, rgba(255, 228, 190, 0.45), rgba(255, 214, 170, 0.12) 55%, transparent 75%);
  animation: heat-lightning 31s linear -12s infinite;
}
@keyframes heat-lightning {
  0%, 89.6%, 94.4%, 100% { opacity: 0; }
  90% { opacity: 0.9; }
  90.7% { opacity: 0.15; }
  91.6% { opacity: 1; }
  93% { opacity: 0.25; }
}

@media (prefers-reduced-motion: reduce) {
  body:is([data-atmosphere="storm"], [data-atmosphere="heavy"]) .backdrop::before,
  body:is([data-atmosphere="storm"], [data-atmosphere="heavy"]) .backdrop::after,
  body:is([data-atmosphere="storm"], [data-atmosphere="heavy"])::before,
  body:is([data-atmosphere="storm"], [data-atmosphere="heavy"])::after { animation: none; display: none; }
}

/* ---------- Live canvas rain (assets/rain.js) -----------------------------
   Opt-in upgrade for storm/heavy pages: a particle canvas where every drop is
   independent, so the rain never repeats the way the tiled sheets above do.
   The script builds the canvas and adds html.rain-live; that flag retires the
   two CSS sheets so the two systems never rain at once. No-JS visitors keep
   the sheets; reduced-motion gets neither. The canvas sits at z-index -1 —
   behind the content, so the frosted-glass cards still blur it. */
.rain-canvas { position: fixed; inset: 0; z-index: -1; pointer-events: none; }
html.rain-live body:is([data-atmosphere="storm"], [data-atmosphere="heavy"]) .backdrop::before,
html.rain-live body:is([data-atmosphere="storm"], [data-atmosphere="heavy"]) .backdrop::after {
  display: none;
}
@media (prefers-reduced-motion: reduce) { .rain-canvas { display: none; } }

/* ---------- Page-to-page cross-fade (View Transitions API) -----------------
   Supporting browsers cross-fade between pages on navigation; the sky fades
   on its own slower clock than the content, so index → window school feels
   like the storm clearing into morning. Unsupported browsers (and file://
   previews — needs http, use a local server) get a normal instant load. */
@view-transition { navigation: auto; }
.backdrop { view-transition-name: sky; }
::view-transition-old(root),
::view-transition-new(root) { animation-duration: 0.35s; }
::view-transition-old(sky),
::view-transition-new(sky) { animation-duration: 1.1s; }
/* NOTE for future sessions: keep this block exactly this simple. Do NOT add
   content-entry timings on the view-transition snapshots or via section/main
   DOM fades — three filmed experiments (2026-07-19) ended in hold-then-pop
   or jank. Prereq for revisiting: modular page restructure (see ROADMAP). */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}

.glass {
  background: var(--glass);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  backdrop-filter: blur(18px) saturate(140%);
  border: 1px solid var(--glass-edge);
  border-radius: var(--radius);
}

/* ---------- Bare sections — text floating on the sky ----------------------
   Too much of a good thing: when every block is frosted glass, none of them
   reads as special. These sections (index family note, testimonial, closing
   CTA) drop the panel and let the text float straight on the atmosphere, so
   the uniform glass boxes above them (hero, promise rail, service cards,
   gallery) keep the rhythm and these breathe. Since the sky is fixed and these scroll
   over its brighter cloud areas, a soft shadow — a halo, not a box — keeps the
   near-white text legible without bringing the panel back. (Bill's call,
   2026-07-20: "trade up the visuals — too much of a good thing.") */
.bare {
  background: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  border: 0;
  text-shadow: 0 1px 14px rgba(9, 7, 20, 0.6), 0 1px 3px rgba(9, 7, 20, 0.5);
}
/* the halo is for text floating on the sky, not the solid CTA pill — the
   button carries its own glow, so keep the shadow off its label */
.bare .call-btn { text-shadow: none; }

.wrap { max-width: 1100px; margin: 0 auto; padding: 0 1.25rem; }

/* ---------- Nav ---------- */
header {
  position: sticky;
  top: 0;
  z-index: 10;
  /* Lift the header out of the page-to-page "root" cross-fade into its own
     view-transition layer (same trick as .backdrop → sky). The browser then
     matches old header → new header and holds it in place: two near-identical
     opaque snapshots cross-faded never dip to transparent, so the header stays
     a rock-steady anchor while the rest of the page fades and the sky drifts.
     Without this, the header rides the root fade — goes see-through and pops
     back solid on every navigation. (Bill's catch, 2026-07-20.) */
  view-transition-name: header;
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  backdrop-filter: blur(18px) saturate(140%);
  background: rgba(13, 10, 28, 0.55);
  border-bottom: 1px solid rgba(255, 255, 255, 0.10);
}
/* ---------- Utility strip -------------------------------------------------
   The secondary pages (our work, videos, FAQs, financing, about) needed to be
   reachable from somewhere other than the footer, but they must NOT compete
   with the products or the phone number — this business runs on calls, and an
   even-weight row would put "FAQs" next to "Impact Windows" as if they were
   the same kind of thing. So: a slim strip in smaller, quieter type above the
   main bar. Two rows, visually even, honest hierarchy. It rides inside
   <header>, so it's covered by the header's view-transition anchor and stays
   put through page changes like everything else up there. */
.utility-bar { border-bottom: 1px solid rgba(255, 255, 255, 0.07); }
.utility-inner {
  max-width: 1500px;
  margin: 0 auto;
  padding: 0.42rem 1.5rem;
  display: flex;
  align-items: center;
  gap: 1.25rem;
  font-size: 0.82rem;
}
.utility-nav {
  display: flex;
  gap: 1.25rem;
  list-style: none;
  margin: 0;
  padding: 0;
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
}
.utility-nav::-webkit-scrollbar { display: none; }
.utility-nav a {
  color: var(--text-soft);
  text-decoration: none;
  white-space: nowrap;
  letter-spacing: 0.01em;
}
.utility-nav a:hover, .utility-nav a:focus-visible { color: var(--text); }
.utility-nav a[aria-current="page"] { color: var(--accent); font-weight: 600; }
.utility-quote {
  /* glass pill, sitting right where the strip meets the call button below.
     The negative vertical margin lets the pill be taller than the strip's
     text without growing the strip itself — --header-h (and every sticky
     offset hanging off it) depends on this bar keeping its height. */
  margin: calc(-0.26rem - 1px) 0 calc(-0.26rem - 1px) auto;
  flex: none;
  color: var(--accent);
  font-weight: 700;
  text-decoration: none;
  white-space: nowrap;
  padding: 0.26rem 0.85rem;
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.08);
  transition: background 0.15s ease, border-color 0.15s ease;
}
.utility-quote:hover, .utility-quote:focus-visible {
  background: rgba(255, 255, 255, 0.15);
  border-color: rgba(255, 255, 255, 0.36);
}
@media (max-width: 900px) { .utility-inner { padding: 0.42rem 1rem; } }
@media (max-width: 640px) {
  /* phones: the floating Call pill and the hero CTAs already carry the quote,
     so the strip keeps only the links people can't reach any other way */
  .utility-quote { display: none; }
  .utility-nav { gap: 1.05rem; }
}

/* Header uses more width than the content column: logo hugs the left,
   call button hugs the right, and the 1fr/auto/1fr grid keeps the page
   links dead-center regardless of how wide the logo side is. */
.nav {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 1rem;
  padding: 0.85rem 1.5rem;
  max-width: 1500px;
  margin: 0 auto;
}
.nav > .brand { justify-self: start; }
.nav > .call-btn { justify-self: end; }
.brand { display: flex; align-items: center; gap: 0.75rem; color: inherit; text-decoration: none; }
.brand img { height: 42px; width: auto; }
.wordmark { line-height: 1.2; }
.wordmark strong {
  display: block;
  font-size: 1.15rem;
  letter-spacing: 0.02em;
}
.wordmark span { font-size: 0.8rem; color: var(--text-soft); letter-spacing: 0.08em; text-transform: uppercase; }

/* ---------- Site nav — logo left, pages center, call button right.
   On narrow screens the page links drop to their own scrollable row so
   nothing collides and the call button never leaves the header. ---------- */
.site-nav { display: flex; gap: 1.05rem; list-style: none; margin: 0 0.5rem; flex-wrap: wrap; justify-content: center; }
.site-nav a {
  color: var(--text-soft);
  text-decoration: none;
  font-size: 0.92rem;
  white-space: nowrap;
  padding: 0.2rem 0.05rem;
  border-bottom: 2px solid transparent;
}
.site-nav a:hover { color: var(--text); }
.site-nav a[aria-current="page"] { color: var(--accent); border-bottom-color: var(--accent); }
@media (max-width: 1280px) { .wordmark span { display: none; } }
@media (max-width: 1080px) {
  /* narrow: brand + call share the top row, links get their own
     full-width scrollable row beneath — nothing ever wraps mid-list */
  .nav { grid-template-columns: minmax(0, 1fr) auto; justify-content: space-between; }
  .nav > .call-btn { grid-row: 1; grid-column: 2; }
  .site-nav {
    grid-column: 1 / -1;
    overflow-x: auto;
    flex-wrap: nowrap;
    justify-content: flex-start;
    margin: 0;
    padding-bottom: 0.25rem;
    scrollbar-width: none;
  }
}

.call-btn {
  display: inline-block;
  padding: 0.6rem 1.15rem;
  border-radius: 999px;
  font-weight: 700;
  font-size: 0.95rem;
  color: #101725;
  background: linear-gradient(135deg, var(--amber), #ff9e3d);
  text-decoration: none;
  box-shadow: 0 4px 22px rgba(255, 158, 61, 0.35);
  white-space: nowrap;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.call-btn:hover { transform: translateY(-1px); box-shadow: 0 6px 28px rgba(255, 158, 61, 0.5); }

/* ---------- Secondary "ghost" button + the CTA pair ------------------------
   The business is phone-first, so the amber .call-btn stays the primary action
   everywhere — one tap, one call. The ghost button is the quieter second door
   for people who'd rather type than talk (quote page). Pair them with
   .cta-pair; never let the ghost outrank the call. It carries its own blur so
   it stays legible on .bare sections floating straight on the sky. */
.ghost-btn {
  display: inline-block;
  padding: 0.6rem 1.15rem;
  border-radius: 999px;
  font-weight: 700;
  font-size: 0.95rem;
  color: var(--text);
  text-decoration: none;
  background: var(--glass-strong);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  backdrop-filter: blur(14px) saturate(140%);
  border: 1px solid var(--glass-edge);
  white-space: nowrap;
  transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease, transform 0.15s ease;
}
.ghost-btn:hover, .ghost-btn:focus-visible {
  background: rgba(183, 155, 255, 0.18);
  border-color: var(--accent);
  transform: translateY(-1px);
}
/* same rule as .call-btn: the halo is for text on the sky, not button labels */
.bare .ghost-btn { text-shadow: none; }

.cta-pair { display: flex; flex-wrap: wrap; gap: 0.85rem; align-items: center; }
/* inside the school pages' promise rail the two buttons stack full-width,
   matching the .rail-card .call-btn rule each of those pages already carries */
.rail-card .ghost-btn { display: block; text-align: center; margin-top: 0.55rem; }

/* ---------- Type & sections ---------- */
section { padding: 3.25rem 0; }
.kicker {
  font-size: 0.8rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
  font-weight: 700;
}
h2 { font-size: clamp(1.5rem, 3.5vw, 2.1rem); margin-top: 0.35rem; }
.section-lede { color: var(--text-soft); margin-top: 0.6rem; max-width: 60ch; }

/* ---------- Hero ---------- */
.hero { padding: clamp(4rem, 12vh, 8rem) 0 3.5rem; }
.hero-card {
  padding: clamp(1.75rem, 4vw, 3rem);
  max-width: 640px;
}
.hero-card .kicker { margin-bottom: 0.6rem; }
.hero h1 {
  font-size: clamp(2rem, 5.5vw, 3.2rem);
  line-height: 1.12;
  letter-spacing: -0.01em;
}
.hero h1 em {
  font-style: normal;
  background: linear-gradient(120deg, var(--accent), #ddd0ff);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.hero p.lede {
  margin-top: 1rem;
  font-size: 1.06rem;
  color: var(--text-soft);
  max-width: 46ch;
}
.hero-cta { margin-top: 1.75rem; display: flex; flex-wrap: wrap; gap: 0.85rem; align-items: center; }
.hero-cta .call-btn { font-size: 1.05rem; padding: 0.8rem 1.5rem; }
.hero-cta .ghost-btn { font-size: 1rem; padding: 0.78rem 1.35rem; }
/* the financing line reads as a note, so it gets its own row under the buttons */
.hero-cta .finance { font-size: 0.95rem; color: var(--text-soft); flex-basis: 100%; }
.hero-cta .finance strong { color: var(--text); }

/* ---------- Trust badges ---------- */
.badges {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 0.9rem;
  margin: 1rem 0 0;
  padding: 0;
  list-style: none;
}
.badges li {
  padding: 0.9rem 1.1rem;
  font-size: 0.92rem;
  line-height: 1.45;
}
.badges strong { display: block; font-size: 0.98rem; }
.badges span { color: var(--text-soft); }

/* ---------- Service cards ---------- */
.services {
  margin-top: 1.75rem;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1rem;
}
.service {
  display: block;
  color: inherit;
  text-decoration: none;
  padding: 1.4rem 1.4rem 1.5rem;
  transition: transform 0.18s ease, background 0.18s ease;
}
.service:hover { transform: translateY(-3px); background: var(--glass-strong); }
.service h3 { font-size: 1.08rem; }
.service p { margin-top: 0.45rem; color: var(--text-soft); font-size: 0.95rem; }
.service .more { display: block; margin-top: 0.7rem; color: var(--accent); font-weight: 600; font-size: 0.9rem; }

/* ---------- Note / quote cards ---------- */
.note-card {
  padding: clamp(1.75rem, 4vw, 2.75rem);
  max-width: 720px;
  margin: 0 auto;
  font-size: 1.05rem;
}
.note-card .kicker { margin-bottom: 0.85rem; }
.note-card p + p { margin-top: 1rem; }
.note-card .signoff { color: var(--text-soft); font-style: italic; }

.quote-card {
  padding: clamp(1.75rem, 4vw, 2.5rem);
  max-width: 720px;
  margin: 1.75rem auto 0;
  text-align: center;
}
.quote-card blockquote { font-size: 1.15rem; line-height: 1.6; }
.quote-card cite { display: block; margin-top: 0.9rem; color: var(--text-soft); font-style: normal; font-size: 0.95rem; }

/* ---------- CTA band ---------- */
.cta-band { text-align: center; padding-bottom: 4.5rem; }
.cta-band .glass { padding: clamp(2rem, 5vw, 3rem); }
/* bare CTA: keep the vertical breathing room the panel used to give */
.cta-band .cta-inner { padding: clamp(1.25rem, 4vw, 2.25rem) 0; }
.cta-band h2 { margin-bottom: 0.5rem; }
.cta-band p { color: var(--text-soft); }
.cta-band .call-btn { margin-top: 1.5rem; font-size: 1.1rem; padding: 0.85rem 1.75rem; }
/* paired closing CTA: the margin moves to the pair so the two pills sit level */
.cta-band .cta-pair { justify-content: center; margin-top: 1.5rem; }
.cta-band .cta-pair .call-btn { margin-top: 0; }
.cta-band .cta-pair .ghost-btn { font-size: 1.05rem; padding: 0.85rem 1.6rem; }

/* ---------- Benefits grid ------------------------------------------------
   "Benefits Of Upgrading Your Windows And Doors" — lifted from the live
   Squarespace site (2026-08-03), same five claims, same numbers. Card shape
   deliberately matches .service so the page reads as one system; no icons,
   the headings do the work. */
.benefits {
  margin-top: 1.75rem;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 1rem;
}
.benefit { padding: 1.4rem; }
.benefit h3 { font-size: 1.05rem; }
.benefit p { margin-top: 0.45rem; color: var(--text-soft); font-size: 0.95rem; }

/* ---------- Crew photo (about page) --------------------------------------
   Full-bleed inside its glass card: the photo IS the card, caption tucked
   under it on the glass. */
.crew-figure { margin: 1.75rem 0 0; padding: 0; overflow: hidden; }
.crew-figure img { display: block; width: 100%; height: auto; }
.crew-figure figcaption {
  padding: 1rem 1.35rem 1.15rem;
  font-size: 0.92rem;
  color: var(--text-soft);
}
.crew-figure figcaption strong { color: var(--text); }

/* ---------- Quote form ----------------------------------------------------
   Mirrors the fields the live site collects (name / email / phone / address /
   project). NOT WIRED YET — see the notice in quote.html; the submit button
   stays disabled until there's a real endpoint behind it, because a form that
   silently eats a homeowner's quote request is exactly the kind of lying this
   site doesn't do. Wiring it = set <form action> + drop the `disabled`. */
.quote-form { display: grid; gap: 1.05rem; margin-top: 1.5rem; }
.field { display: grid; gap: 0.35rem; }
.field label { font-size: 0.9rem; font-weight: 600; }
.field .req { color: var(--amber); }
.field input, .field select, .field textarea {
  font: inherit;
  color: var(--text);
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid var(--glass-edge);
  border-radius: 12px;
  padding: 0.7rem 0.85rem;
  width: 100%;
}
.field input::placeholder, .field textarea::placeholder { color: rgba(207, 201, 221, 0.6); }
/* native dropdown list renders on a light panel — keep option text dark there */
.field select option { color: #1c1630; }
.field input:focus-visible, .field select:focus-visible, .field textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.field textarea { min-height: 130px; resize: vertical; }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1.05rem; }
@media (max-width: 560px) { .field-row { grid-template-columns: 1fr; } }
.call-btn[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
  box-shadow: none;
  transform: none;
  border: 0;
}
.form-note {
  margin-top: 1.1rem;
  padding: 1rem 1.15rem;
  border-radius: 14px;
  background: rgba(255, 180, 92, 0.10);
  border: 1px solid rgba(255, 180, 92, 0.35);
  font-size: 0.95rem;
}
.form-note strong { color: var(--amber); }
/* Links in body copy that aren't buttons or cards: without this they fall back
   to the browser's default link blue, which is off-palette everywhere and hard
   to read on the amber notice. */
main a:not([class]) { color: var(--accent); font-weight: 600; }
.form-note a { color: var(--amber); font-weight: 700; }

/* ---------- Footer ---------- */
footer {
  border-top: 1px solid rgba(255, 255, 255, 0.1);
  background: rgba(13, 10, 28, 0.6);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  padding: 2.25rem 0 6rem;
  font-size: 0.92rem;
  color: var(--text-soft);
}
/* Centred stack, not columns: the link list outgrew a single column once the
   site passed a dozen pages, and a tall left-aligned stack made the whole
   footer lopsided. Now it reads top to bottom — mark, contact, then the links
   in two grouped rows (who we are / how to decide). Bill's layout, 2026-08-03. */
footer .wrap { text-align: center; }
.foot-logo { display: inline-block; }
.foot-logo img { width: 230px; max-width: 100%; height: auto; display: block; }
.foot-contact { margin-top: 1.1rem; line-height: 1.8; }
.foot-links { margin-top: 1.5rem; display: grid; gap: 0.65rem; }
.foot-row {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.55rem 1.6rem;
}
footer a { color: var(--accent); text-decoration: none; }
footer a:hover { text-decoration: underline; }
footer .fine { margin-top: 1.7rem; font-size: 0.8rem; }

/* ---------- Mobile floating call button ---------- */
.float-call {
  position: fixed;
  right: 1rem;
  bottom: 1rem;
  z-index: 20;
  display: none;
}
@media (max-width: 640px) {
  .float-call { display: block; }
  /* phones: the floating Call pill is the CTA, so the header drops its
     call button — the company name owns the top row (Bill's call) */
  .nav { padding: 0.7rem 1rem; gap: 0.6rem; }
  .brand img { height: 34px; }
  .wordmark strong { font-size: 0.98rem; letter-spacing: 0.01em; }
  .nav > .call-btn { display: none; }
}

/* ---------- Homeowner tips: index cards + article pages -------------------
   These are Jeremiah's articles, carried across in his words. They're the only
   pages on the site built for sustained reading rather than scanning, so the
   body copy gets --text at a full 1.75 line-height — the legibility rule with
   no compromises, because someone is going to read 1,200 words of it. */
.tips-grid {
  margin-top: 1.75rem;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 1rem;
}
.tip-card {
  display: flex;
  flex-direction: column;
  padding: 1.5rem 1.6rem 1.6rem;
  color: inherit;
  text-decoration: none;
  transition: transform 0.18s ease, background 0.18s ease;
}
.tip-card:hover { transform: translateY(-3px); background: var(--glass-strong); }
.tip-card h3 { font-size: 1.08rem; line-height: 1.35; }
.tip-card p { margin-top: 0.55rem; color: var(--text-soft); font-size: 0.95rem; }
.tip-date {
  font-size: 0.78rem; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--accent); font-weight: 700; margin-bottom: 0.55rem;
}
.tip-card .more { display: block; margin-top: auto; padding-top: 0.9rem; color: var(--accent); font-weight: 600; font-size: 0.9rem; }

/* The article card takes the FULL content width, same as a .window-story card
   on the school pages (Bill's measurement: "as wide as the windows glass").
   The trick those cards use, and the reason they read wide without becoming a
   wall of text, is that a 200px column sits beside the copy — so the prose
   itself still lands around 800px. Same anatomy here: meta rail left, prose
   right. Below 1000px it stacks and the rail becomes a byline. */
.article-card { padding: clamp(1.6rem, 4vw, 2.75rem); }
@media (min-width: 1000px) {
  .article-card {
    display: grid;
    grid-template-columns: 210px minmax(0, 1fr);
    gap: 2.25rem;
    align-items: start;
  }
  .article-side { position: sticky; top: calc(var(--header-h) + 1.5rem); }
}
.article-side .kicker a { color: inherit; text-decoration: none; }
.article-meta { color: var(--text-soft); font-size: 0.9rem; margin-top: 0.7rem; }
.article-side .article-meta { margin-top: 0.9rem; line-height: 1.6; }
.article-body > *:first-child { margin-top: 0; }
.article-body p { margin-top: 1rem; font-size: 1rem; line-height: 1.75; }
/* Section titles go brand purple — Bill's call, so the eye finds the seams in a
   long read. Both levels, because their originals mix h2 and h3 as section
   markers depending on the article. */
.article-body h2 {
  font-size: clamp(1.3rem, 2.6vw, 1.6rem);
  margin-top: 2.2rem;
  color: var(--accent);
}
.article-body h3 {
  font-size: 1.1rem;
  margin-top: 1.7rem;
  color: var(--accent);
}
.article-body h2 + h3 { margin-top: 1.1rem; }
/* their originals bold part of a heading ("1. <strong>Storm Protection</strong>"),
   which the strong rule below would otherwise repaint white mid-title */
.article-body h2 strong, .article-body h3 strong { color: inherit; }
.article-body ul, .article-body ol { margin: 0.9rem 0 0 1.3rem; }
.article-body li { margin-top: 0.5rem; line-height: 1.7; }
.article-body a { color: var(--accent); font-weight: 600; }
.article-body strong { color: var(--text); }
.article-foot {
  margin-top: 2rem;
  padding-top: 1.4rem;
  border-top: 1px solid var(--glass-edge);
  display: flex;
  flex-wrap: wrap;
  gap: 0.9rem 1.6rem;
  align-items: center;
  font-size: 0.93rem;
}
.article-foot a { color: var(--accent); font-weight: 600; text-decoration: none; }
.article-foot a:hover { text-decoration: underline; }

/* ---------- Contact page -------------------------------------------------
   Bill, 2026-08-03: "old people like contact details brother... a lot of their
   clientele is old people, so a solid contact page is a thing they will be
   looking for." Correct, and it beats the SEO argument for folding this into
   the quote page. Big type, high contrast, nothing clever. */
.contact-grid {
  margin-top: 1.75rem;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1rem;
}
.contact-card { padding: 1.6rem 1.75rem; }
.contact-card .kicker { margin-bottom: 0.7rem; }
.contact-card .big {
  font-size: clamp(1.25rem, 2.4vw, 1.5rem);
  font-weight: 700;
  line-height: 1.3;
  display: inline-block;
  color: var(--text);
  text-decoration: none;
}
a.big:hover, a.big:focus-visible { color: var(--amber); }
.contact-card address { font-style: normal; font-size: 1.05rem; line-height: 1.6; }
.contact-card .note { margin-top: 0.6rem; color: var(--text-soft); font-size: 0.93rem; }
/* hours read as a table, because that's what people are scanning for */
.hours { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.4rem; }
.hours li { display: flex; justify-content: space-between; gap: 1rem; font-size: 1rem; }
.hours .day { color: var(--text-soft); }
.hours .shut { color: var(--text-soft); }

/* The map ships as an image, so the page makes no third-party request and
   needs no API key. Left in its natural colours on purpose: a dark-inverted
   map looks smarter and reads worse, and legibility wins here. */
.map-card { padding: 0; overflow: hidden; margin-top: 1rem; }
.map-card img { display: block; width: 100%; height: auto; }
.map-foot {
  display: flex; flex-wrap: wrap; gap: 0.6rem 1.4rem;
  align-items: center; justify-content: space-between;
  padding: 1.05rem 1.35rem;
}
.map-foot a { color: var(--accent); font-weight: 600; text-decoration: none; }
.map-foot a:hover { text-decoration: underline; }

/* ---------- Screen-reader-only utility ---------- */
.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;
}

/* ---------- Video facades (assets/video.js) -------------------------------
   Thumbnail + play button now, real player only on click. Thumbnails are
   stored locally (assets/video-<id>.jpg) so the page doesn't call YouTube at
   all until someone actually wants to watch. */
.video-grid {
  margin-top: 1.75rem;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 1.25rem;
}
.video-card { padding: 0; overflow: hidden; }
.video-media { position: relative; aspect-ratio: 16 / 9; background: #05040d; }
.video-play { display: block; position: absolute; inset: 0; text-decoration: none; }
/* hqdefault is 4:3 with letterbox bars baked in — cover crops them off */
.video-play img { width: 100%; height: 100%; object-fit: cover; display: block; }
.video-btn {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: 64px; height: 64px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: rgba(13, 10, 28, 0.72);
  border: 2px solid rgba(255, 255, 255, 0.85);
  transition: background 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
}
.video-btn svg { width: 26px; height: 26px; fill: #fff; margin-left: 3px; }
.video-play:hover .video-btn, .video-play:focus-visible .video-btn {
  background: rgba(255, 158, 61, 0.92);
  border-color: var(--amber);
  transform: translate(-50%, -50%) scale(1.06);
}
.video-play:hover .video-btn svg, .video-play:focus-visible .video-btn svg { fill: #101725; }
.video-frame { display: block; width: 100%; aspect-ratio: 16 / 9; border: 0; }
.video-body { padding: 1.1rem 1.35rem 1.35rem; }
.video-body h3 { font-size: 1.02rem; line-height: 1.35; }
.video-body p { margin-top: 0.45rem; color: var(--text-soft); font-size: 0.95rem; }

/* ---------- FAQ accordion -------------------------------------------------
   Native <details>/<summary>: opens with no JS, keyboard and screen-reader
   correct for free. Answers are this page's whole point, so they get --text
   rather than the muted card colour used for supporting copy elsewhere. */
.faq-list { display: grid; gap: 0.7rem; margin-top: 1.2rem; }
.faq-item { overflow: hidden; }
.faq-item summary {
  cursor: pointer;
  padding: 1rem 1.25rem;
  font-weight: 600;
  font-size: 1rem;
  display: flex;
  align-items: flex-start;
  gap: 1rem;
  list-style: none;
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::after {
  content: "+";
  margin-left: auto;
  color: var(--accent);
  font-size: 1.35rem;
  line-height: 1;
  flex: none;
}
.faq-item[open] summary::after { content: "\2212"; }
.faq-item summary:hover, .faq-item summary:focus-visible { color: var(--accent); }
.faq-answer {
  padding: 0 1.25rem 1.2rem;
  color: var(--text);
  font-size: 0.98rem;
  max-width: 68ch;
}

/* readable prose column, shared by the about and financing pages */
.about-body { max-width: 760px; }
.about-body p + p { margin-top: 1rem; }

/* A promise-rail item that's also a door (the financing one). Keeps the rail's
   look — these are promises first, links second — and only colours up on
   hover, so the rail doesn't turn into a wall of purple. Beats
   main a:not([class]) on specificity, which would otherwise recolour it. */
.rail-card li a { color: inherit; text-decoration: none; display: block; }
.rail-card li a strong { transition: color 0.18s ease; }
.rail-card li a:hover strong,
.rail-card li a:focus-visible strong { color: var(--accent); }
.rail-card li a .more-arrow { color: var(--accent); }

/* ---------- Financing ---------- */
.fin-grid {
  margin-top: 1.6rem;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 1rem;
}
.fin-grid li { padding: 1.3rem 1.4rem; list-style: none; }
.fin-grid strong { display: block; font-size: 1.02rem; margin-bottom: 0.3rem; }
.fin-grid span { color: var(--text-soft); font-size: 0.95rem; }
.steps { margin: 1.3rem 0 0; padding: 0; list-style: none; counter-reset: step; display: grid; gap: 0.9rem; }
.steps li { counter-increment: step; display: flex; gap: 0.9rem; align-items: flex-start; }
.steps li::before {
  content: counter(step);
  flex: none;
  width: 30px; height: 30px;
  border-radius: 50%;
  display: grid; place-items: center;
  font-size: 0.88rem; font-weight: 700;
  color: #101725;
  background: linear-gradient(135deg, var(--amber), #ff9e3d);
}
/* Lender terms, carried across verbatim — small, but never grey-on-grey:
   it's the part a customer is entitled to read. */
.legal {
  margin-top: 1.75rem;
  padding: 1.15rem 1.3rem;
  border-radius: 14px;
  border: 1px solid var(--glass-edge);
  background: rgba(9, 7, 20, 0.4);
  font-size: 0.84rem;
  line-height: 1.6;
  color: var(--text-soft);
}

/* ---------- Scroll reveal ----------
   Hidden state only applies once the page's script adds .js to <html>, so
   content stays visible if JavaScript is off or the observer is missing. */
.js .reveal { opacity: 0; transform: translateY(28px); transition: opacity 0.7s ease, transform 0.7s ease; }
.js .reveal.in-view { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .js .reveal { opacity: 1; transform: none; transition: none; }
}
