/* On-Site Custom Drapes & Blinds — site styles
   Brand colors sampled from the original site: red #F31B20, charcoal #2E3838 */

:root {
  --brand-red: #F31B20;
  --brand-dark: #2E3838;
  --text-main: #414141;
  --text-light: #6b6b6b;
  --bg-alt: #f7f5f3;
  --border: #e7e3df;
  --radius: 6px;
  --max-width: 1180px;
  --font-body: "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-head: "Poppins", "Helvetica Neue", Helvetica, Arial, sans-serif;
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  font-family: var(--font-body);
  color: var(--text-main);
  line-height: 1.6;
  background: #fff;
}
img { max-width: 100%; display: block; }
a { color: var(--brand-red); text-decoration: none; }
a:hover { text-decoration: underline; }
h1, h2, h3 { font-family: var(--font-head); color: var(--brand-dark); font-weight: 600; line-height: 1.25; }
h1 { font-size: 2.4rem; margin: 0 0 0.5rem; }
h2 { font-size: 1.6rem; margin: 0 0 1rem; }
h3 { font-size: 1.2rem; margin: 0 0 0.5rem; }
p { margin: 0 0 1rem; }

.btn {
  display: inline-block;
  padding: 0.75rem 1.5rem;
  border-radius: var(--radius);
  font-weight: 600;
  text-decoration: none;
  border: 2px solid transparent;
  transition: all 0.15s ease;
}
.btn:hover { text-decoration: none; opacity: 0.9; }
.btn-dark { background: var(--brand-dark); color: #fff; }
.btn-accent { background: var(--brand-red); color: #fff; }
.btn-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.6rem 1.4rem;
  border-radius: 999px;
  border: 2px solid var(--brand-red);
  color: var(--brand-red);
  font-weight: 700;
  font-size: 0.95rem;
  margin-top: 0.9rem;
}
.btn-pill:hover { background: var(--brand-red); color: #fff; text-decoration: none; }
.btn-pill .arrow { display: inline-block; transition: transform 0.2s ease; }
.btn-pill:hover .arrow { transform: translateX(3px); }

/* ---- Header ---- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: #fff;
  border-bottom: 1px solid var(--border);
}
.header-inner {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0.75rem 1.5rem;
  display: flex;
  align-items: center;
  gap: 1.5rem;
}
.brand img { height: 72px; width: auto; }
.site-nav { flex: 1; }
.nav-list {
  list-style: none;
  display: flex;
  gap: 1.5rem;
  margin: 0;
  padding: 0;
  flex-wrap: wrap;
}
.nav-item > a {
  color: var(--brand-dark);
  font-weight: 600;
  font-size: 0.95rem;
}
.nav-item.active > a, .nav-item > a:hover { color: var(--brand-red); text-decoration: none; }
.nav-item.has-dropdown { position: relative; }
.dropdown {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  list-style: none;
  margin: 0.5rem 0 0;
  padding: 0.5rem 0;
  min-width: 240px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.08);
}
.nav-item.has-dropdown:hover .dropdown { display: block; }
.dropdown li a {
  display: block;
  padding: 0.5rem 1rem;
  color: var(--text-main);
  font-weight: 500;
  font-size: 0.9rem;
}
.dropdown li a:hover { background: var(--bg-alt); color: var(--brand-red); text-decoration: none; }
.header-cta { display: flex; align-items: center; gap: 1rem; }
.header-cta .phone { font-weight: 700; color: var(--brand-dark); white-space: nowrap; }
.nav-toggle {
  display: none;
  background: none;
  border: none;
  cursor: pointer;
  width: 40px; height: 40px;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
}
.nav-toggle span { display: block; height: 2px; background: var(--brand-dark); }

/* ---- Hero (home) ---- */
.hero {
  background-size: cover;
  background-position: center;
  position: relative;
  min-height: 480px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.hero::before {
  content: "";
  position: absolute; inset: 0;
  background: rgba(255,255,255,0.35);
}
.hero-inner {
  position: relative;
  z-index: 1;
  text-align: center;
  max-width: 700px;
  padding: 2.5rem 3rem;
  background: rgba(255,255,255,0.85);
  border-radius: var(--radius);
}
.hero-inner h1 { font-size: 2rem; }
.hero-sub { font-size: 1.3rem; margin-bottom: 1.5rem; }

/* ---- Page banner (inner pages) ---- */
/* Full-bleed by construction, not by a negative-margin/100vw hack: build.js renders
   .page-banner as a direct child of <main>, which is edge-to-edge on inner pages, and
   boxes everything else into .page-wrap. Until 2026-08-10 the banner sat inside the
   max-width column, so every inner-page banner carried white margin down both sides
   and cropped its photo tighter than live's does. Don't put the banner back inside
   .page-wrap. */
.page-banner {
  background-size: cover;
  background-position: center;
  position: relative;
  min-height: 260px;
  display: flex;
  align-items: center;
}
/* The scrim exists to keep white overlay text legible, so it belongs ONLY to the
   overlay treatment. On the two variants that carry no white-on-photo text it would
   just be dimming the photo for nothing — live shows those banners undimmed. */
.page-banner::before { content:""; position:absolute; inset:0; background: rgba(46,56,56,0.55); }
.page-banner-plain::before, .page-banner-titled::before { content: none; }
.page-banner-inner {
  position: relative;
  z-index: 1;
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 2rem 1.5rem;
  color: #fff;
  width: 100%;
}
.page-banner-inner h1 { color: #fff; }

/* bannerStyle: "plain" — bare photo, no text or CTA on it (live's six product
   sub-pages). Slightly shorter than the overlay banner because there is nothing
   inside it that needs vertical room. */
.page-banner-plain { min-height: 300px; }

/* bannerStyle: "title" — photo + a white title box, no phone CTA (live's Products
   index). Reuses the homepage hero's translucent-white box treatment rather than
   inventing a third banner look. */
.page-banner-titled { justify-content: center; }
.page-banner-titlebox {
  position: relative;
  z-index: 1;
  text-align: center;
  padding: 1.75rem 3.5rem;
  margin: 1.5rem;
  background: rgba(255,255,255,0.88);
  border-radius: var(--radius);
}
.page-banner-titlebox h1 { margin: 0; }

/* Plain-text page title — used where live has no banner photo at all, via `noHero: true`
   in build/data.js (Our Work, Accessibility Statement, the Blog index, all 4 blog posts).
   `main` already supplies the max-width and 1.5rem side padding, so this only has to
   restore the vertical rhythm the .page-banner box used to provide: with no padding the
   h1 butts straight up against the nav. Deliberately NOT a 260px min-height band like
   .page-banner — live shows just the title text, no full-bleed box. */
.page-title-plain { padding: 2.5rem 0 0.5rem; }
.page-title-plain h1 { margin-bottom: 0; }
/* .post-meta is white-on-photo inside a banner; on white it needs a real muted colour
   rather than 0.85 opacity of the body text, and it sits ABOVE the h1 here. */
.page-title-plain .post-meta { opacity: 1; color: var(--text-light); margin: 0 0 0.4rem; }

.post-meta { opacity: 0.85; font-size: 0.9rem; }
.phone-pill {
  display: inline-block;
  margin-top: 0.5rem;
  padding: 0.5rem 1.25rem;
  background: var(--brand-red);
  color: #fff;
  border-radius: 999px;
  font-weight: 700;
}

/* ---- Main / sections ---- */
main { max-width: var(--max-width); margin: 0 auto; padding: 0 1.5rem; }
.page-home main { max-width: none; padding: 0; }
.page-home main .section { max-width: var(--max-width); margin: 0 auto; padding: 3rem 1.5rem; }
/* Inner pages: <main> goes full width so the banner can span the viewport, and the
   content column moves down onto .page-wrap. The wrapper carries exactly the
   max-width + side padding <main> used to, so every section inside it renders the
   same as before — the only thing that changed is that the banner is now outside it. */
.page-inner main { max-width: none; padding: 0; }
.page-inner .page-wrap { max-width: var(--max-width); margin: 0 auto; padding: 0 1.5rem; }
.section { padding: 2.5rem 0; border-bottom: 1px solid var(--border); }
.section:last-child { border-bottom: none; }
.section-with-image { display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; align-items: center; }
.section-image img { border-radius: var(--radius); width: 100%; height: 320px; object-fit: cover; }
.section-image video { border-radius: var(--radius); width: 100%; height: 320px; object-fit: cover; display: block; background: #1c2424; }
.section-cta-standalone { grid-column: 1 / -1; text-align: center; margin: 1.5rem 0 0; }

.checklist { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.6rem; }
.checklist li { padding-left: 1.75rem; position: relative; }
.checklist li::before { content: "✓"; position: absolute; left: 0; color: var(--brand-red); font-weight: 700; }

.link-grid, .pin-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1rem; }
.link-card {
  display: block;
  padding: 1rem 1.25rem;
  background: var(--bg-alt);
  border-radius: var(--radius);
  color: var(--brand-dark);
  font-weight: 600;
  border: 1px solid var(--border);
}
.link-card:hover { border-color: var(--brand-red); color: var(--brand-red); text-decoration: none; }
/* Cross-link entries live ships without an anchor. Same card, no hover affordance. */
.link-card-plain { color: var(--text-light); font-weight: 600; cursor: default; }

/* Large clickable photo cards — the Cleaning landing pair and the Service Areas
   city cards. Two-up on the Cleaning page, four-up on Service Areas; auto-fit
   handles both from the one rule. */
.card-links { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 1.25rem; }
.photo-card {
  display: block;
  overflow: hidden;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--bg-alt);
  color: var(--brand-dark);
}
.photo-card img { display: block; width: 100%; height: 220px; object-fit: cover; }
.photo-card-label { display: block; padding: 1rem 1.25rem 0; font-weight: 700; font-size: 1.1rem; }
.photo-card-blurb { display: block; padding: 0.35rem 1.25rem 1rem; color: var(--text-light); font-size: 0.95rem; }
.photo-card-label:last-child { padding-bottom: 1rem; }
/* No image dim on hover — live's cleaning cards have no hover effect at all
   (pointer-tested 2026-08-10). The border/label colour shift is ours and stays as the
   link affordance. */
.photo-card:hover { border-color: var(--brand-red); color: var(--brand-red); text-decoration: none; }
.pin { display: inline-block; padding: 0.6rem 1rem; background: var(--bg-alt); border-radius: 999px; font-size: 0.9rem; }

/* ---- Quick-link ribbon buttons ---- */
/* The hand-picked link buttons live puts just above the footer on three of the six
   product sub-pages. Geometry and type are measured off live's Wix StylableButton:
   220x58, 18px, weight 700, 0.2em tracking, square corners, white label.
   Live fills them #6298AA (blue-grey) — deliberately NOT copied. Jake's call
   2026-08-10 is brand colors only, so the fill is the brand red the rest of the site
   already uses for filled elements, and the hover is the palette's existing darker
   red (#d21419, the same shade .chat-head grades into).
   The trailing letter-spacing on the last character would push the label
   visually left inside a centred box; the extra left padding cancels it. */
.quick-links { display: flex; flex-wrap: wrap; gap: 1rem; }
.quick-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  min-width: 220px;
  min-height: 58px;
  padding: 0.75rem 1.25rem 0.75rem calc(1.25rem + 0.2em);
  background: var(--brand-red);
  color: #fff;
  font-family: var(--font-head);
  font-weight: 700;
  font-size: 1.125rem;
  letter-spacing: 0.2em;
  line-height: 1.2;
  transition: background 0.2s ease;
}
.quick-link:hover { background: #d21419; color: #fff; text-decoration: none; }

/* Narrow centred text column — live's Products index intro block. Same 800px width as
   the photo grid above it, so the two line up. */
.section-narrow { max-width: 800px; margin-inline: auto; }
/* The COLUMN is centred; the copy inside it is not. Live centres only the heading and
   justifies the body copy — its computed text-align on these <p> is `justify`. Setting
   `center` on .section-body instead (as this first did) also centred the 👉 category
   lines, which live left-aligns. Justify is a no-op on lines that do not wrap, since a
   block's last line is never stretched, so the short bullet lines sit flush left on
   their own and only the wrapping intro paragraph actually justifies. */
.section-narrow .section-body h2 { text-align: center; }
.section-narrow .section-body p { text-align: justify; }
/* Live's heading here is two centred lines, the first an underlined link. */
.heading-stacked { text-align: center; }
.heading-stacked a { color: inherit; text-decoration: underline; }
.heading-stacked a:hover { color: var(--brand-red); }

/* ---- Products index photo grid ---- */
/* Live's Products index: two large photo tiles per row, each an underlined text link
   under its photo, no card border and no blurb copy (that is what separates this from
   .photo-card). This replaced a text-only pill list that had no photos at all. */
/* Grid sits in a narrower centered column than the content well, so the tiles read as
   modest thumbnails rather than hero images. Live's tiles measure 358x212; the clone
   was spanning the full ~925px column and rendering 447x300. */
.photo-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 2.5rem 2.5rem;
  max-width: 800px;
  margin-inline: auto;
}
.photo-grid-item { display: block; color: var(--brand-dark); }
/* aspect-ratio, not a fixed height: at 358x212 live's tiles are 1.69:1, and the old
   `height: 300px` forced 1.49:1, hard-cropping every source photo top and bottom.
   object-fit stays as a safety net but should barely engage at the correct ratio. */
.photo-grid-item img {
  width: 100%;
  aspect-ratio: 358 / 212;
  object-fit: cover;
  border-radius: var(--radius);
}
.photo-grid-label {
  display: block;
  margin-top: 0.85rem;
  text-align: center;
  font-family: var(--font-head);
  font-size: 1.05rem;
  font-weight: 600;
  text-decoration: underline;
}
/* No hover effect on the tile. Hover-tested against live with a real pointer
   2026-08-10: transform, opacity, filter and box-shadow are all unchanged on :hover.
   The `img { opacity: .92 }` that used to live here was ours, not live's. */
.photo-grid-item:hover { color: var(--brand-red); text-decoration: none; }

/* ---- Scroll-reveal entrance animations ----
   .reveal-armed is added by main.js, never by the markup: without JS (or without
   IntersectionObserver) the class never lands and the content just renders normally
   instead of being stuck at opacity 0 forever. Never move the hidden state into
   unconditional CSS — that turns a JS failure into an invisible page.

   Live runs two DIFFERENT Wix entrance animations, and both are reproduced below from
   live's own computed styles and keyframes. What the clone had before was a single
   generic fade-up with a stagger on the right column; live has neither a stagger nor
   any vertical slide. */

/* Products index tiles: Wix `fadeIn` + `arcIn`, stacked on each tile.
   The arc is a 3D swing — the tile pivots around an axis half the grid width behind
   itself, rotating 80deg on Y down to 0. Live's container reports --motion-width: 980px,
   hence the 490px radius; it is live's value and is deliberately not tied to the 800px
   grid above. No per-item delay: live fires all tiles together. */
.photo-grid.reveal-armed .photo-grid-item { opacity: 0; }
.photo-grid.reveal-armed .photo-grid-item.is-visible {
  --arc-radius: 490px;
  opacity: 1;
  animation:
    tile-fade-in 0.84s cubic-bezier(0.47, 0, 0.745, 0.715) backwards,
    tile-arc-in 1.2s cubic-bezier(0.86, 0, 0.07, 1) backwards;
}
@keyframes tile-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}
@keyframes tile-arc-in {
  from {
    transform: perspective(800px) translateZ(calc(var(--arc-radius) * -1))
               rotateY(-80deg)
               translateZ(var(--arc-radius));
  }
  to {
    transform: perspective(800px) translateZ(calc(var(--arc-radius) * -1))
               rotateY(0deg)
               translateZ(var(--arc-radius));
  }
}

/* Cleaning index landing cards: Wix `shuttersIn` — a different animation, and a
   deliberate one for a window-treatment company: 12 vertical slats opening like a
   venetian blind. The clip-path is a single polygon encoding 12 disjoint bands; the
   degenerate segments along y=0 are the seams that let one polygon draw 12 rectangles.
   Both cards animate together, no stagger. */
.card-links.reveal-armed .photo-card { opacity: 0; }
.card-links.reveal-armed .photo-card.is-visible {
  opacity: 1;
  animation: card-shutters-in 1.2s cubic-bezier(0.47, 0, 0.745, 0.715) backwards;
}
@keyframes card-shutters-in {
  from {
    clip-path: polygon(0.0000% 0%, 0.0000% 0%, 0.0000% 100%, 0.0000% 100%, 0.0000% 0%, 8.3333% 0%, 8.3333% 0%,
      8.3333% 100%, 8.3333% 100%, 8.3333% 0%, 16.6667% 0%, 16.6667% 0%, 16.6667% 100%, 16.6667% 100%,
      16.6667% 0%, 25.0000% 0%, 25.0000% 0%, 25.0000% 100%, 25.0000% 100%, 25.0000% 0%, 33.3333% 0%,
      33.3333% 0%, 33.3333% 100%, 33.3333% 100%, 33.3333% 0%, 41.6667% 0%, 41.6667% 0%, 41.6667% 100%,
      41.6667% 100%, 41.6667% 0%, 50.0000% 0%, 50.0000% 0%, 50.0000% 100%, 50.0000% 100%, 50.0000% 0%,
      58.3333% 0%, 58.3333% 0%, 58.3333% 100%, 58.3333% 100%, 58.3333% 0%, 66.6667% 0%, 66.6667% 0%,
      66.6667% 100%, 66.6667% 100%, 66.6667% 0%, 75.0000% 0%, 75.0000% 0%, 75.0000% 100%,
      75.0000% 100%, 75.0000% 0%, 83.3333% 0%, 83.3333% 0%, 83.3333% 100%, 83.3333% 100%, 83.3333% 0%,
      91.6667% 0%, 91.6667% 0%, 91.6667% 100%, 91.6667% 100%, 91.6667% 0%);
  }
  to {
    clip-path: polygon(0.0000% 0%, 8.3333% 0%, 8.3333% 100%, 0.0000% 100%, 0.0000% 0%, 8.3333% 0%, 16.6667% 0%,
      16.6667% 100%, 8.3333% 100%, 8.3333% 0%, 16.6667% 0%, 25.0000% 0%, 25.0000% 100%, 16.6667% 100%,
      16.6667% 0%, 25.0000% 0%, 33.3333% 0%, 33.3333% 100%, 25.0000% 100%, 25.0000% 0%, 33.3333% 0%,
      41.6667% 0%, 41.6667% 100%, 33.3333% 100%, 33.3333% 0%, 41.6667% 0%, 50.0000% 0%, 50.0000% 100%,
      41.6667% 100%, 41.6667% 0%, 50.0000% 0%, 58.3333% 0%, 58.3333% 100%, 50.0000% 100%, 50.0000% 0%,
      58.3333% 0%, 66.6667% 0%, 66.6667% 100%, 58.3333% 100%, 58.3333% 0%, 66.6667% 0%, 75.0000% 0%,
      75.0000% 100%, 66.6667% 100%, 66.6667% 0%, 75.0000% 0%, 83.3333% 0%, 83.3333% 100%,
      75.0000% 100%, 75.0000% 0%, 83.3333% 0%, 91.6667% 0%, 91.6667% 100%, 83.3333% 100%, 83.3333% 0%,
      91.6667% 0%, 100.0000% 0%, 100.0000% 100%, 91.6667% 100%, 91.6667% 0%);
  }
}

@media (prefers-reduced-motion: reduce) {
  .photo-grid.reveal-armed .photo-grid-item,
  .card-links.reveal-armed .photo-card {
    opacity: 1;
    transform: none;
    clip-path: none;
    animation: none;
  }
}

.cta-band {
  background: var(--brand-dark);
  color: #fff;
  text-align: center;
  border-radius: var(--radius);
  padding: 3rem 2rem;
}
.cta-band h2, .cta-band p { color: #fff; }
.cta-band .btn { margin-top: 0.5rem; }

/* Explicit column counts, not auto-fill: the Our Work gallery holds 21 photos, and 21
   divides evenly by 3 and by 1 but not by 4 or 5. auto-fill resolved to 5-across (and
   then 4-across) at desktop width, leaving one orphaned photo alone on the last row
   with empty cells beside it. Three across fills 7 complete rows. */
.gallery-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.9rem; }
@media (max-width: 900px) { .gallery-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .gallery-grid { grid-template-columns: 1fr; } }
.gallery-item img { height: 240px; width: 100%; object-fit: cover; border-radius: var(--radius); display: block; }

.blog-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 1.5rem; }
.blog-card { display: block; padding: 1.5rem; background: var(--bg-alt); border-radius: var(--radius); border: 1px solid var(--border); color: var(--text-main); }
.blog-card:hover { border-color: var(--brand-red); text-decoration: none; }
.blog-card h3 { color: var(--brand-dark); }
/* Featured (lead) blog card: photo above the title. The padding moves to an inner
   inset so the image can run edge to edge inside the card's rounded border. */
.blog-card-featured { padding: 0; overflow: hidden; }
.blog-card-featured img { width: 100%; height: 200px; object-fit: cover; display: block; }
.blog-card-featured h3, .blog-card-featured p { padding-left: 1.5rem; padding-right: 1.5rem; }
.blog-card-featured h3 { margin-top: 1.25rem; }
.blog-card-featured p:last-child { padding-bottom: 1.5rem; }
.blog-date { color: var(--text-light); font-size: 0.85rem; margin-bottom: 0.5rem; }

.contact-section { display: grid; grid-template-columns: 1fr 1.2fr; gap: 2.5rem; }
.contact-info .lead { font-size: 1.2rem; color: var(--brand-dark); }
.contact-form { display: grid; gap: 0.9rem; }
.contact-form label { display: grid; gap: 0.35rem; font-weight: 600; font-size: 0.9rem; color: var(--brand-dark); }
.contact-form input, .contact-form textarea {
  padding: 0.65rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-family: inherit;
  font-size: 1rem;
}
.form-note { font-size: 0.8rem; color: var(--text-light); font-style: italic; }

/* Spam honeypot (see HONEYPOT in build/build.js). Positioned off-screen rather than
   display:none or visibility:hidden — the simple bots this targets skip fields hidden
   those two ways, but happily fill one that is technically laid out. Real visitors
   never see or tab into it. */
.form-hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* Inline submit feedback. Empty until main.js writes to it, so it takes no vertical
   space on a fresh form — :empty keeps it from opening a gap under the button. */
.form-status { margin: 0; font-size: 0.9rem; line-height: 1.5; }
.form-status:empty { display: none; }
.form-status-pending { color: var(--text-light); }
.form-status-ok { color: #1c6b3f; font-weight: 600; }
.form-status-error { color: var(--brand-red); font-weight: 600; }

/* ---- Footer ---- */
.site-footer { background: var(--bg-alt); margin-top: 2rem; border-top: 1px solid var(--border); }
.footer-inner {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 2.5rem 1.5rem;
  display: grid;
  grid-template-columns: 1.2fr 1fr 1fr;
  gap: 2rem;
}
/* width:auto is load-bearing, do not drop it. The logo is the only <img> the
   build gives width/height attributes to (for CLS), and those attributes act as
   a presentational hint of width:359px. Setting height alone here does NOT keep
   the aspect ratio - the hint supplies the width and the logo renders 359x48,
   i.e. stretched to 7.5:1 instead of its true 2.66:1. .brand img and
   .brand-bar-inner img both neutralise it the same way. */
.footer-brand img { height: 48px; width: auto; margin-bottom: 0.75rem; }
.footer-links ul { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.5rem; }
.footer-links a { color: var(--text-main); }
.footer-social { display: flex; flex-direction: row; flex-wrap: wrap; gap: 0.65rem; align-self: start; }
.social-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--brand-dark);
  color: #fff;
  transition: background 0.2s ease, transform 0.2s ease;
}
.social-icon svg { width: 18px; height: 18px; fill: currentColor; }
.social-icon:hover { background: var(--brand-red); transform: translateY(-3px); text-decoration: none; }
.footer-bottom { text-align: center; padding: 1rem; font-size: 0.85rem; color: var(--text-light); border-top: 1px solid var(--border); }

/* ---- Responsive ---- */
@media (max-width: 900px) {
  .section-with-image, .contact-section { grid-template-columns: 1fr; }
  .footer-inner { grid-template-columns: 1fr; }
}
@media (max-width: 700px) {
  .photo-grid { grid-template-columns: 1fr; gap: 2rem; }
  /* No height override here: an explicit height beats aspect-ratio, which would put
     the old 1.49:1 crop back on mobile only. Single-column tiles keep live's ratio. */
  /* Full-width stacked buttons rather than a 220px pill floating in a narrow column */
  .quick-links { flex-direction: column; }
  .quick-link { width: 100%; font-size: 1rem; letter-spacing: 0.15em; }
  .page-banner-titlebox { padding: 1.25rem 1.5rem; }
  .page-banner-plain { min-height: 200px; }
}
@media (max-width: 800px) {
  .nav-toggle { display: flex; }
  .site-nav {
    display: none;
    position: absolute;
    top: 100%; left: 0; right: 0;
    background: #fff;
    border-bottom: 1px solid var(--border);
  }
  .site-nav.open { display: block; }
  .nav-list { flex-direction: column; gap: 0; padding: 1rem 1.5rem; }
  .nav-item { border-bottom: 1px solid var(--border); padding: 0.5rem 0; }
  .dropdown { position: static; box-shadow: none; border: none; display: block; padding-left: 1rem; }
  .header-inner { flex-wrap: wrap; }
  .header-cta { margin-left: auto; }
  .hero-inner h1 { font-size: 1.5rem; }
  .hero-sub { font-size: 1.05rem; }
}
/* Narrow phones: the header row could not fit and pushed the page sideways.
   .header-cta is a flex row holding a Contact button plus a `white-space: nowrap`
   phone number, so its min-content width never shrank; add the ~191px logo (72px tall
   at the logo's 2.66:1) and 1.5rem gaps and the row exceeded a 420px viewport, which
   made the WHOLE document scroll horizontally. Fixes, in order of effect: let
   .header-cta wrap, allow it to shrink below min-content (min-width: 0), trim the
   logo, and tighten the gutters. */
@media (max-width: 480px) {
  .header-inner { padding: 0.75rem 1rem; gap: 0.75rem; }
  .brand img { height: 52px; }
  .header-cta { flex-wrap: wrap; justify-content: flex-end; gap: 0.5rem; min-width: 0; }
  .header-cta .phone { font-size: 0.9rem; }
  .header-cta .btn { padding: 0.5rem 0.9rem; font-size: 0.9rem; }
}

/* ---- "Find Us On" row + static photo strip (homepage) ----
   Replaced a rotating carousel on 2026-08-08. Live puts the "Find Us On" label inline
   with the Houzz badge and the social buttons, then shows all four photos at once in a
   plain grid — no slideshow, no arrows, no dots. */
.find-us {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 1.25rem;
  margin: 0 0 1.5rem;
}
.find-us-label {
  font-family: var(--font-head);
  font-weight: 700;
  font-size: 1.5rem;
  color: var(--brand-dark);
  margin: 0 0.75rem 0 0;
}
.find-us-badge {
  display: block;
  line-height: 0;
  border-radius: 6px;
  overflow: hidden;
  transition: transform 0.2s, box-shadow 0.2s;
}
.find-us-badge img {
  height: 38px;
  width: auto;
  display: block;
}
/* The Houzz badge carries a wordmark, so it needs more room than the square
   Instagram/Facebook tiles beside it — matching live, where it is the largest mark. */
.find-us-badge-wide img { height: 46px; }
.find-us-badge:hover { transform: translateY(-2px); box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18); }

.photo-strip {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1rem;
}
.photo-strip img {
  width: 100%;
  height: 210px;
  object-fit: cover;
  display: block;
  border-radius: 6px;
}
@media (max-width: 800px) {
  .photo-strip { grid-template-columns: repeat(2, 1fr); }
  .photo-strip img { height: 160px; }
  .find-us-label { font-size: 1.25rem; margin-right: 0; }
}

/* ---- brand logo strip ---- */
.brand-bar { border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }
.brand-bar-inner {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 2.5rem;
  padding: 0.5rem 0;
}
.brand-bar-inner img {
  height: 40px;
  width: auto;
  max-width: 240px;
  object-fit: contain;
  filter: grayscale(35%);
  opacity: 0.85;
  transition: filter 0.2s, opacity 0.2s;
}
.brand-bar-inner a:hover img, .brand-bar-inner img:hover { filter: none; opacity: 1; }

/* Rotating groups of brand logos (live site cycles 4 then 3). Slides are stacked in
   one grid cell rather than absolutely positioned, so the strip's height is the
   tallest group and the page never reflows as it rotates. With JS off, only the
   first group is `active` and it still shows real logos.

   Centering is pinned explicitly here: the track is forced to a single full-width
   minmax(0,1fr) column and every slide is stretched to fill it, so a group centers on
   the page no matter how many logos it holds. Left to `display:grid` alone the track is
   auto-sized, which centers the 4-logo group and the 3-logo group differently the
   moment the container stops being wider than the widest slide. */
.brand-bar-rotator { display: grid; grid-template-columns: minmax(0, 1fr); }
.brand-bar-slide {
  grid-area: 1 / 1;
  width: 100%;
  justify-self: stretch;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.6s ease;
}
.brand-bar-slide.active { opacity: 1; visibility: visible; }
.brand-bar-dots { display: flex; justify-content: center; gap: 0.5rem; margin-top: 0.35rem; }
.brand-bar-dot {
  width: 7px;
  height: 7px;
  padding: 0;
  border-radius: 50%;
  border: none;
  background: var(--border);
  cursor: pointer;
}
.brand-bar-dot.active { background: var(--brand-red); }
@media (prefers-reduced-motion: reduce) {
  .brand-bar-slide { transition: none; }
}

/* ---- rotating hero banner (homepage slideshow) ---- */
.hero-slideshow { overflow: hidden; }
.hero-slideshow .hero-slide {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  opacity: 0;
  transition: opacity 0.9s ease;
  z-index: 0;
}
.hero-slideshow .hero-slide.active { opacity: 1; }
.hero-slideshow::before { z-index: 1; }
.hero-slideshow .hero-inner { z-index: 2; }
.hero-dots {
  position: absolute;
  bottom: 16px;
  left: 0;
  right: 0;
  display: flex;
  justify-content: center;
  gap: 9px;
  z-index: 2;
}
.hero-dot {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  border: none;
  background: rgba(46, 56, 56, 0.35);
  cursor: pointer;
  padding: 0;
  transition: background 0.2s, transform 0.2s;
}
.hero-dot.active { background: #f31b20; transform: scale(1.2); }

/* ============ AI chat widget (markup injected by assets/js/chat-widget.js) ============
   Same architecture and craft as the Custom Voice Automations and Rapid Lending
   Solutions widgets (pulsing toggle, first-visit teaser, session-persisted
   history), rendered in On-Site's light red/charcoal palette instead of their
   dark glass panels — this site is white-background, so a dark floating panel
   would read as a bolted-on third-party chat box. */
#onsiteChat {
  position: fixed;
  bottom: 22px;
  right: 22px;
  z-index: 200; /* above the sticky site header (z-index 100) */
  font-family: var(--font-body);
}
/* CRITICAL: the panel/teaser rules below set `display`, which would otherwise beat
   the UA stylesheet's [hidden]{display:none} and leave the widget stuck open. */
#onsiteChat [hidden] { display: none !important; }

/* Pulsing ring draws the eye on first visit, quiets once the panel is opened
   (chat-widget.js drops .chat-pulse on the first click, so it only returns on a
   fresh page load - a tab you already clicked in will look static).
   The expanding ring matches the CVA/RLS keyframe shape and 2.2s timing exactly.
   The scale breathe is a step BEYOND both siblings: on a white page the ring
   alone is far less visible than it is over their dark backgrounds, so the
   button itself needs to move a little to read as alive. */
@keyframes chatPulse {
  0%   { transform: scale(1);     box-shadow: 0 0 0 0 rgba(243, 27, 32, 0.5), 0 6px 18px rgba(46, 56, 56, 0.3); }
  50%  { transform: scale(1.055); }
  70%  { box-shadow: 0 0 0 14px rgba(243, 27, 32, 0), 0 6px 18px rgba(46, 56, 56, 0.3); }
  100% { transform: scale(1);     box-shadow: 0 0 0 0 rgba(243, 27, 32, 0), 0 6px 18px rgba(46, 56, 56, 0.3); }
}
.chat-pulse { animation: chatPulse 2.2s ease-out infinite; }
@keyframes teaserIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

#chatTeaser {
  position: absolute;
  bottom: 70px;
  right: 0;
  width: max-content;
  max-width: 230px;
  animation: teaserIn 0.3s ease forwards;
  cursor: pointer;
}
/* same glass family as the panel, but a small bubble of text floating over
   photography needs more ground under it, so it sits higher up the opacity range */
#chatTeaser .teaser-inner {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  background: rgba(255, 255, 255, 0.78);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  border: 1px solid rgba(46, 56, 56, 0.1);
  border-radius: 14px;
  border-bottom-right-radius: 4px;
  box-shadow: 0 10px 28px rgba(46, 56, 56, 0.18);
  padding: 12px 15px;
  font-size: 0.88rem;
  line-height: 1.45;
  color: var(--brand-dark);
}
#chatTeaserClose {
  background: none;
  border: 0;
  color: var(--text-light);
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
  padding: 0;
  flex-shrink: 0;
  margin-top: -1px;
}
#chatTeaserClose:hover { color: var(--brand-red); }

#chatToggle {
  width: 58px;
  height: 58px;
  border-radius: 50%;
  border: 0;
  background: var(--brand-red);
  color: #fff;
  display: grid;
  place-items: center;
  cursor: pointer;
  box-shadow: 0 4px 16px rgba(46, 56, 56, 0.35);
  transition: transform 0.15s ease, box-shadow 0.2s ease, background 0.15s ease;
}
/* scale, not translateY: while the pulse is running its keyframe owns `transform`,
   so a translate here would simply be ignored and then jump once the pulse stops */
#chatToggle:hover {
  background: #d21419;
  transform: scale(1.06);
  box-shadow: 0 8px 22px rgba(46, 56, 56, 0.4);
}
#chatToggle svg { width: 25px; height: 25px; }

/* Frosted glass panel body, translated from CVA rather than copied.
   CVA's .glass is rgba(255,255,255,0.04) over a DARK page - 4% white reads as
   frosted there and would be invisible here. On a white site the equivalent
   effect is a high-opacity white veil doing the same job: you can see the page
   photography moving behind it, blurred, but body text stays legible. The
   header bar stays fully opaque, exactly as CVA's does. */
#chatPanel {
  position: absolute;
  bottom: 74px;
  right: 0;
  width: 92vw;
  max-width: 380px;
  height: 70vh;
  max-height: 560px;
  background: rgba(255, 255, 255, 0.62);
  backdrop-filter: blur(16px) saturate(140%);
  -webkit-backdrop-filter: blur(16px) saturate(140%);
  border: 1px solid rgba(46, 56, 56, 0.1);
  border-radius: 14px;
  box-shadow: 0 22px 55px rgba(46, 56, 56, 0.28);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
/* Without backdrop-filter there is no blur, so a 62% veil would just be washed
   out and hard to read over hero photography. Fall back to the opaque panel. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  #chatPanel { background: #fff; }
  #chatTeaser .teaser-inner { background: #fff; }
}
/* Brand red, not charcoal. --brand-dark (#2E3838) is a real brand color, but as a
   large solid fill it just reads black and looks like a generic third-party chat
   box. Red is what the site already uses for filled elements (.btn-accent,
   .phone-pill), so this matches the site's own treatment rather than inventing one.
   The slight gradient to #d21419 (already in the palette, the button hover shade)
   is not decoration: flat #F31B20 puts white text at 4.2:1, and darkening the
   lower half lifts the text contrast while still reading as brand red. */
.chat-head {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 14px 16px;
  flex-shrink: 0;
  background: linear-gradient(160deg, var(--brand-red) 0%, #d21419 100%);
  color: #fff;
}
.chat-head p { margin: 0; font-size: 0.92rem; font-weight: 600; font-family: var(--font-head); }
/* near-white, not the old grey: a muted grey subtitle on red is unreadable */
.chat-head .chat-sub { font-size: 0.75rem; color: rgba(255, 255, 255, 0.92); }
/* Slat mark — three stacked bars, a nod to a blind rather than CVA's voice
   waveform. White now the banner is red; the red slats would have vanished. */
.chat-slats { display: grid; gap: 3px; width: 22px; flex-shrink: 0; }
.chat-slats i {
  display: block;
  height: 3px;
  border-radius: 2px;
  background: #fff;
}
.chat-slats i:nth-child(2) { opacity: 0.75; }
.chat-slats i:nth-child(3) { opacity: 0.55; }

/* transparent so the panel's frosted backdrop shows through the message area -
   an opaque fill here would cover the glass over most of the panel */
#chatMessages {
  flex: 1;
  overflow-y: auto;
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 11px;
  font-size: 0.9rem;
  background: transparent;
}
.chat-row { display: flex; }
.chat-row.is-user { justify-content: flex-end; }
.chat-bubble {
  max-width: 82%;
  padding: 10px 14px;
  border-radius: 14px;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.is-user .chat-bubble {
  background: var(--brand-red);
  color: #fff;
  border-bottom-right-radius: 4px;
}
/* Nearly opaque on purpose. CVA's bot bubble is translucent because its text is
   white on a dark page; here the text is dark and has to stay readable over
   whatever photo is blurring through, so the bubble carries its own ground. */
.is-bot .chat-bubble {
  background: rgba(255, 255, 255, 0.92);
  border: 1px solid rgba(46, 56, 56, 0.1);
  color: var(--text-main);
  border-bottom-left-radius: 4px;
}

#chatForm {
  display: flex;
  gap: 8px;
  align-items: center;
  padding: 12px;
  flex-shrink: 0;
  border-top: 1px solid rgba(46, 56, 56, 0.1);
  /* the input row and the legal line below it share one denser ground, so the
     bottom of the panel reads as a solid footer under the frosted body - the
     same way CVA's composer sits below its glass message area */
  background: rgba(255, 255, 255, 0.78);
}
#chatInput {
  flex: 1;
  min-width: 0;
  padding: 10px 16px;
  border-radius: 999px;
  font: inherit;
  font-size: 0.9rem;
  color: var(--text-main);
  background: rgba(255, 255, 255, 0.8);
  border: 1px solid rgba(46, 56, 56, 0.14);
}
#chatInput::placeholder { color: var(--text-light); }
#chatInput:focus {
  outline: none;
  border-color: rgba(243, 27, 32, 0.6);
  box-shadow: 0 0 0 3px rgba(243, 27, 32, 0.14);
}
#chatSend {
  width: 38px;
  height: 38px;
  flex-shrink: 0;
  border: 0;
  border-radius: 50%;
  background: var(--brand-red);
  color: #fff;
  display: grid;
  place-items: center;
  cursor: pointer;
  transition: background 0.15s;
}
#chatSend:hover { background: #d21419; }
.chat-legal {
  font-size: 0.68rem;
  color: var(--text-light);
  text-align: center;
  padding: 0 14px 11px;
  margin: 0;
  line-height: 1.4;
  /* 0.78, not the panel's 0.62: this line is 0.68rem in a muted grey, and over a
     blurred dark hero photo a lighter veil drops it under 4.5:1 contrast */
  background: rgba(255, 255, 255, 0.78);
}

@media (max-width: 800px) {
  #onsiteChat { bottom: 14px; right: 14px; }
  #chatPanel { height: 72vh; }
}
@media (prefers-reduced-motion: reduce) {
  .chat-pulse { animation: none; }
  #chatTeaser { animation: none; }
}
